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!
shift
/ (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