I analyzed some binaries in x86/x86-64 using some obfuscation tricks. One was called overlapping instructions. Can someone explain how does this obfuscation work and how to work around?
-
I have wondered what the proper term for this is. I have heard 'instruction scission' used fairly frequently.– lynksCommented Apr 3, 2013 at 14:14
-
I encounter several way of speaking about this technique. But, it is true that none was broadly adopted by the community. "Overlaping Instructions" was the most used I can find since now. But, I might have read only a small part of the existing documentation about it.– perrorCommented Apr 4, 2013 at 8:14
4 Answers
The paper Static Analysis of x86 Executables explains overlapping instructions quite well. The following example is taken from it (page 28):
0000: B8 00 03 C1 BB mov eax, 0xBBC10300
0005: B9 00 00 00 05 mov ecx, 0x05000000
000A: 03 C1 add eax, ecx
000C: EB F4 jmp $-10
000E: 03 C3 add eax, ebx
0010: C3 ret
By looking at the code, it is not apparent what the value of eax will be at the return instruction (or that the return instruction is ever reached, for that matter). This is due to the jump from 000C to 0002, an address which is not explicitly present in the listing (jmp $-10 denotes a relative jump from the current program counter value, which is 0xC, and 0xC10 = 2). This jump transfers control to the third byte of the five byte long move instruction at address 0000. Executing the byte sequence starting at address 0002 unfolds a completely new instruction stream:
0000: B8 00 03 C1 BB mov eax, 0xBBC10300
0005: B9 00 00 00 05 mov ecx, 0x05000000
000A: 03 C1 add eax, ecx
000C: EB F4 jmp $-10
0002: 03 C1 add eax, ecx
0004: BB B9 00 00 00 mov ebx, 0xB9
0009: 05 03 C1 EB F4 add eax, 0xF4EBC103
000E: 03 C3 add eax, ebx
0010: C3 ret
It would be interesting to know if/how Ida Pro and especially the Hex Rays plugin handle this. Perhaps @IgorSkochinsky can comment on this...
It's also known as the 'jump in the middle' trick.
explanation
execution rules
- most instructions take more than one byte to be encoded
- they can take up to 15 bytes on modern CPUs
- execution can start at any position as long as permissions are valid
so any byte following the first one of an instruction can be re-used to start another instruction.
abusing disassemblers
- straighforward disassemblers start the next instruction right after the end of the last one.
so such disassemblers (that don't follow the flow) will hide the instruction that is in the middle of a visible one.
examples
trivial
00: EB 01 jmp 3
02: 68 c3 90 90 90 push 0x909090c3
will effectively execute as
00: EB 01 jmp 3
03: C3 retn
...
as the first jmp
skips the first byte 68
(which encodes an immediate push) of the following instruction.
multiple overlaps
from this example, 69 84
defines an imul
instruction that can take up to 11 bytes. Thus you can fit several lines of instruction in its 'fake' operands.
00: EB02 jmp 4
02: 69846A40682C104000EB02 imul eax, [edx + ebp*2 + 0102C6840], 0x002EB0040
0D: ....
will actually be executed as
00: EB02 jmp 4
04: 6A40 push 040
06: 682C104000 push 0x40102C
0B: EB02 jmp 0xF
0F: ...
instruction overlapping itself
The instruction is jumping in the 2nd byte of itself:
00: EBFF jmp 1
02: C0C300 rol bl, 0
will actually be executed as
00: EBFF jmp 1
01: FFC0 inc eax
03: C3 retn
different CPU modes
this obfuscation can be extended to jumping to the same EIP but in different CPU mode:
- 64b CPUs still supports 32b instruction
- 64b mode is using
0x33
forcs
- some instructions are available only in a particular mode:
arpl
in 32b modemovsxd
in 64b mode
so you can jump to the same EIP
but with a different CS
, and get different instructions.
In this example, this code is first executed in 32b mode:
00: 63D8 arpl ax,bx
02: 48 dec eax
03: 01C0 add eax,eax
05: CB retf
and then re-executed in 64 bit mode as:
00: 63D8 movsxd rbx,eax
02: 4801C0 add rax,rax
05: CB retf
In this case, the instructions are overlapping, not because of a different EIP, but because the CPU temporarily changed from 32b to 64b mode.
-
1Is it possible to change from 32 to 64-bit mode as a program running in user space on any major operating systems?– DougallCommented Apr 9, 2013 at 0:26
-
1@Dougall yes. On Windows it is done by
X86SwitchTo64BitMode()
(or manually with a far call/jump with the segment selector 33). However I'm quite certain that this is specific to the windows WOW64 implementation and not in applicable in another OS. Commented Sep 4, 2014 at 20:17 -
@Ange, can you update the link code.google.com/p/corkami/source/browse/trunk/src/CoST/… ? Thank you!– robertCommented Jul 3, 2017 at 9:09
Almost any multi-byte instruction can be used as an overlapping instruction in x86/x86_64. The reason is very easy: x86 and x86_64 instruction sets are CISC. Which means, among other things, that the instructions doesn't have a fixed length. So, as the instruction are variable length, carefully writing that machine code, every instruction is susceptible of hiding overlapping instructions.
For example, given the following code:
[0x00408210:0x00a31e10]> b
0x000050f5 (01) 56 PUSH ESI
0x000050f6 (04) 8b742408 MOV ESI, [ESP+0x8]
0x000050fa (01) 57 PUSH EDI
0x000050fb (03) c1e603 SHL ESI, 0x3
0x000050fe (06) 8bbe58a04000 MOV EDI, [ESI+0x40a058]
0x00005104 (01) 57 PUSH EDI
0x00005105 (06) ff15f4804000 CALL 0x004080f4 ; 1 KERNEL32.dll!GetModuleHandleA
0x0000510b (02) 85c0 TEST EAX, EAX
0x0000510d (02) 750b JNZ 0x0000511a ; 2
Let's suppose that somewhere after the last instruction there is a jump in the middle of some instruction in the displayed code as, for example, to the 2nd byte in the MOV ESI... instruction:
[0x000050f7:0x00405cf7]> c
0x000050f7 (02) 7424 JZ 0x0000511d ; 1
0x000050f7 ----------------------------------------------------------------------
0x000050f9 (03) 0857c1 OR [EDI-0x3f], DL
0x000050fc (02) e603 OUT 0x3, AL
It turns out that this instruction changes to a JZ. Which is valid. Jumping to the 3rd byte...
[0x000050f7:0x00405cf7]> s +1
[0x000050f8:0x00405cf8]> c
0x000050f8 (02) 2408 AND AL, 0x8
0x000050fa (01) 57 PUSH EDI
0x000050fb (03) c1e603 SHL ESI, 0x3
0x000050fe (06) 8bbe58a04000 MOV EDI, [ESI+0x40a058]
Jumping to the 2nd byte of the CALL instruction:
[0x000050f5:0x00405cf5]> s 0x5106
[0x00005106:0x00405d06]> c
0x00005106 (05) 15f4804000 ADC EAX, 0x4080f4 ; '\x8e\x91'
0x0000510b (02) 85c0 TEST EAX, EAX
0x0000510d (02) 750b JNZ 0x0000511a ; 1
As you can see, virtually any multi-byte instruction is susceptible of being used as an overlapping instruction.
This anti-reversing trick is quite often used with opaque predicates in order to f**k the flow graph.
-
So, you mean that there is no way to build such list ? Another point that surprise me a lot about x86/x86-64 opcodes, is its capacity to resynchronise after a while to the original instruction flow. This property helps also a lot in making instruction overlapping. Though, I have no idea why resynchronisation is working so nicely.– perrorCommented Apr 4, 2013 at 8:20
Because x86 instructions can be any length and don't need to be aligned, one instruction's immediate value can be another instruction altogether. For example:
00000000 0531C0EB01 add eax,0x1ebc031
00000005 055090EB01 add eax,0x1eb9050
0000000A 05B010EB01 add eax,0x1eb10b0
0000000F EBF0 jmp short 0x1
This does exactly what it says, until the jump. When it jumps, the immediate value being added to eax become an instruction, so the code looks like:
00000000 05 db 5
00000001 31C0 xor ax,ax xor ax, ax
00000003 EB01 jmp short 0x6
00000005 05 db 5
00000006 50 push ax push ax
00000007 90 nop
00000008 EB01 jmp short 0xb
0000000A 05 db 5
0000000B B010 mov al,0x10 mov al,0x10
....
The instructions which are actually significant are shown in the right-hand column. In this example, short jump instructions are used to skip the add eax
part of the instruction (05
). It should be noted that this could be done more effectively by using an single-byte to eat the 05
s, like 3C05
which is cmp al, 0x5
, and would be harmless in code that doesn't care about the flags.
In the pattern above, you easily replace all the 05
s with 90
(nop) to view the correct disassembly. This can be made trickier by using the 05
s as immediate values to hidden code (that the execution depends on). In reality, the person obfuscating the code would probably not use add eax
over and over again, and might change the execution order to make it messier to trace.
I prepared a sample using the pattern above. This is a 32-bit Linux ELF file in base64. The effect of the hidden code is running execve("//usr/bin/python", 0, 0)
. I suggest you don't run random binaries from SE answers. You can, however, use it to test your disassemblers. IDA, Hopper and objdump all fail miserably at first glance, although I imagine you can get IDA to do it correctly somehow.
f0VMRgEBAQAAAAAAAAAAAAIAAwABAAAAYIAECDQAAAAoAQAAAAAAADQAIAABACgAAwACAAEAAAAA
AAAAAIAECACABAgUAQAAFAEAAAUAAAAAEAAAAAAAAAAAAAAAAAAABTHA6wEFUJDrAQWwEOsBBffg
6wEF9+DrAQWJw+sBBbRu6wEFsG/rAQX34+sBBbRo6wEFsHTrAQVQkOsBBbR56wEFsHDrAQX34+sB
BbQv6wEFsG7rAQVQkOsBBbRp6wEFsGLrAQX34+sBBbQv6wEFsHLrAQVQkOsBBbRz6wEFsHXrAQX3
4+sBBbQv6wEFsC/rAQVQkOsBBTHJ6wEF9+HrAQWJ4+sBBbAL6wEFzYDrAelN////AC5zaHN0cnRh
YgAudGV4dAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAEA
AAAGAAAAYIAECGAAAAC0AAAAAAAAAAAAAAAQAAAAAAAAAAEAAAADAAAAAAAAAAAAAAAUAQAAEQAA
AAAAAAAAAAAAAQAAAAAAAAA=