Adventures in Shellcode Obfuscation! Part 11: Jargon

By Red Siege | August 29, 2024

by Mike Saunders, Principal Consultant

 

 

This blog is the eleventh 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.

During one assessment, I found my payload was being blocked by CrowdStrike Falcon due to the high entropy of the payload. The high entropy was due to the my use of AES-encrypted shellcode embedded in the loader. I ended up getting around this check by compiling in a large array of dictionary words which caused the overall entropy of the payload to be much lower. If you’d like to read more about how that worked, you can read this blog.

Going Further

After that assessment, I started to think about a better way to defeat entropy checks than simply compiling in a bunch of words. I was also thinking about if it was possible to use unique words to represent shellcode characters. Eventually, I came up with Jargon. At its core, Jargon is essentially a substitution cipher. Shellcode can be made up of 256 unique values – 0 through 255 or 0x00 through 0xff. If we first build a dictionary of 256 unique words and put them in an array, each word’s position in the array could represent a byte of shellcode. Let’s take a look at how that works.
The translation table below contains only five words. In our loader, we’d need 256 words, but this will be sufficient for demonstration purposes. The word in the first position in the array, position 0, is day. This word will represent 0x00. dog represents 0x01, cat is 0x02, and so on. It’s important that each word in our translation table is unique. Each word represents a unique byte of shellcode, so duplication of words would cause a collision.

unsigned char* translation_table[5] = { "day", "dog", "cat", "fish", "horse" };

Now that we have a populated array of words, we need to start translating our shellcode into words. We do this by first defining a new array. We’ll call it translated_shellcode. We need to define an indexing variable. We’ll use idx for this demonstration. We then iterate through our array of raw shellcode bytes and look up the word at the position of the translation table by using that shellcode byte’s numeric value as the index. In other words, if our shellcode byte was 0x02, we’d look up the word at translation_table[0x02], which is cat in our example above. We’d then insert cat into our translated shellcode table at our current index value. in other words, translated_shellcode[idx] = "cat";. Using our translation table above, if our shellcode was \x02\x01\03\x00\x04, the translated shellcode table would look like the following:

unsigned char* translated_shellcode[5] = { "cat", "dog", "fish", "day", "horse" };

At this point, we could stick this in a shellcode loader and our loader wouldn’t contain any actual shellcode! But it’s unusable in its current state. At runtime, we need to translate this back to raw shellcode. To do this, we’ll use two for loops. Assuming our raw shellcode is 598 bytes, we declare a new variable to store our translated shellcode. We’ll use a for loop to iterate through each position in our translated shellcode table. Inside that loop, we’ll use another loop to iterate through the translation table.

Let’s assume we’re in the first iteration of our outer loop. Using the code below, that means sc_index is 0. In our inner loop, we use another index variable, tt_index, meaning translation table index. We use strcmp to test whether the word at translation_table[tt_index] is equal to the word at translated_shellcode[sc_index]. If not, we increment tt_index until we find a match. Once we have a match, we place the current value of tt_index into our shellcode array using shellcode[sc_index] = tt_index. At that point, we break out of the loop and translate the next word in the translated_shellcode array.

unsigned char shellcode[598] = { 0x00 };
for (int sc_index = 0; sc_index < 598; sc_index++) {
        for (int tt_index = 0; tt_index <= 255; tt_index++) {
            if (strcmp(translation_table[tt_index], translated_shellcode[sc_index]) == 0) {
                    shellcode[sc_index] = tt_index;
                    break;
            }
        }
}

And that’s it! Once we’ve iterated through the entire translated_shellcode array, we’ve reconstructed our raw shellcode, and we can use it in our loader. So how does it far on VirusTotal? Only four engines detected it. That’s not too shabby.

Jargon VT Score

I noticed, however, that one of the engines that detected the test program was the Microsoft engine. I ran the program through ThreatCheck and found that Defender indeed detected something in the program. However, looking at the output, we can see that it doesn’t appear that it’s detecting shellcode.

ThreatCheck Results

Suspecting that Microsoft has signatured the translation routine, I inserted a printf(“”); inside the outer loop of the translation routine. After recompiling, ThreatCheck didn’t report any bad bytes in the file. Reanalyzing the payload with VirusTotal showed the detections went down to three. Even better!

Jargon with printf Score

Try It Yourself

If you want to try out Jargon, you can get it here. You can find the code for 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.