Opaque predicates
Today we’re gonna see a packer that’s using some nice anti deobfuscation/emulation tricks, the binary i’m talking aboust goes by the name of “ddos-bin” (MD5: 959F78B4BECAEBEA65D68CE5AE1A9F31) and from the first instructions annunces itself like a very boring target, let’s see some of the tricks it’s using..
Section A
xor ebx, ebx mov ebx, fs:[ebx] std nop mov ebx, [ebx] std repne add ebx, 1 jz short loc_401040
there are some known tricks.. like useless instructions (std, nop) and invalid
prefixes (repne before add) that are easily identifiable and then there is an opaque predicate.
As you can see it gets the pointer to the last EXCEPTION_REGISTRATION struct, that happens to be the default one, installed at thread start, and then gets the prev field that it knows will have value FFFFFFFFh since no new handler has been installed.
Once you add 1 you get 0 and the jump will be always taken.
This trick poses no problem to a reverser, but an automated tool for deobfuscation or emulation should be specifically instructed to handle this case.
Section B
mov edx, large fs:30h ... movzx edx, byte ptr [edx+65h] mov ebx, edx ... dec ebx ... inc ebx js near ptr loc_4010FA+1
What about this one ? It takes the PEB pointer and then the byte at offset 65h,
that is already strange since it’s not aligned, but what is it reading ?
let’s see the PEB structure:
+0x060 UnicodeCaseTableData : Ptr32 Void +0x064 NumberOfProcessors : Uint4B +0x068 NtGlobalFlag : Uint4B
so at +64h you could have something like
01 00 00 00 or 02 00 00 00
and at 65 you will have (unless you have >= 256 processors) always 0,
after the last inc ebx is 0 and SF=0 and the jump will never be taken.
Section C
... ecx = 7C86986Ch ... movzx esi, byte ptr [ecx] ; esi = C2h jbe short $+2 stc sub esi, 18h jnz short loc_401189 ; esi = AAh ... loc_401189: sub esi, 0AAh ;esi = 0 jz short loc_401198 ; alway taken
7C86986Ch is the next to last byte of GetLongPathNameA function (check kernel32.dll on windows XP). The last instruction of the function is retn 0Ch so the last bytes are:
C2 0C 00
and this ‘knowledge’ is used to generate two opaque predicates (see checks before the two jumps).
After this nice sequence of tricks and thousands of jumps here and there it finally starts decrypting some of its code, it first calls VirtualProtect to set RWX permission to an area of memory that it later decrypts.
Control is transferred to this new decrypted section of code, this new stage allocates some memory with VirtualAlloc and copies in it two chunks of its own code.
.text:00402841 mov dword ptr [esp+2Ch], 9CE0D4Ah
.text:00402849 lea eax, byte_404549[ebp]
; call function to get function address
.text:0040284F call eax ; 004028A5
...
.text:00402859 push 40h ; RWX
.text:0040285B push 3000h ; RESERVE | COMMIT
.text:00402860 mov edx, ss:dword_4044AA[ebp]
.text:00402866 add edx, ss:dword_4044B2[ebp]
.text:0040286C push edx ; size
.text:0040286D push 0
.text:0040286F call eax ; VirtualAlloc
...
.text:0040287D mov edi, eax
.text:0040287F mov esi, ss:dword_4044A6[ebp] ; 299fh
.text:00402885 add esi, [esp+4] ; = 40299f
.text:00402889 mov ecx, ss:dword_4044AA[ebp] ; a6c6 / size
.text:0040288F rep movsb ; copy to allocated space
.text:00402891 mov esi, ss:dword_4044AE[ebp]
.text:00402897 add esi, [esp+4]
.text:0040289B mov ecx, ss:dword_4044B2[ebp]
.text:004028A1 rep movsb
.text:004028A3 push eax ; jump to allocated space 40299f
.text:004028A4 retn
The api resolution is done manually using the module list pointed by PEB, functions are resolved when needed and identified using the crc32 of their name (9CE0D4Ah is the crc32 of ‘VirtualAlloc’).
Once more, control is passed to this new (last) stage, whose aim is to prepare to execute the embedded executable. First of all space is allocated to contain the decrypted executable using LocalAlloc, the binary is then descrambled (the first time) using as key of 16 bytes from the previous stage, and descrambled (again) and finally copied in the newly alloced space.
The final stage of the packer consist in loading the embedded executable, but instead of writing it to a file and creating a new process, the loading process is done manually replacing the code and data of the current process with those of the packed binary.
Once the substitution process has been completed and the imports resolved control is passed to the entry point (OEP).
so.. in the end all of this for what ? :)
the protected binary that extracts (in turn) a kernel driver and installs it..
but that’s for the next post :P

