首页
社区
课程
招聘
[原创] lua pwn 初探 —— SECCONCTF 2022 lslice
发表于: 2022-12-1 14:48 15933

[原创] lua pwn 初探 —— SECCONCTF 2022 lslice

2022-12-1 14:48
15933

author: h1k0

lslice 是 SECCON CTF 2022 出的一道 Lua Pwn,比赛时一直没找到漏洞点,对 Lua 也完全不熟悉,因此该题不得不放弃。后续在 github 上看到了别人的 writeup 并复现了一下,发现题目其实并不难,只是需要用到 Lua 中的某些特殊方法。

题目的描述是这样的:

Pull Request: Add slice method for Lua table,Commit: cfbe378f906061ee56f91acfbdf569d0d3fb9556

也就是说,出题人基于 Lua 项目的某个提交,为 lua 中的 table 添加了 slice 方法。

拿到题目附件后,发现题目给了一个 patch 文件,编译好的目标 binary 以及一些其他部属相关的文件,使用以下命令:

patch.diff 进行简要分析,发现主要做了 3 处修改:

也就是说,该题的目标就是劫持控制流到 win 函数,那么 tslice 就是需要我们重点关注的函数。

出题人添加的 tslice 函数如下所示,简单来说,该函数获取目标 table 的长度 len,以及 startend 两个参数,之后对这些参数进行一些检查,并创建一个新的 table,最后将 startend 之间的原始 table 数据拷贝到新创建的目标 table 处。

经过多次检查,我们并没有在这个函数里找到可以被利用的点(所以当时放弃了qwq)。但后来看了别人的 wp 之后,发现该函数的漏洞点在于 api 使用,我们检查 aux_getn 这个函数:

这个函数最后会通过 luaL_len 来获取 table 的长度,但是通过 luaL_len 获取到的 table 长度,一定是正确的吗?

其实做题时候我们也想到过是不是 api 使用出现了问题,但是检查了其他 table 的函数,发现在 api 的使用上似乎没有太大的区别 hhh,所以我认为,这个漏洞的 root cause 是 slice 这个功能,在 lua 里不应该被这么简单的实现。

Lua 的原表是解决这道题目的关键,有关 Lua 原表是什么网上的资料太多,大家直接搜索即可,这里仅给出一个例子,如下所示:

使用 lua 可执行文件运行该脚本,得到的结果如下所示:

这也就意味着,我们可以通过设置 Lua 中某个 table 的原表,来使得这个 table 的长度被 “修改” 为原表中 __len 函数返回的值。

经过实验,luaL_len 函数返回的 table 长度的确会被 table 的元表所影响,因此借助元表,我们就可以在 tslice 函数中控制 len ,绕过相关检查,从而控制 startend,达到 OOB 的效果,这样我们就可以将一些 table 之外的数据复制到新的 table 中。

在 Lua 中,如果我们执行 print(table.pack) 命令,就会打印出 table 中 tpack 函数的地址,我们可以借此获得 PIE 基址,进而获得 win 函数地址。

Lua 解释器在实现过程中有多个重要的基础数据结构(比如表示 Lua 虚拟机状态的 gloabl_Statelua_State ,以及在函数调用中扮演重要角色的 CallInfo 等结构)。这里对 lua_State 和函数调用进行简要介绍,lua_State 结构体中一些关键成员变量如下所示:

有关 Lua 的结构体和函数调用的具体过程,大家可参考 这位大佬写的博客 ,此处暂不做迁移,仅仅做以下简要的总结:

具体到本题中,在调用 tslice 函数时,lua 虚拟机的栈结构如下图所示,其中栈宽度为 0x10 字节(lua 虚拟机中的栈结构体)

Lua stack 1

在调用 lua_createtable 函数之后,lua 虚拟机将新创建的 table 放在栈上,如下图所示:
Lua stack 2

在 lua 中,Table 的声明如下,其中需要我们重点关注的是 array 成员。可以看到,arrayTValue 类型的指针,保存着 Table 中的数据。

TValue 的声明如下,包含了成员 Valuett_,其中 Value 是一个 union,代表值本身,而 tt_ 则代表该值的类型(lua 中值的类型具体分为了很多,可以在源码中自行分析)。

(看起来这里的代码高亮有点问题)

利用成功的截图如下所示:
Success

 
 
git clone git@github.com:lua/lua.git
git checkout cfbe378f906061ee56f91acfbdf569d0d3fb9556
git apply ../patch.diff
git clone git@github.com: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;
}
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))
#define aux_getn(L,n,w)    (checktab(L, n, (w) | TAB_L), luaL_len(L, n))
x = {1, 2, 3, 4, 5}
print(#x)
 
metatable = {__len = function() return 100 end}
setmetatable(x, metatable)
print(#x)
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 test.lua
5
100
 
❯ ./lua
Lua 5.4.5  Copyright (C) 1994-2022 Lua.org, PUC-Rio
> print(table.pack)
function: 0x55ebbe5b3220
❯ ./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;
  // ...
};
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;
  // ...
};
 
 
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;
typedef struct Table {

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2022-12-1 14:59 被hikonaka编辑 ,原因: 添加题目附件
上传的附件:
收藏
免费 5
支持
分享
最新回复 (1)
雪    币: 1935
活跃值: (4185)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
mark
2022-12-4 22:57
0
游客
登录 | 注册 方可回帖
返回
//