Recently, I've found interesting post about solving Mexican crackme.
Let's look in-depth how the solution works and how is it automatically solving this crackme with radare2 and python.
To analyze binary we can use:
radare2 Untitled1.exe
We can analyze all symbols and entry points with aa
. After this, we can list functions using afl
(all functions list).
With s main
(seek) we can position ourselves at the start of main and we can disassemble function with pdf
(print disassembly of function).
We can also use pdf @main
.
Disassembly:
│ int main (int argc, char **argv, char **envp);
│ ; var int32_t var_1ch @ esp+0x1c
│ ; CALL XREF from entry0 @ 0x4013dd
│ 0x0040162c 55 push ebp
│ 0x0040162d 89e5 mov ebp, esp
│ 0x0040162f 83e4f0 and esp, 0xfffffff0
│ 0x00401632 83ec20 sub esp, 0x20
│ 0x00401635 e886090000 call sym.___main
│ 0x0040163a c744241cc100. mov dword [var_1ch], 0xc1 ; [0xc1:4]=-1 ; 193
│ 0x00401642 817c241cc100. cmp dword [var_1ch], 0xc1
│ ┌─< 0x0040164a 7e07 jle 0x401653
│ │ 0x0040164c e8affeffff call sym flag() ; sym.flag
│ ┌──< 0x00401651 eb0c jmp 0x40165f
│ │└─> 0x00401653 c70424034040. mov dword [esp], str.try_harder ; [0x404003:4]=0x20797274 ; "try harder"
│ │ 0x0040165a e8d9100000 call sym._printf ; int printf(const char *format)
│ │ ; CODE XREF from main @ 0x401651
│ └──> 0x0040165f b800000000 mov eax, 0
│ 0x00401664 c9 leave
└ 0x00401665 c3 ret
We see that it will compare 0xc1 with 0xc1 which will always go to fail condition.
If we look at function flag with pdf @sym.flag
, we see:
┌ (fcn) sym.flag 300
│ sym.flag ();
│ ; var int32_t var_ch @ ebp-0xc
│ ; var int32_t var_4h @ esp+0x4
│ ; CALL XREF from main @ 0x40164c
│ 0x00401500 55 push ebp
│ 0x00401501 89e5 mov ebp, esp
│ 0x00401503 83ec28 sub esp, 0x28
│ 0x00401506 c704241d0000. mov dword [esp], 0x1d ; [0x1d:4]=-1 ; 29
│ 0x0040150d e8ee110000 call sym._malloc ; void *malloc(size_t size)
│ 0x00401512 8945f4 mov dword [var_ch], eax
│ 0x00401515 8b45f4 mov eax, dword [var_ch]
│ 0x00401518 c60066 mov byte [eax], 0x66 ; 'f'
│ ; [0x66:1]=255 ; 102
│ 0x0040151b 8b45f4 mov eax, dword [var_ch]
│ 0x0040151e 83c001 add eax, 1
│ 0x00401521 c6006c mov byte [eax], 0x6c ; 'l'
│ ; [0x6c:1]=255 ; 108
│ 0x00401524 8b45f4 mov eax, dword [var_ch]
│ 0x00401527 83c002 add eax, 2
│ 0x0040152a c60061 mov byte [eax], 0x61 ; 'a'
│ ; [0x61:1]=255 ; 97
│ 0x0040152d 8b45f4 mov eax, dword [var_ch]
│ 0x00401530 83c003 add eax, 3
│ 0x00401533 c60067 mov byte [eax], 0x67 ; 'g'
│ ; [0x67:1]=255 ; 103
...
...
It will add all chars of a flag to the array and print them. So let's take a look at solution 1. We need to have r2pipe installed (pip install r2pipe
).
#!/usr/bin/env python3
import r2pipe
r = r2pipe.open("Untitled1.exe")
# 'analyze all" (all symbols and entry-points)
r.cmd("aa")
# seek to symbol 'flag'
r.cmd("s sym.flag")
# print dissembly of function
r.cmd("pdf")
# configure radare2 search engine to search in given range
r.cmd("e search.from = 0x00401500")
r.cmd("e search.to = 0x0040162b")
# search hexadecimal value
r.cmd("/x c600")
flag = bytearray()
# Print hex px,
# j suffix can be used in most of the radare2 commands to get a JSON output
# pxj 3 - print 3 hex bytes in JSON format
# @@ used for looping
for byte_triplet in r.cmd("pxj 3 @@hit0").split('\n'):
try:
byte_triplet = eval(byte_triplet)
flag.append(byte_triplet[2])
except Exception as e:
pass
print(f"FLAG: {flag}")
We will set search range to function we want to analyse and then search for all move eax instructions (c600xx move eax, xx
) and extract byte being moved.
Solution 2, we can patch cmp
instruction so that the program will print the flag for us.
#!/usr/bin/env python3
import r2pipe
import subprocess
subprocess.call(["cp", "./Untitled1.exe", "Cracked.exe"])
# Open file in write mode
r = r2pipe.open("Cracked.exe", ['-w'])
r.cmd('aa')
def print_addr_content(addr):
r.cmd(f"s {addr}")
print(f"Current disas of line at {addr}: {r.cmd('pd 1 ~[3-6]')}")
print(f"Current bytecode of line at {addr}: {r.cmd('pd 1 ~[2]')}")
out = list(map(hex, eval(r.cmd("pxj 6"))))
print(f"Values at {addr}: {out}")
r.cmd(f"s -")
print_addr_content(0x00401642)
print("[!] PATCHING BINARY")
# seek to address
r.cmd("s 0x00401642+4")
r.cmd("px 1")
# w1 write byte
# - decrement
# 1 (how many bytes to decrement)
r.cmd("w1- 1")
r.cmd("px 1")
r.cmd("s 0x00401642")
print("[!] BINARY PATCHED")
print_addr_content(0x00401642)
After this, we can simply run the patched binary to get the flag. Both solutions are really nice way to get more familiar with radare2 scripting.
Comments
comments powered by Disqus