该笔记目的是为了方便自己做题,所以每个打法写的非常简便,且部分内容为从各位师傅的文章中直接复制,没有记录更加深入的原理
部分结构源码
_IO_FILE
1 | struct _IO_FILE |
_IO_jump_t
1 | struct _IO_jump_t |
struct _IO_wide_data
1 | struct _IO_wide_data |
_IO_wfile_jumps
1 | const struct _IO_jump_t _IO_wfile_jumps libio_vtable = |
_IO_printf_buffer_as_file_jumps
1 | static const struct _IO_jump_t _IO_printf_buffer_as_file_jumps libio_vtable = |
_IO_cookie_jumps
1 | static const struct _IO_jump_t _IO_cookie_jumps libio_vtable = { |
Tcache Stashing Unlink Attack
- 利用流程
1、假设目前tcache bin
中已经有五个堆块,并且相应大小的small bin
中已经有两个堆块,由bk
指针连接为:chunk_A<-chunk_B
2、利用漏洞修改chunk_A
的bk
为fake chunk
,并且修改fake chunk
的bk
为target_addr - 0x10
3、通过calloc()
越过tcache bin
,直接从small bin
中取出chunk_B
返回给用户,并且会将chunk_A
以及其所指向的fake chunk
放入tcache bin
(这里只会检测chunk_A
的fd
指针是否指向了chunk_B
)
4、在fake chunk
放入tcache bin
之前,执行了bck->fd = bin;
的操作(这里的bck
就是fake chunk
的bk
,也就是target_addr - 0x10
),故target_addr - 0x10
的fd
,也就target_addr
地址会被写入一个与libc
相关大数值(可利用)
5、再申请一次,就可以从tcache
中获得fake chunk
的控制权
poc(how2heap):
1 | // glibc 2.36 |
fastbin reverse into tcache
- 利用流程:
1、申请14
个fastbin
范围内的chunk
,然后释放7
个用来填满tcache
,然后将剩下的七个释放进fastbin
中
2、修改第一个放进fastbin
的chunk
的fd
为&target
3、通过申请7
个chunk
将tcache
清空
4、申请一个chunk
,此时由于tcache
中已经没有chunk
了,所以会去fastbin
里拿,然后将fastbin
中剩余的chunk
转到tcache
中,fastbin
中有7
个,分配一个,剩下六个,依次放进tcache
中,而最先放入fastbin
中的chunk
的fd
被我们改成了&target
,所以tcache
会将target
也当做一个chunk
放进tcache
中
5、此时tcache
中有7
个chunk
,最后一个chunk
即为target
poc(how2heap):
1 | // glibc 2.36 |
largebin attack
- 利用流程:
1、在largebin list
中放入一个堆块A
,并利用UAF
等漏洞在bk_nextsize
写入target_addr - 0x20
2、释放一个大小略小于堆块A
的堆块B
进入到同一个largebin list
,此时就会在target_addr
中写入堆块B的地址
利用_IO_2_1_stdout_泄露地址
通常将_flags
设置为0xfbad1800
设置_IO_write_base
指向想要泄露的地方 _IO_write_ptr
指向泄露结束的地址
之后遇到puts
或printf
就会将_IO_write_base
指向的内容打印出来
1 | payload = p64(0xfbad1800) + p64(0) * 3 + '\x00' |
通过off_by_null进行unlink
注意:通常第一个被释放的chunk
不会被进行合并
某次做题时进行的操作
1 | ptr = heap_base+0x2c0 # ptr指向第一个chunk的fd指针的位置(chunk 0) |
chunk1的中间部分+chunk2+chunk3进行合并
house of botcake
通常在存在UAF
漏洞,但只能对其进行delete
操作时使用
利用步骤
1、先将tcache bin
填满(大小要大于0x80
)
2、再连续free
两个连着的堆块(A
在B
的上方,A
不能进入tcache bin
且 B
的大小要与第一步tcache bin
中的相等),使其合并后进入unsorted bin
3、从tcache bin
中取出一个堆块,空出一个位置
4、将Chunk B
利用UAF
漏洞,再次释放到tcache bin
中,并申请回unsorted bin
中的Chunk A & B
合并的大堆块(部分),修改Chunk B
的next
指针指向任意地址,并申请到任意地址的控制权
house of kiwi(<= 2.36)
- 将
_IO_file_jumps
中的_IO_new_file_sync
修改为setcontext + 61
注意:该_IO_file_jumps
位于stderr
中 - 其中
rdx
指向_IO_helper_jumps_addr
,rdi
指向_IO_2_1_stderr_addr
- 可以通过在
_IO_helper_jumps_addr + 0xA0
的位置写入rop
链也可以通过在libc
中找gadget
将rdi
转移到rbx
中 - 通过
__malloc_assert
触发该攻击
house of cat
- 依旧为将
stderr
所指向的区域伪造一个fake_io_file
- 利用模板
1 | fake_io_addr = heapbase+0xb00 # 伪造的fake_IO结构体的地址 |
- size为
0x118
模板二:
1 | fake_file = flat({ |
- 最终通过
_malloc_assert
来触发攻击
从2.36
版本开始,删除了_malloc_assert
对stderr
的刷新,因此通过该方法触发攻击的手段生效,但可以使用fsop
的方法来触发攻击(FSOP
需将vtable
改为IO_wfile_jumps+0x30
,并且要将_IO_list_all
指向可控的fake_file
地址)
house of 秦月汉关
修改libc
中的部分got.plt
节来执行我们想要的函数
例子
puts函数调用时会隐式的调用strlen
函数,而strlen
函数的got.plt
节在libc
中,如果我们将strlen
的got.plt
中的内容改为system
函数的地址,那么执行puts("/bin/sh")->strlen("/bin/sh")
就会变成puts("/bin/sh")->system("/bin/sh")
进而getshell
poc:
1 |
|
house of apple2
主要是因为系统没有对wfile_vtable
中函数指针的合法性进行检测,导致可以让我们进行利用
利用链:
1 | _IO_wfile_underflow_mmap |
在_IO_2_1_stderr_
的位置伪造一个fake_file
Csome版fake_file
:
1 | fake_file = flat({ |
通过exit
或者主函数返回触发该攻击
改方法也可以用于打rop
,将0x28
处设置为magic gadget
的地址,0x0
处布置好内容用于将指定的地址设置进rbx
中并使rip
能够指向setcontext+61
1 | # stderr-0x40+0x18 == 0 -> FILE->_wide_data->_IO_wirite_base == 0 |
house of husk
改方法主要是利用printf
函数在进行格式化字符串时会调用__printf_arginfo_table
和__printf_function_table
中的函数指针所指向的函数,此时我我们可以劫持这2个指针所以指向的地址,然后在该地址中写入ogg
的地址,在下次调用printf
函数时即可getshell
这里要注意,在进行格式化字符串时操作系统会对这两个指针判断是否空,如果为空则会调用calloc
来分配内存来给这两个表,其中每一个表的大小都为0x100
.需要注意的是,在我们伪造的表中,格式化字符所对应的函数指针要么是0
,要么是一个合法的地址
假设现在__printf_function_table
和__printf_arginfo_table
分别被填上了chunk 4
与chunk 8
的堆块地址(chunk header
)
方法一:
1 | one_gadget = libc.address + 0xe6c7e |
由于有堆块头,所以格式化字符的索引要减2
,这样写就满足了__printf_function_table
不为空,进入了printf_positional
函数,并调用了__printf_arginfo_table
中的函数指针
方法二:
1 | one_gadget = libc.address + 0xe6ed8 |
__printf_arginfo_table和__printf_function_table的地址可以直接通过p &
来查找
可以利用下面这个函数来查找相关地址:
1 | /* Register FUNC to be called to format SPEC specifiers. */ |
函数 vfprintf
中的部分源码:
1 | if (__glibc_unlikely (__printf_function_table != NULL |
house of pig
主要是利用_IO_str_overflow
这个io
函数,该函数中可以完成malloc
、memcpy
、free
一条龙服务
且该函数汇编中存在下面这段代码
1 | 0x00007ffff7e2f0dd <+61>: mov rdx,QWORD PTR [rdi+0x28] |
rdi指向IO_FILE
结构体首地址,可以方便我们来通过setcontext+61
来打srop
高版本没有hook
函数,可是_IO_str_overflow
中存在memset
函数的调用,而且该函数的got
表在glibc
,glibc
中的got
表可写,所以可以修改got
表来达到和hook
相同的效果
fake_file
1 | # 在0xa0的tcache链表头伪造一个memset_got_addr的地址 |
需要注意的是,在memset
之前仍然有free(IO->buf_base)
,因此需要伪造一下memset_got_addr
的fake chunk
的堆块头,以及其next chunk
的堆块头
最终通过exit()
函数或者程序正常退出触发攻击
house of emma
house of emma主要利用了_IO_cookie_jumps
这个vtable
利用house of KiWi
配合house of emma
的调用链为__malloc_assert
-> __fxprintf
-> __vfxprintf
-> locked_vfxprintf
-> __vfprintf_internal
-> _IO_new_file_xsputn
( => _IO_cookie_write
),这里用的是_IO_cookie_write
函数,用其他的当然也同理
fs寄存器的值和地址可以通过x/16gx pthread_self()
指令来查看
fake_file:
1 | # __pointer_chk_guard为tls[0x30]处的值,用于对数据进行加密 |
最终通过_malloc_hook
来触发(<=2.36
)
当然也可以通过_IO_flush_all
来触发,不过由于在exit
函数的调用过程中会出现很多会出现调用别的给加密过的函数指针,由于__pointer_chk_guard
已经给我们修改,所以这些函数指针在给调用的时候会给报错,即该方法的利用比较麻烦,不建议使用
劫持tls_dtor_list,利用__call_tls_dtors拿到权限
主要功能为getshell
,当然也可以利用setcontext+61
打srop
dtor_list:
1 | struct dtor_list |
__call_tls_dtors:
1 | void __call_tls_dtors (void) |
可以看到func
其实是一个函数指针,而obj
则是指向该函数的参数的位置
我们可以将tls_dtor_list
指向我们伪造的堆地址,将func
改为system
的地址(ogg
也可以),obj
指向/bin/sh\x00
即可getshell
注意,这里对func
这个函数指针进行了加密,所以要泄露出__pointer_chk_guard
的值或将其修改为已知的值
fs寄存器的值和地址可以通过x/16gx pthread_self()
指令来查看
模板:
1 | ROL = lambda val, r_bits, max_bits: \ |
若是想orw
,那么可以让func
成员为magic_gadget
的相关数据,将rdi
与rdx
转换后,再调用setcontext + 61
走SROP
即可
最终通过exit()
函数退出程序触发攻击
house of apple3 + house of 一骑当千
这个模板为两种打法相结合,house of 一骑当千
的好处是打orw
时不需要用到magic gadget
。house of apple3
的关注点主要是对成员_codecvt
的利用,当wfile
的vtable
给上保护后依然可以使用
模板:
1 | import numpy as np |
house of some
Csome学长提出的利用链,主要是通过伪造fake_file
来造成任意地址写和任意地址读
其中fake_file
分为write_file
和read_file
,令_IO_list_all
指向一个read_file
,并令write_file
的_chain
指向将要执行的read
函数所写入的地址。在进行fsop
时会遍历到read_file
执行read
函数,此时写入write_file
,并令其_chain
指向另外一个我们已经写好的read_file
。此时由于我们第一个read_flie
的_chain
已经指向我们read
函数所写入的地址,而该地址已经被我们写入了write_file
,我们可以通过该write_file
来泄露任意地址。执行完wirte
函数后rip
则会继续遍历到我们下一个read_file
。我们便可以不断的进行read-write-read
这样一个过程,可以无数次泄露程序的所有地址和修改任何地址中的内容
write_file:
1 | fake_file_write = flat({ |
read_file:
1 | fake_file_read = flat({ |
Csome的自动化脚本:
1 | from pwn import * |
在编写exp
时,只需通过from House_of_some import HouseOfSome
将该包导入即可
house of banana
🍌🍌🍌🍌🍌🍌🍌🍌🍌🍌🍌🍌🍌
通过puts函数来触发IO链
通过源码我们可以发现,puts
函数在启用时会调用虚表中的_IO_wfile_xsputn
函数,因此这里给了我们机会来修改虚表偏移来触发io
链
我们可以将fake_file
直接写在_IO_2_1_stdout_
的位置上面或者修改stdout
指针指向我们的fake_file
首先是用于getshell
的house of apple2
1 | fake_file = flat({ |
然后是用于打orw
的house of apple2
1 | fake_file = flat({ |
house of some改进版
这里就贴某次比赛的exp
,优点是可以不泄露堆地址
1 | fake_io_read = flat({ |
house of some2
原文链接:https://blog.csome.cc/p/house-of-some-2/ 只能说非常的暴力
1 | from bisect import * |
参考文章
https://www.cnblogs.com/Sta8r9/p/17586219.html(house of cat)
https://bbs.kanxue.com/thread-273895.htm#msg_header_h3_6(House of cat新型glibc中IO利用手法解析 && 第六届强网杯House of cat详解)
https://www.anquanke.com/post/id/235598(House OF Kiwi)
https://bbs.kanxue.com/thread-273832.htm([原创]House of apple 一种新的glibc中IO攻击方法 (2))
https://bbs.kanxue.com/thread-272098.htm#msg_header_h3_13([原创] CTF 中 glibc堆利用 及 IO_FILE 总结)
https://bbs.kanxue.com/thread-276031.htm([原创]无路远征——GLIBC2.37后时代的IO攻击之道(二)house_of_秦月汉关)
https://bbs.kanxue.com/thread-273863.htm#msg_header_h3_0([原创]House of apple 一种新的glibc中IO攻击方法 (3))
https://bbs.kanxue.com/thread-276056.htm([原创]无路远征——GLIBC2.37后时代的IO攻击之道(五)house_of_一骑当千)