0x00 UAF原理
如上代码所示,指针p1申请内存,打印其地址,值
然后释放p1
指针p2申请同样大小的内存,打印p2的地址,p1指针指向的值
Gcc编译,运行结果如下:
p1与p2地址相同,p1指针释放后,p2申请相同的大小的内存,操作系统会将之前给p1的地址分配给p2,修改p2的值,p1也被修改了。
由此我们可以知道:
1.在free一块内存后,接着申请大小相同的一块内存,操作系统会将刚刚free掉的内存再次分配。
根本原因是dllmalloc:
参考资料:http://blog.csdn.net/ycnian/article/details/12971863
当应用程序调用free()释放内存时,如果内存块小于256kb,dlmalloc并不马上将内存块释放回内存,而是将内存块标记为空闲状态。这么做的原因有两个:一是内存块不一定能马上释放会内核(比如内存块不是位于堆顶端),二是供应用程序下次申请内存使用(这是主要原因)。当dlmalloc中空闲内存量达到一定值时dlmalloc才将空闲内存释放会内核。如果应用程序申请的内存大于256kb,dlmalloc调用mmap()向内核申请一块内存,返回返还给应用程序使用。如果应用程序释放的内存大于256kb,dlmalloc马上调用munmap()释放内存。dlmalloc不会缓存大于256kb的内存块,因为这样的内存块太大了,最好不要长期占用这么大的内存资源。
2.通过p2能够操作p1,如果之后p1继续被使用(use after free),则可以达到通过p2修改程序功能等目的。
在网上找了一个有UAF漏洞的ctf程序,参考博客如下:
http://www.syjzwjj.com/use-after-free-tutorial/
作者已经分析的很详细了,通过对整个程序的调试,来理解UAF的利用。
1. 结合IDA与程序运行,简要理解程序的功能。
选择1可以留言,信息会存到1个链表中
选择2将会遍历链表找到对应的节点,打印节点信息
打印完,可以对其进行删除修改等操作
链表节点结构如下
Tips:在逆向代码中应用数据结构参考《IDA Pro权威指南》第8章数据类型与数据结构
2. UAF漏洞代码
链表节点被删除后,可以继续进入modify函数,modify函数之后可以继续进入modify函数。
Delete函数如下:
Delete函数中对节点的进行了free操作,如果在循环代码中,进行delete操作,释放节点之后,再选择2进入modify函数。
Modify函数如下:
Modify函数从用户读取数据,然后拷贝到对应的指针中,但此时使用的是一个已经释放的指针。当输入content时,会取content的长度作为大小分配内存,当分配内存大小等于msg结构大小(48字节,通过前面的结构获得)时,会将刚才释放的内存分配给content指针。
如下所示
Content指向了msg结构本身
接着将content拷贝到content指针中,即我们的输入会拷贝到这个被释放的节点内存中。
在循环代码中,modify完之后可以继续进入modify。 此时会再对msg结构的author,title,content指针指向的地址进行拷贝。 由于上一步已经能够对msg结构进行随意更改了,所以将几个memcpy的目的地址修改成想要的地址即可进行任意内存(属于该程序的合法内存)的修改了。
至此我们已经能够完成任意内存地址的修改了。
下面需要考虑的就是完成一些命令执行的利用。
3. 漏洞利用执行命令
要执行命令,需要调用system函数,但是代码中并没有system函数,需要如何完成命令执行呢?
可以利用linux的延迟加载功能,改变strlen函数的指向,将原本要执行的strlen,改成执行system。
Tips:延迟加载
当调用标准函数时,需要从其他so文件中将标准函数加载进来,并不直接调用函数的地址,而是通过一张中间表跳转到函数的真正地址。
以strlen函数的调用为例
在程序调试中,打印0x804c04c的信息
整个过程如下:
Call strlen跳转到strlen函数,里面只有一句jmp ds:off_804c04c
当程序运行起来时0x804c04c里的值为0xb7658210,才是strlen的真正地址
即0x804c04c中存储libc库中的strlen的真正地址。
如果将0x804c04c的值改掉,改成system的地址0xb7614360。 虽然看起来调用的是strlen,但真正执行的是system函数。
修改前:
Call strlen
Strlen:
Jmp 0x804c04c
0x804c04c: 0xb7658210(strlen)
修改后:
Call strlen
Strlen:
Jmp 0x804c04c
0x804c04c: 0xb7614360 (system)
Tips: 寻址system的真正地址
由于整个程序并没有调用system函数,所以在程序的重定位表中找不到system。 所以需要自己定位一下system在这个程序中的真正地址。
Libc被装到0xb75d6000-0xb777a000 地址空间,大小为0x1a4000
编写程序调用system函数
调试运行查看其libc地址空间
被装入到0xb7e10000-0xb7fb4000,大小也为0x1a4000。
所以system在漏洞程序中的地址应为 =(system在调用程序中的地址-调用程序libc起始地址+漏洞程序libc起始地址)
0xb7e4e360
System在漏洞程序中地址= 0xb7e4e360-0xb7e10000+0xb75d6000= 0xb7614360
4. poc运行效果
执行一个mkdir hack命令建立一个hack目录
Poc片段
5. 过程回顾
1) delete函数中释放节点
2) modify函数传入被释放的指针
3) modify函数中分配内存大小可控,通过分配与节点相同的大小,取得被释放内存的控制权
4) 修改将要被拷贝的目的地址msg->author指针指向将要被执行的函数strlen的中间表地址
5) 将strlen指向的真实strlen地址,修改为system的真实地址
6) 看似执行call strlen,实则执行了system函数
在指针释放后再申请相同大小的内存,系统会将释放的地址进行分配,以提高系统运行速度,因此可以修改到被释放的内存数据,如果被释放的指针继续被使用,则会造成UAF漏洞。
通过UAF漏洞,可能可以造成一些任意内存的修改,结合代码特点,可能会造成任意内存的读取或者,严重的能够造成任意命令的执行,获得shell。 取决于被释放的指针是怎么使用的。
相关知识点:gdb调试(如何调试fork出来的程序),延迟加载(plt与got),标准函数在内存中的定位,UAF修改被释放指针内容的原因dllmalloc