- Random Access Memory
- Posts
- Solving IOLI Crackmes Using Radare2 [Part-I]
Solving IOLI Crackmes Using Radare2 [Part-I]
As I was learning reverse engineering using r2, i tried the IOLI crackmes. This post covers the solutions of two crackmes(0x00,0x01) using r2.
crackme0x00
Let’s open the binary in radare
r2 -A crackme0x00
This command will open crackme0x00 and analayse the binary (-A option is used for analyse) and give the r2 shell.
The information about the binary will be available in i command .
iz- prints strings in data sections
As you can see , the password will be in the data section itself easy peasy !!.
root@n00b:~/Desktop/RE/radare2-workshop-2015/IOLI-crackme/bin-linux# r2 -A crackme0x00[x] Analyze all flags starting with sym. and entry0 (aa)[x] Analyze function calls (aac)[x] Analyze len bytes of instructions for references (aar)[x] Check for objc referencesWarning: aao experimental on 32bit binaries[x] Check for vtables[x] Type matching analysis for all functions (aaft)[x] Propagate noreturn information[x] Use -AA or aaaa to perform additional experimental analysis. -- There's more than one way to skin a cat[0x08048360]> iz[Strings]nth paddr vaddr len size section type string―――――――――――――――――――――――――――――――――――――――――――――――――――――――0 0x00000568 0x08048568 24 25 .rodata ascii IOLI Crackme Level 0x00\n1 0x00000581 0x0848581 10 11 .rodata ascii Password: 2 0x0000058f 0x0804858f 6 7 .rodata ascii 2503823 0x00000596 0x08048596 18 19 .rodata ascii Invalid Password!\n4 0x000005a9 0x080485a9 15 16 .rodata ascii Password OK :)\n[0x08048360]>
You may wonder how the password is stored in data section, the point here is the global variables. In c, gloable variable’s values are stored in data sections . So, what we can understand from this binary is that the password has been initialzed in a global variable and that’s why we see the value of the global variable in our data section. Pls check out this blog
crackme0x01
Open the binary in radare using the same command as above
Now, if we use iz or izz (which gives all the strings in the binary whereas iz only gives strings in data section), we can’t find our password for the binary. so let’s analyse what the binary contains
First we analyse the functions that are in the binary
afl - gives the functions that are in the binary
[0x08048330]> afl0x08048330 1 33 entry00x080482fc 1 6 sym.imp.__libc_start_main0x08048380 6 47 sym.__do_global_dtors_aux0x080483b0 4 50 sym.frame_dummy0x080484e0 4 35 sym.__do_global_ctors_aux0x080484d0 1 5 sym.__libc_csu_fini0x08048504 1 26 sym._fini0x08048460 4 99 sym.__libc_csu_init0x080484d5 1 4 sym.__i686.get_pc_thunk.bx0x080483e4 4 113 main0x080482d4 1 23 sym._init0x08048354 3 33 fcn.080483540x0804830c 1 6 sym.imp.scanf0x0804831c 1 6 sym.imp.printf[0x08048330]>
As we can see from the above, these functions are present in the binary. When the binary gets started the libc_csu_init is called which in-turns call the entry0 function which in-turns calls the main function. Main function is the function where the starting c logic for the binary will be present.
we disassemble only the main function using
pdf @ main
where pdf is for print disassemble function. You can get more help on the command using ? ex: p?,p??,pd??
[0x080483e4]> pdf @ main ; DATA XREF from entry0 @ 0x8048347 ┌ 113: int main (int argc, char **argv, char **envp); │ ; var uint32_t var_4h @ ebp-0x4 │ ; var int32_t var_sp_4h @ esp+0x4│ 0x080483e4 55 push ebp│ 0x080483e5 89e5 mov ebp, esp│ 0x080483e7 83ec18 sub esp, 0x18│ 0x080483ea 83e4f0 and esp, 0xfffffff0│ 0x080483ed b800000000 mov eax, 0│ 0x080483f2 83c00f add eax, 0xf ; 15│ 0x080483f5 83c00f add eax, 0xf ; 15│ 0x080483f8 c1e804 shr eax, 4│ 0x080483fb c1e004 shl eax, 4│ 0x080483fe 29c4 sub esp, eax│ 0x08048400 c70424288504. mov dword [esp], str.IOLI_Crackme_Level_0x01 ; [0x8048528:4]=0x494c4f49 ; "IOLI Crackme Level 0x01\n" ; const char *format │ 0x08048407 e810ffffff call sym.imp.printf ; int printf(const char *format) │ 0x0804840c c70424418504. mov dword [esp], str.Password: ; [0x8048541:4]=0x73736150 ; "Password: " ; const char *format │ 0x08048413 e804ffffff call sym.imp.printf ; int printf(const char *format) │ 0x08048418 8d45fc lea eax, [var_4h]│ 0x0804841b 89442404 mov dword [var_sp_4h], eax│ 0x0804841f c704244c8504. mov dword [esp], 0x804854c ; [0x804854c:4]=0x49006425 ; const char *format │ 0x08048426 e8e1feffff call sym.imp.scanf ; int scanf(const char *format) │ 0x0804842b 817dfc9a1400. cmp dword [var_4h], 0x149a│ ┌─< 0x08048432 740e je 0x8048442│ │ 0x08048434 c704244f8504. mov dword [esp], str.Invalid_Password ; [0x804854f:4]=0x61766e49 ; "Invalid Password!\n" ; const char *format │ │ 0x0804843b e8dcfeffff call sym.imp.printf ; int printf(const char *format) │ ┌──< 0x08048440 eb0c jmp 0x804844e│ ││ ; CODE XREF from main @ 0x8048432│ │└─> 0x08048442 c70424628504. mov dword [esp], str.Password_OK_: ; [0x8048562:4]=0x73736150 ; "Password OK :)\n" ; const char *format │ │ 0x08048449 e8cefeffff call sym.imp.printf ; int printf(const char *format) │ │ ; CODE XREF from main @ 0x8048440│ └──> 0x0804844e b800000000 mov eax, 0│ 0x08048453 c9 leave└ 0x08048454 c3 ret[0x080483e4]>
Before going straight to understand the assembly code , read the intel blog and also this helper for the intel blog
As you have guessed , the binary is a 32 bit binary . There are two ways to find it, one is to use file command other we can guess by looking at the assembly itself since eax,edx and all e’s in the registers denotes 32 bit registers if the eax was rax and edx as rdx then it is a 64 bit registers which in-turn denotes 64 bit binary.
The first 10 line of assembly code is for function prologue which is used to setup the stack . Y stack? because the main function is called and when a function is called a stack will be created to contain the local varibles of the called functions. Note: stack starts from higher addresses (read on memory layout in c for more)
After the function stack is set, printf function is called
So we can start to understand the assembly from the 11th line.
│ 0x08048400 c70424288504. mov dword [esp], str.IOLI_Crackme_Level_0x01 ; [0x8048528:4]=0x494c4f49 ; "IOLI Crackme Level 0x01\n" ; const char *format │ 0x08048407 e810ffffff call sym.imp.printf ; int printf(const char *format) │ 0x0804840c c70424418504. mov dword [esp], str.Password: ; [0x8048541:4]=0x73736150 ; "Password: " ; const char *format │ 0x08048413 e804ffffff call sym.imp.printf
The mov command will move the IOLI_Crackme_Level_0x01 string to esp’s location so esp is nothing but the stack pointer and we can deduce that the string is passed as an argument to printf function and the print will be called and the string IOLI_Crackme_Level_0x01 will be displayed and the again the next printf will be called in the same manner.
│ 0x08048418 8d45fc lea eax, [var_4h]│ 0x0804841b 89442404 mov dword [var_sp_4h], eax│ 0x0804841f c704244c8504. mov dword [esp], 0x804854c ; [0x804854c:4]=0x49006425 ; const char *format │ 0x08048426 e8e1feffff call sym.imp.scanf ; int scanf(const char *format) │ 0x0804842b 817dfc9a1400. cmp dword [var_4h], 0x149a
The next command is lea which will load the address of var_4h , so var_4h is the location where our input is stored. How do we know that it is in var_4h the input will be stored , it is because the scanf contains two arguments , one is the format string and the other is the variable to store the input. In assembly the argument will be loaded from right to left( it depends on calling conventions ) and since the var_4h is loaded first, the input will be in var_4h. As you can see the format string is the next one to be loaded which is available in 0x804854c. We can extract the string from 0x804854c to see the stored format string by using pfz
pfz @ 0x804854c
which is print format as string @ addr
[0x08048330]> pfz @ 0x804854c0x0804854c = "%d"
The input will be stored in var_4h so lets rename the var_4h as input for better understanding , we can rename those variable names which is given by r2 (note: r2 is the one which provides these varaible names for our understanding and it will not be in the binary) using afvn
afvn input var_4h
use afv? for understanding what the command does
But before issuing the afvn command we should seek the address to main function so that r2 can find tha var_4h which is only available in main function only ,so it will look like this
[0x08048330]> s main[0x080483e4]> afvn input var_4h[0x080483e4]> pdf @ main ; DATA XREF from entry0 @ 0x8048347┌ 113: int main (int argc, char **argv, char **envp);│ ; var uint32_t input @ ebp-0x4│ ; var int32_t var_sp_4h @ esp+0x4│ 0x080483e4 55 push ebp│ 0x080483e5 89e5 mov ebp, esp│ 0x080483e7 83ec18 sub esp, 0x18│ 0x080483ea 83e4f0 and esp, 0xfffffff0│ 0x080483ed b800000000 mov eax, 0│ 0x080483f2 83c00f add eax, 0xf ; 15│ 0x080483f5 83c00f add eax, 0xf ; 15│ 0x080483f8 c1e804 shr eax, 4│ 0x080483fb c1e004 shl eax, 4│ 0x080483fe 29c4 sub esp, eax│ 0x08048400 c70424288504. mov dword [esp], str.IOLI_Crackme_Level_0x01 ; [0x8048528:4]=0x494c4f49 ; "IOLI Crackme Level 0x01\n" ; const char *format │ 0x08048407 e810ffffff call sym.imp.printf ; int printf(const char *format) │ 0x0804840c c70424418504. mov dword [esp], str.Password: ; [0x8048541:4]=0x73736150 ; "Password: " ; const char *format │ 0x08048413 e804ffffff call sym.imp.printf ; int printf(const char *format) │ 0x08048418 8d45fc lea eax, [input]│ 0x0804841b 89442404 mov dword [var_sp_4h], eax│ 0x0804841f c704244c8504. mov dword [esp], 0x804854c ; [0x804854c:4]=0x49006425 ; const char *format │ 0x08048426 e8e1feffff call sym.imp.scanf ; int scanf(const char *format) │ 0x0804842b 817dfc9a1400. cmp dword [input], 0x149a│ ┌─< 0x08048432 740e je 0x8048442│ │ 0x08048434 c704244f8504. mov dword [esp], str.Invalid_Password ; [0x804854f:4]=0x61766e49 ; "Invalid Password!\n" ; const char *format │ │ 0x0804843b e8dcfeffff call sym.imp.printf ; int printf(const char *format) │ ┌──< 0x08048440 eb0c jmp 0x804844e│ ││ ; CODE XREF from main @ 0x8048432│ │└─> 0x08048442 c70424628504. mov dword [esp], str.Password_OK_: ; [0x8048562:4]=0x73736150 ; "Password OK :)\n" ; const char *format │ │ 0x08048449 e8cefeffff call sym.imp.printf ; int printf(const char *format) │ │ ; CODE XREF from main @ 0x8048440│ └──> 0x0804844e b800000000 mov eax, 0│ 0x08048453 c9 leave└ 0x08048454 c3 ret[0x080483e4]>
The last part of the binary is checking the password input value to the correct value using cmp
│ 0x0804842b 817dfc9a1400. cmp dword [input], 0x149a│ ┌─< 0x08048432 740e je 0x8048442│ │ 0x08048434 c704244f8504. mov dword [esp], str.Invalid_Password ; [0x804854f:4]=0x61766e49 ; "Invalid Password!\n" ; const char *format │ │ 0x0804843b e8dcfeffff call sym.imp.printf ; int printf(const char *format) │ ┌──< 0x08048440 eb0c jmp 0x804844e│ ││ ; CODE XREF from main @ 0x8048432│ │└─> 0x08048442 c70424628504. mov dword [esp], str.Password_OK_: ; [0x8048562:4]=0x73736150 ; "Password OK :)\n" ; const char *format │ │ 0x08048449 e8cefeffff call sym.imp.printf ; int printf(const char *format)
As said, the cmp instruction compares our input variable which contains our input for password to 0x149. so 0x149 is in hex ofcouse you can use python or other ways to convert hex to int but in radare2 we can convert using ?
[0x080483e4]> ? 0x149aint32 5274uint32 5274hex 0x149aoctal 012232unit 5.2Ksegment 0000:049astring "\x9a\x14"fvalue: 5274.0float: 0.000000fdouble: 0.000000binary 0b0001010010011010trits 0t21020100[0x080483e4]>
As you can see, the int format for our hex value 0x149a is 5274. And if we continue the assembly code, after cmp it jumps if equal (je) to 0x8048442 which is nothing but printing password ok which means we found the password ;).
crackme0x02
As there will no information about the password in data sections , we jump to analyse the assembly code.
We will open the binary using r2 -A crackme0x02 and pdf @ main.
[0x08048330]> pdf @ main ; DATA XREF from entry0 @ 0x8048347┌ 144: int main (int argc, char **argv, char **envp);│ ; var uint32_t var_ch @ ebp-0xc│ ; var signed int var_8h @ ebp-0x8│ ; var int32_t input @ ebp-0x4│ ; var int32_t var_sp_4h @ esp+0x4│ 0x080483e4 55 push ebp│ 0x080483e5 89e5 mov ebp, esp│ 0x080483e7 83ec18 sub esp, 0x18│ 0x080483ea 83e4f0 and esp, 0xfffffff0│ 0x080483ed b800000000 mov eax, 0│ 0x080483f2 83c00f add eax, 0xf ; 15│ 0x080483f5 83c00f add eax, 0xf ; 15│ 0x080483f8 c1e804 shr eax, 4│ 0x080483fb c1e004 shl eax, 4│ 0x080483fe 29c4 sub esp, eax│ 0x08048400 c70424488504. mov dword [esp], str.IOLI_Crackme_Level_0x02 ; [0x8048548:4]=0x494c4f49 ; "IOLI Crackme Level 0x02\n" ; const char *format │ 0x08048407 e810ffffff call sym.imp.printf ; int printf(const char *format) │ 0x0804840c c70424618504. mov dword [esp], str.Password: ; [0x8048561:4]=0x73736150 ; "Password: " ; const char *format │ 0x08048413 e804ffffff call sym.imp.printf ; int printf(const char *format) │ 0x08048418 8d45fc lea eax, [input]│ 0x0804841b 89442404 mov dword [var_sp_4h], eax│ 0x0804841f c704246c8504. mov dword [esp], 0x804856c ; [0x804856c:4]=0x50006425 ; const char *format │ 0x08048426 e8e1feffff call sym.imp.scanf ; int scanf(const char *format) │ 0x0804842b c745f85a0000. mov dword [var_8h], 0x5a ; 'Z' ; 90│ 0x08048432 c745f4ec0100. mov dword [var_ch], 0x1ec ; 492│ 0x08048439 8b55f4 mov edx, dword [var_ch]│ 0x0804843c 8d45f8 lea eax, [var_8h]│ 0x0804843f 0110 add dword [eax], edx│ 0x08048441 8b45f8 mov eax, dword [var_8h]│ 0x08048444 0faf45f8 imul eax, dword [var_8h]│ 0x08048448 8945f4 mov dword [var_ch], eax│ 0x0804844b 8b45fc mov eax, dword [input]│ 0x0804844e 3b45f4 cmp eax, dword [var_ch]│ ┌─< 0x08048451 750e jne 0x8048461│ │ 0x08048453 c704246f8504. mov dword [esp], str.Password_OK_: ; [0x804856f:4]=0x73736150 ; "Password OK :)\n" ; const char *format │ │ 0x0804845a e8bdfeffff call sym.imp.printf ; int printf(const char *format) │ ┌──< 0x0804845f eb0c jmp 0x804846d│ ││ ; CODE XREF from main @ 0x8048451│ │└─> 0x08048461 c704247f8504. mov dword [esp], str.Invalid_Password ; [0x804857f:4]=0x61766e49 ; "Invalid Password!\n" ; const char *format │ │ 0x08048468 e8affeffff call sym.imp.printf ; int printf(const char *format) │ │ ; CODE XREF from main @ 0x804845f│ └──> 0x0804846d b800000000 mov eax, 0│ 0x08048472 c9 leave└ 0x08048473 c3 ret[0x08048330]>
Let’s analyse the binary, The first 10 lines is for setting up the function’s stack and after that IOLI_Crackme_Level_0x02 is loaded and sent as an argument to printf and it is called and the same for next printf.
mat) │ 0x08048418 8d45fc lea eax, [input]│ 0x0804841b 89442404 mov dword [var_sp_4h], eax│ 0x0804841f c704246c8504. mov dword [esp], 0x804856c ; [0x804856c:4]=0x50006425 ; const char *format │ 0x08048426 e8e1feffff call sym.imp.scanf ; int scanf(const char *format)
The above assembly is same as crackme0x01 and our input will be in var_4h and we will change the r2 variable name var_4h to input for better understanding.
│ 0x0804842b c745f85a0000. mov dword [var_8h], 0x5a ; 'Z' ; 90│ 0x08048432 c745f4ec0100. mov dword [var_ch], 0x1ec ; 492│ 0x08048439 8b55f4 mov edx, dword [var_ch]│ 0x0804843c 8d45f8 lea eax, [var_8h]│ 0x0804843f 0110 add dword [eax], edx│ 0x08048441 8b45f8 mov eax, dword [var_8h]│ 0x08048444 0faf45f8 imul eax, dword [var_8h]│ 0x08048448 8945f4 mov dword [var_ch], eax│ 0x0804844b 8b45fc mov eax, dword [input]│ 0x0804844e 3b45f4 cmp eax, dword [var_ch]│ ┌─< 0x08048451 750e jne 0x8048461│ │ 0x08048453 c704246f8504. mov dword [esp], str.Password_OK_: ; [0x804856f:4]=0x73736150 ; "Password OK :)\n" ; const char *format │ │ 0x0804845a e8bdfeffff call sym.imp.printf ; int printf(const char *format) │ ┌──< 0x0804845f eb0c jmp 0x804846d│ ││ ; CODE XREF from main @ 0x8048451│ │└─> 0x08048461 c704247f8504. mov dword [esp], str.Invalid_Password ; [0x804857f:4]=0x61766e49 ; "Invalid Password!\n" ; const char *format │ │ 0x08048468 e8affeffff call sym.imp.printf ; int printf(const char *format)
This is the important part in the binary. The first mov statement will load 0x5a which is 90. But we see ‘Z’ in radare near the statement, because r2 tries to convert to char and 90 is ‘Z’ letter in ascii. So, we can deduce that there is a variable has value 90 and also another variable of value 492. Let’s rename those variable names to int90 and int492.
[0x08048330]> s main[0x080483e4]> afvn int90 var_8h[0x080483e4]> afvn int492 var_ch[0x080483e4]> [0x080483e4]> pdf ; DATA XREF from entry0 @ 0x8048347 ┌ 144: int main (int argc, char **argv, char **envp); │ ; var uint32_t int492 @ ebp-0xc │ ; var signed int int90 @ ebp-0x8 │ ; var int32_t input @ ebp-0x4 │ ; var int32_t var_sp_4h @ esp+0x4 │ 0x080483e4 55 push ebp │ 0x080483e5 89e5 mov ebp, esp │ 0x080483e7 83ec18 sub esp, 0x18 │ 0x080483ea 83e4f0 and esp, 0xfffffff0 │ 0x080483ed b800000000 mov eax, 0 │ 0x080483f2 83c00f add eax, 0xf ; 15 │ 0x080483f5 83c00f add eax, 0xf ; 15 │ 0x080483f8 c1e804 shr eax, 4 │ 0x080483fb c1e004 shl eax, 4 │ 0x080483fe 29c4 sub esp, eax │ 0x08048400 c70424488504. mov dword [esp], str.IOLI_Crackme_Level_0x02 ; [0x8048548:4]=0x494c4f49 ; "IOLI Crackme Level 0x02\n" ; const char *format │ 0x08048407 e810ffffff call sym.imp.printf ; int printf(const char *format) │ 0x0804840c c70424618504. mov dword [esp], str.Password: ; [0x8048561:4]=0x73736150 ; "Password: " ; const char *format │ 0x08048413 e804ffffff call sym.imp.printf ; int printf(const char *format) │ 0x08048418 8d45fc lea eax, [input] │ 0x0804841b 89442404 mov dword [var_sp_4h], eax│ 0x0804841f c704246c8504. mov dword [esp], 0x804856c ; [0x804856c:4]=0x50006425 ; const char *format │ 0x08048426 e8e1feffff call sym.imp.scanf ; int scanf(const char *format) │ 0x0804842b c745f85a0000. mov dword [int90], 0x5a ; 'Z' ; 90│ 0x08048432 c745f4ec0100. mov dword [int492], 0x1ec ; 492│ 0x08048439 8b55f4 mov edx, dword [int492]│ 0x0804843c 8d45f8 lea eax, [int90]│ 0x0804843f 0110 add dword [eax], edx│ 0x08048441 8b45f8 mov eax, dword [int90]│ 0x08048444 0faf45f8 imul eax, dword [int90]│ 0x08048448 8945f4 mov dword [int492], eax│ 0x0804844b 8b45fc mov eax, dword [input]│ 0x0804844e 3b45f4 cmp eax, dword [int492]│ ┌─< 0x08048451 750e jne 0x8048461│ │ 0x08048453 c704246f8504. mov dword [esp], str.Password_OK_: ; [0x804856f:4]=0x73736150 ; "Password OK :)\n" ; const char *format │ │ 0x0804845a e8bdfeffff call sym.imp.printf ; int printf(const char *format) │ ┌──< 0x0804845f eb0c jmp 0x804846d│ ││ ; CODE XREF from main @ 0x8048451│ │└─> 0x08048461 c704247f8504. mov dword [esp], str.Invalid_Password ; [0x804857f:4]=0x61766e49 ; "Invalid Password!\n" ; const char *format │ │ 0x08048468 e8affeffff call sym.imp.printf ; int printf(const char *format) │ │ ; CODE XREF from main @ 0x804845f│ └──> 0x0804846d b800000000 mov eax, 0│ 0x08048472 c9 leave└ 0x08048473 c3 ret[0x080483e4]>
Ofcourse you may have doubt , why the variable int90 can’t be a character. There are two reasons, one is the mov statement has dword[int90] which tells you to move 4 bytes (which is integer size) to int90 and if it was char then the statment should have mov byte[int90],90 since char is 1 byte. The other reason is that as we analyse the code the binary does some addition and multipication operation on two variables if one is char and other is int then there should be a movzx kindof statement to convert int to char and as we dont find such we can surely say that those two variables are integers.
Continuing from where we left, two variables are initialized int90 and int492 . So the next statement will move int492 to edx and move int90 to eax and add those and the result will be in int492. The reason y there is lea instruction instead of mov statment is that if the assembly use mov statment for loading int90 then the added result will be stored in eax itself and there should be another line to move eax to int90, kindof inefficient and always compiler tries to give efficient compiled codes. The next statements will mutliply int90 and int90 and the result will be in int90 . So the operations that is done will be,
int90=int90+int492int90=int90*int90cmp input,int90
And the final value of the operation will be 338724 and we found the password!.
I know it is not good to continue after this as you may feel brainfucked, it’s okay . Start solving the crackme’s and if you can’t solve it, you can refer this blog.
Will see you in the next blog post.
Reply