Adventures in Shellcode Obfuscation! Part 12: Jigsaw

By Justin Connors | September 6, 2024

by Mike Saunders, Principal Consultant

 

 

This blog is the twelfth in a series of blogs on obfuscation techniques for hiding shellcode. You can find the rest of the series here. If you’d like to try these techniques out on your own, you can find the code we’ll be using on the Red Siege GitHub.

Getting Started

When we think about how shellcode gets detected through static analysis, AV/EDR engines are detecting a series of bytes associated with something known to be bad. That bad thing might be shellcode from Cobalt Strike or another well-known C2, or a Meterpreter payload. If the bytes don’t occur in the right order, the signature is broken.

While contemplating that fact, I started to think about how we could break up that signature. If you’ve been reading along with this series, you know we’ve already discussed a number of different methods to accomplish this task. I wanted to devise a method that didn’t rely on encryption and didn’t use one of the methods we’ve already discussed.

My wife likes to work on jigsaw puzzles. If you dump a box of puzzle pieces on the table, the assorted pieces are unrecognizable from the completed puzzle. There are really only two ways you can reassemble the puzzle – brute force, and by using the picture on the box as a guide to what the completed puzzle looks like.

I was thinking about puzzles and how I could implement that concept with shellcode. What I came up with was Jigsaw. While I’ve written about Jigsaw before, I’ll discuss it here again. For this to work, I’d need to come up with a way of randomly shuffling the jig saw pieces – our shellcode – and a way to know what it looked like when it was reassembled – the picture on the box.

To demonstrate how this works, I’ll use the first 10 bytes of a Meterpreter reverse_http payload. The following code creates a Python list containing the bytes.

shellcode = [0xfc, 0x48, 0x83, 0xe4, 0xf0, 0xe8, 0xcc, 0x00, 0x00, 0x00]

To shuffle the “box” of shellcode, we first need to create a new list the same size as our shellcode. If our shellcode is 10 bytes, then this list will contain the numbers 0 – 9. Then, we can use Python’s shuffle function from the random module. This will randomly shuffle all of the numbers in the list. The result would look something like the following:

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

This randomized list will be our map for reassembling our shellcode. To obfuscate our shellcode, we now need to create a new list. I’ll call this list jigsaw. To populate this list, we iterate through the randomized list (positions above). For each position in the list, we grab the byte at that position from our shellcode list and insert it into our jigsaw list. In our example above, the first value in positions is 1, so we’d grab the byte at shellcode[1] and place it in shuffled[0]. Next, we’d grab shellcode[6] and place it at jigsaw[1]. We’ll continue this process until we’ve iterated through all of positions, populating our jigsaw list. When we’re done, that list would look like this:

jigsaw = [0x48, 0xcc, 0x00, 0x83, 0xe8, 0x00, 0x00, 0xf0, 0x83]

We now have a list of randomized positions, and a list of shuffled shellcode. To reconstruct our shellcode in our loader program, we need to reverse the process. First, we’ll create a character array, shellcode, to store our shellcode bytes. Next, we’ll create an integer to store the index in the positions array that we need to modify. Then, we’ll create a for loop to iterate through our positions array. We set the value of position to be number stored in positions[idx]. We then look up the byte at jigsaw[idx] and place it in our shellcode array at shellcode[position]. When we’ve iterated through the entire positions array, we will have reconstructed our raw shellcode and placed it in the shellcode array.

unsigned char jigsaw[598] = { 0x00, 0x3b, 0xc3, 0x44, 0x00, 0x58 ... };
int positions[598] = { 546, 434, 584, 155, 561, 365 ... };

unsigned char shellcode[598] = { 0x00 };
int position = 0;

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

As we saw with Jigsaw, Defender is detecting the jigsaw reconstruction routine. I broke that detection by placing a printf inside the for loop, but there are many different ways you could break this signature. After ensuring that Defender did not detect the test program, I uploaded it to VirusTotal. As you can see below, only four engines identified the test program as bad. That’s pretty good!

Jigsaw VT Score

Try it Yourself

You can find the example code for this article as well as the other articles in this series at the Red Siege GitHub.

Stay Tuned

This blog is part of a larger series on obfuscation techniques. Stay tuned for our next installment!

 


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

Improving Your Simple Windows Domain for Offensive Testing: Installing MS SQL Server Express on Windows Server 2022 Server Core Edition

By Red Siege | September 3, 2026

by Justin Palk A couple of years ago, I put together a series on standing up a simple Windows AD domain in a lab environment. This article is part of […]

Learn More
Improving Your Simple Windows Domain for Offensive Testing: Installing MS SQL Server Express on Windows Server 2022 Server Core Edition

Improving Your Simple Windows Domain for Offensive Testing: Sysmon

By Red Siege | August 13, 2026

by Justin Palk A couple of years ago, I put together a series on standing up a simple Windows AD domain in a lab environment. This article is part of […]

Learn More
Improving Your Simple Windows Domain for Offensive Testing: Sysmon

Improving Your Simple Windows Domain for Offensive Testing: Elastic Defend EDR

By Red Siege | July 7, 2026

 by Justin Palk A couple of years ago, I put together a series on standing up a simple Windows AD domain in a lab environment. This is the start of […]

Learn More
Improving Your Simple Windows Domain for Offensive Testing: Elastic Defend EDR

Find Out What’s Next

Stay in the loop with our upcoming events.