Today we will take a look at this crackme.
One of the first things we notice if we open given exe in Ghidra is:
local_414 = *(uint *)(*(int *)(*(int *)(in_FS_OFFSET + 0x18) + 0x30) + 2) & 0xff;
...
...
if (local_414 == 0) {
....
}
This is actually anti-debugger trick that diverts execution to a false password check way if it detects debugger.
mov eax, large fs:18h ; Offset 18h has self-pointer to TEB
mov eax, [eax+30h] ; Offset 30h has pointer to PEB
movzx eax, byte ptr[eax+2] ; PEB.BeingDebugged
test eax, eax
This is equivalent to IsDebuggerPresent
call.
The TIB (Thread Information Block) of the current thread can be accessed as an offset of segment register FS for Win32. It is common to first get a linear self-referencing pointer to it stored at FS:[0x18].
On offset 0x30 of TIB we have a pointer to PEB (Process Environment Block). From there with offset 0x02 we can access BeingDebugged
flag. BeingDebugged
flag is set to 1 if the process is being debugged, 0 otherwise.
To bypass this check we can use radare2 and just patch it:
radare2.exe -w CrackMe_1.exe
[0x004075a5]> s 0x00403b3c
[0x00403b3c]> pd 6
0x00403b3c 81e2ff000000 and edx, 0xff ; 255
0x00403b42 89542414 mov dword [esp + 0x14], edx
0x00403b46 e825e5ffff call 0x402070
0x00403b4b e8d0e9ffff call 0x402520
0x00403b50 6a06 push 6 ; 6
0x00403b52 e818380000 call 0x40736f
[0x00403b3c]> "wa xor edx, edx; nop; nop; nop; nop;"
Written 6 byte(s) (xor edx, edx; nop; nop; nop; nop;) = wx 31d290909090
[0x00403b3c]> pd 6
0x00403b3c 31d2 xor edx, edx
0x00403b3e 90 nop
0x00403b3f 90 nop
0x00403b40 90 nop
0x00403b41 90 nop
0x00403b42 89542414 mov dword [esp + 0x14], edx
[0x00403b3c]>
After this change, we can use simply debug the given exe. I used x64dbg. We can just run app to the password prompt, pause executing enter the password and run to user code.
One interesting detail is that the program is using Ftring to encrypt strings. Ftring will generate instructions to set flags corresponding to the given letter and then use lahf
instruction to transfer them to al
register. For example, to generate the letter A
following instruction sequence will be used:
xor al, al
mov cl, 225
add cl, 9
setc bl
xor al, bl
shl al, 1
mov dl, 144
dec dl
lahf
test ah, 10h
jz RHpFexiD
xor al, 1
RHpFexiD:
shl al, 1
mov dh, 2
inc dh
setz bl
xor al, bl
shl al, 1
mov edx, 803704434
inc edx
setp bl
xor al, bl
shl al, 1
mov si, 27175
sub si, 20613
setz bl
xor al, bl
shl al, 1
mov bl, 149
add bl, 39
setp bl
xor al, bl
shl al, 1
mov dh, 59
sub dh, 218
lahf
test ah, 10h
jz IWsajNHQ
xor al, 1
IWsajNHQ:
shl al, 1
mov cx, 19329
add cx, 16579
setp bl
xor al, bl
// Writing char 'A' to memory
mov byte ptr[static_array + 0], al
We can locate reasonably well the ftring instruction sequences using radare2 with:
"/a lahf; test ah, 0x10;"
Unfortunately, radare2 had trouble with lahf
instruction but it turned to be very easy to fix. Just make sure to use the latest version of radare2 (and git is recommended way to get radare2 anyway).
Once we extract instruction sequences we can use unicorn to emulate them and see what are they evaluating to. Example of using unicorn to decode the fstring sequences is given bellow:
from unicorn import *
from unicorn.x86_const import *
X86_CODE32_ARR = [ "32c0b61b80c66d0f92c332c3d0e0b27ffec20f98c332c3d0e0f90f92c332c3d0e0b2c680c23a0f92c332c3d0e0beb79c913081ee50a1e1150f92c332c3d0e0b580fecd0f98c332c3d0e066be2f5f66460f9ac332c3d0e066be0000664e0f98c332c3", "32c0b1aa80c1a70f98c332c3d0e0b1a0fec90f9ac332c3d0e0b200feca0f98c332c3d0e066ba6aba6681c22a810f9ac332c3d0e0b1c8fec10f94c332c3d0e066be0080664e0f98c332c3d0e0bf1a1c119e81c7e6e3ee610f94c332c3d0e066bafcdd6681c204220f94c332c3",
"32c0b3ad80eb9f0f94c332c3d0e066b9000066490f98c332c3d0e0bf697ca53381ef697ca5330f94c332c3d0e0b6f580eec90f98c332c3d0e0b1fb80e9560f92c332c3d0e0b267feca0f94c332c3d0e066b9b0276681c18ca59ff6c41074023401d0e0b1affec19ff6c41074023401",
"32c0f80f92c332c3d0e0b15180c16b0f98c332c3d0e066bfb9086681c707f89ff6c41074023401d0e0b157fec99ff6c41074023401d0e066bbffff66430f94c332c3d0e0bbbfdd791f4b0f9ac332c3d0e066bf67626681c75cd70f92c332c3d0e0badb2036c4420f94c332c3",
"32c0bffebb799e81effa67247f0f9ac332c3d0e066ba5782664a0f9ac332c3d0e0b63980eeaf9ff6c41074023401d0e066bbf0a26681ebc0dc9ff6c41074023401d0e0b580fecd0f98c332c3d0e0b9ffffff7f410f98c332c3d0e0b301fecb0f94c332c3d0e0ba2042c27b81ead42d50730f9ac332c3"]
ADDRESS = 0x1000000
for ftring_insc in X86_CODE32_ARR:
try:
ftring_insc_dec = bytes.fromhex(ftring_insc)
# Initialize emulator in X86-32bit mode
mu = Uc(UC_ARCH_X86, UC_MODE_32)
# map 2MB memory for this emulation
mu.mem_map(ADDRESS, 2 * 1024 * 1024)
# write machine code to be emulated to memory
mu.mem_write(ADDRESS, ftring_insc_dec)
# emulate machine code in infinite time
mu.emu_start(ADDRESS, ADDRESS + len(ftring_insc_dec))
r_al = mu.reg_read(UC_X86_REG_AL)
print(f'>>> AL = {hex(r_al)} {chr(r_al)}')
except UcError as e:
print("ERROR: %s" % e)
I was kinda lazy and didn't finish it completely. To automate example more we could either map memory properly or implement a handler that will skip store/read instructions and just print values from al
when they are called.
Comments
comments powered by Disqus