Tags: antidebug bytecode rev vm 

Rating: 5.0

# YAY! (988 pts)
This challenge is another PE (32-bit this time, so no hex-rays in my IDA Home). The task's description was:

> unpackme if you can !

which was a bit misleading as we'll see later. The binary had some "weird" anti-debugging tricks which were for the most by ScyllaHide on x64dbg for me. The first anti-debugger trick I encoutered was:
```
push 0
push 0
push ThreadHideFromDebugger
push 0FFFFFFFEh
call esi ; NtSetInformationThread
push 4
push 4
push ThreadHideFromDebugger
push 0FFFFFFFEh
call esi ; NtSetInformationThread
```
which basically shuts down all debugging features of the process who calls this function (if not hooked by ScyllaHide :þ). Then there was a quite huge function in 0x4057B0 that seemed to scrap syscalls numbers from NTDLL, but I didn't spend time to identify what syscalls.

Then another anti-debug function that did "WinVerifyTrust" calls to check some DLL integrity, calling scrapped syscall from previous function and putting vectored exception handler after that. For some reason, the function failed, but I just forced EIP to bypass the check after, so I could trigger the useful part of the binary.

Once the antidebugs bypassed, the binary connects to a server to fetch a string, which is actually:
```
0,1,7,13,9,0,8,4,1,0,10,1,0,11,25,12,2,1,14,2,1,1,1,5,10,16,18,17,0,4,1,0,10,1,0,11,53,19,2,20,3,10,2,3,11,48,21,6,1,1,1,5,32,22,6
```
This string is then splitted by "," character and each int is stored in array. This array is just bytecode for a custom VM, which contains 24 handlers. After some reversing, I was able to decode this bytecode and got this:
```
0,1 // weird init
7 // print welcome
13 // input flag
9,0 // mov r0, flag_size
8 // print flag, srand(0x10c9) and creates an array of (rand() % 7) + 1
4,1,0 // mov r1, 0
10,1,0 // cmp r1, r0
11,25 // jz @26
12,2,1 // mov r2, flag[r1]
14,2 // push r2
1,1,1 // add r1, 1
5,10 // jmp @10
16 // crypt_input_buf
18 // put_serial_vec
17,0 // mov r0, serial_len
4,1,0 // mov r1, 0
10,1,0 // cmp r1, r0
11,53 // jz @53
19,2 // mov r2, enc_input
20,3 // mov r3, input
10,2,3 // cmp r2, r3
11,48 // jz 48
21,6 // print badboy
1,1,1 // add r1, 1
5,32 // jmp @32
22,6 // print goodboy
```
The interesting handler is "crypt_input_buf" handler, which looks like this:
```
.text:004022BD cmp [esi+VMContext.flagbuffer.sz1], 0 ; jumptable 00402151 case 16
.text:004022C1 mov [ebp+i], 0
.text:004022CB jbe def_402151 ; jumptable 00402151 default case
.text:004022D1 mov edi, [esi+VMContext.input_vec.end]
.text:004022D4 mov edx, [esi+VMContext.randvec.end]
.text:004022D7 nop word ptr [eax+eax+00000000h]
.text:004022E0
.text:004022E0 loc_4022E0: ; CODE XREF: DecodeInstruction+30E↓j
.text:004022E0 mov edi, [edi-4]
.text:004022E3 mov ecx, 8
.text:004022E8 mov edx, [edx-4]
.text:004022EB mov eax, edi
.text:004022ED sub ecx, edx
.text:004022EF sar eax, cl
.text:004022F1 mov ecx, edx
.text:004022F3 shl edi, cl
.text:004022F5 or eax, edi
.text:004022F7 movzx ecx, al
.text:004022FA mov eax, [esi+VMContext.enc_input.end]
.text:004022FD mov [ebp+var_C4], ecx
.text:00402303 cmp eax, [esi+VMContext.enc_input.alloc_end]
.text:00402306 jz short loc_402310
.text:00402308 mov [eax], ecx
.text:0040230A add [esi+VMContext.enc_input.end], 4
.text:0040230E jmp short loc_402320
.text:00402310 ; ---------------------------------------------------------------------------
.text:00402310
.text:00402310 loc_402310: ; CODE XREF: DecodeInstruction+2D6↑j
.text:00402310 lea ecx, [ebp+var_C4]
.text:00402316 push ecx ; int
.text:00402317 push eax ; Src
.text:00402318 lea ecx, [esi+VMContext.enc_input]
.text:0040231B call vector_append
```

It's basically a ROL unoptimized by the compiler, with the random array built by the "8" opcode. Then, the "encrypted" username is compared to some buffer defined at the beginning of the function:
```
mov dword ptr [ebp+var_44], 2F16F25Fh
mov dword ptr [ebp+var_44+4], 37CAE6AFh
mov dword ptr [ebp+var_44+8], 0D8D8B037h
mov dword ptr [ebp+var_44+0Ch], 0B1C10EFAh
mov dword ptr [ebp+var_44+10h], 0D56C1AF5h
mov dword ptr [ebp+var_44+14h], 46AFBEADh
mov dword ptr [ebp+var_44+18h], 0E67DC2B9h
mov dword ptr [ebp+var_44+1Ch], 0C63ABDE4h
mov dword ptr [ebp+var_44+20h], 59EB67CAh
mov dword ptr [ebp+var_44+24h], 0DE1DB047h
mov dword ptr [ebp+var_44+28h], 3BDAFAC9h
mov dword ptr [ebp+var_44+2Ch], 0C62C76F6h
mov word ptr [ebp+var_44+30h], 0CCh ; 'Ì'
```

Given this, we can write a decode script to get the flag:
```python
import ctypes

crt = ctypes.cdll.msvcrt

crt.srand(0x10c9)
encodedflag = bytes.fromhex("5F F2 16 2F AF E6 CA 37 37 B0 D8 D8 FA 0E C1 B1 F5 1A 6C D5 AD BE AF 46 B9 C2 7D E6 E4 BD 3A C6 CA 67 EB 59 47 B0 1D DE C9 FA DA 3B F6 76 2C C6 CC".replace(" ", ""))
arr = [(crt.rand() % 7) + 1 for i in range(len(encodedflag))]
decarr = arr[::-1]
lol = []
for i in range(len(arr)):
ret = ((encodedflag[i] << (8 - decarr[i])) | (encodedflag[i] >> decarr[i])) & 0xff
lol.append(ret)
print(bytes(lol[::-1]))
```
Which gives `flag{vm_rotate_vectors_and__much_cpp_classes_yay}`.

Original writeup (https://github.com/ret2school/ctf/blob/master/2021/securinets/rev/README.md).