Shellcode Source Mutations

One of the main benefits of writing your shellcode in assembly is that you have full control over the structure of the shellcode.

For example, the content and order of the functions in the source file can (obviously) be changed and the code compiled to produce a new version of your shellcode. These changes don’t have to be functional however, we can use automated tools to mutate the shellcode source so that each time we compile it the functionality stays the same, but the contents are changed.

This then means that the resultant shellcode will have a different size, file hash, byte order etc, which will make it harder to reliably detect both statically and in memory.

This ability is orthogonal to shellcode encryption etc, as at some point encrypted and encoded shellcode needs to be decrypted and decoded and descrambled so that it can actually be executed, and at this point it may get detected.

Let’s make use of a concrete, if a little contrived, example.

Test Case

We can take the nasm source code for some MessageBox shellcode from Didier Stevens, compile it as per his instructions and inject it and we successfully get a message box – so far so good.

Testing the default shellcode.

If we were to extract this shellcode as a blue teamer and want to write detections to catch it, we may note the hash, examine the contents and the disassembly and then write a yara rule to be able to catch it in memory or on disk.

As show below, we can take a quick peek at the binary using binary refinery.

Taking a quick peek at the binary using binary refinery.

We also note the sha256 hash is a8fb8c2b46ab00c0c5bc6aa8d9d6d5263a8c4d83ad465a9c50313da17c85fcb3.

Rizin can be used to examine the shellcode disassembly.

Examining the shellcode disassembly using rizin.

If we were to write a very quick yara rule for this, we may choose to focus on the initial bytes which perform some setup. Replacing the offsets (e.g. [rbx + 0x113]) with wildcards and taking the bytes up to the second call at 0x0000001b we can write a quick yara rule that matches the shellcode in memory and on disk, but nothing else in e.g. C:\Windows\System32 (testing for false positives).

A quick-and-dirty yara rule for the shellcode.

The rule matches the shellcode on disk and in memory and triggers no false positives against anything in C:\Windows\System32.

The rule matches the shellcode on disk and in memory and triggers no false positives against anything in C:\Windows\System32.

So we have a reliable yara rule and add it to our threat hunts, all good right?

Shellcode Mutator

This is where the Shellcode Mutator project comes in. This simple python script will parse nasm source code and insert sets of instructions at random intervals that ‘do nothing’, but will then alter and byte order and file hash of the shellcode at the cost of increased size.

The script is easy enough to use, taking a source code ‘template’, an out file, a morph percentage and a flag to set x86 vs x64 mode.

Help text for shellcode mutator.

This script has some basic logic to check source lines but essentially has to sets of instructions that can be expanded upon, one for x86 and one for x64. Each entry in these instruction sets should, after all instructions have executed, leave all registers and flags in the same state as before they were executed to ensure that the shellcode can continue without erroring.

The default "no instructions" sets.

Along with some other logic, the script will place these instruction sets at random intervals (dictated by the morph percentage) before the instructions specified in the assembly_instructions variable:

Instructions that are used as triggers for the mutations.

If we run the script against our MessageBox shellcode, setting a morph percentage of 15% we get a source code file that is 57 lines instead of 53. Compiling that shellcode and executing the yara search shows that it is not caught and only the original shellcode matches.

The mutated MessageBox shellcode no longer matches our yara rule.

Examining the disassembly of the binary file shows that it has inserted a nop (0x90) instruction into the bytes that we matched upon (in addition to at other places). This of course also changed the file hash.

The instruction that caused our yara rule not to match.

There is an element of luck of course. We need to make sure that we change enough bytes that any yara rules will no longer match without actually knowing what those yara rules are (or any other detections). Increasing the morph percentage then will increase the number of alterations made and the likelihood of bypassing any rules at the cost of increased shellcode size.

Of course the big question is, does our shellcode still run?

Testing the morphed shellcode still works!

Winning!

References

The post Shellcode Source Mutations appeared first on Nettitude Labs.

Article Link: Shellcode Source Mutations - Nettitude Labs