pwnable.tw BookWriter
题目概述
这是pwnable.tw的一道题目,也是我学习IO_FILE结构遇到的第一个题目。这道题目有四个选择,可以add、view、edit、edit name四个功能,申请的page的大小没有限制。
1 | Welcome to the BookWriter ! |
开保护的情况:
1 | Arch: amd64-64-little |
题目漏洞
这个题目有3个漏洞,首先在程序接受content的输入时调用了以下函数,该函数会判断字符串最后一个字符是否为’\n’,如果为’\n’,才会将’\n’替换为’\0’,但如果输入的content恰好是输入的size的长度,最后结尾无’\n’,在printf时可能导致数据泄露。这个函数在add page和edit page时都调用这个函数接受content的输入。
1 | __int64 __fastcall sub_400856(__int64 a1, unsigned int a2) |
第二个漏洞是程序在edit page时,edit输入完content之后,是调用strlen()来判断coontent的长度,并存入对应的size数组,但如果content的结尾没有’\0’,那strlen()会将next chunk的size域加进去,这样在下一次edit的时候,我们就可以覆盖next chunk的size域。
1 | int edit_page() |
第三个是add page时判断page的个数是否大于8,在edit page时是判断page的个数是否大于7,正确应该是只能添加8个page,因为page_list的大小为64个字节,因为add可以添加9个page,那么page_list会溢出,正好溢出到size_list,这样我们就能在add第九个page时修改第一个page的size,由于page_list中存储的是page在堆上的起始地址,地址的数字对于size来说都比较大,那么我们就能在edit第一个page的时候达到堆的溢出,溢出的长度很大。
1 | int add_page() |
利用过程
首先,这道题目没有free,因为可以修改next chunk的size域,那我们可以,然后申请一个比top chunk大的内存来使top chunk进入unsorted bin中,从而获得一个freed chunk。
首先add一个page,第一次edit修改其size,第二次edit覆盖top chunk的size域。
1 | #overwrite topchunk |
修改后的堆的分布如下:
1 | pwndbg> x /8gx 0x603000 |
再申请一个大的chunk,旧的top chunk进入unsorted bin中。此时,旧的top chunk的fd和bk为main_arena+0x58,此时再add一个chunk,就可以泄露libc的地址。
1 | add_page(0x1000,'2222') #idx=1 |
page在堆上的分布如下所示。
1 | pwndbg> x /8gx 0x603000 #idx0 |
由于我们需要在堆上伪造fake _IO_FILE,因此还需要泄露堆的地址。这里使用第四个功能edit name,因为author存储在bss段上,长度为0x40,后面与它相邻的就是存储page addr的地址,当修改的author长度为0x40字节时,利用”%s”遇到’\0’才会截断的特点,泄露page在堆上的地址。
1 | info() |
修改后的author紧邻page_list。
1 | pwndbg> x /10gx 0x602060 |
现在已经申请了idx0-idx2三个page,继续申请page至idx8,idx8也就是第九个page的地址覆盖size_list的前8个字节,将第一个page的size修改为较大的数。
1 | for i in range(3,9): #idx3~idx8 |
1 | pwndbg> x /10gx 0x6020a0 |
这样在编辑idx0的内容时可以接受非常大的输入。
接下来进行house of orange的构造。首先是Unsorted Bin Attack,将unsorted bin中的chunk也就是旧的top chunk的bk修改为_IO_list_all-0x10,将_IO_list_all的内容修改为main_arena+0x58。
修改之前的chunk idx0和unsorted bin中的分布是:
1 | pwndbg> x /8gx 0x603000 #idx0 |
在旧的top chunk处构造fake _IO_FILE,将其起始地址的内容覆写为”/bin/sh”,size域修改为0x61,使其进入到以_IO_list_all为头部的_IO_FILE链中。
1 | payload = 0x2e * p64(0) #(0x603180 - 0x603010)/8 = 0x2e |
这里构造的条件是:
1 | _IO_vtable_offset (fp) == 0 |
在第一个条件中,fp->_vtable_offset的偏移是0x82,在exp将其伪造成0。
在第三个条件中,需要伪造fp->_wide_data->_IO_write_ptr和fp->_wide_data->_IO_write_base的值,_wide_data的偏移是0x50,调试可发现这两个值在fp->_wide_data中的偏移,其中rax是fp->_wide_data,0x20是_IO_write_ptr的偏移,0x18是_IO_write_base的偏移。
所以在exp中构造如下:
1 | IO_list_all = libc_base + libc.symbols['_IO_list_all'] |
最后在旧的top chunk处构造的fake _IO_FILE:
1 | pwndbg> p (*(struct _IO_FILE_plus*)0x603180) |
构造的虚表:
1 | pwndbg> p (*(struct _IO_jump_t *)0x603278) |
最后触发_IO_flush_all_lockp->_IO_OVERFLOW(fp, EOF)也就是_IO_flush_all_lockp->system(“/bin/sh”),获得shell。
1 | p.recvuntil('Your choice :') |
HCTF 2017 babyprintf
题目概述
这是HCTF2017的一道pwn题目,libc版本是2.24,可以申请不超过0x1000的chunk,有一个明显的格式化字符串的漏洞。
1 | $ ./babyprintf |
题目开保护的情况:
1 | Arch: amd64-64-little |
题目漏洞
对于这个格式化字符串的漏洞,题目中开了FORTIFY保护,这个保护针对格式化字符串有一些对抗,可以看到所有的printf都替换成了__printf_chk:
1 | void __fastcall __noreturn main(__int64 a1, char **a2, char **a3) |
关于格式化字符串详细的介绍可以看这篇文章。简单来说,这个保护有以下两条限制:
1 | 含有%nd的格式化字符串不能位于程序内存的可写地址。 |
但其实比如%1$x%2$x这样的格式化字符串后面我也没有用到,我只使用格式化字符串漏洞去泄露libc了,而且用的是连续的”%p”。
程序中还用了gets()来接受输入,导致一个堆溢出的漏洞,相当于对输入的数据没有限制,无限溢出。这里就想到了house of orange,但时libc的版本是2.24,增加了新的check,但仍然可以利用_IO_str_jumps中的一些函数。
1 | //glibc-2.24 /libio/strops.c |
这里利用的是__libc_IO_vtables中的_IO_str_finish。
利用过程
首先利用溢出修改top chunk的size域:
1 | def input(size,data): |
申请一个较大的chunk,并输入格式化字符串泄露libc,栈里面正好有一个__libc_start_main+0x241。
1 | ##leak libc base |
接下来同样是Unsorted Bin Attack,将旧的top chunk的bk修改为_IO_list_all-0x10,因为我们只能在申请chunk的时候输入string的内容,但是因为有一个溢出的漏洞,我们可以输入任意长度的内容,所以可以申请一个chunk后,再越过这个chunk的大小去修改unsorted bin中的bk。另外,构造符合跳转到_IO_str_finish的条件,_IO_str_finish函数实现如下。
1 | //glibc-2.24 /libio/strops.c |
可以看到我们需要构造的条件有:
1 | fp->_IO_buf_base为真,(fp->_flags & _IO_USER_BUF)为假 |
可以看到_IO_str_finish的条件很简单,但是如何去触发_IO_str_finish,参考这篇文章的思路,一方面利用fclose(fp),另一方面可以利用_IO_flush_all_lockp->_IO_OVERFLOW(fp, EOF),_IO_OVERFLOW(fp, EOF)是正确执行时根据函数跳转表中的偏移得到函数__overflow,如果我们将虚表地址修改为实际虚表地址-0x8,符合__libc_IO_vtables地址安全检查的范围内,在实际执行时_IO_str_overflow偏移-8处正好是_IO_str_finish。所以我们在构造时也需要满足_IO_flush_all_lockp->_IO_OVERFLOW(fp, EOF)的条件:
1 | fp->_mode <= 0 |
综合_IO_str_finish构造条件,得到最终构造条件如下:
1 | //_IO_OVERFLOW(fp, EOF): |
关于_s._free_buffer关于fp的偏移,可以在IDA中看到正好是0xe8:
exp中payload最终构造如下:
1 | _IO_str_jumps = libc_base + 0x3be4c0 |
最终触发_IO_flush_all_lockp,获得shell:
1 | p.sendline('1') |
pwnable.tw seethefile
待补充
题目概述
题目漏洞
利用过程
参考
http://tacxingxing.com/2018/01/12/pwnabletw-bookwriter/
https://bbs.pediy.com/thread-222735.htm
https://veritas501.space/2017/12/13/IO%20FILE%20%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/
https://firmianay.gitbooks.io/ctf-all-in-one/doc/4.13_io_file.html