Hello and welcome back to the shellcode obfuscation series! If you've missed the previous episodes, take your time and catch up here:

X86 Shellcode Obfuscation - Part 1

X86 Shellcode Obfuscation - Part 2

Last time, I've added obfuscation support for most common x86 instructions, which allowed to process the obfuscation output several times in order to get even better results. The obfuscated code output now, while being pretty well obfuscated, still is pretty easy to navigate as the execution flow is not changed. I will fix it this episode as I explain methods of implementing full blown execution flow obfuscation by injecting dozens of jumps to make the code output unrecognizable.

Execution Flow Deception

Every assembly code consists of calls and jumps separated by instructions that reside in the middle. These are commonly called branch instructions as they allow the code to branch out into multiple directions.

Let's take a look at the following code:

00390000 B8 02000000 MOV EAX,2 00390005 B9 06000000 MOV ECX,6 0039000A 03C1 ADD EAX,ECX 0039000C 85C0 TEST EAX,EAX 0039000E 74 05 JE SHORT 00390015 00390010 83F8 08 CMP EAX,8 00390013 ^75 EB JNZ SHORT 00390000 00390015 C3 RETN

This simple code puts some values in EAX and ECX registers. After that it adds ECX to EAX and jumps to 00390015 if EAX == 0 (which is false). Later it compares if the value of EAX == 8 and if not, jumps back to the beginning. If EAX == 8 the jump is not taken and RETN is executed.

That sample code will be used for demonstration purposes. You can see the code is pretty straight forward and easy to navigate. Is there any way we can disturb its execution flow to make it both harder to read and harder to step through? There sure is!

The purpose of execution flow obfuscation will be to add as many unnecessary jumps as possible, while retaining the original code functionality. The obfuscation tool should go over every instruction one by one and decide at each step wether the jump should be placed at that spot. If it decides the jump is to be placed, it will pick a random place in the shellcode to jump to. The "jump-to" place will need to handle inserted code, but as there will already be other code in this place, we need to split the code with another jump, that will jump over our inserted code.

It may be a bit hard to explain in words, but hopefully it gets clearer with the following example.

Lets say we are inserting a jump at 00390010 and we randomly picked instruction at address 0039000A to insert into. This is how our sample code will look like after the jump is inserted:

00390000 B8 02000000 MOV EAX,2 00390005 B9 06000000 MOV ECX,6 0039000A EB 06 JMP SHORT 00390012 <-- inserted code with a jump split, jumping over the inserted code 0039000C 83F8 08 CMP EAX,8 0039000F ^75 EF JNZ SHORT 00390000 00390011 C3 RETN <-- inserted code ends here 00390012 03C1 ADD EAX,ECX 00390014 85C0 TEST EAX,EAX 00390016 ^74 F9 JE SHORT 00390011 00390018 ^EB F2 JMP SHORT 0039000C <-- inserted jump

You can see that the code was split at 0039000A with a jump that jumps over the inserted code. The execution order is preserved, but the flow is now different. If you follow the instructions one by one, you can see that the instructions are executed in perfect order as they were in the original sample.

This is basically the whole idea. Not much to add here, but what if we can up the game with...

Anti-disassembly Tricks

I will focus here on a very popular method that involves inserting a wild byte in-between instructions, which is never executed, but effectively makes byte-by-byte disassemblers trip and fail miserably. Smart disassemblers that follow the execution flow of instructions, won't be fooled by this (IDA is handling this very well), but other debuggers/disassemblers that focus on disassembling instructions one by one from top to bottom will have problems (like OllyDbg).

Let's imagine we have the following code:

00390000 B9 01000000 MOV ECX,1 00390005 03C1 ADD EAX,ECX

Now we insert a jump at 00390000 that will point to the next instruction:

00390000 EB 00 JMP SHORT 00390002 00390002 B9 01000000 MOV ECX,1 00390007 03C1 ADD EAX,ECX

Simple right? Now that the jump is in place and it is set to jump to the next instruction, what will happen if we insert 0xE8 byte in the middle between JMP SHORT and MOV ECX,1 ?

00390000 EB 00 JMP SHORT 00390003 00390002 E8 B9010000 CALL 003901C0 00390007 0003 ADD BYTE PTR DS:[EBX],AL 00390009 C100 00 ROL DWORD PTR DS:[EAX],0

As you can see, the disassembler that tried to disassemble instructions one by one, came across our 0xE8 byte and interpreted it as a next instruction opcode, which is a CALL followed by an immediate relative address. The disassembler doesn't know that the inserted 0xE8 wild byte is never executed and instead tries its best to make sense of the machine code it sees. This one-byte shift also affects disassembly of other instructions that follow.

The obfuscation tool inserts a lot of jumps in order to change the execution flow and it would be a waste if we didn't put some wild bytes, after the jumps, here and there to make the disassembled code practically unreadable in debuggers/disassemblers that do not analyze the execution flow.

Here is the full list of wild bytes that I decided to use with their corresponding instruction opcodes:

68 - PUSH IMM32 81 - ADD/OR/ADC/SBB/AND/SUB/XOR IMM32 83 - ADD/OR/ADC/SBB/AND/SUB/XOR IMM8 D8 - FADD/FMUL/FCOM/FCOMP/FSUB/FSUBR/FDIV/FDIVR M32REAL D9 - FLD/FXCH/FST/FNOP/... DA - FIADD/FCMOVB/FIMUL/FCMOVE/FICOM/... DC - FADD/FMUL/FCOM/FCOMP/FSUB/FSUBR/FDIV/FDIVR M64REAL DE - FIADD/FCMOVB/FIMUL/FCMOVE/FICOM/... E8 - CALL E9 - JMP EA - JMP FAR F7 - TEST/NOT/NEG/MUL/IMUL/DIV/IDIV

Conclusion

With all of the obfuscation features in place, let's see what will happen if we run the sample code through the 1-pass obfuscation process with highest level of control flow mixing. For reference this is the untouched sample code in its original form:

0 00000000: b802000000 MOV EAX, 0x2 1 00000005: b906000000 MOV ECX, 0x6 2 0000000a: 01c8 ADD EAX, ECX 3 0000000c: 85c0 TEST EAX, EAX 4 0000000e: 7405 JZ 0x15 5 00000010: 83f808 CMP EAX, 0x8 6 00000013: 75eb JNZ 0x0 7 00000015: c3 RET

And here is the same code after obfuscation:

0 00000000: b837272a61 MOV EAX, 0x612a2737 1 00000005: eb3a JMP 0x41 2 00000007: eb2f JMP 0x38 3 00000009: 81c06de78ae1 ADD EAX, 0xe18ae76d 4 0000000f: eb16 JMP 0x27 5 00000011: 81e87daf9577 SUB EAX, 0x7795af7d 6 00000017: e994000000 JMP 0xb0 7 0000001c: 83 7 0000001d: eb08 JMP 0x27 8 0000001f: bb0089c4e5 MOV EBX, 0xe5c48900 9 00000024: eb53 JMP 0x79 10 00000026: 68 10 00000027: ebe8 JMP 0x11 11 00000029: f7 11 0000002a: eb0c JMP 0x38 12 0000002c: 81e9699dec08 SUB ECX, 0x8ec9d69 13 00000032: e9ab000000 JMP 0xe2 14 00000037: 83 14 00000038: 81c038e2d5f6 ADD EAX, 0xf6d5e238 15 0000003e: ebc9 JMP 0x9 16 00000040: d8 16 00000041: eb56 JMP 0x99 17 00000043: eb4b JMP 0x90 18 00000045: eb05 JMP 0x4c 19 00000047: 75b7 JNZ 0x0 20 00000049: eb2d JMP 0x78 21 0000004b: e9 21 0000004c: b995f3fde5 MOV ECX, 0xe5fdf395 22 00000051: eb1a JMP 0x6d 23 00000053: eb13 JMP 0x68 24 00000055: 81ebef03dbd0 SUB EBX, 0xd0db03ef 25 0000005b: eb08 JMP 0x65 26 0000005d: 39d8 CMP EAX, EBX 27 0000005f: e98f000000 JMP 0xf3 28 00000064: 81 28 00000065: eb51 JMP 0xb8 29 00000067: ea 29 00000068: 01c8 ADD EAX, ECX 30 0000006a: eb06 JMP 0x72 31 0000006c: d8 31 0000006d: ebbd JMP 0x2c 32 0000006f: 81 32 00000070: eb13 JMP 0x85 33 00000072: 85c0 TEST EAX, EAX 34 00000074: eb0c JMP 0x82 35 00000076: eb01 JMP 0x79 36 00000078: c3 RET 37 00000079: 81ebeb56863b SUB EBX, 0x3b8656eb 38 0000007f: eb52 JMP 0xd3 39 00000081: d8 39 00000082: eb27 JMP 0xab 40 00000084: f7 40 00000085: eb09 JMP 0x90 41 00000087: 81e93cdbac3f SUB ECX, 0x3facdb3c 42 0000008d: ebd9 JMP 0x68 43 0000008f: ea 43 00000090: 81c0c757b8d5 ADD EAX, 0xd5b857c7 44 00000096: eba0 JMP 0x38 45 00000098: ea 45 00000099: ebf5 JMP 0x90 46 0000009b: dc 46 0000009c: eb09 JMP 0xa7 47 0000009e: 81c388775b66 ADD EBX, 0x665b7788 48 000000a4: ebaf JMP 0x55 49 000000a6: d8 49 000000a7: eb00 JMP 0xa9 50 000000a9: eb05 JMP 0xb0 51 000000ab: 74cb JZ 0x78 52 000000ad: eb21 JMP 0xd0 53 000000af: d8 53 000000b0: 81c0dc665268 ADD EAX, 0x685266dc 54 000000b6: eb09 JMP 0xc1 55 000000b8: 81c396e99ded ADD EBX, 0xed9de996 56 000000be: eb9d JMP 0x5d 57 000000c0: 83 57 000000c1: eb09 JMP 0xcc 58 000000c3: 81f14ffbe7e8 XOR ECX, 0xe8e7fb4f 59 000000c9: ebbc JMP 0x87 60 000000cb: d8 60 000000cc: eb1d JMP 0xeb 61 000000ce: eb12 JMP 0xe2 62 000000d0: 53 PUSH EBX 63 000000d1: eb09 JMP 0xdc 64 000000d3: 81eb3c8f5c2d SUB EBX, 0x2d5c8f3c 65 000000d9: ebc3 JMP 0x9e 66 000000db: 81 66 000000dc: e93effffff JMP 0x1f 67 000000e1: e8 67 000000e2: 81c1e1c939fa ADD ECX, 0xfa39c9e1 68 000000e8: ebd9 JMP 0xc3 69 000000ea: da 69 000000eb: e95cffffff JMP 0x4c 70 000000f0: 83 70 000000f1: eb00 JMP 0xf3 71 000000f3: 5b POP EBX 72 000000f4: e94effffff JMP 0x47 73 000000f9: da

Please keep in mind that this code is properly disassembled as we knew where the wild bytes are and we could filter them out during the disassembly process.

Unaware of the wild bytes, poor OllyDbg disassembles the code like this:

00390000 B8 37272A61 MOV EAX,612A2737 00390005 EB 3A JMP SHORT 00390041 00390007 EB 2F JMP SHORT 00390038 00390009 81C0 6DE78AE1 ADD EAX,E18AE76D 0039000F EB 16 JMP SHORT 00390027 00390011 81E8 7DAF9577 SUB EAX,7795AF7D 00390017 E9 94000000 JMP 003900B0 0039001C 83EB 08 SUB EBX,8 0039001F BB 0089C4E5 MOV EBX,E5C48900 00390024 EB 53 JMP SHORT 00390079 00390026 68 EBE8F7EB PUSH EBF7E8EB 0039002B 0C 81 OR AL,81 0039002D -E9 699DEC08 JMP 09259D9B 00390032 E9 AB000000 JMP 003900E2 00390037 8381 C038E2D5 F6 ADD DWORD PTR DS:[ECX+D5E238C0],-0A 0039003E ^EB C9 JMP SHORT 00390009 00390040 D8EB FSUBR ST,ST(3) 00390042 56 PUSH ESI 00390043 EB 4B JMP SHORT 00390090 00390045 EB 05 JMP SHORT 0039004C 00390047 ^75 B7 JNZ SHORT 00390000 00390049 EB 2D JMP SHORT 00390078 0039004B -E9 B995F3FD JMP FE2C9609 00390050 E5 EB IN EAX,0EB ; I/O command 00390052 1AEB SBB CH,BL 00390054 1381 EBEF03DB ADC EAX,DWORD PTR DS:[ECX+DB03EFEB] 0039005A D0EB SHR BL,1 0039005C 0839 OR BYTE PTR DS:[ECX],BH 0039005E D8E9 FSUBR ST,ST(1) 00390060 8F00 POP DWORD PTR DS:[EAX] 00390062 0000 ADD BYTE PTR DS:[EAX],AL 00390064 81EB 51EA01C8 SUB EBX,C801EA51 0039006A EB 06 JMP SHORT 00390072 0039006C D8EB FSUBR ST,ST(3) 0039006E BD 81EB1385 MOV EBP,8513EB81 00390073 C0EB 0C SHR BL,0C 00390076 EB 01 JMP SHORT 00390079 00390078 C3 RETN 00390079 81EB EB56863B SUB EBX,3B8656EB 0039007F EB 52 JMP SHORT 003900D3 00390081 D8EB FSUBR ST,ST(3) 00390083 27 DAA 00390084 F7EB IMUL EBX 00390086 0981 E93CDBAC OR DWORD PTR DS:[ECX+ACDB3CE9],EAX 0039008C 3F AAS 0039008D ^EB D9 JMP SHORT 00390068 0039008F EA 81C0C757 B8D5 JMP FAR D5B8:57C7C081 ; Far jump 00390096 ^EB A0 JMP SHORT 00390038 00390098 EA EBF5DCEB 0981 JMP FAR 8109:EBDCF5EB ; Far jump 0039009F C3 RETN 003900A0 8877 5B MOV BYTE PTR DS:[EDI+5B],DH 003900A3 -66:EB AF JMP SHORT 00000055 003900A6 D8EB FSUBR ST,ST(3) 003900A8 00EB ADD BL,CH 003900AA 05 74CBEB21 ADD EAX,21EBCB74 003900AF D881 C0DC6652 FADD DWORD PTR DS:[ECX+5266DCC0] 003900B5 68 EB0981C3 PUSH C38109EB 003900BA 96 XCHG EAX,ESI 003900BB -E9 9DEDEB9D JMP 9E24EE5D 003900C0 83EB 09 SUB EBX,9 003900C3 81F1 4FFBE7E8 XOR ECX,E8E7FB4F 003900C9 ^EB BC JMP SHORT 00390087 003900CB D8EB FSUBR ST,ST(3) 003900CD 1D EB1253EB SBB EAX,EB5312EB 003900D2 0981 EB3C8F5C OR DWORD PTR DS:[ECX+5C8F3CEB],EAX 003900D8 2D EBC381E9 SUB EAX,E981C3EB 003900DD 3E:FFFF ??? ; Unknown command 003900E0 FFE8 JMP FAR EAX ; Illegal use of register 003900E2 81C1 E1C939FA ADD ECX,FA39C9E1 003900E8 ^EB D9 JMP SHORT 003900C3 003900EA DAE9 FUCOMPP 003900EC 5C POP ESP 003900ED FFFF ??? ; Unknown command 003900EF FF83 EB005BE9 INC DWORD PTR DS:[EBX+E95B00EB] 003900F5 4E DEC ESI 003900F6 FFFF ??? ; Unknown command 003900F8 FFDA CALL FAR EDX ; Illegal use of register

Have fun reversing that!

This part, covering execution flow obfuscation feature, sums up the subject of x86 shellcode obfuscation. I have learned a lot in the process of doing this research and I hope I've managed to show you something new.

From this point forward I may start working on porting the Python obfuscator to C/C++ and develop a stand-alone x86 obfuscation library that may be later used in more advanced projects I have in mind.

Such obfuscation engine would make a strong foundation into development of software anti-cracking protections. My next research may be finding ways to translate x86 instructions into other CPU instructions and writing an emulator engine in assembly language that would emulate the translated virtual instructions in x86 CPU architecture. This is as far as I know the best way to protect your critical code from prying eyes.

The tool in its current form, should do perfectly fine in preventing AV software from detecting known shellcodes embedded in executable files. Metasploit payloads, though, require some work first as they are not fully prepared for obfuscation. Generated payloads assume their execution flow and length won't be modified, so in order to decrease their size, fixed relative offsets are used.

Source code

As always, you can get the latest version of the tool on GitHub.

If you liked this project, you have suggestions or you just want to say hi, you can find me on Twitter @mrgretzky or Google+.

Stay tuned for more posts!

EOF