lua虚拟机逆向

lua 虚拟机的简单分析使用及其逆向

lua 虚拟机

  • 翻译为字节码之后运行
  • 是一个基于寄存器的虚拟机
  • 通过栈与其他语言进行数据交互
  • 虚拟机状态由结构体 lua_state 保存

c 调用 lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
/*在 c++ 中使用
extern "C"{
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
*/
int main()
{
lua_State* L = luaL_newstate();
luaL_openlibs(L);
if (luaL_loadfile(L, "test.lua") != 0 || lua_pcall(L, 0, 0, 0) != 0)
{
printf("loadfile failed! %s\n", lua_tostring(L, -1));
}
lua_close(L);
return 0;
}

​ 在用 VS 中使用需要先将 lua 源码编译为 .lib ,如下图所示到 项目-属性 进行设置

​ 之后在项目中使用的时候需要设置附加包含目录为 lua 头文件所在路径

​ 附加库目录为之前所编译好的 .lib 文件所在路径

lua API

1
2
3
4
5
6
7
8
9
10
11
int lua_pcall (lua_State *L, int nargs, int nresults, int msgh);
void lua_call (lua_State *L, int nargs, int nresults);

int luaL_loadfile (lua_State *L, const char *filename);
int luaL_loadstring (lua_State *L, const char *s);

int luaL_dofile (lua_State *L, const char *filename);
//宏定义 (luaL_loadfile(L, filename) || lua_pcall(L, 0, LUA_MULTRET, 0))
int luaL_dostring (lua_State *L, const char *str);
//宏定义 (luaL_loadstring(L, str) || lua_pcall(L, 0, LUA_MULTRET, 0))

虚拟机逆向

  • 在逆向的时候要先找到几个关键函数(例如 lua_pcall luaL_loadfile等)
  • 在没有函数符号信息的时候可以通过 bindiff 恢复符号

lua_pcall 调用分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
//基于 lua 5.3.4 的源码
#define lua_pcall(L,n,r,f) lua_pcallk(L, (n), (r), (f), 0, NULL)


LUA_API int lua_pcallk (lua_State *L, int nargs, int nresults, int errfunc,
lua_KContext ctx, lua_KFunction k) {
struct CallS c;
int status;
ptrdiff_t func;
lua_lock(L);
api_check(L, k == NULL || !isLua(L->ci),
"cannot use continuations inside hooks");
api_checknelems(L, nargs+1);
api_check(L, L->status == LUA_OK, "cannot do calls on non-normal thread");
checkresults(L, nargs, nresults);
if (errfunc == 0)
func = 0;
else {
StkId o = index2addr(L, errfunc);
api_checkstackindex(L, errfunc, o);
func = savestack(L, o);
}
c.func = L->top - (nargs+1); /* function to be called */
if (k == NULL || L->nny > 0) { /* no continuation or no yieldable? */
c.nresults = nresults; /* do a 'conventional' protected call */
status = luaD_pcall(L, f_call, &c, savestack(L, c.func), func);
}
else { /* prepare continuation (call is already protected by 'resume') */
CallInfo *ci = L->ci;
ci->u.c.k = k; /* save continuation */
ci->u.c.ctx = ctx; /* save context */
/* save information for error recovery */
ci->extra = savestack(L, c.func);
ci->u.c.old_errfunc = L->errfunc;
L->errfunc = func;
setoah(ci->callstatus, L->allowhook); /* save value of 'allowhook' */
ci->callstatus |= CIST_YPCALL; /* function can do error recovery */
luaD_call(L, c.func, nresults); /* do the call */
ci->callstatus &= ~CIST_YPCALL;
L->errfunc = ci->u.c.old_errfunc;
status = LUA_OK; /* if it is here, there were no errors */
}
adjustresults(L, nresults);
lua_unlock(L);
return status;
}

//lua_pcallk 内部调用 luaD_call

void luaD_call (lua_State *L, StkId func, int nResults) {
if (++L->nCcalls >= LUAI_MAXCCALLS)
stackerror(L);
if (!luaD_precall(L, func, nResults)) /* is a Lua function? */
luaV_execute(L); /* call it */
L->nCcalls--;
}

//以下是 opcode 执行
void luaV_execute (lua_State *L) {
CallInfo *ci = L->ci;
LClosure *cl;
TValue *k;
StkId base;
ci->callstatus |= CIST_FRESH; /* fresh invocation of 'luaV_execute" */
newframe: /* reentry point when frame changes (call/return) */
lua_assert(ci == L->ci);
cl = clLvalue(ci->func); /* local reference to function's closure */
k = cl->p->k; /* local reference to function's constant table */
base = ci->u.l.base; /* local copy of function's base */
/* main loop of interpreter */
for (;;) {
Instruction i;
StkId ra;
vmfetch();
vmdispatch (GET_OPCODE(i)) {
vmcase(OP_MOVE) {
setobjs2s(L, ra, RB(i));
vmbreak;
}
vmcase(OP_LOADK) {
TValue *rb = k + GETARG_Bx(i);
setobj2s(L, ra, rb);
vmbreak;
}
vmcase(OP_LOADKX) {
TValue *rb;
lua_assert(GET_OPCODE(*ci->u.l.savedpc) == OP_EXTRAARG);
rb = k + GETARG_Ax(*ci->u.l.savedpc++);
setobj2s(L, ra, rb);
vmbreak;
}
vmcase(OP_LOADBOOL) {
setbvalue(ra, GETARG_B(i));
if (GETARG_C(i)) ci->u.l.savedpc++; /* skip next instruction (if C) */
vmbreak;
}
vmcase(OP_LOADNIL) {
int b = GETARG_B(i);
do {
setnilvalue(ra++);
} while (b--);
vmbreak;
}
vmcase(OP_GETUPVAL) {
int b = GETARG_B(i);
setobj2s(L, ra, cl->upvals[b]->v);
vmbreak;
}
vmcase(OP_GETTABUP) {
TValue *upval = cl->upvals[GETARG_B(i)]->v;
TValue *rc = RKC(i);
gettableProtected(L, upval, rc, ra);
vmbreak;
}
vmcase(OP_GETTABLE) {
StkId rb = RB(i);
TValue *rc = RKC(i);
gettableProtected(L, rb, rc, ra);
vmbreak;
}
vmcase(OP_SETTABUP) {
TValue *upval = cl->upvals[GETARG_A(i)]->v;
TValue *rb = RKB(i);
TValue *rc = RKC(i);
settableProtected(L, upval, rb, rc);
vmbreak;
}
vmcase(OP_SETUPVAL) {
UpVal *uv = cl->upvals[GETARG_B(i)];
setobj(L, uv->v, ra);
luaC_upvalbarrier(L, uv);
vmbreak;
}
vmcase(OP_SETTABLE) {
TValue *rb = RKB(i);
TValue *rc = RKC(i);
settableProtected(L, ra, rb, rc);
vmbreak;
}
vmcase(OP_NEWTABLE) {
int b = GETARG_B(i);
int c = GETARG_C(i);
Table *t = luaH_new(L);
sethvalue(L, ra, t);
if (b != 0 || c != 0)
luaH_resize(L, t, luaO_fb2int(b), luaO_fb2int(c));
checkGC(L, ra + 1);
vmbreak;
}
vmcase(OP_SELF) {
const TValue *aux;
StkId rb = RB(i);
TValue *rc = RKC(i);
TString *key = tsvalue(rc); /* key must be a string */
setobjs2s(L, ra + 1, rb);
if (luaV_fastget(L, rb, key, aux, luaH_getstr)) {
setobj2s(L, ra, aux);
}
else Protect(luaV_finishget(L, rb, rc, ra, aux));
vmbreak;
}
vmcase(OP_ADD) {
TValue *rb = RKB(i);
TValue *rc = RKC(i);
lua_Number nb; lua_Number nc;
if (ttisinteger(rb) && ttisinteger(rc)) {
lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc);
setivalue(ra, intop(+, ib, ic));
}
else if (tonumber(rb, &nb) && tonumber(rc, &nc)) {
setfltvalue(ra, luai_numadd(L, nb, nc));
}
else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_ADD)); }
vmbreak;
}
vmcase(OP_SUB) {
TValue *rb = RKB(i);
TValue *rc = RKC(i);
lua_Number nb; lua_Number nc;
if (ttisinteger(rb) && ttisinteger(rc)) {
lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc);
setivalue(ra, intop(-, ib, ic));
}
else if (tonumber(rb, &nb) && tonumber(rc, &nc)) {
setfltvalue(ra, luai_numsub(L, nb, nc));
}
else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_SUB)); }
vmbreak;
}
vmcase(OP_MUL) {
TValue *rb = RKB(i);
TValue *rc = RKC(i);
lua_Number nb; lua_Number nc;
if (ttisinteger(rb) && ttisinteger(rc)) {
lua_Integer ib = ivalue(rb); lua_Integer ic = ivalue(rc);
setivalue(ra, intop(*, ib, ic));
}
else if (tonumber(rb, &nb) && tonumber(rc, &nc)) {
setfltvalue(ra, luai_nummul(L, nb, nc));
}
else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_MUL)); }
vmbreak;
}
vmcase(OP_DIV) { /* float division (always with floats) */
TValue *rb = RKB(i);
TValue *rc = RKC(i);
lua_Number nb; lua_Number nc;
if (tonumber(rb, &nb) && tonumber(rc, &nc)) {
setfltvalue(ra, luai_numdiv(L, nb, nc));
}
else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_DIV)); }
vmbreak;
}
vmcase(OP_BAND) {
TValue *rb = RKB(i);
TValue *rc = RKC(i);
lua_Integer ib; lua_Integer ic;
if (tointeger(rb, &ib) && tointeger(rc, &ic)) {
setivalue(ra, intop(&, ib, ic));
}
else { Protect(luaT_trybinTM(L, rb, rc, ra, TM_BAND)); }
vmbreak;
}

//下略.....

​ 此处 luaV_execute 的源码可被魔改,例如替换 opcodes (即改变在 enum 中的顺序),有关 opcodes 的具体内容可以在 lopcodes.hlopcodes.c 中查看

luac 文件提取出来之后可以通过 unluac ,luacdec 来进行反编译,如果 opcodes 是被替换过的话可以在逆向完后用 luadecluaopswap 进行替换

Ref

作者

0wl

发布于

2022-02-28

更新于

2022-03-01

许可协议

评论