Introducing Jigsaw: Shellcode Obfuscation

By Red Siege | March 18, 2024

by Mike Saunders, Principal Consultant

As offensive security professionals, we often find ourselves writing custom shellcode loaders for initial access and code execution. Unless we’re using a fully custom C2, there’s a good chance our shellcode will get detected and get us burned. As a result, we find ourselves having to come up with creative solutions to hide our code and avoid detection.

The days of using simple base64-encoding, or XOR’ing our code with a single-byte key are long since passed. I would venture to guess that using some kind of encryption is the most common approach to solving this problem. However, as with everything, our choices sometimes have unintended consequences.

Without inserting some superfluous code sequence in the middle of the routine, using XOR with a multibyte key is signatured and will lead to detection in some endpoint products like Microsoft Defender. Hint: if you want to use XOR with a multibyte key, try disabling compiler optimization. Using other encryption algorithms like RC4 or AES comes with a cost – higher entropy. High entropy can be a problem with some endpoint protection products. To a trained analyst, use of known encryption algorithms might send signals that you’re trying to hide something. I’ve even seen Defender fire alerts when I used a Rust loader and simply imported the RC4 or AES crates. I never even used them.

There are lots of methods to get around the entropy problem I referenced earlier. I even wrote a tool to help with this. Jargon uses a lookup table to substitute words for shellcode bytes. As a result, you have a “shellcode-less” loader that doesn’t result in increased entropy. On the down side, depending on how large the words in your source dictionary are, you end up with a pretty large executable.

 

Yet another shellcode obfuscation method

I was thinking of a way to obfuscate shellcode that didn’t involve encryption, and didn’t make the payload significantly larger. If I could shuffle all the bytes of a given shellcode payload and keep track of where all the bytes went, I’d have a way to avoid getting detected based on signatured byte sequences. This approach also wouldn’t result in increased entropy. To anyone analyzing the payload, the shellcode would look like a jigsaw puzzle dumped on the table and with no picture on the box as a reference.

Eventually, I came up with Jigsaw. Jigsaw is a Python script that reads a raw shellcode file and creates a randomized array sized equal to the number of bytes in the shellcode. Jigsaw then uses random.shuffle() to shuffle the array into random order. Each entry in this array represents the position of a byte in our shellcode. At that point, it’s a matter of iterating through our array of positions, grabbing the shellcode byte at that position, and storing it in a new array.

Here’s a contrived example to help illustrate the concept. Our shellcode consists of the following 10 bytes:

shellcode = [ 0xfc, 0xe8, 0x89, 0x00, 0x00, 0x00, 0x60, 0x89, 0xe5, 0x31 ]

To generate our array of randomized positions, we’d do the following:

>>> positions = list(range(0,10))
>>> random.shuffle(positions)
>>> positions
[1, 6, 8, 2, 5, 9, 7, 0, 4, 3]

If we iterate through positions, look up the byte at the corresponding position in the shellcode array, and store that value in a new array, our shuffled shellcode then becomes:

shuffled_shellcode = [ 0xe8, 0x60, 0xe5, 0x89, 0x00, 0x31, 0x89, 0xfc, 0x00, 0x00 ]

At this point, we just need to build the logic in whatever language we’re going to write our loader in. This example would work for a C or C++ loader:

unsigned int calc_len = 10;
char calc_payload[10] = { 0x00 };
int position;

// Reconstruct the payload
for (int idx = 0; idx < sizeof(positions) / sizeof(positions[0]); idx++) {
        position = positions[idx];
        calc_payload[position] = jigsaw[idx];
}

In theory, this should allow us to store our shellcode in a manner that won’t result in detection. Of course, there is the possibility that the right sequence of bytes could be stored in an order that results in detection. It’s even possible that random.shuffle could generate an array of positions that exactly matches our shellcode. It’s always a good idea to test your payload for detections before you send it out in the world.

 

Detection

I couldn’t think of an obvious way of detecting this method of obfuscation. At some point, the deobfuscated shellcode will exist in memory and could be detected there. The methods used to load and execute the shellcode would also present opportunities for detection.

 

Conclusion

I did some searching to find examples of prior art. I’m sure I’m not the first person to have this idea, but I didn’t immediately find any examples. If you know of one, please drop me a note on Discord, X, or open up a Github issue so I can give credit where it’s due.

 


 

About Principal Security Consultant Mike Saunders

Mike Saunders is Red Siege Information Security’s Principal Consultant. Mike has over 25 years of IT and security expertise, having worked in the ISP, banking, insurance, and agriculture businesses. Mike gained knowledge in a range of roles throughout his career, including system and network administration, development, and security architecture. Mike is a highly regarded and experienced international speaker with notable cybersecurity talks at conferences such as DerbyCon, Circle City Con, SANS Enterprise Summit, and NorthSec, in addition to having more than a decade of experience as a penetration tester. You can find Mike’s in-depth technical blogs and tool releases online and learn from his several offensive and defensive-focused SiegeCasts. He has been a member of the NCCCDC Red Team on several occasions and is the Lead Red Team Operator for Red Siege Information Security.

Certifications:
GCIH, GPEN, GWAPT, GMOB, CISSP, and OSCP

Connect on Twitter & LinkedIn

Adventures in Shellcode Obfuscation! Part 4: RC4 with a Twist

By Red Siege | July 8, 2024

by Mike Saunders, Principal Security Consultant This blog is the fourth in a series of blogs on obfuscation techniques for hiding shellcode. You can find the rest of the series […]

Learn More
Adventures in Shellcode Obfuscation! Part 4: RC4 with a Twist

Adventures in Shellcode Obfuscation! Part 3: Encryption

By Red Siege | July 1, 2024

By Mike Saunders, Principal Security Consultant   This blog is the third in a series of blogs on obfuscation techniques for hiding shellcode. You can find the rest of the […]

Learn More
Adventures in Shellcode Obfuscation! Part 3: Encryption

Phone Switch Labs CTF – Walk-Through

By Red Siege | June 26, 2024

by Douglas Berdeaux, Senior Security Consultant CTF redsiege.com/phoneswitch Getting Started Phone phreaking is the practice of exploring and hacking telephones, telephone switches, telephone test equipment, and physically exploring the telephone […]

Learn More
Phone Switch Labs CTF – Walk-Through

Find Out What’s Next

Stay in the loop with our upcoming events.