IOLI - crackme 0x03
Source : https://dustri.org/b/defeating-ioli-with-radare2.html
crackme 0x03
IOLI Crackme Level 0x03
Password: qsdmlkj
Invalid Password!
main function
radare2 automatically gives names to the variables used
; var int local_ch @ ebp-0xc
; var int local_8h @ ebp-0x8
; var int local_4h @ ebp-0x4
; var int local_4h_2 @ esp+0x4
; DATA XREF from 0x08048377 (entry0)
Function prologue
0x08048498 push ebp
0x08048499 mov ebp, esp
0x0804849b sub esp, 0x18
0x0804849e and esp, 0xfffffff0
0x080484a1 mov eax, 0
0x080484a6 add eax, 0xf
0x080484a9 add eax, 0xf
0x080484ac shr eax, 4
0x080484af shl eax, 4
0x080484b2 sub esp, eax
Displaying the initial message.
0x080484b4 mov dword [esp], str.IOLI_Crackme_Level_0x03 ; [0x8048610:4]=0x494c4f49 ; "IOLI Crackme Level 0x03\n" ; const char * format
0x080484bb call sym.imp.printf ;[1] ; int printf(const char *format)
0x080484c0 mov dword [esp], str.Password: ; [0x8048629:4]=0x73736150 ; "Password: " ; const char * format
0x080484c7 call sym.imp.printf ;[1] ; int printf(const char *format)
The code gets the user’s input and stores it in local_4h_2
0x080484cc lea eax, [local_4h]
0x080484cf mov dword [local_4h_2], eax
0x080484d3 mov dword [esp], 0x8048634 ; [0x8048634:4]=0x6425 ; const char * format
0x080484da call sym.imp.scanf ;[2] ; int scanf(const char *format)
The unknown part.
0x080484df mov dword [local_8h], 0x5a ; 'Z' ; 90
0x080484e6 mov dword [local_ch], 0x1ec ; 492
0x080484ed mov edx, dword [local_ch]
0x080484f0 lea eax, [local_8h]
0x080484f3 add dword [eax], edx
0x080484f5 mov eax, dword [local_8h]
0x080484f8 imul eax, dword [local_8h]
0x080484fc mov dword [local_ch], eax
0x080484ff mov eax, dword [local_ch]
0x08048502 mov dword [local_4h_2], eax
0x08048506 mov eax, dword [local_4h]
0x08048509 mov dword [esp], eax
0x0804850c call sym.test ;[3]
0x08048511 mov eax, 0
0x08048516 leave
0x08048517 ret
Understanding the unknown part
To have a better understanding of the unknown part I wrote a small assembly that does the same thing and debugged it. I wrote the debugged info in the comments.
The addresses of the variables used are
0x6000108 a 5
0x600010c b 10
0x6000110 c 7
0x6000114 d 3
The equivalent code is :
segment .data
a dd 5
b dd 10
c dd 7
d dd 3
segment .text
global start
start:
mov dword [a],0x5a ; a = 90
mov dword [b],0x1ec ; b = 492
mov edx, dword [b] ; edx = 492
lea eax, [a] ; eax = 0x6000108
add dword [eax],edx ; a = a + edx = 90 + 492 = 582
mov eax, dword [a] ; eax = 582
imul eax, dword [a] ; eax = 582^2 = 338724
mov dword [b], eax ; b = 338724
mov eax, dword [b] ; eax = 338724 => useless instruction
mov dword [c], eax ; c = 338724
mov eax, dword [d] ; eax = d
mov dword [esp], eax
xor eax,eax ; useless instruction used to debug previous one
In the end this code computes 582^2 = 338724 and is placing the result in
a, b and c which are respectively variables local_8h, local_ch and local_4h_2
in function main.
Also the code puts d (which is variable local_4h in function main)
on the stack to prepare the call to sym.test.
sym.test
The sym.test function compares arg_8h and arg_ch.
If the two are equal, the sym.shift function is called with the argument
“Lqydolg#Sdvvzrug$”. Otherwise, the sym.shift function is called with
“Sdvvzrug#RN$$$#=,”.
/ (fcn) sym.test 42
| sym.test (int arg_8h, int arg_ch);
| ; arg int arg_8h @ ebp+0x8
| ; arg int arg_ch @ ebp+0xc
| ; CALL XREF from 0x0804850c (sym.main)
| 0x0804846e push ebp
| 0x0804846f mov ebp, esp
| 0x08048471 sub esp, 8
| 0x08048474 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8
| 0x08048477 cmp eax, dword [arg_ch] ; [0xc:4]=-1 ; 12
| ,=< 0x0804847a je 0x804848a ;[1]
| | 0x0804847c mov dword [esp], str.Lqydolg_Sdvvzrug ; [0x80485ec:4]=0x6479714c ; "Lqydolg#Sdvvzrug$"
| | 0x08048483 call sym.shift ;[2]
| ,==< 0x08048488 jmp 0x8048496 ;[3]
| || ; JMP XREF from 0x0804847a (sym.test)
| |`-> 0x0804848a mov dword [esp], str.Sdvvzrug_RN ; [0x80485fe:4]=0x76766453 ; "Sdvvzrug#RN$$$#=,"
| | 0x08048491 call sym.shift ;[2]
| | ; JMP XREF from 0x08048488 (sym.test)
| `--> 0x08048496 leave
\ 0x08048497 ret
sym.shift
The first thing we can see is that the function ends by calling printf,
which means that this is the function displaying the string “Invalid Password!”.
After the function prologue, the function strlen is called to get the length
of the argument passed as a parameter (either “Lqydolg#Sdvvzrug$” or
“Sdvvzrug#RN$$$#=,”). The result is then compared with local_7ch.
0x08048424 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8
0x08048427 mov dword [esp], eax ; const char * s
0x0804842a call sym.imp.strlen ;[1] ; size_t strlen(const char *s)
0x0804842f cmp dword [local_7ch], eax ; [0x13:4]=-1 ; 19
0x08048432 jae 0x8048450 ;[2]
If 0 (local_7ch) is above or equal to the length of string in eax then the code jumps to the final part which displays a message :
0x08048450 b 8d4588 lea eax, [local_78h] ;get address of string "Invalid password!" in eax
0x08048453 034584 add eax, dword [local_7ch] ;add length of string to eax to go to the end of the string
0x08048456 c60000 mov byte [eax], 0 ;add a \x00 to end the string
0x08048459 8d4588 lea eax, [local_78h] ;load pointer to the string in eax
0x0804845c 89442404 mov dword [local_4h], eax ;move pointer to the string to local_4h
0x08048460 c70424e88504. mov dword [esp], 0x80485e8 ; [0x80485e8:4]=0xa7325 ; const char * format
0x08048467 e8e4feffff call sym.imp.printf ;[4] ; int printf(const char *format)
If we put a breakpoint at 0x08048450 with db 0x08048450 and start the program
with dc, we will see that the variable local_78h contains the string
“Invalid password!”.
We use ps @ebp-0x78 to print the string in local_78h.
And to get which command should be used to display local_78h we used
afvd local_78h.
If the length of the string in eax is strictly positive, the following code is executed (starting from 0x8048434):
| .-> 0x08048424 8b4508 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8
| : 0x08048427 890424 mov dword [esp], eax ; const char * s
| : 0x0804842a e811ffffff call sym.imp.strlen ;[1] ; size_t strlen(const char *s)
| : 0x0804842f 394584 cmp dword [local_7ch], eax ; [0x13:4]=-1 ; 19
| ,==< 0x08048432 731c jae 0x8048450 ;[2]
| |: 0x08048434 8d4588 lea eax, [local_78h] ;address of ebp-0x78 in eax
| |: 0x08048437 89c2 mov edx, eax ;address of ebp-0x78 in edx
| |: 0x08048439 035584 add edx, dword [local_7ch] ;
| |: 0x0804843c 8b4584 mov eax, dword [local_7ch]
| |: 0x0804843f 034508 add eax, dword [arg_8h]
| |: 0x08048442 0fb600 movzx eax, byte [eax]
| |: 0x08048445 2c03 sub al, 3
| |: 0x08048447 8802 mov byte [edx], al
| |: 0x08048449 8d4584 lea eax, [local_7ch]
| |: 0x0804844c ff00 inc dword [eax]
| |`=< 0x0804844e ebd4 jmp 0x8048424 ;[3]
When we debug this code we can see that the string “Invalid Password!” is being
constructed withing the loop.
The interesting part being the instruction sub al, 3.
From this we suppose that a ceasar cipher is being used to mask the strings in
the assembly and the rotation is 3.
To test this hypothesis we can use rahash2.
:> !rahash2 -E rot -S s:-3 -s 'Lqydolg#Sdvvzrug$\n'
Invalid#Password$
It appears we were right ! Now rahash2 does not shift special character. A special python 2.7 script will help us get the correct string.
print "".join([chr(ord(i)-0x3) for i in 'LqydolgSdvvzrug$'])
InvalidPassword!
/ (fcn) sym.shift 90
| sym.shift (int arg_8h);
| ; var int local_7ch @ ebp-0x7c
| ; var int local_78h @ ebp-0x78
| ; arg int arg_8h @ ebp+0x8
| ; var int local_4h @ esp+0x4
| ; CALL XREF from 0x08048491 (sym.test)
| ; CALL XREF from 0x08048483 (sym.test)
| 0x08048414 55 push ebp
| 0x08048415 89e5 mov ebp, esp
| 0x08048417 81ec98000000 sub esp, 0x98
| 0x0804841d c74584000000. mov dword [local_7ch], 0
| ; JMP XREF from 0x0804844e (sym.shift)
| .-> 0x08048424 8b4508 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8
| : 0x08048427 890424 mov dword [esp], eax ; const char * s
| : 0x0804842a e811ffffff call sym.imp.strlen ;[1] ; size_t strlen(const char *s)
| : 0x0804842f 394584 cmp dword [local_7ch], eax ; [0x13:4]=-1 ; 19
| ,==< 0x08048432 731c jae 0x8048450 ;[2]
| |: 0x08048434 8d4588 lea eax, [local_78h]
| |: 0x08048437 89c2 mov edx, eax
| |: 0x08048439 035584 add edx, dword [local_7ch]
| |: 0x0804843c 8b4584 mov eax, dword [local_7ch]
| |: 0x0804843f 034508 add eax, dword [arg_8h]
| |: 0x08048442 0fb600 movzx eax, byte [eax]
| |: 0x08048445 2c03 sub al, 3
| |: 0x08048447 8802 mov byte [edx], al
| |: 0x08048449 8d4584 lea eax, [local_7ch]
| |: 0x0804844c ff00 inc dword [eax]
| |`=< 0x0804844e ebd4 jmp 0x8048424 ;[3]
| | ; JMP XREF from 0x08048432 (sym.shift)
| `--> 0x08048450 8d4588 lea eax, [local_78h]
| 0x08048453 034584 add eax, dword [local_7ch]
| 0x08048456 c60000 mov byte [eax], 0
| 0x08048459 8d4588 lea eax, [local_78h]
| 0x0804845c 89442404 mov dword [local_4h], eax
| 0x08048460 c70424e88504. mov dword [esp], 0x80485e8 ; [0x80485e8:4]=0xa7325 ; const char * format
| 0x08048467 e8e4feffff call sym.imp.printf ;[4] ; int printf(const char *format)
| 0x0804846c c9 leave
\ 0x0804846d c3 ret