Four years ago, I wrote a blogpost about how to defeat the IOLI crackmes serie. After giving an unplanned workshop at the r2con 2017 (because the main room was too small to handle the unexpected number of attendees, I ended up giving a workshop with xvilka), without any slides not preparation, after waking up at 3am (it was fun), I realised that I'm not using radare2 like I did 4 years ago, and that radare2 itself has changed quite a lot, hence this refreshed blogpost.
A funny note about the IOLI crackmes is that they were written by a radare2 contributor:
I wrote IOLI crackmes to teach my wife a bit of reversing around 10 years ago, I'm amazed they are still useful nowadays...
This might explain why some keys are suspiciously looking like a birthday date ;)
crackme0x0
$ r2 -A ./crackme0x00
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
-- Disassemble?! No Disassemble Johnny No. 5!!!
[0x0804842e]> pdf @ main
;-- main:
┌ (fcn) main 127
│ main ();
│ ; var int local_18h @ ebp-0x18
│ ; var int local_4h @ esp+0x4
│ ; DATA XREF from 0x08048377 (entry0)
│ 0x08048414 55 push ebp
│ 0x08048415 89e5 mov ebp, esp
│ 0x08048417 83ec28 sub esp, 0x28 ; '('
│ 0x0804841a 83e4f0 and esp, 0xfffffff0
│ 0x0804841d b800000000 mov eax, 0
│ 0x08048422 83c00f add eax, 0xf
│ 0x08048425 83c00f add eax, 0xf
│ 0x08048428 c1e804 shr eax, 4
│ 0x0804842b c1e004 shl eax, 4
│ 0x0804842e 29c4 sub esp, eax
│ 0x08048430 c70424688504. mov dword [esp], str.IOLI_Crackme_Level_0x00_n ; [0x8048568:4]=0x494c4f49 ; "IOLI Crackme Level 0x00\n" ; const char * format
│ 0x08048437 e804ffffff call sym.imp.printf ; int printf(const char *format)
│ 0x0804843c c70424818504. mov dword [esp], str.Password: ; [0x8048581:4]=0x73736150 ; "Password: " ; const char * format
│ 0x08048443 e8f8feffff call sym.imp.printf ; int printf(const char *format)
│ 0x08048448 8d45e8 lea eax, [ebp - 0x18]
│ 0x0804844b 89442404 mov dword [esp + 4], eax
│ 0x0804844f c704248c8504. mov dword [esp], 0x804858c ; [0x804858c:4]=0x32007325 ; const char * format
│ 0x08048456 e8d5feffff call sym.imp.scanf ; int scanf(const char *format)
│ 0x0804845b 8d45e8 lea eax, [ebp - 0x18]
│ 0x0804845e c74424048f85. mov dword [esp + 4], str.250382 ; [0x804858f:4]=0x33303532 ; "250382" ; const char *s2
│ 0x08048466 890424 mov dword [esp], eax ; const char * s2
│ 0x08048469 e8e2feffff call sym.imp.strcmp ; int strcmp(const char *s1, const char *s2)
│ 0x0804846e 85c0 test eax, eax
│ ┌─< 0x08048470 740e je 0x8048480
│ │ 0x08048472 c70424968504. mov dword [esp], str.Invalid_Password__n ; [0x8048596:4]=0x61766e49 ; "Invalid Password!\n" ; const char * format
│ │ 0x08048479 e8c2feffff call sym.imp.printf ; int printf(const char *format)
│┌──< 0x0804847e eb0c jmp 0x804848c
││└─> 0x08048480 c70424a98504. mov dword [esp], str.Password_OK_:__n ; [0x80485a9:4]=0x73736150 ; "Password OK :)\n" ; const char * format
││ 0x08048487 e8b4feffff call sym.imp.printf ; int printf(const char *format)
││ ; JMP XREF from 0x0804847e (main)
│└──> 0x0804848c b800000000 mov eax, 0
│ 0x08048491 c9 leave
└ 0x08048492 c3 ret
[0x0804842e]>
Radare2 is smart enough to show the argument of the strcmp
function at
0x08048469
, meaning that the flag is likely 250382
:
$ ./crackme0x00
IOLI Crackme Level 0x00
Password: 250382
Password OK :)
crackme0x01
jvoisin@kaa 18:02 /tmp/IOLI-crackme/bin-linux r2 ./crackme0x01 -A
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
-- Greetings, human.
[0x08048330]> pdf @ main
;-- main:
┌ (fcn) main 113
│ main ();
│ ; var int local_4h @ ebp-0x4
│ ; var int local_4h_2 @ esp+0x4
│ ; DATA XREF from 0x08048347 (entry0)
│ 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
│ 0x080483f5 83c00f add eax, 0xf
│ 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_n ; [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, [local_4h]
│ 0x0804841b 89442404 mov dword [local_4h_2], 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 [local_4h], 0x149a ; [0x149a:4]=-1
│ ┌─< 0x08048432 740e je 0x8048442
│ │ 0x08048434 c704244f8504. mov dword [esp], str.Invalid_Password__n ; [0x804854f:4]=0x61766e49 ; "Invalid Password!\n"
│ │ 0x0804843b e8dcfeffff call sym.imp.printf ; int printf(const char *format)
│ ┌──< 0x08048440 eb0c jmp 0x804844e
│ ││ ; JMP XREF from 0x08048432 (main)
│ │└─> 0x08048442 c70424628504. mov dword [esp], str.Password_OK_:__n ; [0x8048562:4]=0x73736150 ; "Password OK :)\n"
│ │ 0x08048449 e8cefeffff call sym.imp.printf ; int printf(const char *format)
│ │ ; JMP XREF from 0x08048440 (main)
│ └──> 0x0804844e b800000000 mov eax, 0
│ 0x08048453 c9 leave
└ 0x08048454 c3 ret
[0x08048330]> ps @ 0x804854c
%d
[0x08048330]>
The binary is reading a number (%d
format in scanf
), then comparing it
against 0x149a
. You can change the base of the immediate in the listing
with the command ahi d @ 0x0804842b
to get a nice listing, or simply use the calculator (?
) to get every common base:
[0x0804842b]> ? 0x149a
5274 0x149a 012232 5.2K 0000:049a 5274 "\x9a\x14" 0001010010011010 5274.0 5274.000000f 5274.000000
[0x08048330]> ahi d @ 0x0804842b
[0x08048330]> pdf @ main
;-- main:
┌ (fcn) main 113
│ main ();
│ ; var int local_4h @ ebp-0x4
│ ; var int local_4h_2 @ esp+0x4
│ ; DATA XREF from 0x08048347 (entry0)
│ 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
│ 0x080483f5 83c00f add eax, 0xf
│ 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_n ; [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, [local_4h]
│ 0x0804841b 89442404 mov dword [local_4h_2], 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 [local_4h], 5274 ; [0x149a:4]=-1
│ ┌─< 0x08048432 740e je 0x8048442
│ │ 0x08048434 c704244f8504. mov dword [esp], str.Invalid_Password__n ; [0x804854f:4]=0x61766e49 ; "Invalid Password!\n"
│ │ 0x0804843b e8dcfeffff call sym.imp.printf ; int printf(const char *format)
│ ┌──< 0x08048440 eb0c jmp 0x804844e
│ ││ ; JMP XREF from 0x08048432 (main)
│ │└─> 0x08048442 c70424628504. mov dword [esp], str.Password_OK_:__n ; [0x8048562:4]=0x73736150 ; "Password OK :)\n"
│ │ 0x08048449 e8cefeffff call sym.imp.printf ; int printf(const char *format)
│ │ ; JMP XREF from 0x08048440 (main)
│ └──> 0x0804844e b800000000 mov eax, 0
│ 0x08048453 c9 leave
└ 0x08048454 c3 ret
Checking the flag:
$ ./crackme0x01
IOLI Crackme Level 0x01
Password: 5274
Password OK :)
$
crackme0x2
jvoisin@kaa 18:13 /tmp/IOLI-crackme/bin-linux r2 -A ./crackme0x02
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
-- Execute commands on a temporary offset by appending '@ offset' to your
command.
[0x0804842b]> pdf @ main
;-- main:
┌ (fcn) main 144
│ main ();
│ ; 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 0x08048347 (entry0)
│ 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
│ 0x080483f5 83c00f add eax, 0xf
│ 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_n ; [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, [ebp - 4]
│ 0x0804841b 89442404 mov dword [esp + 4], 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 [ebp - 8], 0x5a ; 'Z'
│ 0x08048432 c745f4ec0100. mov dword [ebp - 0xc], 0x1ec
│ 0x08048439 8b55f4 mov edx, dword [ebp - 0xc]
│ 0x0804843c 8d45f8 lea eax, [ebp - 8]
│ 0x0804843f 0110 add dword [eax], edx
│ 0x08048441 8b45f8 mov eax, dword [ebp - 8]
│ 0x08048444 0faf45f8 imul eax, dword [ebp - 8]
│ 0x08048448 8945f4 mov dword [ebp - 0xc], eax
│ 0x0804844b 8b45fc mov eax, dword [ebp - 4]
│ 0x0804844e 3b45f4 cmp eax, dword [ebp - 0xc]
│ ┌─< 0x08048451 750e jne 0x8048461
│ │ 0x08048453 c704246f8504. mov dword [esp], str.Password_OK_:__n ; [0x804856f:4]=0x73736150 ; "Password OK :)\n"
│ │ 0x0804845a e8bdfeffff call sym.imp.printf ; int printf(const char *format)
│┌──< 0x0804845f eb0c jmp 0x804846d
│││ ; JMP XREF from 0x08048451 (main)
││└─> 0x08048461 c704247f8504. mov dword [esp], str.Invalid_Password__n ; [0x804857f:4]=0x61766e49 ; "Invalid Password!\n"
││ 0x08048468 e8affeffff call sym.imp.printf ; int printf(const char *format)
││ ; JMP XREF from 0x0804845f (main)
│└──> 0x0804846d b800000000 mov eax, 0
│ 0x08048472 c9 leave
└ 0x08048473 c3 ret
[0x0804842b]>
The interesting thing is happening between 0x0804842b
and 0x0804844b
,
where the value that we're entering is compared against a computed one,
without any side-effect, so we can simply emulate this part with ESIL instead
of resorting to pen and paper:
[0x08048330]> s 0x0804842b
[0x0804842b]> aeim
[0x0804842b]> aeip
[0x0804842b]> aesu 0x0804844e
You can check that it's emulating things correctly by switching to the
V
isual mode, and stepping with F8
if you're used to Ollydbg or s
for a
more radare2-ish way.
We can use the powerful pf
to print the data formatted directly (d
for an integer,
like with printf(3)
), instead of converting it with ahi
:
[0x0804842b]> pf d @ ebp - 0xc
0x00177ff4 = 338724
[0x0804842b]>
$ ./crackme0x02
IOLI Crackme Level 0x02
Password: 338724
Password OK :)
$
crackme0x03
[0x080484df]> pdf @ sym.main
;-- main:
┌ (fcn) sym.main 128
│ ; DATA XREF from 0x08048377 (entry0)
│ 0x08048498 55 push ebp
│ 0x08048499 89e5 mov ebp, esp
│ 0x0804849b 83ec18 sub esp, 0x18
│ 0x0804849e 83e4f0 and esp, 0xfffffff0
│ 0x080484a1 b800000000 mov eax, 0
│ 0x080484a6 83c00f add eax, 0xf
│ 0x080484a9 83c00f add eax, 0xf
│ 0x080484ac c1e804 shr eax, 4
│ 0x080484af c1e004 shl eax, 4
│ 0x080484b2 29c4 sub esp, eax
│ 0x080484b4 c70424108604. mov dword [esp], str.IOLI_Crackme_Level_0x03_n ; [0x8048610:4]=0x494c4f49 ; "IOLI Crackme Level 0x03\n"
│ 0x080484bb e890feffff call sym.imp.printf ; int printf(const char *format)
│ 0x080484c0 c70424298604. mov dword [esp], str.Password: ; [0x8048629:4]=0x73736150 ; "Password: "
│ 0x080484c7 e884feffff call sym.imp.printf ; int printf(const char *format)
│ 0x080484cc 8d45fc lea eax, [local_4h]
│ 0x080484cf 89442404 mov dword [local_4h_2], eax
│ 0x080484d3 c70424348604. mov dword [esp], 0x8048634 ; [0x8048634:4]=0x6425
│ 0x080484da e851feffff call sym.imp.scanf ; int scanf(const char *format)
│ 0x080484df c745f85a0000. mov dword [local_8h], 0x5a ; 'Z'
│ 0x080484e6 c745f4ec0100. mov dword [local_ch], 0x1ec
│ 0x080484ed 8b55f4 mov edx, dword [local_ch]
│ 0x080484f0 8d45f8 lea eax, [local_8h]
│ 0x080484f3 0110 add dword [eax], edx
│ 0x080484f5 8b45f8 mov eax, dword [local_8h]
│ 0x080484f8 0faf45f8 imul eax, dword [local_8h]
│ 0x080484fc 8945f4 mov dword [local_ch], eax
│ 0x080484ff 8b45f4 mov eax, dword [local_ch]
│ 0x08048502 89442404 mov dword [local_4h_2], eax
│ 0x08048506 8b45fc mov eax, dword [local_4h]
│ 0x08048509 890424 mov dword [esp], eax
│ 0x0804850c e85dffffff call sym.test
│ 0x08048511 b800000000 mov eax, 0
│ 0x08048516 c9 leave
└ 0x08048517 c3 ret
[0x080484df]> pdf @ sym.test
┌ (fcn) sym.test 42
│ ; CALL XREF from 0x0804850c (sym.main)
│ 0x0804846e 55 push ebp
│ 0x0804846f 89e5 mov ebp, esp
│ 0x08048471 83ec08 sub esp, 8
│ 0x08048474 8b4508 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8
│ 0x08048477 3b450c cmp eax, dword [arg_ch] ; [0xc:4]=-1 ; 12
│ ┌─< 0x0804847a 740e je 0x804848a
│ │ 0x0804847c c70424ec8504. mov dword [esp], str.Lqydolg_Sdvvzrug_ ; [0x80485ec:4]=0x6479714c ; "Lqydolg#Sdvvzrug$"
│ │ 0x08048483 e88cffffff call sym.shift
│ ┌──< 0x08048488 eb0c jmp 0x8048496
│ ││ ; JMP XREF from 0x0804847a (sym.test)
│ │└─> 0x0804848a c70424fe8504. mov dword [esp], str.Sdvvzrug_RN______ ; [0x80485fe:4]=0x76766453 ; "Sdvvzrug#RN$$$#=,"
│ │ 0x08048491 e87effffff call sym.shift
│ │ ; JMP XREF from 0x08048488 (sym.test)
│ └──> 0x08048496 c9 leave
└ 0x08048497 c3 ret
[0x080484df]>
Again, a block of assembly without side-effect, and then a function that
suspiciously looks like a goodboy/badboy one, with the sym.shift
symbol
looking like a decrypt'n'print function: it's taking an encrypted-looking
string, and is called in both branches of the function.
Same procedure than for the previous crackme, ESIL:
[0x08048360]> s 0x080484df
[0x080484df]> aeim
[0x080484df]> aeip
[0x080484df]> aesu 0x08048506
[0x080484df]> dr=
oeax 0x00000000 eax 0x00052b24 ebx 0x00000000 ecx 0x00000000
edx 0x000001ec esi 0x00000000 edi 0x00000000 esp 0x00178000
ebp 0x00178000 eip 0x08048506 eflags
[0x080484df]> ? eax
338724 0x52b24 01225444 330.8K 5000:0b24 338724 "$+\x05" 000001010010101100100100 338724.0 338724.000000f 338724.000000
$ ./crackme0x03
IOLI Crackme Level 0x03
Password: 338724
Password OK!!! :)
$
crackme0x04
For this one, we'll use the visual mode: analyse the binary by launching
radare2 with -A
, seek to main
with s main
, enter ascii-graph mode with
VV
(you can switch to visual mode with V
, and then hit V
again (or space
)
to get into grpah mode) then jump to the sym.check
symbol by pressing gc
(since those are the bracketed letters next to it).
You can adjust the zoom level with the +
/-
keys, move around with
hjkl
like in vim, select nodes with tab
and shift-tab
, and of course,
since you're in radare2, get help with ?
.
┌────────────────────┐
│ [0x8048484] ;[ga] │
└────────────────────┘
v
│
.───────────────────────.
┌────────────────────┐ │
│ 0x8048498 ;[gd] │ │
└────────────────────┘ │
f t │
┌───────────┘ └──────┐ │
│ │ │
┌────────────────────┐ ┌────────────────────┐│
│ 0x80484a8 ;[gg] │ │ 0x80484fb ;[gc] ││
└────────────────────┘ └────────────────────┘│
f t │
┌─────────┘ └─────┐ │
│ │ │
┌────────────────────┐┌────────────────────┐ │
│ 0x80484dc ;[gj] ││ 0x80484f4 ;[gf] │ │
└────────────────────┘└────────────────────┘ │
└────────────────────────────┘
Likely a badboy in [gc]
(you can see the Password incorrect!
string if
you zoom enough, with the +
key.), then we have two checks, one in [gd]
,
and the other in [gg]
:
[0x08048484]> pdb @ 0x8048498
0x08048498 8b4508 mov eax, dword [arg_8h] ; [0x8:4]=-1 ; 8
0x0804849b 890424 mov dword [esp], eax
0x0804849e e8e1feffff call sym.imp.strlen ; size_t strlen(const char *s)
0x080484a3 3945f4 cmp dword [local_ch], eax ; [0x13:4]=-1 ; 19
┌─< 0x080484a6 7353 jae 0x80484fb
[0x08048484]>
This check is likely to prevent the loop from reading behind the entered string.
[0x080483d0]> pdb @ 0x80484a8
0x080484a8 8b45f4 mov eax, dword [local_ch]
0x080484ab 034508 add eax, dword [arg_8h]
0x080484ae 0fb600 movzx eax, byte [eax]
0x080484b1 8845f3 mov byte [local_dh], al
0x080484b4 8d45fc lea eax, [local_4h]
0x080484b7 89442408 mov dword [local_8h_2], eax
0x080484bb c74424043886. mov dword [local_4h_2], 0x8048638 ; [0x8048638:4]=0x50006425
0x080484c3 8d45f3 lea eax, [local_dh]
0x080484c6 890424 mov dword [esp], eax
0x080484c9 e8d6feffff call sym.imp.sscanf ; int sscanf(const char *s, const char *format, ...)
0x080484ce 8b55fc mov edx, dword [local_4h]
0x080484d1 8d45f8 lea eax, [local_8h]
0x080484d4 0110 add dword [eax], edx
0x080484d6 837df80f cmp dword [local_8h], 0xf ; [0xf:4]=-1 ; 15
┌─< 0x080484da 7518 jne 0x80484f4
[0x080483d0]>
A call to sscanf
recognized by radare2, with %d
as format (ps @ 0x8048638
to check), with the result added to local_8h
, that is later compared to
0xf
. You can verify that local_8h
isn't modified anywhere else by
highlighting it by typing / local_8h
in visual mode. So our key simply has to
be numeric, and its sum must be 0xf
, aka 16
:
$ ./crackme0x04
IOLI Crackme Level 0x04
Password: 1111111111111111
Password OK!
$
crackme0x05
The sym.main
and sym.check
functions are the same, except for the presence
of a sym.parell
function in the sym.check
one:
┌───────────────────────────────────┐
│ [0x8048484] ;[gc] │
│ │
│ (fcn) sym.parell 68 │
│ push ebp │
│ mov ebp, esp │
│ sub esp, 0x18 │
│ lea eax, [local_4h] │
│ mov dword [local_8h], eax │
│ mov dword [local_4h_2], 0x8048668 │
│ mov eax, dword [arg_8h] │
│ mov dword [esp], eax │
│ call sym.imp.sscanf;[ga] │
│ mov eax, dword [local_4h] │
│ and eax, 1 │
│ test eax, eax │
│ jne 0x80484c6;[gb] │
└───────────────────────────────────┘
f t
┌────────────────────┘ └──────────────┐
│ │
┌─────────────────────────────────────┐ ┌──────────────────┐
│ 0x80484ae ;[gf] │ │ 0x80484c6 ;[gb] │
│ ; "Password OK!\n" │ │ leave │
│ mov dword [esp], str.Password_OK__n │ │ ret │
│ call sym.imp.printf;[gd] │ └──────────────────┘
│ mov dword [esp], 0 │
│ call sym.imp.exit;[ge] │
└─────────────────────────────────────┘
Nothing funky, a simple and eax, 1
check, to see if the key is divisible by
2
:
$ ./crackme0x05
IOLI Crackme Level 0x05
Password: 55222
Password OK!
$
crackme0x06
The 6th crackme is similar to the previous one, except the presence
of a sym.dummy
function that looks a bit ugly:
┌────────────────────┐
│ [0x804851a] ;[gd] │
└────────────────────┘
f t
┌───────┘ └──────────────┐
│ │
┌──────────────────┐ │
│ 0x8048550 ;[ge] │ │
└──────────────────┘ │
v │
│ │
│ .─────────. │
┌──────────────────┐│ │
│ 0x8048557 ;[gf] ││ │
└──────────────────┘│ │
f t │ │
┌───────┘ └─────────│──────────┐ │
│ │ │ │
┌────────────────────┐ │ ┌────────────────────┐
│ 0x804855d ;[gh] │ │ │ 0x8048586 ;[gc] │
└────────────────────┘ │ └────────────────────┘
f t │
┌─────────────┘ │ │
│ │ │
┌────────────────────┐ │ │
│ 0x8048567 ;[gk] │ │ │
└────────────────────┘ │ │
┌────────────────────┐ │
│ 0x804857f ;[gg] │ │
└────────────────────┘ │
└─────────────────┘
We can get a summary of what the function is doing:
[0x080484b4]> pds
0x080484ee str.LOLO
0x080484fc call sym.imp.strncmp
0x08048535 call sym.imp.sscanf
0x08048547 call sym.dummy
0x08048567 str.Password_OK__n
0x0804856e call sym.imp.printf
0x0804857a call sym.imp.exit
[0x080484b4]>
Odds are that it'll compare an provided string against the LOLO
one.
But we're already providing a key, so where is crackme getting this string
from?
$ r2 -d ./crackme0x06
Process with PID 11804 started...
= attach 11804 11804
bin.baddr 0x08048000
Using 0x8048000
asm.bits 32
-- Experts agree, security holes suck, and we fixed some of them!
[0xf76dfa20]> db sym.imp.strncmp
[0xf76dfa20]> dc
IOLI Crackme Level 0x06
Password: 55222
hit breakpoint at: 80484fc
[0x080484fc]> pd 1
;-- eip:
0x080484fc b e8d7feffff call sym.imp.strncmp
[0x080484fc]> drr
eax 0xffec6693 eax stack R W 0x5f474458 (XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0) --> ascii
ebx 0x00000000 ecx
ecx 0x00000000 ecx
edx 0xffec647c edx stack R W 0xffec6693 --> eax stack R W 0x5f474458 (XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0) --> ascii
esi 0x00000001 esi
edi 0xf76ad000 (/lib/i386-linux-gnu/libc-2.24.so) edi library R W 0x1b5db0
esp 0xffec62d0 esp stack R W 0xffec6693 --> eax stack R W 0x5f474458 (XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0) --> ascii
ebp 0xffec62e8 ebp stack R W 0xffec6308 --> stack R W 0xffec6338 --> stack R W 0xffec63d8 --> stack R W 0x0 --> ecx
eip 0x080484fc (LOAD0) (/tmp/IOLI-crackme/bin-linux/crackme0x06) eip program R X 'call 0x80483d8' 'crackme0x06'
xfs 0x00000000 ecx
xgs 0x00000063 ascii
xcs 0x00000023 ascii
xss 0x0000002b ascii
eflags 1I eflags
oeax 0xffffffff oeax
[0x080484fc]> dc
hit breakpoint at: 80484fc
[0x080484fc]> drr
eax 0xffec66c7 eax stack R W 0x5f474458 (XDG_CONFIG_DIRS=/etc/xdg/xdg-xubuntu:/etc/xdg:/etc/xdg) --> ascii
ebx 0x00000000 ebx
ecx 0x00000004 ecx
edx 0xffec647c edx stack R W 0xffec6693 --> stack R W 0x5f474458 (XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0) --> ascii
esi 0x00000001 esi
edi 0xf76ad000 (/lib/i386-linux-gnu/libc-2.24.so) edi library R W 0x1b5db0
esp 0xffec62d0 esp stack R W 0xffec66c7 --> eax stack R W 0x5f474458 (XDG_CONFIG_DIRS=/etc/xdg/xdg-xubuntu:/etc/xdg:/etc/xdg) --> ascii
ebp 0xffec62e8 ebp stack R W 0xffec6308 --> stack R W 0xffec6338 --> stack R W 0xffec63d8 --> stack R W 0x0 --> ebx
eip 0x080484fc (LOAD0) (/tmp/IOLI-crackme/bin-linux/crackme0x06) eip program R X 'call 0x80483d8' 'crackme0x06'
xfs 0x00000000 ebx
xgs 0x00000063 ascii
xcs 0x00000023 ascii
xss 0x0000002b ascii
eflags 1I eflags
oeax 0xffffffff oeax
[0x080484fc]>
By putting a breakpoint on strncmp
and dereferencing the pointers with the
drr
command, we can guess that it's looking in the environnement variables:
$ LOLO=1337 ./crackme0x06
IOLI Crackme Level 0x06
Password: 55222
Password OK!
$
crackme0x07
Against, similar to the previous one, with some extra nodes in the sym.dummy
function (the symboles are stripped, but you can recognise the function by the
shape of their control-flow graphs, so it's ok.):
You can quickly get the dissasemblies of the nodes by getting in the minimap
mode by mashing the -
key until the graph is small enough, then move between
the node with tab
and shift-tab
.
[ 0x804857f ]
; JMP XREF from 0x080485b5 (sub.sscanf_542)
; [0x9:4]=-1
; 9 __8542__
cmp dword [local_8h], 9 f t
jg 0x80485b7;[gc] ┌───┘ │
│ │
__8578__ │
v │
┌───────────┘ │
│ .────────. │
<@@@@@@> │ │
f t │ │
┌────┘ └────────│─────┐ │
│ │ │ │
__8585__ │ _85b7__
f t │
│ └─────────┐ │
│ │ │
__858f__ __85b0__ │
f t └────┘
┌──┘ └───┐
│ │
__8598__ │
v │
└──────┐ │
│ │
__85a4__
We can see that there is a comparison against the number 9
, likely the
maximal/minimal size of our key: if you hightlight the local_8h
variable,
you'll see that it's used: it's incremented by on on every loop passage, so
yeah, likely the length of our string, and the jump after the condition is a
jump if greater
, so our key has to be less than 9 chars long:
$ LOLO= ./crackme0x07
IOLI Crackme Level 0x07
Password: 111111118
Password OK!
$ LOLO= ./crackme0x07
IOLI Crackme Level 0x07
Password: 1111111117
Password Incorrect!
$
crackme0x8 and crackme0x9
They are (almost?) the same than crackme0x07
, I'm quite sure you'll figure how
to solve them on your own.
[0x00000000]> ?E Good luck and have fun with r2
.--. .--------------------------------.
| _| | |
| O O < Good luck and have fun with r2 |
| | | | |
|| | / `--------------------------------'
|`-'|
`---'
[0x00000000]>