reversing you-know-what #1 - obf and pre-init

this will be a quick primer on a certain antitamper’s internals. you should know what antitamper im talking about here. this’ll mainly just cover the obfuscation part and some pre-init stuff. note im by no means the best reverse engineer on the planet nor am i good in this field at all so i might be wrong in some stuff. this is a very surface-level analysis so erm yes Lol

opening up the lego game’s main dll file, you’ll notice that you won’t be able to disassemble stuff with IDA:

mov dword ptr [rsp+178h+var_A8], OC24DE5C6h
mov eax, dword ptr [rsp+178h+var_A8]
cmp eax, OFC89AD33h; Compare Two Operands
jb short loc_182777020; Jump if Below (CF = 1)
db 2Eh;
db 48h; K
db 0A2h
db 64h; d
db 0F5h
db 59h; Y
db 70h; p
db 2Ch;,
db 92h
db OFAh
db 64h; d
db 4Fh; 0
db 69h; i
db 0B1h


loc_182777020:
mov dword ptr [rsp+178h+var_A8], 0D7FF27FFh
mov eax, dword ptr [rsp+178h+var_A8]
...

the AT implements this across every function you’ll see in IDA. these weird sequences are also jumbled in with dead instructions scattered throughout the binary.

for example, here’s a small disassembly snippet showing one of the few tricks this AT tries to pull.

MC MNEM
C7 85 50040000 9727EFD7 mov [rbp + 00000450], D7EF2797
8B 85 50040000 mov eax, [rbp + 00000450]
48 8B 85 700D0000 mov rax, [rbp + 00000D70]
8B 50 0C mov edx, [rax + 0C]
8D 4A FE lea ecx, [rdx + 02]
83 F9 1E cmp ecx, 1E
0F87 52010000 ja 22E845
48 8D 05 06590400 lea rax, [232DC0]
48 63 0C 88 movsxd rcx, dword ptr [rax + rcx*4] ; rcx * 4
48 01 C1 add rcx, rax
FF E1 jmp rcx ; THIS LINE
C7 85 50040000 1005A043 mov [rbp + 00000450], 43A00510

above FF E1 jmp rcx is add rcx, rax, where rax is a jmptable, rcx is a hardcoded offset for the relative address of the next instruction, with the size of each entry being the usual 4 bytes.

in addition to this, some functions also intentionally bombards the stack with opaque values:

mov [rsp+arg_8], rdx
push rbp
push r15
push r14
push r13
push rsi
push rdi
push rbx
sub rsp, 80h ; Integer Subtraction
lea rbp, [rdx+80h] ; Load Effective Address
movaps [rsp+118h+var_E8], xmm15 ; Move Aligned Four Packed Single-FP
movdqa [rsp+118h+var_C8], xmm14 ; Move Aligned Double Quadword
movdqa [rsp+118h+var_C8], xmm13 ; Move Aligned Double Quadword
movaps [rsp+118h+var_E8], xmm12 ; Move Aligned Four Packed Single-FP
movdqa [rsp+118h+var_A8], xmm11 ; Move Aligned Double Quadword
movaps [rsp+118h+var_98], xmm10 ; Move Aligned Four Packed Single-FP
movaps [rsp+118h+var_88], xmm9 ; Move Aligned Four Packed Single-FP
movaps [rsp+118h+var_78], xmm8 ; Move Aligned Four Packed Single-FP
movaps [rsp+118h+var_68], xmm7 ; Move Aligned Four Packed Single-FP
movaps [rsp+118h+var_58], xmm6 ; Move Aligned Four Packed Single-FP
movaps xmm6, [rsp+118h+var_58] ; Move Aligned Four Packed Single-FP
movaps xmm7, [rsp+118h+var_68] ; Move Aligned Four Packed Single-FP
movaps xmm8, [rsp+118h+var_78] ; Move Aligned Four Packed Single-FP
movaps xmm9, [rsp+118h+var_88] ; Move Aligned Four Packed Single-FP
....
; even more opaque storage
add rsp, 80h ; Add
pop rbx
pop rdi
pop rsi
pop r13
pop r14
pop r15
pop rbp
retn ; Return Near from Procedure

making their stack frame massive to overwhelm analysis.

another method the AT uses to obfuscate is instead of calling exported functions, they invoke inlined syscalls. here is a short walkthrough on one of them:

48 8D 55 70 lea rdx,[rbp+70]
48 C7 C1 EFFFFFFFFF.. mov rcx,FFFFFFFFFFFFFFFE
FF D3 call rbx
85 C0 test eax,eax

as you see, rbx holds the address of their method stub. the stub looks like this:

mov r10, rcx
mov eax, 0000018D
test byte ptr [7FFE0308], 01
jne 1D11CF302DF
syscall
ret
int 2E
ret

following the call you can see the syscall invocation method is written to an rx allocator. interesting! lets jump back to FF D3 call rbx and look at the stuff above it:

0F B6 14 0F movzx edx, byte ptr [rdi+rcx]
48 8B 9D 180D0000 mov rbx, [rbp+00000D18]
48 89 F8 mov rax,rdi
48 8D 7B 01 lea rdi,[rbx+01]
48 89 BD 180D0000 mov [rbp+00000D18],rdi
83 E3 3F and ebx,3F
32 94 1D 60050000 xor dl,[rbp+rbx+00000560]
88 14 0E mov [rsi+rcx],dl
0F B6 54 08 01 movzx edx, byte ptr [rax+rcx+01]
48 8B 9D 180D0000 mov rbx, [rbp+00000D18]
48 8D 7B 01 lea rdi,[rbx+01]
48 89 BD 180D0000 mov [rbp+00000D18],rdi
48 89 C7 mov rdi,rax
83 E3 3F and ebx,3F
32 94 1D 60050000 xor dl,[rbp+rbx+00000560]
88 54 0E 01 mov [rsi+rcx+01],dl
48 83 C1 02 add rcx,02
49 39 C9 cmp r9,rcx
75 A9 jne image+28CB80
E9 51450000 jmp image+28C14D

interestingly there are two ops to [rsi+rcx], namely 88 54 0E 01 mov [rsi+rcx+01],dl and 88 14 0E mov [rsi+rcx],dl. dynamic analysis on the AT shows that rsi is actually the ptr to the syscall invocation method. weird; as it seems like its writing over the rx alloc for the method, but that alloc couldnt possible do that as it didnt have write perms. if you look closely however it actually turns out they arent the same addresses - they have 2 seperate allocs mapped to the same physical addy but with 2 seperate perms to confuse you.

so, before they call our method to shoot a syscall, they move rx alloc into rbx. interesting!

finally, lets see their LdrInitializeThunk hook. if you dont know what LdrInitializeThunk is, it basically is the entry point for a new thread in a process. when the windows kernel makes a new thread it jumps to it, which performs varius init ops before jumping to the actual start address of the thread. the writes occur in this are interesting:

mov [rsi+rcx+01], dl
mov [rsi+rcx], dl
mov [r14+rcx+01], dl
mov [r14+rcx], dl
...

i wonder what this could possibly be Lol.

LdrInitializeThunk

im sure you still remember this. the fact that they hook this means that our AT can fully manipulate any new thread as its created before we jump to its start address, and therefore can decide if the thread can continue or be terminated.

creating a thread externally does not work and nothing fucking happens because the AT blocks non-wl’d threads. the whitelisting in question can be traced back to a few of their imports. if you open up said AT and use a certain tool to fix the imports you’ll see that they hook certain ntdll imports. one of these is NtCreateThreadEx.

internally speaking, newly made threads by the binary or loaded modules get whitelisted. this ntdll hook then creates a stub that decrypts the start address and jumps to it. after that, they screw up the start address of the thread to the mstub.

by the time a new thread spawns and hits the LdrInitThunk hook, our AT will know if it is a legitimate thread or not. ill provide a disassembly of this stub. you can see it moving and backing up registers before it decrypts the actual start address, later resolving it and finally jumping to it:

48 C7 C1 F0B88462 mov rcx,6284B8F0 
48 29 C8 sub rax,rcx ; rax = rax - rcx
4C 8B 3C 24 mov r15,[rsp] ; restore value from the 0th stack slot -> r15
4C 8B 74 24 08 mov r14,[rsp+08] ; restore
4C 8B 6C 24 10 mov r13,[rsp+10] ; blah blah
4C 8B 64 24 18 mov r12,[rsp+18]
4C 8B 5C 24 20 mov r11,[rsp+20]
4C 8B 54 24 28 mov r10,[rsp+28]
4C 8B 4C 24 30 mov r9,[rsp+30]
4C 8B 44 24 38 mov r8,[rsp+38]
48 8B 7C 24 40 mov rdi,[rsp+40] 
48 8B 6C 24 48 mov rbp,[rsp+48]
48 8B 5C 24 50 mov rbx,[rsp+50]
48 8B 4C 24 58 mov rcx,[rsp+58]
48 83 C4 60 add rsp,60 ; match real thread start
50 push rax ; put resolved addr in ret slot
C3 ret ; continue to true thread EP

cool! next time ill further this analysis and look at how this AT does page encryption, before i (try to, and likely fail to) devise a way to attack it.