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!