From 6aea95c5c0e9afa8dfd0a012b773f5992bc6dc95 Mon Sep 17 00:00:00 2001 From: "Azamat H. Hackimov" Date: Mon, 18 Nov 2024 01:34:01 +0300 Subject: [PATCH] Update and cleanup "Crackmes" chapter Improve spelling, use common Markdown syntax. Update most of rizin invocations. For all changed command invocation was used rizin 0.7.3. --- src/crackmes/avatao/01-reverse4/bytecode.md | 3 +- .../avatao/01-reverse4/first_steps.md | 7 +- .../avatao/01-reverse4/instructionset.md | 31 ++- src/crackmes/avatao/01-reverse4/intro.md | 3 +- src/crackmes/avatao/01-reverse4/main.md | 11 +- src/crackmes/avatao/01-reverse4/outro.md | 3 +- src/crackmes/avatao/01-reverse4/rizin.md | 3 +- src/crackmes/avatao/01-reverse4/vmloop.md | 27 ++- .../find-the-easy-pass/identification.md | 2 +- src/crackmes/intro.md | 4 +- src/crackmes/ioli/intro.md | 6 +- src/crackmes/ioli/ioli_0x00.md | 14 +- src/crackmes/ioli/ioli_0x01.md | 100 +++++----- src/crackmes/ioli/ioli_0x02.md | 176 ++++++++++-------- src/crackmes/ioli/ioli_0x03.md | 50 ++--- src/crackmes/ioli/ioli_0x04.md | 84 ++++----- src/crackmes/ioli/ioli_0x05.md | 11 +- src/crackmes/ioli/ioli_0x06.md | 29 +-- src/crackmes/ioli/ioli_0x07.md | 117 ++++++------ src/crackmes/ioli/ioli_0x08.md | 11 +- src/crackmes/ioli/ioli_0x09.md | 18 +- 21 files changed, 356 insertions(+), 354 deletions(-) diff --git a/src/crackmes/avatao/01-reverse4/bytecode.md b/src/crackmes/avatao/01-reverse4/bytecode.md index ebc465a3..3233c822 100644 --- a/src/crackmes/avatao/01-reverse4/bytecode.md +++ b/src/crackmes/avatao/01-reverse4/bytecode.md @@ -1,5 +1,4 @@ -.bytecode ---------- +## .bytecode Well, we did the reverse engineering part, now we have to write a program for the VM with the instruction set described in the previous paragraph. Here is diff --git a/src/crackmes/avatao/01-reverse4/first_steps.md b/src/crackmes/avatao/01-reverse4/first_steps.md index 61e536ec..d69b35cd 100644 --- a/src/crackmes/avatao/01-reverse4/first_steps.md +++ b/src/crackmes/avatao/01-reverse4/first_steps.md @@ -1,5 +1,4 @@ -.first_steps ------------- +## .first_steps OK, enough of praising rizin, lets start reversing this stuff. First, you have to know your enemy: @@ -34,7 +33,7 @@ binsz 8620 > be used to extract information (imports, symbols, libraries, etc.) about > binary executables. As always, check the help (rz-bin -h)! -So, its a dynamically linked, stripped, 64bit Linux executable - nothing fancy +So, it's a dynamically linked, stripped, 64bit Linux executable - nothing fancy here. Let's try to run it: ``` @@ -92,7 +91,7 @@ We can list all the strings rizin found: [0x00400720]> ``` -> ***rizin tip***: rizin puts so called flags on important/interesting offsets, and +> ***rizin tip***: rizin puts so-called flags on important/interesting offsets, and > organizes these flags into flagspaces (strings, functions, symbols, etc.) You > can list all flagspaces using *fs*, and switch the current one using > *fs [flagspace]* (the default is \*, which means all the flagspaces). The diff --git a/src/crackmes/avatao/01-reverse4/instructionset.md b/src/crackmes/avatao/01-reverse4/instructionset.md index d320d6a9..c191580a 100644 --- a/src/crackmes/avatao/01-reverse4/instructionset.md +++ b/src/crackmes/avatao/01-reverse4/instructionset.md @@ -1,21 +1,20 @@ -.instructionset ---------------- +## .instructionset We've now reversed all the VM instructions, and have a full understanding about how it works. Here is the VM's instruction set: -| Instruction | 1st arg | 2nd arg | What does it do? -| ----------- | ------- | ------- | ---------------- -| "A" | "M" | arg2 | \*sym.current_memory_ptr += arg2 -| | "P" | arg2 | sym.current_memory_ptr += arg2 -| | "C" | arg2 | sym.written_by_instr_C += arg2 -| "S" | "M" | arg2 | \*sym.current_memory_ptr -= arg2 -| | "P" | arg2 | sym.current_memory_ptr -= arg2 -| | "C" | arg2 | sym.written_by_instr_C -= arg2 -| "I" | arg1 | n/a | instr_A(arg1, 1) -| "D" | arg1 | n/a | instr_S(arg1, 1) -| "P" | arg1 | n/a | \*sym.current_memory_ptr = arg1; instr_I("P") -| "X" | arg1 | n/a | \*sym.current_memory_ptr ^= arg1 +| Instruction | 1st arg | 2nd arg | What does it do? | +|-------------|---------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| "A" | "M" | arg2 | \*sym.current_memory_ptr += arg2 | +| | "P" | arg2 | sym.current_memory_ptr += arg2 | +| | "C" | arg2 | sym.written_by_instr_C += arg2 | +| "S" | "M" | arg2 | \*sym.current_memory_ptr -= arg2 | +| | "P" | arg2 | sym.current_memory_ptr -= arg2 | +| | "C" | arg2 | sym.written_by_instr_C -= arg2 | +| "I" | arg1 | n/a | instr_A(arg1, 1) | +| "D" | arg1 | n/a | instr_S(arg1, 1) | +| "P" | arg1 | n/a | \*sym.current_memory_ptr = arg1; instr_I("P") | +| "X" | arg1 | n/a | \*sym.current_memory_ptr ^= arg1 | | "J" | arg1 | n/a | arg1_and_0x3f = arg1 & 0x3f;
if (arg1 & 0x40 != 0)
  arg1_and_0x3f \*= -1
if (arg1 >= 0) return arg1_and_0x3f;
else if (\*sym.written_by_instr_C != 0) {
  if (arg1_and_0x3f < 0)
    ++\*sym.good_if_ne_zero;
  return arg1_and_0x3f;
} else return 2; | -| "C" | arg1 | n/a | \*sym.written_by_instr_C = arg1 -| "R" | arg1 | n/a | return(arg1) +| "C" | arg1 | n/a | \*sym.written_by_instr_C = arg1 | +| "R" | arg1 | n/a | return(arg1) | diff --git a/src/crackmes/avatao/01-reverse4/intro.md b/src/crackmes/avatao/01-reverse4/intro.md index 509d5ae3..e7864cab 100644 --- a/src/crackmes/avatao/01-reverse4/intro.md +++ b/src/crackmes/avatao/01-reverse4/intro.md @@ -1,5 +1,4 @@ -Avatao R3v3rs3 4 ------- +## Avatao R3v3rs3 4 After a few years of missing out on wargames at [Hacktivity](https://hacktivity.com), this year I've finally found the time to diff --git a/src/crackmes/avatao/01-reverse4/main.md b/src/crackmes/avatao/01-reverse4/main.md index 5e2a7847..8259bf07 100644 --- a/src/crackmes/avatao/01-reverse4/main.md +++ b/src/crackmes/avatao/01-reverse4/main.md @@ -1,5 +1,4 @@ -.main ------ +## .main As I was saying, I usually take a look at the entry point, so let's just do that: @@ -46,12 +45,12 @@ look at a function: > It is possible to bring up the prompt in visual mode using the *:* key, and > you can use *o* to seek. -Lets read main node-by-node! The first block looks like this: +Let's read main node-by-node! The first block looks like this: ![main bb-0c63](img/main/bb-0c63.png) We can see that the program reads a word (2 bytes) into the local variable named -*local_10_6*, and than compares it to 0xbb8. That's 3000 in decimal: +*local_10_6*, and then compares it to 0xbb8. That's 3000 in decimal: ``` [0x00400c63]> % 0xbb8 @@ -179,7 +178,7 @@ the bytecode, and exits: OK, so now we know that we have to supply a bytecode that will generate that string when executed. As we can see on the minimap, there are still a few more -branches ahead, which probably means more conditions to meet. Lets investigate +branches ahead, which probably means more conditions to meet. Let's investigate them before we delve into *vmloop*! If you take a look at the minimap of the whole function, you can probably @@ -239,7 +238,7 @@ more checks: This piece of code may look a bit strange if you are not familiar with x86_64 specific stuff. In particular, we are talking about RIP-relative addressing, where offsets are described as displacements from the current instruction -pointer, which makes implementing PIE easier. Anyways, rizin is nice enough to +pointer, which makes implementing PIE easier. Anyway, rizin is nice enough to display the actual address (0x602104). Got the address, flag it! ``` diff --git a/src/crackmes/avatao/01-reverse4/outro.md b/src/crackmes/avatao/01-reverse4/outro.md index 97dbaefd..0931e5f4 100644 --- a/src/crackmes/avatao/01-reverse4/outro.md +++ b/src/crackmes/avatao/01-reverse4/outro.md @@ -1,5 +1,4 @@ -.outro ------- +## .outro Well, what can I say? Such VM, much reverse! :) diff --git a/src/crackmes/avatao/01-reverse4/rizin.md b/src/crackmes/avatao/01-reverse4/rizin.md index dec29085..8adfe7f1 100644 --- a/src/crackmes/avatao/01-reverse4/rizin.md +++ b/src/crackmes/avatao/01-reverse4/rizin.md @@ -1,5 +1,4 @@ -.rizin --------- +## .rizin I've decided to solve the reversing challenges using [rizin](http://www.rizin.org/r/), a free and open source reverse engineering diff --git a/src/crackmes/avatao/01-reverse4/vmloop.md b/src/crackmes/avatao/01-reverse4/vmloop.md index d439f65c..38cd0d24 100644 --- a/src/crackmes/avatao/01-reverse4/vmloop.md +++ b/src/crackmes/avatao/01-reverse4/vmloop.md @@ -1,5 +1,4 @@ -.vmloop -------- +## .vmloop ``` [offset]> fcn.vmloop @@ -24,7 +23,7 @@ First, lets analyze what we already have! First, *rdi* is put into local_3. Since the application is a 64bit Linux executable, we know that *rdi* is the first function argument (as you may have recognized, the automatic analysis of arguments and local variables was not entirely correct), and we also know that -*vmloop*'s first argument is the bytecode. So lets rename local_3: +*vmloop*'s first argument is the bytecode. So let's rename local_3: ``` :> afvn local_3 bytecode @@ -102,7 +101,7 @@ This is how the disassembly looks like after we add this metadata: ``` As we can see, the address 0x400c04 is used a lot, and besides that there are 9 -different addresses. Lets see that 0x400c04 first! +different addresses. Let's see that 0x400c04 first! ![vmloop bb-0c04](img/vmloop/bb-0c04.png) @@ -161,7 +160,7 @@ how we can create the missing basic blocks for the instructions: ``` It is also apparent from the disassembly that besides the instructions there -are three more basic blocks. Lets create them too! +are three more basic blocks. Let's create them too! ``` [0x00400ec0]> afb+ 0x00400a45 0x00400c15 0x00400c2d-0x00400c15 0x400c3c 0x00400c2d @@ -187,7 +186,7 @@ By the way, here is how IDA's graph of this same function looks like for compari ![IDA graph](img/vmloop_ida.png) As we browse through the disassembly of the *instr_LETTER* basic blocks, we -should realize a few things. The first: all of the instructions starts with a +should realize a few things. The first: all the instructions starts with a sequence like these: ![vmloop bb-0a80](img/vmloop/bb-0a80.png) @@ -196,9 +195,9 @@ sequence like these: It became clear now that the 9 dwords at *sym.instr_dirty* are not simply indicators that an instruction got executed, but they are used to count how many -times an instruction got called. Also I should have realized earlier that +times an instruction got called. Also, I should have realized earlier that *sym.good_if_le_9* (0x6020f0) is part of this 9 dword array, but yeah, well, I -didn't, I have to live with it... Anyways, what the condition +didn't, I have to live with it... Anyway, what the condition "*sym.good_if_le_9* have to be lesser or equal 9" really means is that *instr_P* can not be executed more than 9 times: @@ -249,7 +248,7 @@ that address! ``` Oh, and by the way, I do have a hunch that *instr_C* also had a function call in -the original code, but it got inlined by the compiler. Anyways, so far we have +the original code, but it got inlined by the compiler. Anyway, so far we have these two instructions: - *instr_R(a1):* returns with *a1* @@ -396,9 +395,9 @@ not the case here - e.g. the larger grey boxes are clearly not identical. This is something I'm definitely going to take a deeper look at after I've finished this writeup. -Anyways, after we get over the shock of being lied to, we can easily recognize +Anyway, after we get over the shock of being lied to, we can easily recognize that *instr_S* is basically a reverse-*instr_A*: where the latter does addition, -the former does subtraction. To summarize this: +the former does' subtraction. To summarize this: - *arg1* == "M": subtracts *arg2* from the byte at *sym.current_memory_ptr*. - *arg1* == "P": steps *sym.current_memory_ptr* backwards by *arg2* bytes. @@ -433,8 +432,8 @@ It's local var rename time again! This function is pretty straightforward also, but there is one oddity: const_M is never used. I don't know why it is there - maybe it is supposed to be some -kind of distraction? Anyways, this function simply writes *arg1* to -*sym.current_memory_ptr*, and than calls *instr_I("P")*. This basically means +kind of distraction? Anyway, this function simply writes *arg1* to +*sym.current_memory_ptr*, and then calls *instr_I("P")*. This basically means that *instr_P* is used to write one byte, and put the pointer to the next byte. So far this would seem the ideal instruction to construct most of the "Such VM! MuCH reV3rse!" string, but remember, this is also the one that can be used only @@ -442,7 +441,7 @@ MuCH reV3rse!" string, but remember, this is also the one that can be used only ###instr_X -Another simple one, rename local vars anyways! +Another simple one, rename local vars anyway! ``` :> afvn local_1 arg1 diff --git a/src/crackmes/hackthebox/find-the-easy-pass/identification.md b/src/crackmes/hackthebox/find-the-easy-pass/identification.md index 78d1501e..f1db81ba 100644 --- a/src/crackmes/hackthebox/find-the-easy-pass/identification.md +++ b/src/crackmes/hackthebox/find-the-easy-pass/identification.md @@ -3,7 +3,7 @@ After un-compressing the challenge file `Find The Easy Pass.zip`, we can find a file named `EasyPass.exe` inside it. -We using `rz-bin` to identify the executable file. +We're using `rz-bin` to identify the executable file. ```bash C:\Users\User\Desktop\htb>rz-bin -I EasyPass.exe diff --git a/src/crackmes/intro.md b/src/crackmes/intro.md index 694945ef..65a3a1f8 100644 --- a/src/crackmes/intro.md +++ b/src/crackmes/intro.md @@ -1,5 +1,5 @@ Crackmes ======== -Crackmes (from "crack me" challenge) are the training ground for reverse engineering people. This section will go over tutorials on how to defeat various crackmes using rizin. - +Crackmes (from "crack me" challenge) are the training ground for reverse engineering people. This section will go over +tutorials on how to defeat various crackmes using Rizin. diff --git a/src/crackmes/ioli/intro.md b/src/crackmes/ioli/intro.md index da09aefc..d79d992c 100644 --- a/src/crackmes/ioli/intro.md +++ b/src/crackmes/ioli/intro.md @@ -1,6 +1,8 @@ IOLI CrackMes ============= -The IOLI crackme is a good starting point for learning rizin. This is a set of tutorials based on the tutorial at [dustri](https://dustri.org/b/defeating-ioli-with-radare2.html) +The IOLI crackme is a good starting point for learning Rizin. This is a set of tutorials based on the tutorial +at [dustri](https://dustri.org/b/defeating-ioli-with-radare2.html) -The IOLI crackmes are available at a locally hosted [mirror](https://github.com/rizinorg/book/raw/master/src/crackmes/ioli/IOLI-crackme.tar.gz) +The IOLI crackmes are available at a locally hosted +[mirror](https://github.com/rizinorg/book/raw/master/src/crackmes/ioli/IOLI-crackme.tar.gz) diff --git a/src/crackmes/ioli/ioli_0x00.md b/src/crackmes/ioli/ioli_0x00.md index 0e99a2e7..300fa2e8 100644 --- a/src/crackmes/ioli/ioli_0x00.md +++ b/src/crackmes/ioli/ioli_0x00.md @@ -1,5 +1,4 @@ -IOLI 0x00 -========= +# IOLI 0x00 This is the first IOLI crackme, and the easiest one. @@ -10,15 +9,16 @@ Password: 1234 Invalid Password! ``` -The first thing to check is if the password is just plaintext inside the file. In this case, we don't need to do any disassembly, and we can just use rz-bin with the -z flag to search for strings in the binary. +The first thing to check is if the password is just plaintext inside the file. In this case, we don't need to do +any disassembly, and we can just use rz-bin with the -z flag to search for strings in the binary. ``` -$ rz-bin -z ./crackme0x00 +$ rz-bin -z ./crackme0x00 [Strings] -nth paddr vaddr len size section type string -――――――――――――――――――――――――――――――――――――――――――――――――――――――― +nth paddr vaddr len size section type string +--------------------------------------------------------------------------- 0 0x00000568 0x08048568 24 25 .rodata ascii IOLI Crackme Level 0x00\n -1 0x00000581 0x08048581 10 11 .rodata ascii Password: +1 0x00000581 0x08048581 10 11 .rodata ascii Password: 2 0x0000058f 0x0804858f 6 7 .rodata ascii 250382 3 0x00000596 0x08048596 18 19 .rodata ascii Invalid Password!\n 4 0x000005a9 0x080485a9 15 16 .rodata ascii Password OK :)\n diff --git a/src/crackmes/ioli/ioli_0x01.md b/src/crackmes/ioli/ioli_0x01.md index 16592e59..21efef32 100644 --- a/src/crackmes/ioli/ioli_0x01.md +++ b/src/crackmes/ioli/ioli_0x01.md @@ -1,5 +1,4 @@ -IOLI 0x01 -========= +# IOLI 0x01 This is the second IOLI crackme. @@ -15,10 +14,10 @@ Let's check for strings with rz-bin. ``` $ rz-bin -z ./crackme0x01 [Strings] -nth paddr vaddr len size section type string -――――――――――――――――――――――――――――――――――――――――――――――――――――――― +nth paddr vaddr len size section type string +--------------------------------------------------------------------------- 0 0x00000528 0x08048528 24 25 .rodata ascii IOLI Crackme Level 0x01\n -1 0x00000541 0x08048541 10 11 .rodata ascii Password: +1 0x00000541 0x08048541 10 11 .rodata ascii Password: 2 0x0000054f 0x0804854f 18 19 .rodata ascii Invalid Password!\n 3 0x00000562 0x08048562 15 16 .rodata ascii Password OK :)\n ``` @@ -27,64 +26,58 @@ This isn't going to be as easy as 0x00. Let's try disassembly with rizin. ``` $ rizin ./crackme0x01 --- Use `zoom.byte=printable` in zoom mode ('z' in Visual mode) to find strings [0x08048330]> aa [0x08048330]> pdf @ main ; DATA XREF from entry0 @ 0x8048347 -/ 113: int main (int argc, char **argv, char **envp); -| ; var int32_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" -| 0x08048407 e810ffffff call sym.imp.printf ; int printf(const char *format) -| 0x0804840c c70424418504. mov dword [esp], str.Password: ; [0x8048541:4]=0x73736150 ; "Password: " -| 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 -| 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" -| | 0x0804843b e8dcfeffff call sym.imp.printf ; int printf(const char *format) -| ,==< 0x08048440 eb0c jmp 0x804844e -| |`-> 0x08048442 c70424628504. mov dword [esp], str.Password_OK_: ; [0x8048562:4]=0x73736150 ; "Password OK :)\n" -| | 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 +┌ int main(int argc, char **argv, char **envp); +│ ; var int32_t var_18h @ stack - 0x18 +│ ; var int32_t var_8h @ stack - 0x8 +│ 0x080483e4 push ebp +│ 0x080483e5 mov ebp, esp +│ 0x080483e7 sub esp, 0x18 +│ 0x080483ea and esp, 0xfffffff0 +│ 0x080483ed mov eax, 0 +│ 0x080483f2 add eax, 0xf ; 15 +│ 0x080483f5 add eax, 0xf ; 15 +│ 0x080483f8 shr eax, 4 +│ 0x080483fb shl eax, 4 +│ 0x080483fe sub esp, eax +│ 0x08048400 mov dword [esp], str.IOLI_Crackme_Level_0x01 ; [0x8048528:4]=0x494c4f49 ; "IOLI Crackme Level 0x01\n" +│ 0x08048407 call sym.imp.printf ; sym.imp.printf ; int printf(const char *format) +│ 0x0804840c mov dword [esp], str.Password: ; [0x8048541:4]=0x73736150 ; "Password: " +│ 0x08048413 call sym.imp.printf ; sym.imp.printf ; int printf(const char *format) +│ 0x08048418 lea eax, [var_8h] +│ 0x0804841b mov dword [var_18h], eax +│ 0x0804841f mov dword [esp], 0x804854c ; [0x804854c:4]=0x49006425 +│ 0x08048426 call sym.imp.scanf ; sym.imp.scanf ; int scanf(const char *format) +│ 0x0804842b cmp dword [var_8h], 0x149a +│ ┌─< 0x08048432 je 0x8048442 +│ │ 0x08048434 mov dword [esp], str.Invalid_Password ; [0x804854f:4]=0x61766e49 ; "Invalid Password!\n" +│ │ 0x0804843b call sym.imp.printf ; sym.imp.printf ; int printf(const char *format) +│ ┌──< 0x08048440 jmp 0x804844e +│ │└─> 0x08048442 mov dword [esp], str.Password_OK_: ; [0x8048562:4]=0x73736150 ; "Password OK :)\n" +│ │ 0x08048449 call sym.imp.printf ; sym.imp.printf ; int printf(const char *format) +│ │ ; CODE XREF from main @ 0x8048440 +│ └──> 0x0804844e mov eax, 0 +│ 0x08048453 leave +└ 0x08048454 ret ``` -"aa" tells rizin to analyze the whole binary, which gets you symbol names, among things. +"aa" tells Rizin to analyze the whole binary, which gets you symbol names, among things. -"pdf" stands for +"pdf" stands for **p**rint, **d**isassemble, **f**unction. -* Print - -* Disassemble - -* Function - -This will print the disassembly of the main function, or the `main()` that everyone knows. You can see several things as well: weird names, arrows, etc. +This will print the disassembly of the main function, or the `main()` that everyone knows. You can see several +things as well: weird names, arrows, etc. * "imp." stands for imports. Those are imported symbols, like printf() - * "str." stands for strings. Those are strings (obviously). -If you look carefully, you'll see a `cmp` instruction, with a constant, 0x149a. `cmp` is an x86 compare instruction, and the 0x in front of it specifies it is in base 16, or hex (hexadecimal). +If you look carefully, you'll see a `cmp` instruction, with a constant, 0x149a. `cmp` is an x86 compare instruction, +and the 0x in front of it specifies it is in base 16, or hex (hexadecimal). ``` -0x0804842b 817dfc9a140. cmp dword [ebp + 0xfffffffc], 0x149a +│ 0x0804842b cmp dword [var_8h], 0x149a ``` You can use rizin's `%` command to display 0x149a in another numeric base. @@ -114,7 +107,9 @@ Password: 5274 Password OK :) ``` -Bingo, the password was 5274. In this case, the password function at 0x0804842b was comparing the input against the value, 0x149a in hex. Since user input is usually decimal, it was a safe bet that the input was intended to be in decimal, or 5274. Now, since we're hackers, and curiosity drives us, let's see what happens when we input in hex. +Bingo, the password was 5274. In this case, the password function at 0x0804842b was comparing the input against +the value, 0x149a in hex. Since user input is usually decimal, it was a safe bet that the input was intended to be +in decimal, or 5274. Now, since we're hackers, and curiosity drives us, let's see what happens when we input in hex. ``` $ ./crackme0x01 @@ -123,6 +118,7 @@ Password: 0x149a Invalid Password! ``` -It was worth a shot, but it doesn't work. That's because `scanf()` will take the 0 in 0x149a to be a zero, rather than accepting the input as actually being the hex value. +It was worth a shot, but it doesn't work. That's because `scanf()` will take the 0 in 0x149a to be a zero, +rather than accepting the input as actually being the hex value. And this concludes IOLI 0x01. diff --git a/src/crackmes/ioli/ioli_0x02.md b/src/crackmes/ioli/ioli_0x02.md index f875f88f..91822a1e 100644 --- a/src/crackmes/ioli/ioli_0x02.md +++ b/src/crackmes/ioli/ioli_0x02.md @@ -1,5 +1,4 @@ -IOLI 0x02 -========= +# IOLI 0x02 This is the third one. @@ -11,110 +10,122 @@ Invalid Password! ``` Firstly, let's check it with rz-bin. + ``` $ rz-bin -z ./crackme0x02 [Strings] -nth paddr vaddr len size section type string -――――――――――――――――――――――――――――――――――――――――――――――――――――――― +nth paddr vaddr len size section type string +--------------------------------------------------------------------------- 0 0x00000548 0x08048548 24 25 .rodata ascii IOLI Crackme Level 0x02\n -1 0x00000561 0x08048561 10 11 .rodata ascii Password: +1 0x00000561 0x08048561 10 11 .rodata ascii Password: 2 0x0000056f 0x0804856f 15 16 .rodata ascii Password OK :)\n 3 0x0000057f 0x0804857f 18 19 .rodata ascii Invalid Password!\n ``` Similar to 0x01, there's no explicit password string here. So, it's time to analyze it with Rizin. + ``` +$ rizin ./crackme0x02 [0x08048330]> aa [x] Analyze all flags starting with sym. and entry0 (aa) -[0x08048330]> pdf@main +[0x08048330]> pdf @ main ; DATA XREF from entry0 @ 0x8048347 -/ 144: int main (int argc, char **argv, char **envp); -| ; var int32_t var_ch @ ebp-0xc -| ; var int32_t var_8h @ ebp-0x8 -| ; var int32_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 c70424488504. mov dword [esp], str.IOLI_Crackme_Level_0x02 ; [0x8048548:4]=0x494c4f49 ; "IOLI Crackme Level 0x02\n" -| 0x08048407 e810ffffff call sym.imp.printf ; int printf(const char *format) -| 0x0804840c c70424618504. mov dword [esp], str.Password: ; [0x8048561:4]=0x73736150 ; "Password: " -| 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 c704246c8504. mov dword [esp], 0x804856c ; [0x804856c:4]=0x50006425 -| 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 [var_4h] -| 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" -| | 0x0804845a e8bdfeffff call sym.imp.printf ; int printf(const char *format) -| ,==< 0x0804845f eb0c jmp 0x804846d -| |`-> 0x08048461 c704247f8504. mov dword [esp], str.Invalid_Password ; [0x804857f:4]=0x61766e49 ; "Invalid Password!\n" -| | 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 - -``` - -With the experience of solving crackme0x01, we can first locate the position of `cmp` instruction by using this simple oneliner: -``` -[0x08048330]> pdf@main~cmp -| 0x0804844e 3b45f4 cmp eax, dword [var_ch] -``` - -Unfortunately, the variable compared to `eax` is stored in the stack. We can't check the value of this variable directly. It's a common case in reverse engineering that we have to derive the value of the variable from the previous sequence. As the amount of code is relatively small, it can be easily done. +┌ int main(int argc, char **argv, char **envp); +│ ; var int32_t var_18h @ stack - 0x18 +│ ; var int32_t var_10h @ stack - 0x10 +│ ; var int32_t var_ch @ stack - 0xc +│ ; var int32_t var_8h @ stack - 0x8 +│ 0x080483e4 push ebp +│ 0x080483e5 mov ebp, esp +│ 0x080483e7 sub esp, 0x18 +│ 0x080483ea and esp, 0xfffffff0 +│ 0x080483ed mov eax, 0 +│ 0x080483f2 add eax, 0xf ; 15 +│ 0x080483f5 add eax, 0xf ; 15 +│ 0x080483f8 shr eax, 4 +│ 0x080483fb shl eax, 4 +│ 0x080483fe sub esp, eax +│ 0x08048400 mov dword [esp], str.IOLI_Crackme_Level_0x02 ; [0x8048548:4]=0x494c4f49 ; "IOLI Crackme Level 0x02\n" +│ 0x08048407 call sym.imp.printf ; sym.imp.printf ; int printf(const char *format) +│ 0x0804840c mov dword [esp], str.Password: ; [0x8048561:4]=0x73736150 ; "Password: " +│ 0x08048413 call sym.imp.printf ; sym.imp.printf ; int printf(const char *format) +│ 0x08048418 lea eax, [var_8h] +│ 0x0804841b mov dword [var_18h], eax +│ 0x0804841f mov dword [esp], 0x804856c ; [0x804856c:4]=0x50006425 +│ 0x08048426 call sym.imp.scanf ; sym.imp.scanf ; int scanf(const char *format) +│ 0x0804842b mov dword [var_ch], 0x5a ; 'Z' ; 90 +│ 0x08048432 mov dword [var_10h], 0x1ec ; 492 +│ 0x08048439 mov edx, dword [var_10h] +│ 0x0804843c lea eax, [var_ch] +│ 0x0804843f add dword [eax], edx +│ 0x08048441 mov eax, dword [var_ch] +│ 0x08048444 imul eax, dword [var_ch] +│ 0x08048448 mov dword [var_10h], eax +│ 0x0804844b mov eax, dword [var_8h] +│ 0x0804844e cmp eax, dword [var_10h] +│ ┌─< 0x08048451 jne 0x8048461 +│ │ 0x08048453 mov dword [esp], str.Password_OK_: ; [0x804856f:4]=0x73736150 ; "Password OK :)\n" +│ │ 0x0804845a call sym.imp.printf ; sym.imp.printf ; int printf(const char *format) +│ ┌──< 0x0804845f jmp 0x804846d +│ │└─> 0x08048461 mov dword [esp], str.Invalid_Password ; [0x804857f:4]=0x61766e49 ; "Invalid Password!\n" +│ │ 0x08048468 call sym.imp.printf ; sym.imp.printf ; int printf(const char *format) +│ │ ; CODE XREF from main @ 0x804845f +│ └──> 0x0804846d mov eax, 0 +│ 0x08048472 leave +└ 0x08048473 ret +``` + +With the experience of solving crackme0x01, we can first locate the position of `cmp` instruction by using +this simple oneliner: + +``` +[0x08048330]> pdf @ main~cmp +│ 0x0804844e cmp eax, dword [var_10h] +``` + +Unfortunately, the variable compared to `eax` is stored in the stack. We can't check the value of this variable +directly. It's a common case in reverse engineering that we have to derive the value of the variable from +the previous sequence. As the amount of code is relatively small, it can be easily done. for example: + ``` -| 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 +│ 0x080483ed mov eax, 0 +│ 0x080483f2 add eax, 0xf ; 15 +│ 0x080483f5 add eax, 0xf ; 15 +│ 0x080483f8 shr eax, 4 +│ 0x080483fb shl eax, 4 +│ 0x080483fe sub esp, eax ``` We can easily get the value of `eax`. It's 16. -Directly looking at the disassembly gets hard when the scale of program grows. Rizin's flagship decompiler [rz-ghidra](https://github.com/rizinorg/rz-ghidra) might be of help, here. You can install it easily: +Directly looking at the disassembly gets hard when the scale of program grows. Rizin's flagship decompiler +[rz-ghidra](https://github.com/rizinorg/rz-ghidra) might be of help, here. You can install it easily: + ``` rz-pm -i rz-ghidra ``` Decompile `main()` with the following command (like `F5` in IDA): -```C -[0x08048330]> pdg + +``` +[0x080483e4]> pdg @ main + undefined4 main(void) { - uint32_t var_ch; - undefined4 var_8h; - int32_t var_4h; + int32_t var_18h; + int32_t var_10h; + int32_t var_ch; + int32_t var_8h; - printf("IOLI Crackme Level 0x02\n"); - printf("Password: "); - scanf(0x804856c, &var_4h); - if (var_4h == 0x52b24) { - printf("Password OK :)\n"); + sym.imp.printf("IOLI Crackme Level 0x02\n"); + sym.imp.printf("Password: "); + sym.imp.scanf(0x804856c, &var_8h); + if (var_8h == 0x52b24) { + sym.imp.printf("Password OK :)\n"); } else { - printf("Invalid Password!\n"); + sym.imp.printf("Invalid Password!\n"); } return 0; } @@ -123,14 +134,19 @@ undefined4 main(void) It's more human-readable now. To check the string in `0x804856c`, we can: * Seek * Print the string +* + ``` -[0x08048330]> s 0x804856c +[0x080483e4]> s 0x804856c [0x0804856c]> ps %d ``` -It's exactly the format string of `scanf()`. And rz-ghidra recognizes that the second argument (eax) is a pointer and it points to `var_4h`. Which means our input will be stored in `var_4h`. -We can easily write out the pseudo code here. +It's exactly the format string of `scanf()`. And rz-ghidra recognizes that the second argument (eax) is a pointer, +and it points to `var_8h`. Which means our input will be stored in `var_8h`. + +We can easily write out the pseudocode here. + ```C var_ch = (var_8h + var_ch)^2; if (var_ch == our_input) diff --git a/src/crackmes/ioli/ioli_0x03.md b/src/crackmes/ioli/ioli_0x03.md index 97bf19c1..092c026a 100644 --- a/src/crackmes/ioli/ioli_0x03.md +++ b/src/crackmes/ioli/ioli_0x03.md @@ -1,5 +1,4 @@ -IOLI 0x03 -========= +# IOLI 0x03 This is the fourth crackme. @@ -21,7 +20,6 @@ nth paddr vaddr len size section type string 1 0x000005fe 0x080485fe 17 18 .rodata ascii Sdvvzrug#RN$$$#=, 2 0x00000610 0x08048610 24 25 .rodata ascii IOLI Crackme Level 0x03\n 3 0x00000629 0x08048629 10 11 .rodata ascii Password: - ``` Note that the 'Invalid Password!' and the 'Password OK :)' strings have been seemingly replaced by random @@ -29,7 +27,8 @@ gibberish. Let's analyze. -```C +``` +$ rizin ./crackme0x03 [0x08048360]> aaa [0x08048360]> pdg @ main @@ -48,8 +47,8 @@ undefined4 main(void) } ``` -This looks quite straightforward, `var_8h` is the result of `scanf` which the function `sym.test(var_8h, 0x52b24)` apparently -compares to the value `0x52b24`. +This looks quite straightforward, `var_8h` is the result of `scanf` which the function `sym.test(var_8h, 0x52b24)` +apparently compares to the value `0x52b24`. And indeed entering the decimal value of `0x52b24` (338724) gives us a pass. @@ -75,12 +74,13 @@ void sym.test(int32_t arg_4h, unsigned long arg_8h) return; } ``` -It's a two path conditional jump which compares two parameters and then does a shift. We can guess that `shift()` is most likely -some sort of decoding step of the seemingly random strings (shift cipher, e.g. Caesar cipher). + +It's a two path conditional jump which compares two parameters and then does a shift. We can guess that `shift()` +is most likely some sort of decoding step of the seemingly random strings (shift cipher, e.g. Caesar cipher). To confirm our suspicions let's analyze `sym.shift()`. -```C +``` [0x08048360]> pdg @ sym.shift // WARNING: Variable defined which should be unmapped: var_98h @@ -129,7 +129,7 @@ We can see that each character in `str` is subtracted by 3 to produce the final With this knowledge we can take a shot at decoding the strings. We can use the `pos` command to apply the subtraction needed for decoding and printing the result. -```bash +```shell $ rizin ./crackme0x03 [0x08048360]> aaa [0x08048360]> fs strings @@ -148,27 +148,29 @@ $ rizin ./crackme0x03 0x0804860e 29fd ). ``` -However, some functions may not be as easy to understand as this one, in which case it may be useful to be able to run the code. -Rizin provides us two ways of doing this: by using the debugger, or by emulation (using ESIL). +However, some functions may not be as easy to understand as this one, in which case it may be useful to be able to run +the code. Rizin provides us two ways of doing this: by using the debugger, or by emulation (using ESIL). -Let's first see how we can achieve this using the debugger. We will be wanting to pass the encoded strings to `shift()`. We know -`shift` takes one parameter `s`, which is an address to a (null terminated) string. We can see where on the stack local variables and -arguments are using the `afvl` command. +Let's first see how we can achieve this using the debugger. We will be wanting to pass the encoded strings +to `shift()`. We know `shift` takes one parameter `s`, which is an address to a (null terminated) string. +We can see where on the stack local variables and arguments are using the `afvl` command. -```bash +```shell $ rizin -d ./crackme0x03 -[0xec0ff970]> aa -[0xec0ff970]> afvl @ sym.shift +[0xf7f04630]> aa +[0xf7f04630]> afvl @ sym.shift var int32_t var_98h @ stack - 0x98 -var unsigned long var_80h @ stack - 0x80 +var int32_t var_80h @ stack - 0x80 var int32_t var_7ch @ stack - 0x7c -arg const char *s @ stack + 0x4 +arg int32_t arg_4h @ stack + 0x4 ``` We can see that `s` starts at a 4 byte offset from the stack pointer. -```bash -[0xec0ff970]> dcu main # run until program start +```shell +[0xf7f04630]> dcu main # run until program start +Continue until 0x08048498 +hit breakpoint at: 0x8048498 [0x08048498]> *esp+4=str.Lqydolg_Sdvvzrug # 'push' address onto the stack (note the 4 byte offset) [0x08048498]> dr eip=sym.shift # set instruction pointer to start of shift() [0x08048498]> dcr # run shift() until it returns @@ -179,8 +181,8 @@ Invalid Password! Password OK!!! :) ``` -Emulation is a bit more tricky because we can't make external calls to functions like `strlen()` and `printf()`. So we have to manually -skip over them and set the registers accordingly. Below is an example. +Emulation is a bit more tricky because we can't make external calls to functions like `strlen()` and `printf()`. +So we have to manually skip over them and set the registers accordingly. Below is an example. ```bash [0x08048414]> s 0x08048445 # the 'sub al, 0x03' diff --git a/src/crackmes/ioli/ioli_0x04.md b/src/crackmes/ioli/ioli_0x04.md index 7d749e5c..36b1144e 100644 --- a/src/crackmes/ioli/ioli_0x04.md +++ b/src/crackmes/ioli/ioli_0x04.md @@ -1,5 +1,4 @@ -IOLI 0x04 -========= +# IOLI 0x04 This is the fifth crackme. @@ -17,7 +16,7 @@ nth paddr vaddr len size section type string Checking for strings we see that our old friends "Password OK!" and "Password Incorrect!" are back in their unobfuscated forms. -```c +``` $ rizin ./crackme0x04 [0x080483d0]> aaa [0x080483d0]> pdg @ main @@ -39,47 +38,46 @@ undefined4 main(void) This time though, `scanf` takes a *string* and passes it to a function called `check`. -```c +``` [0x080483d0]> pdg @ sym.check -// WARNING: Variable defined which should be unmapped: var_28h -// WARNING: Variable defined which should be unmapped: var_24h -// WARNING: [rz-ghidra] Detected overlap for variable var_11h -void sym.check(int32_t arg_4h) +// WARNING: Variable defined which should be unmapped: format +// WARNING: Variable defined which should be unmapped: args + +void sym.check(char *s) { uint32_t uVar1; - int32_t var_28h; - int32_t var_24h; - undefined var_11h; - int32_t var_10h; - int32_t var_ch; + char *format; + va_list args; + char *var_11h; + unsigned long var_ch; int32_t var_8h; var_ch = 0; - var_10h = 0; + stack0xfffffff0 = 0; while( true ) { - uVar1 = sym.imp.strlen(arg_4h); - if (uVar1 <= (uint32_t)var_10h) break; - var_11h = *(undefined *)(var_10h + arg_4h); - sym.imp.sscanf(&var_11h, 0x8048638, &var_8h); + uVar1 = sym.imp.strlen(s); + if (uVar1 <= stack0xfffffff0) break; + var_11h._0_1_ = s[stack0xfffffff0]; + sym.imp.sscanf(&var_11h, data.08048638, &var_8h); var_ch = var_ch + var_8h; if (var_ch == 0xf) { sym.imp.printf("Password OK!\n"); sym.imp.exit(0); } - var_10h = var_10h + 1; + unique0x00003f80 = stack0xfffffff0 + 1; } sym.imp.printf("Password Incorrect!\n"); return; } + [0x080483d0]> afvl @ sym.check -var int32_t var_28h @ stack - 0x28 -var int32_t var_24h @ stack - 0x24 -var int32_t var_11h @ stack - 0x11 -var int32_t var_10h @ stack - 0x10 -var int32_t var_ch @ stack - 0xc +var const char *format @ stack - 0x28 +var va_list args @ stack - 0x24 +var const char *var_11h @ stack - 0x11 +var unsigned long var_ch @ stack - 0xc var int32_t var_8h @ stack - 0x8 -arg int32_t arg_4h @ stack + 0x4 +arg const char *s @ stack + 0x4 [0x080483d0]> ps @ 0x8048638 @!2 %d ``` @@ -88,8 +86,8 @@ A few things to note: `sscanf` in the `while` loop takes an integer ("%d"), and `var_8h`, which is subsequently used to increment `var_ch`. As soon as `var_ch` equals 15 (0xf) we gain entry. -Other than that however it may not be very obvious at first glance what exactly is going on here. So let's start a debugging session -to execute the function. +Other than that however it may not be very obvious at first glance what exactly is going on here. So let's start +a debugging session to execute the function. ```bash $ rizin -d ./crackme0x04 @@ -102,9 +100,9 @@ We will want to pass our own strings to `check`, so let's allocate some memory a ```bash [0x08048509]> dm+ 512 @ -1 # Allocate 512 bytes at anywhere (-1) -ra0=0xf3643000 -[0x08048509]> wz "letmein" @ 0xf3643000 # Write null-terminated string to our allocated memory -[0x08048509]> *esp+4=0xf3643000 # store the address under `arg_4h` (stack + 0x04) +ra0=0xf7fbb000 +[0x08048509]> wz "letmein" @ 0xf7fbb000 # Write null-terminated string to our allocated memory +[0x08048509]> *esp+4=0xf7fbb000 # store the address under `arg_4h` (stack + 0x04) ``` The password check completes if `var_ch` equals 15 (0xf) so let's add a breakpoint that prints the @@ -117,13 +115,13 @@ try to find it using `pdf @ sym.check`. [0x08048508]> db @ 0x080484d6 # set breakpoint [0x08048508]> dbc 'pxw 1 @ esp-0xc' @ 0x080484d6 # execute command on break [0x08048508]> dcr # execute until return -0xffa833f0 0x00000004 . # l -0xffa833f0 0x00000008 . # e -0xffa833f0 0x0000000c . # t -0xffa833f0 0x00000010 . # m -0xffa833f0 0x00000014 . # e -0xffa833f0 0x00000018 . # i -0xffa833f0 0x0000001c . # n +0xffeefb00 0x000000ff . # l +0xffeefb00 0x000000fe . # e +0xffeefb00 0x000000fd . # t +0xffeefb00 0x000000fc . # m +0xffeefb00 0x000000fb . # e +0xffeefb00 0x000000fa . # i +0xffeefb00 0x000000f9 . # n Password Incorrect! ``` @@ -134,14 +132,14 @@ of `var_ch`. This was never overwritten because `sscanf` didn't encounter any nu So let's try giving it a number as input. ```bash -[0x08048508]> wz "1234" @ 0xf3643010 -[0x08048508]> *esp+4=0xf3643010 +[0x08048508]> wz "1234" @ 0xf7fbb010 +[0x08048508]> *esp+4=0xf7fbb010 [0x08048508]> dr eip=sym.check [0x08048508]> dcr -0xffa833f0 0x00000001 . # 1 -0xffa833f0 0x00000003 . # 2 -0xffa833f0 0x00000006 . # 3 -0xffa833f0 0x0000000a . # 4 +0xffeefb00 0x00000001 . # 1 +0xffeefb00 0x00000003 . # 2 +0xffeefb00 0x00000006 . # 3 +0xffeefb00 0x0000000a . # 4 Password Incorrect! ``` diff --git a/src/crackmes/ioli/ioli_0x05.md b/src/crackmes/ioli/ioli_0x05.md index 746fe920..41f4ae3c 100644 --- a/src/crackmes/ioli/ioli_0x05.md +++ b/src/crackmes/ioli/ioli_0x05.md @@ -1,5 +1,4 @@ -IOLI 0x05 -========= +# IOLI 0x05 This is the sixth crackme. @@ -16,7 +15,8 @@ nth paddr vaddr len size section type string No interesting strings, so let's analyze. -```c +``` +$ rizin ./crackme0x05 [0x080483d0]> aa [0x080483d0]> pdg @ main @@ -33,6 +33,7 @@ undefined4 main(void) sym.check((int32_t)&var_7ch); return 0; } + [0x080483d0]> ps @ 0x80486b2 %s ``` @@ -72,6 +73,7 @@ void sym.check(int32_t arg_4h) sym.imp.printf("Password Incorrect!\n"); return; } + [0x080483d0]> ps @ 0x8048668 @! 2 %d ``` @@ -79,7 +81,7 @@ void sym.check(int32_t arg_4h) We can see that `check` is mostly the same, except that this time the digit sum has to equal 16 (0x10), after which a function named `parell` is called. -```c +``` [0x080483d0]> pdg @ sym.parell // WARNING: Variable defined which should be unmapped: var_18h @@ -98,6 +100,7 @@ void sym.parell(int32_t arg_4h) } return; } + [0x080483d0]> ps @ 0x8048668 @! 2 %d ``` diff --git a/src/crackmes/ioli/ioli_0x06.md b/src/crackmes/ioli/ioli_0x06.md index 7582395c..59f626aa 100644 --- a/src/crackmes/ioli/ioli_0x06.md +++ b/src/crackmes/ioli/ioli_0x06.md @@ -1,5 +1,4 @@ -IOLI 0x06 -========= +# IOLI 0x06 Onto the seventh crackme. @@ -26,7 +25,9 @@ Password Incorrect! No dice, so let's take a closer look. -```c +``` +$ rizin ./crackme0x06 +[0x08048400]> aa [0x08048400]> pdg @ main // WARNING: [rz-ghidra] Detected overlap for variable var_11h @@ -42,6 +43,7 @@ undefined4 main(undefined4 placeholder_0, undefined4 placeholder_1, char **envp) sym.check((int32_t)&var_7ch, (int32_t)envp); return 0; } + [0x08048400]> ps @ 0x8048787 %s [0x08048400]> afvl @ main @@ -52,7 +54,7 @@ arg char **envp @ stack + 0xc This looks the same as before, except the program's environment variables `envp` are passed to `check`. -```c +``` [0x08048400]> pdg @ sym.check // WARNING: Variable defined which should be unmapped: var_28h @@ -90,7 +92,7 @@ void sym.check(int32_t arg_4h, int32_t arg_8h) This looks mostly the same as well. If we follow `envp` (now named `arg_8h`) we can see it gets passed to `parell`. -```c +``` [0x08048400]> pdg @ sym.parell // WARNING: Variable defined which should be unmapped: var_18h @@ -121,7 +123,7 @@ void sym.parell(int32_t arg_4h, int32_t arg_8h) We can see that the parity check is still in place, except it's now in a loop that executes 10 times, but only if `dummy()` returns non-zero. -```c +``` [0x08048400]> pdg @ sym.dummy // WARNING: Variable defined which should be unmapped: var_18h @@ -149,13 +151,12 @@ undefined4 sym.dummy(undefined4 placeholder_0, int32_t arg_8h) ``` Living up to its name, `dummy` does not use its first parameter at all, only the second one is used which is the -`envp` parameter from `main`. Apparently some part of `envp` has to equal "LOL" (only the first 3 characters are used, note the '3' -in `strncmp`). +`envp` parameter from `main`. Apparently some part of `envp` has to equal "LOL" (only the first 3 characters are used +, note the '3' in `strncmp`). It will be easier to figure out how `dummy` works if we run the code, so let's use the debugger again! - -```bash +```shell $ rizin -d ./crackme0x06 [0xe8570cd0]> aa [0xe8570cd0]> dcu sym.dummy @@ -167,9 +168,10 @@ hit breakpoint at: 0x80484b4 Now we should be at the start of `dummy`, let's see where we can place a breakpoint. -```asm -[0x08048502]> pdf +``` +[0x080484b4]> pdf ; CALL XREF from sym.parell @ 0x8048547 + ;-- eip: ┌ sym.dummy(int32_t arg_8h); │ ; var int32_t var_18h @ stack - 0x18 │ ; var int32_t var_14h @ stack - 0x14 @@ -194,7 +196,6 @@ Now we should be at the start of `dummy`, let's see where we can place a breakpo │ │╎ 0x080484ee mov dword [var_18h], 0x8048738 ; str.LOLO │ │╎ ; [0x8048738:4]=0x4f4c4f4c ; "LOLO" │ │╎ 0x080484f6 mov eax, dword [ecx + edx] -│ │╎ ;-- eip: │ │╎ 0x080484f9 mov dword [esp], eax │ │╎ 0x080484fc call sym.imp.strncmp ; sym.imp.strncmp ; int strncmp(const char *s1, const char *s2, size_t n) │ │╎ 0x08048501 test eax, eax @@ -212,7 +213,7 @@ Now we should be at the start of `dummy`, let's see where we can place a breakpo The instruction at `0x080484f9` looks like a good spot. This is just before `strncmp` is called, so we can see what value is passed to it. -```bash +``` [0x08048502]> db @ 0x080484f9 [0x08048502]> dbc 'psi @r:eax' @ 0x080484f9 [0x08048502]> dcr diff --git a/src/crackmes/ioli/ioli_0x07.md b/src/crackmes/ioli/ioli_0x07.md index d15bc7b8..6616bbc3 100644 --- a/src/crackmes/ioli/ioli_0x07.md +++ b/src/crackmes/ioli/ioli_0x07.md @@ -1,24 +1,23 @@ -IOLI 0x07 -========= +# IOLI 0x07 Already onto the eighth crackme! -```bash +```shell $ rz-bin -z ./crackme0x07 [Strings] -nth paddr vaddr len size section type string -――――――――――――――――――――――――――――――――――――――――――――――――――――――― +nth paddr vaddr len size section type string +--------------------------------------------------------------------------- 0 0x000007a8 0x080487a8 4 5 .rodata ascii LOLO 1 0x000007ad 0x080487ad 20 21 .rodata ascii Password Incorrect!\n 2 0x000007c5 0x080487c5 13 14 .rodata ascii Password OK!\n 3 0x000007d3 0x080487d3 5 6 .rodata ascii wtf?\n 4 0x000007d9 0x080487d9 24 25 .rodata ascii IOLI Crackme Level 0x07\n -5 0x000007f2 0x080487f2 10 11 .rodata ascii Password: +5 0x000007f2 0x080487f2 10 11 .rodata ascii Password: ``` Doing our routine strings check we see another new contender, wtf? Literally. -```c +``` $ rizin ./crackme0x07 [0x08048400]> aa [0x08048400]> pdg @ main @@ -41,7 +40,7 @@ undefined4 main(undefined4 placeholder_0, undefined4 placeholder_1, char **envp) Upping the difficulty, `check` is no longer exported so it's now listed as `fcn.080485b9`. To make our lives a bit easier, let's set the name manually. -```c +``` [0x08048400]> afn check @ fcn.080485b9 [0x08048400]> pdg @ check @@ -88,12 +87,13 @@ void check(int32_t arg_4h, int32_t arg_8h) } ``` -This looks like the `check` we've seen in previous version except there is now a parity check slapped on the end of it where -the string "wtf?" is printed. +This looks like the `check` we've seen in previous version except there is now a parity check slapped on the end of it +where the string "wtf?" is printed. Before we can continue to the other functions, they have to be analyzed first. We can analyze all functions recursively -using `afr`. -```c +using `afr`. + +``` [0x08048400]> afr @ check [0x08048400]> pdg @ check @@ -139,13 +139,14 @@ void check(int32_t arg_4h, int32_t arg_8h) return; } ``` + The reason we're doing it this way in this case, is because `aaa` will cause some critical information to be omitted: namely the code that prints `"wtf?"`, more on that later. -For now though let's first check out `fcn.08048542`. We can probably already guess its identity as the code structure remains largely unchanged -from the previous versions. But it can't hurt to do our due diligence. +For now though let's first check out `fcn.08048542`. We can probably already guess its identity as the code structure +remains largely unchanged from the previous versions. But it can't hurt to do our due diligence. -```c +``` [0x08048400]> pdg @ fcn.08048542 // WARNING: Variable defined which should be unmapped: var_18h @@ -176,10 +177,11 @@ void fcn.08048542(int32_t arg_4h, int32_t arg_8h) } ``` -That does indeed look like `parell` from the previous versions. And that must make `fcn.080484b4` `dummy`. But look, there's an extra `if` inside -the parity check! Apparently some global variable has to be set to `1` in order for the password to be valid. +That does indeed look like `parell` from the previous versions. And that must make `fcn.080484b4` `dummy`. But look, +there's an extra `if` inside the parity check! Apparently some global variable has to be set to `1` in order for +the password to be valid. -```c +``` [0x08048400]> pdg @ fcn.080484b4 // WARNING: Variable defined which should be unmapped: var_18h @@ -210,12 +212,12 @@ undefined4 fcn.080484b4(undefined4 placeholder_0, int32_t arg_8h) [0x08048400]> afn parell @ fcn.08048542 ``` -And this must be `dummy`... With an addition. Can you spot it? This is where that global variable that we saw earlier gets set! -On the line containing `*(undefined4 *)0x804a02c = 1;`, more specifically. +And this must be `dummy`... With an addition. Can you spot it? This is where that global variable that we saw earlier +gets set! On the line containing `*(undefined4 *)0x804a02c = 1;`, more specifically. But before we continue let's see if there are any other references to or from this global variable. -```c +``` [0x08048400]> axf @ 0x804a02c [0x08048400]> axt @ 0x804a02c dummy 0x8048505 [DATA] mov dword [0x804a02c], 1 @@ -224,7 +226,7 @@ parell 0x804858f [DATA] cmp dword [0x804a02c], 1 It doesn't appear to be the case, so let's go back to `check`. -```c +``` [0x08048400]> pdg @ check // WARNING: Variable defined which should be unmapped: var_28h @@ -272,11 +274,10 @@ void check(int32_t arg_4h, int32_t arg_8h) We still have one unidentified function left: `fcn.08048524`. - -```c +``` [0x08048400]> pdg @ fcn.08048524 -void fcn.08048524 noreturn (void) +void fcn.08048524(void) { sym.imp.printf("Password Incorrect!\n"); // WARNING: Subroutine does not return @@ -284,9 +285,10 @@ void fcn.08048524 noreturn (void) } ``` -This doesn't seem to do much, other than to print that the password is incorrect and exit. So let's call it `print_and_exit`. +This doesn't seem to do much, other than to print that the password is incorrect and exit. So let's call it +`print_and_exit`. -```c +``` [0x08048400]> afn print_and_exit @ fcn.08048524 [0x08048400]> pdg @ check @@ -333,9 +335,9 @@ void check(int32_t arg_4h, int32_t arg_8h) } ``` -Interestingly, `print_and_exit` is called unconditionally before the second parity check, meaning it is never executed under normal -circumstances. If we had used `aaa` to analyze this binary, Rizin would have noticed this and it would have simply omitted it from -the disassembly and decompilation outputs. +Interestingly, `print_and_exit` is called unconditionally before the second parity check, meaning it is never executed +under normal circumstances. If we had used `aaa` to analyze this binary, Rizin would have noticed this, and it would +have simply omitted it from the disassembly and decompilation outputs. If you happen to accidentally (or intentionally) run `aaa`, you can remove all function analysis using `af-*`, after which you can run `aa`, followed by `afr` where needed. @@ -348,13 +350,13 @@ With that being said, it doesn't seem like the password constraints have changed Before we close Rizin however let's save this as a project first, so we don't lose all our hard work naming the functions. -```c +``` [0x08048400]> Ps crackme0x07.rzdb ``` And as we concluded, the passwords from the previous version still work. -```bash +```shell $ LOL= ./crackme0x07 IOLI Crackme Level 0x07 Password: 88 @@ -366,19 +368,19 @@ Password: 12346 Password OK! ``` -## Wtf? +## WTF? -We could go to the next one. Technically we've solved this crackme. But we have some unfinished business: the `wtf?` string. Let's see if we can -find a way to reach the code that's supposed to write it to the console! +We could go to the next one. Technically we've solved this crackme. But we have some unfinished business: the `wtf?` +string. Let's see if we can find a way to reach the code that's supposed to write it to the console! -It's easy enough using the debugger: we can simply set the instruction pointer to some location after the `print_and_exit` function (remember -`dr eip=
`). +It's easy enough using the debugger: we can simply set the instruction pointer to some location after +the `print_and_exit` function (remember `dr eip=
`). -We can reopen the current file in debug mode using the `ood` command. We do need an environment variable set that starts with `LOL`, we -can achieve this using the `dor` command. And let's also set a breakpoint at the location `print_and_exit` is called so we can jump -over it manually. +We can reopen the current file in debug mode using the `ood` command. We do need an environment variable set that +starts with `LOL`, we can achieve this using the `dor` command. And let's also set a breakpoint at the location +`print_and_exit` is called so we can jump over it manually. -```bash +``` [0x08048400]> ood Process with PID 191704 started... [0xf173fcd0]> dor setenv=LOL=O @@ -391,43 +393,39 @@ Password: 2 hit breakpoint at: 0x804862a ``` -Now we should be at the instruction that reads `call print_and_exit` (confirm with `pd 1 @ eip`). Now we need to find the address of the -instruction that comes after this one and set the instruction pointer to equal this value. +Now we should be at the instruction that reads `call print_and_exit` (confirm with `pd 1 @ eip`). Now we need to find +the address of the instruction that comes after this one and set the instruction pointer to equal this value. -```bash -[0x0804862a]> pd 2 @ eip ``` -```asm +[0x0804862a]> pd 2 @ eip │ ;-- eip: │ 0x0804862a b call print_and_exit ; print_and_exit │ 0x0804862f mov eax, dword [arg_8h] -``` -```bash [0x0804862a]> dr eip=0x0804862f [0x0804862a]> pd 1 @ eip -``` -```asm │ ;-- eip: │ 0x0804862f mov eax, dword [arg_8h] ``` With the `print_and_exit` function skipped we can continue execution. -```bash +``` [0x0804862a]> dc wtf? (191704) Process exited with status=0x0 [0xf3608579]> doc # close the debugging session ``` -We've successfully triggered the `wtf?` code using the debugger. But that's no fun! Let's see if there is a way we can reach that -code (semi-)naturally. +We've successfully triggered the `wtf?` code using the debugger. But that's no fun! Let's see if there is a way we can +reach that code (semi-)naturally. -In order for the `print_and_exit` function to be called we have to fail `parell` or the digit sum `check`. Failing `parell` is tricky -because the same version has to succeed after `print_and_exit` in order for our desired string to be printed. So we'll have -to fail the digit sum check, which means making sure that our digit sum will not land on 16 during the computation. +In order for the `print_and_exit` function to be called we have to fail `parell` or the digit sum `check`. +Failing `parell` is tricky because the same version has to succeed after `print_and_exit` in order for our desired +string to be printed. So we'll have to fail the digit sum check, which means making sure that our digit sum will +not land on 16 during the computation. -Easy enough! The only problem we have is that `exit` stops the process... But what if we were to make our own version of `exit`? +Easy enough! The only problem we have is that `exit` stops the process... But what if we were to make our own version +of `exit`? ```c void exit(int status) { @@ -442,8 +440,9 @@ void exit(int status) { ``` This turns `exit` into something that, well, *doesn't* exit. `__builtin_return_address` is used to look two -call frames up for a return address (the return address of `print_and_exit`) and jumps to it. Let's save it to a file called `exit.c`. -Compile it to a shared library using `gcc -m32 -shared -o libexit.so exit.c` and then we can preload it using `LD_PRELOAD`. +call frames up for a return address (the return address of `print_and_exit`) and jumps to it. Let's save it to a file +called `exit.c`. Compile it to a shared library using `gcc -m32 -shared -o libexit.so exit.c` and then we can preload +it using `LD_PRELOAD`. ```bash $ LD_PRELOAD=./libexit.so LOL= ./crackme0x07 diff --git a/src/crackmes/ioli/ioli_0x08.md b/src/crackmes/ioli/ioli_0x08.md index bb158402..facdb9df 100644 --- a/src/crackmes/ioli/ioli_0x08.md +++ b/src/crackmes/ioli/ioli_0x08.md @@ -1,5 +1,4 @@ -IOLI 0x08 -========= +# IOLI 0x08 Time for the ninth crackme. @@ -13,13 +12,13 @@ nth paddr vaddr len size section type string 2 0x000007c5 0x080487c5 13 14 .rodata ascii Password OK!\n 3 0x000007d3 0x080487d3 5 6 .rodata ascii wtf?\n 4 0x000007d9 0x080487d9 24 25 .rodata ascii IOLI Crackme Level 0x08\n -5 0x000007f2 0x080487f2 10 11 .rodata ascii Password: +5 0x000007f2 0x080487f2 10 11 .rodata ascii Password: ``` -It looks like no new strings have been added. Before we jump into analyzing however, let's first see which functions have changed from the -previous version. We can get a nice overview using `rz-diff`. +It looks like no new strings have been added. Before we jump into analyzing however, let's first see which functions +have changed from the previous version. We can get a nice overview using `rz-diff`. -```diff +``` $ rz-diff -t functions crackme0x07 crackme0x08 .--------------------------------------------------------------------------------------------------------------------------. | name0 | size0 | addr0 | type | similarity | addr1 | size1 | name1 | diff --git a/src/crackmes/ioli/ioli_0x09.md b/src/crackmes/ioli/ioli_0x09.md index 2b335a6f..956edccf 100644 --- a/src/crackmes/ioli/ioli_0x09.md +++ b/src/crackmes/ioli/ioli_0x09.md @@ -1,11 +1,10 @@ -IOLI 0x09 -========= +# IOLI 0x09 And that brings us onto the last crackme. We can also use `rz-diff` to check for string differences. -```diff +``` $ rz-diff -t strings crackme0x08 crackme0x09 --- crackme0x08 +++ ./crackme0x09 @@ -23,7 +22,7 @@ $ rz-diff -t strings crackme0x08 crackme0x09 The only change is the version info (from 0x08 to 0x09). So let's check for functions. -```diff +``` $ rz-diff -t functions crackme0x08 ./crackme0x09 .--------------------------------------------------------------------------------------------------------------------------. | name0 | size0 | addr0 | type | similarity | addr1 | size1 | name1 | @@ -52,7 +51,7 @@ $ rz-diff -t functions crackme0x08 ./crackme0x09 We can see that a few functions have been changed. So let's check it out! We can also see that this version strips the symbol names again, but that should be no problem. We can easily identify them using the functions diff. -```bash +```shell $ rizin ./crackme0x09 [0x08048420]> aa [0x08048420]> afr @ main # recursively analyze functions, starting from main @@ -60,8 +59,6 @@ $ rizin ./crackme0x09 [0x08048420]> afn parell @ fcn.08048589 [0x08048420]> afn che @ fcn.0804855d [0x08048420]> afn dummy @ fcn.080484d4 -``` -```c [0x08048420]> pdg @ main // WARNING: Variable defined which should be unmapped: var_8h @@ -90,14 +87,13 @@ Looking at the functions diff we can see that `fcn.08048766` is named `__i686.ge in position-independent code to get the addresses of global constants (like string constants). Let's see if we can find out to which strings these offsets resolve to, but let's first give this new function a name. - -```bash +``` [0x08048420]> afn sym.__i686.get_pc_thunk.bx @ fcn.08048766 ``` To compute the addresses we can use ESIL. But we need to initialize it first. -```bash +``` [0x08048420]> s main [0x080486ee]> aei [0x080486ee]> aeip @@ -132,8 +128,6 @@ what particular string was printed here. ```bash [0x080486ef]> CC "IOLI Crackme Level 0x09" @ eip [0x080486ef]> pd 1 @ eip -``` -```asm │ ;-- eip: │ 0x08048722 call sym.imp.printf ; sym.imp.printf ; IOLI Crackme Level 0x09 ; int printf(const char *format) ```