X86 Shellcode Obfuscation - Part 3

X86 Shellcode Obfuscation - Part 3

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
E9 - JMP


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!