Skip to content

Latest commit

 

History

History
419 lines (362 loc) · 23.2 KB

0x00.md

File metadata and controls

419 lines (362 loc) · 23.2 KB

This writeup demonstrates the use of two tools, Radare2 and GDB with Pwndbg, in a couple of different uses. For instructions on how to set up these tools, see How to Configure Your Environment for instructions on setting up Kali and installation of radare2, pwndbg, and more.

Setting up the IOLI CrackMe challenge

First, let's download and extract the IOLI CrackMe challenge binaries:

root@kali:~# wget http://pof.eslack.org/tmp/IOLI-crackme.tar.gz
root@kali:~# tar -xvzf IOLI-crackme.tar.gz
root@kali:~# cd IOLI-crackme/bin-linux

Don't know Radare2 yet?

I can definitely relate. Check out my Radare2 cheat sheet, but you'll also want to refer to teh Radare2 book.

Dissecting crackme0x00

For this writeup, we'll be focusing on the simplest one -- crackme0x00. Run it to get a feel for what it does.

root@kali:~#  ./crackme0x00 
IOLI Crackme Level 0x00
Password: test
Invalid Password!
root@kali:~# 

It asks you for a password, and apparently does some kind of check. Let's find a way around the password check.

Reviewing the crackme0x00 disassembly

Refer to the Radare2 tutorial for a quick walkthrough of the commands and hotkeys you need to know.

Before we dig in, take a moment to review the assembly instructions of the main function:

root@kali:~/IOLI-crackme/bin-linux# r2 -d ./crackme0x00
[0xf77d8ac0]> aaaa
[0xf77d8ac0]> pdf @ main
 
n:
/ (fcn) sym.main 127
|   sym.main ();
|           ; var int local_18h @ ebp-0x18
|           ; var int local_4h @ esp+0x4
|           ; JMP XREF from 0x08048377 (entry0)
|           ; 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 LEA str.IOLI_Crackme_Level_0x00_n ; "IOLI Crackme Level 0x00." @ 0x8048568
|           0x08048437      e804ffffff     call sym.imp.printf
|           0x0804843c      c70424818504.  mov dword [esp], str.Password: ; [0x8048581:4]=0x73736150 LEA str.Password: ; "Password: " @ 0x8048581
|           0x08048443      e8f8feffff     call sym.imp.printf
|           0x08048448      8d45e8         lea eax, [ebp - local_18h]
|           0x0804844b      89442404       mov dword [esp + local_4h], eax
|           0x0804844f      c704248c8504.  mov dword [esp], 0x804858c  ; [0x804858c:4]=0x32007325
|           0x08048456      e8d5feffff     call sym.imp.scanf
|           0x0804845b      8d45e8         lea eax, [ebp - local_18h]
|           0x0804845e      c74424048f85.  mov dword [esp + local_4h], str.250382 ; [0x804858f:4]=0x33303532 LEA str.250382 ; "250382" @ 0x804858f
|           0x08048466      890424         mov dword [esp], eax
|           0x08048469      e8e2feffff     call sym.imp.strcmp
|           0x0804846e      85c0           test eax, eax
|       ,=< 0x08048470      740e           je 0x8048480
|       |   0x08048472      c70424968504.  mov dword [esp], str.Invalid_Password__n ; [0x8048596:4]=0x61766e49 LEA str.Invalid_Password__n ; "Invalid Password!." @ 0x8048596
|       |   0x08048479      e8c2feffff     call sym.imp.printf
|      ,==< 0x0804847e      eb0c           jmp 0x804848c
|      |`-> 0x08048480      c70424a98504.  mov dword [esp], str.Password_OK_:__n ; [0x80485a9:4]=0x73736150 LEA str.Password_OK_:__n ; "Password OK :)." @ 0x80485a9
|      |    0x08048487      e8b4feffff     call sym.imp.printf
|      |    ; JMP XREF from 0x0804847e (sym.main)
|      `--> 0x0804848c      b800000000     mov eax, 0
|           0x08048491      c9             leave
\           0x08048492      c3             ret

Of interest:

There are a few instructions above that are of interest. Feel free to reference back, or just read through to get a feel for how the program works:

  • 0x08048443: A password prompt is printed via printf().
  • 0x08048456: The password is read in from user input via scanf().
  • 0x08048469: The user-input password is compared to the correct password via strcmp().
  • 0x0804846e: The result of strcmp() is returned. By convention, this return value is passed via the "EAX" CPU register.
  • 0x0804846e: The EAX register is tested. This instruction will affect another CPU register, EFLAGS. One of the bits in the EFLAGS register will tell us if EAX is equal to zero (the zero flag).
  • 0x08048470: This instruction will jump to 0x8048480 if EAX was equal to zero (which will happen if the strcmp() password check succeeds). If the password check fails, the next instruction will be executed (0x08048472).
  • 0x08048472: The "Invalid Password" sequence of instructions begin here. This looks like a bad place.
  • 0x08048480: The "Password OK" sequence of instructions begin here. We need to redirect the flow of the program to end up here.

Solutions

There are three immediately apparent (to me) ways to solve this challenge:

  • Find the password string: Find the correct password string, stored within the file. Since this is a simple challenge, you could guess at what this might be, simply by feeding the file to the strings command, but let's be certain by using good tools.
  • Modify the return value: Debug the binary, set a breakpoint where the binary returns from the strcmp(). By modifying the return value (stored in EAX), we can trick the rest of the program into thinking that the our (incorrect) password and the correct password string are a perfect match.
  • Patch the binary: A conditional jump instruction (JE), seen at 0x08048470, is the last part of the verification that the strings match. If we change this instruction to be an unconditional jump instruction (JMP), the password check will always succeed.

To that end, I've solved this simple challenge six different times. The three above techniquies are demonstrated with two different tools, Radare and GDB+pwndbg, albeit slightly out of order:

Solution #1: Using Radare2 to modify the return value from strcmp()

root@kali:~/Desktop/IOLI-crackme/bin-linux# r2 -d ./crackme0x00

[0xf77d8ac0]> aaaa

[0xf77d8ac0]> s main
[0x08048414]> pdf
            ;-- main:
/ (fcn) sym.main 127
|   sym.main ();
|           ; var int local_18h @ ebp-0x18
|           ; var int local_4h @ esp+0x4
|           ; JMP XREF from 0x08048377 (entry0)
|           ; 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 LEA str.IOLI_Crackme_Level_0x00_n ; "IOLI Crackme Level 0x00." @ 0x8048568
|           0x08048437      e804ffffff     call sym.imp.printf
|           0x0804843c      c70424818504.  mov dword [esp], str.Password: ; [0x8048581:4]=0x73736150 LEA str.Password: ; "Password: " @ 0x8048581
|           0x08048443      e8f8feffff     call sym.imp.printf
|           0x08048448      8d45e8         lea eax, [ebp - local_18h]
|           0x0804844b      89442404       mov dword [esp + local_4h], eax
|           0x0804844f      c704248c8504.  mov dword [esp], 0x804858c  ; [0x804858c:4]=0x32007325
|           0x08048456      e8d5feffff     call sym.imp.scanf
|           0x0804845b      8d45e8         lea eax, [ebp - local_18h]
|           0x0804845e      c74424048f85.  mov dword [esp + local_4h], str.250382 ; [0x804858f:4]=0x33303532 LEA str.250382 ; "250382" @ 0x804858f
|           0x08048466      890424         mov dword [esp], eax
|           0x08048469      e8e2feffff     call sym.imp.strcmp
|           0x0804846e      85c0           test eax, eax
|       ,=< 0x08048470      740e           je 0x8048480
|       |   0x08048472      c70424968504.  mov dword [esp], str.Invalid_Password__n ; [0x8048596:4]=0x61766e49 LEA str.Invalid_Password__n ; "Invalid Password!." @ 0x8048596
|       |   0x08048479      e8c2feffff     call sym.imp.printf
|      ,==< 0x0804847e      eb0c           jmp 0x804848c
|      |`-> 0x08048480      c70424a98504.  mov dword [esp], str.Password_OK_:__n ; [0x80485a9:4]=0x73736150 LEA str.Password_OK_:__n ; "Password OK :)." @ 0x80485a9
|      |    0x08048487      e8b4feffff     call sym.imp.printf
|      |    ; JMP XREF from 0x0804847e (sym.main)
|      `--> 0x0804848c      b800000000     mov eax, 0
|           0x08048491      c9             leave
\           0x08048492      c3             ret

[0x08048414]> db 0x0804846e              # Set a breakpoint right after strcmp()
[0x08048414]> dc                         # Begin execution, should break after strcmp()
IOLI Crackme Level 0x00
Password: test
hit breakpoint at: 804846e
= attach 35135 1
[0x0804846e]> dr~eax                     # Show the regsiters, specifically the return value from strcmp()
oeax = 0x00000001
eax = 0xffffffff
[0x0804846e]> dr eax=0                   # Change the return value from 1 to 0
[0x0804846e]> dr~eax                     # Confirm that the change worked
oeax = 0xffffffff
eax = 0x00000000
[0x0804846e]> dc                         # Continue execution
Password OK :)
PTRACE_EVENT_EXIT pid=35067, status=0
[0xf77a1d49]>                            # SUCCESS!

Solution #2: Using Radare2 to find the password string inside the binary

Work smarter, not harder.

In our disassembly above, the instruction at 0x0804845e, where we are moving a pointer to a string "250382" into [esp + local_4h]. Based on that comparison, we've got a good guess at the password:

root@kali:~/Desktop/IOLI-crackme/bin-linux# ./crackme0x00 
IOLI Crackme Level 0x00
Password: 250382
Password OK :)

Solution #3: Using Radare2 to patch the binary in memory, bypassing the check of the strcmp() return value

Password? We don't need no stinkin' password!

Let's modify the instructions themselves to make the binary always work, no matter what password we give it!

root@kali:~/Desktop/IOLI-crackme/bin-linux# r2 -d ./crackme0x00

There's an instruction at 0x08048470 (je 0x8048480) that is altering the flow of the program based on the result of the strcmp(). If we change this from a conditional jump (JE) to an unconditional jump (JMP), the password check will always succeeed.

For this to work, we have to launch radare2 with -d (debug) in addition to -w to enable writing (modifying) the executable:

root@kali:~/IOLI-crackme/bin-linux# r2 -N -d -w crackme0x00
Process with PID 37590 started...
= attach 37590 37590
bin.baddr 0x08048000
Assuming filepath /root/IOLI-crackme/bin-linux/crackme0x00
asm.bits 32
[0xf77bfac0]> s 0x08048470                                 # Seek to the JE instruction
[0x08048470]> pd 1                                         # Output a single line of disassembly
            0x08048470      740e           je 0x8048480
[0x08048470]> wa jmp 0x8048480
Written 2 bytes (jmp 0x8048480) = wx eb0e
[0x08048470]> pd 1                                         # Output a single line of disassembly.  Note the 0x74 is now 0xEB.
            0x08048470      eb0e           jmp 0x8048480
[0x08048470]> dc                                           # Run the program to test our changes.
IOLI Crackme Level 0x00
Password: test
Password OK :)
PTRACE_EVENT_EXIT pid=37590, status=0
= attach 37590 1
[0xf77bdd49]> quit                                         # SUCCESS!
Do you want to quit? (Y/n)
Do you want to kill the process? (Y/n)

Now, in theory, we patched the binary on disk, but it doesn't work! :-(

root@kali:~/IOLI-crackme/bin-linux# ./crackme0x00 
IOLI Crackme Level 0x00
Password: test
Invalid Password!

If you want to patch the binary on disk, we have to repeat these steps, but without a debugger. The r2 command is just a shortcut for radare2, with -N saying "don't load my ~/.radare2rc" and -w saying "enable read-write" node on the file. Note the lack of -d for debugging means we won't have the d series of commands.

root@kali:~/IOLI-crackme/bin-linux# r2 -N -w crackme0x00
[0x08048360]> oo+                                     # Turns out this is apparently unnecessary.
File crackme0x00 reopened in read-write mode
[0x08048360]> s 0x08048470; wa jmp 0x8048480; pd 1    # Patch the binary in one line
Written 2 bytes (jmp 0x8048480) = wx eb0e
            0x08048470      eb0e           jmp 0x8048480
[0x08048470]> quit
root@kali:~/IOLI-crackme/bin-linux# ./crackme0x00
IOLI Crackme Level 0x00
Password: test
Password OK :)
root@kali:~/IOLI-crackme/bin-linux#                   # SUCCESS!

Solution #4: Using GDB+pwndbg to find the password string inside the binary

root@kali:~/Desktop/IOLI-crackme/bin-linux# gdb ./crackme0x00 
GNU gdb (Debian 7.11.1-2) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Loaded 94 commands.  Type pwndbg [filter] for a list.
Reading symbols from ./crackme0x00...(no debugging symbols found)...done.
pwndbg> x/40i main                                                            # Look at the main function
   0x8048414 <main>:	push   %ebp
   0x8048415 <main+1>:	mov    %esp,%ebp
   0x8048417 <main+3>:	sub    $0x28,%esp
   0x804841a <main+6>:	and    $0xfffffff0,%esp
   0x804841d <main+9>:	mov    $0x0,%eax
   0x8048422 <main+14>:	add    $0xf,%eax
   0x8048425 <main+17>:	add    $0xf,%eax
   0x8048428 <main+20>:	shr    $0x4,%eax
   0x804842b <main+23>:	shl    $0x4,%eax
   0x804842e <main+26>:	sub    %eax,%esp
   0x8048430 <main+28>:	movl   $0x8048568,(%esp)
   0x8048437 <main+35>:	call   0x8048340 <printf@plt>
   0x804843c <main+40>:	movl   $0x8048581,(%esp)
   0x8048443 <main+47>:	call   0x8048340 <printf@plt>
   0x8048448 <main+52>:	lea    -0x18(%ebp),%eax
   0x804844b <main+55>:	mov    %eax,0x4(%esp)
   0x804844f <main+59>:	movl   $0x804858c,(%esp)
   0x8048456 <main+66>:	call   0x8048330 <scanf@plt>
   0x804845b <main+71>:	lea    -0x18(%ebp),%eax
   0x804845e <main+74>:	movl   $0x804858f,0x4(%esp)
   0x8048466 <main+82>:	mov    %eax,(%esp)
   0x8048469 <main+85>:	call   0x8048350 <strcmp@plt>                        # <---- There's our string comparison!
   0x804846e <main+90>:	test   %eax,%eax
   0x8048470 <main+92>:	je     0x8048480 <main+108>
   0x8048472 <main+94>:	movl   $0x8048596,(%esp)
   0x8048479 <main+101>:	call   0x8048340 <printf@plt>
   0x804847e <main+106>:	jmp    0x804848c <main+120>
   0x8048480 <main+108>:	movl   $0x80485a9,(%esp)
   0x8048487 <main+115>:	call   0x8048340 <printf@plt>
   0x804848c <main+120>:	mov    $0x0,%eax
   0x8048491 <main+125>:	leave  
   0x8048492 <main+126>:	ret    
   0x8048493:	nop
   0x8048494:	nop
   0x8048495:	nop
   0x8048496:	nop
   0x8048497:	nop
   0x8048498:	nop
   0x8048499:	nop
   0x804849a:	nop
pwndbg> break *main+85                                                      # Set a breakpoint right before the string comparison
Breakpoint 1 at 0x8048469
pwndbg> run
Starting program: /root/Desktop/IOLI-crackme/bin-linux/crackme0x00 
IOLI Crackme Level 0x00
Password: test

Breakpoint 1, 0x08048469 in main ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
[────────────────────────────────────────────────────────────────────────────────────────────────────REGISTERS────────────────────────────────────────────────────────────────────────────────────────────────────]
*EAX  0xffffd390 ◂— 'test'
 EBX  0x0
*ECX  0xf7fae000 ◂— 0x1b2db0
*EDX  0xf7faf87c ◂— 0x0
*EDI  0xf7fae000 ◂— 0x1b2db0
*ESI  0x1
*EBP  0xffffd3a8 ◂— 0x0
*ESP  0xffffd370 —▸ 0xffffd390 ◂— 'test'
*EIP  0x8048469 (main+85) —▸ 0xfffee2e8 ◂— 0x0
[──────────────────────────────────────────────────────────────────────────────────────────────────────CODE───────────────────────────────────────────────────────────────────────────────────────────────────────]
0x8048469 <main+85>     call   strcmp@plt                    <0x8048350>
        s1: 0xffffd390 ◂— 'test'
        s2: 0x804858f ◂— '250382'
 
   0x804846e <main+90>     test   eax, eax
   0x8048470 <main+92>     je     main+108                      <0x8048480>
 
   0x8048472 <main+94>     mov    dword ptr [esp], 0x8048596
   0x8048479 <main+101>    call   printf@plt                    <0x8048340>
 
   0x804847e <main+106>    jmp    main+120                      <0x804848c>

   0x804848c <main+120>    mov    eax, 0
   0x8048491 <main+125>    leave  
   0x8048492 <main+126>    ret    
 
   0x8048493               nop    
   0x8048494               nop    
[──────────────────────────────────────────────────────────────────────────────────────────────────────STACK──────────────────────────────────────────────────────────────────────────────────────────────────────]
00:0000esp  0xffffd370 —▸ 0xffffd390 ◂— 'test'
01:00040xffffd374 —▸ 0x804858f ◂— xor    dh, byte ptr [0x32383330] /* '250382' */
02:00080xffffd378 —▸ 0xffffd3a8 ◂— 0x0
03:000c│      0xffffd37c —▸ 0x80484bb (__libc_csu_init+27) ◂— lea    eax, [ebx - 0xe8]
04:00100xffffd380 ◂— 0x1
05:00140xffffd384 —▸ 0xf7fae000 ◂— 0x1b2db0
06:00180xffffd388 ◂— 0x0
07:001c│      0xffffd38c —▸ 0xf7e29a2b (__cxa_atexit+27) ◂— add    esp, 0x10
[────────────────────────────────────────────────────────────────────────────────────────────────────BACKTRACE────────────────────────────────────────────────────────────────────────────────────────────────────]
 ► f 0  8048469 main+85
   f 1 f7e13276 __libc_start_main+246
Breakpoint *main+85

And there's our string, handily parsed out by pwndbg as s2 (the second of two strings to be compared within strcmp().

Solution #5: Using GDB+pwndbg to modify the return value from strcmp()

Start as above, but now:

pwndbg> stepover           # Used to execute the instruction we're pointing at, without having GDB follow into the function.
pwndbg> set $eax=0         # Modify the return value of strcmp() to SUCCESS (0)
pwndbg> continue           # Continue execution of the program.
Continuing.
Password OK :)
[Inferior 1 (process 35543) exited normally]
pwndbg>                    # SUCCESS!

Solution #6: Using GDB+pwndbg to patch the binary in memory, bypassing the check of the strcmp() return value

We'll find the address of the conditional jump instruction (JE) and modify a single byte to change it to an unconditional jump instruction (JMP). Since GDB doesn't have a handy way to modify an instruction, we have to know that the byte that represents an unconditional short jump in the Intel x86 instruction set is 0xeb.

pwndbg> break *main+92
pwndbg> run
Starting program: /root/Desktop/IOLI-crackme/bin-linux/crackme0x00 
IOLI Crackme Level 0x00
Password: test

Breakpoint 1, 0x08048470 in main ()

[ ... snip ... ]

pwndbg> x/i main+92                                     # DISASSEMBLE THE INSTRUCTION AT main() + 92 BYTES
   0x8048470 <main+92>:	je     0x8048480 <main+108>
pwndbg> set *(char*)(main+92)=0xeb                      # SET A SINGLE BYTE (CHAR*) TO 0xEB
pwndbg> x/i main+92                                     # VERIFY OUR WORK
   0x8048470 <main+92>:	jmp    0x8048480 <main+108>
pwndbg> continue
Continuing.
Password OK :)
[Inferior 1 (process 35637) exited normally]