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]
mov     ebx, [ebx]
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
sub     esi, 18h
jnz     short loc_401189 ; esi = AAh
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 😛


%d bloggers like this: