背景知识
这个题目同样是2018强网杯线上赛的pwn题—-note,题目可以在这里下载。
这里涉及到两个知识点:
1:malloc_consolidate对fastbin的合并。
2:在程序开启RELRO技术之后,不能对GOT表进行改写的情况下,利用覆盖__realloc_hook来实现控制流劫持。
realloc函数的处理流程
对于realloc函数,我以往的认识大概就是具有对指定地址堆块的重新分配功能。如果新申请的内存大小小于或者等于原先的内存,那么就会返回原先的堆块指针;如果新申请的内存堆块大小大于原先的内存,那么就会新开辟一块内存返回给调用者,并将原先内存的内容拷贝到新申请的内存空间中。
但是,对于realloc是以怎样的步骤来申请新的内存的却不是很清楚。
和以往一样,查看一下源码,这里只关注原先的内存不够用的情况:
1 | /* Try to expand forward into top */ |
1:检查下一块是否是top chunk,如果是并且top chunk的size满足要求,则直接到top chunk中进行扩展原先的内存。
2:如果第一步不能满足,则检查下一块是否是空闲的chunk,如果是并且size满足要求,则从下一个chunk中进行扩充。
3:如果前两步都不能满足,就只能通过malloc申请新的内存空间,如果新申请的内存是原先内存的下一个chunk,则不进行复制,释放原内存的操作,直接将两块内存合并;如果新申请的内存不是原先内存的下一个chunk,就需要进行复制,释放原先内存的操作。
在第三步进入malloc操作的时候,如果申请的内存过于大,以至于small bins中没有合适的内存块可以供使用,就会触发malloc_consolidate操作,这个操作会将fast bins中的所有chunk都取出来并进行合并。
上文,realloc操作的第三步,会对原先的内存进行free操作,如果原先的内存是属于fast bins的,结合这里的malloc_consolidate操作,会触发unlink操作。
什么是RELRO技术,怎么绕过
RELRO技术就是重定位只读技术,主要是为了防御针对修改GOT表的攻击。
重定位只读分为部分RELRO(Partial RELRO)与完全RELRO(Full RELRO)两种。
部分RELRO:在程序装入后,将其中一些段(如.dynamic)标记为只读,防止程序的一些重定位信息被修改。
完全RELRO:在部分RELRO的基础上,在程序装入时,直接解析完所有符号并填入对应的值,此时所有的GOT表项都已初始化,且不装入link_map与_dl_runtime_resolve的地址(二者都是程序动态装载的重要结构和函数)。
可以看到,当程序启用完全RELRO时,传统的GOT劫持的方式也不再可用。
但完全RELRO对程序性能的影响也相对较大,因为其相当于禁用了用于性能优化的动态装载机制,将程序中可能不会用到的一些动态符号装入,当程序导入的外部符号很多时,将带来一定程度的额外开销。
为了绕过RELRO,我们可以利用__realloc_hook的特性。
修改存在于glibc.data段的记录hook函数的指针变量__realloc_hook,若glibc发现此变量的值不为0,则在进行realloc操作时会直接调用此变量中记录的函数地址,从而达到劫持控制流的目的。
1 | void * __libc_realloc (void *oldmem, size_t bytes) |
note中的一字节堆溢出漏洞
note开启的安全措施
1 | pwndbg> checksec |
这是note程序的一些主要操作:
1 | void user_function() |
note程序提供了一些操作,均涉及到对堆内存的操作,在change_title函数中,存在一个off-by-one漏洞。
1 | char *change_title() |
check_badchar函数会对输入的字符进行检查,如果输入的字符是预先定义的bad_char,则直接跳出循环。
1 | bad char |
这里存在一个问题,如果在v3等于40的时候,输入一个bad_char,就会发生一字节的溢出。由于title数组存在于堆上,所以会覆盖下一个堆块的size域,结合前面的两个知识点,就可以实现任意地址写操作。
我们可以利用@
字符进行溢出。
利用过程
查看bss段的分布,可以发现title数组指针是一个非常好的利用点。
1 | .bss:0000000000602050 change_content_limit dd ? |
为了触发unlink,我们需要两个连续的free chunk,通过调试可以发现,title正好是content的前一个chunk。
至此,我们的利用思路就很明显了:
1:在title中伪造一个free chunk,其fd和bk值可以分别为title指针的减24和减16值,并通过off-by-one修改content的size域的inuse位为0。
2:在change_content操作中,可以先通过申请一个很大的内存,使得realloc必须执行上文的第三步,这样会使得原先的content被free掉,成为free状态的fast bin chunk。
3:上面申请的大内存必定和top chunk相邻,因此,这一步也需要再申请一个足够大内存,其大小必须大于top chunk和当前content的大小之和,由于这时fast bin中已经有内容,所以会触发malloc_consolidate操作,对fast bin进行合并,这时就会触发unlink,使得title指针指向了其自身减去24的位置,此时便可以实现任意地址写。
利用代码如下:
1 | from pwn import * |