题目为 2022研究生赛初赛adv_lua
分析了一下Lua 5.4的bytecode,注意不是5.3,也不是5.5(截至目前还未推出),opcode在版本间不保持稳定
Lua 5.3参考:Lua 5.3 Bytecode Reference — Ravi Programming Language 0.1 documentation (the-ravi-programming-language.readthedocs.io),备用
bytecode格式可以看源码:Lua 5.4.4 source code - lopcodes.h
里面已经有比较清晰的bytecode格式了
/*===========================================================================
We assume that instructions are unsigned 32-bit integers.
All instructions have an opcode in the first 7 bits.
Instructions can have the following formats:
3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
iABC C(8) | B(8) |k| A(8) | Op(7) |
iABx Bx(17) | A(8) | Op(7) |
iAsBx sBx (signed)(17) | A(8) | Op(7) |
iAx Ax(25) | Op(7) |
isJ sJ(25) | Op(7) |
A signed argument is represented in excess K: the represented value is
the written unsigned value minus K, where K is half the maximum for the
corresponding unsigned argument.
===========================================================================*/
标着sX的(例如sC, sB等),实际值等于字面量减(1 << (位数 - 1)),也就是减最大值的一半。
opcode:
00: OP_MOVE,/* A B R[A] := R[B] */
01: OP_LOADI,/* A sBx R[A] := sBx */
02: OP_LOADF,/* A sBx R[A] := (lua_Number)sBx */
03: OP_LOADK,/* A Bx R[A] := K[Bx] */
04: OP_LOADKX,/* A R[A] := K[extra arg] */
05: OP_LOADFALSE,/* A R[A] := false */
06: OP_LFALSESKIP,/*A R[A] := false; pc++ (*) */
07: OP_LOADTRUE,/* A R[A] := true */
08: OP_LOADNIL,/* A B R[A], R[A+1], ..., R[A+B] := nil */
09: OP_GETUPVAL,/* A B R[A] := UpValue[B] */
0A: OP_SETUPVAL,/* A B UpValue[B] := R[A] */
0B: OP_GETTABUP,/* A B C R[A] := UpValue[B][K[C]:string] */
0C: OP_GETTABLE,/* A B C R[A] := R[B][R[C]] */
0D: OP_GETI,/* A B C R[A] := R[B][C] */
0E: OP_GETFIELD,/* A B C R[A] := R[B][K[C]:string] */
0F: OP_SETTABUP,/* A B C UpValue[A][K[B]:string] := RK(C) */
10: OP_SETTABLE,/* A B C R[A][R[B]] := RK(C) */
11: OP_SETI,/* A B C R[A][B] := RK(C) */
12: OP_SETFIELD,/* A B C R[A][K[B]:string] := RK(C) */
13: OP_NEWTABLE,/* A B C k R[A] := {} */
14: OP_SELF,/* A B C R[A+1] := R[B]; R[A] := R[B][RK(C):string] */
15: OP_ADDI,/* A B sC R[A] := R[B] + sC */
16: OP_ADDK,/* A B C R[A] := R[B] + K[C]:number */
17: OP_SUBK,/* A B C R[A] := R[B] - K[C]:number */
18: OP_MULK,/* A B C R[A] := R[B] * K[C]:number */
19: OP_MODK,/* A B C R[A] := R[B] % K[C]:number */
1A: OP_POWK,/* A B C R[A] := R[B] ^ K[C]:number */
1B: OP_DIVK,/* A B C R[A] := R[B] / K[C]:number */
1C: OP_IDIVK,/* A B C R[A] := R[B] // K[C]:number */
1D: OP_BANDK,/* A B C R[A] := R[B] & K[C]:integer */
1E: OP_BORK,/* A B C R[A] := R[B] | K[C]:integer */
1F: OP_BXORK,/* A B C R[A] := R[B] ~ K[C]:integer */
20: OP_SHRI,/* A B sC R[A] := R[B] >> sC */
21: OP_SHLI,/* A B sC R[A] := sC << R[B] */
22: OP_ADD,/* A B C R[A] := R[B] + R[C] */
23: OP_SUB,/* A B C R[A] := R[B] - R[C] */
24: OP_MUL,/* A B C R[A] := R[B] * R[C] */
25: OP_MOD,/* A B C R[A] := R[B] % R[C] */
26: OP_POW,/* A B C R[A] := R[B] ^ R[C] */
27: OP_DIV,/* A B C R[A] := R[B] / R[C] */
28: OP_IDIV,/* A B C R[A] := R[B] // R[C] */
29: OP_BAND,/* A B C R[A] := R[B] & R[C] */
2A: OP_BOR,/* A B C R[A] := R[B] | R[C] */
2B: OP_BXOR,/* A B C R[A] := R[B] ~ R[C] */
2C: OP_SHL,/* A B C R[A] := R[B] << R[C] */
2D: OP_SHR,/* A B C R[A] := R[B] >> R[C] */
2E: OP_MMBIN,/* A B C call C metamethod over R[A] and R[B] (*) */
2F: OP_MMBINI,/* A sB C k call C metamethod over R[A] and sB */
30: OP_MMBINK,/* A B C k call C metamethod over R[A] and K[B] */
31: OP_UNM,/* A B R[A] := -R[B] */
32: OP_BNOT,/* A B R[A] := ~R[B] */
33: OP_NOT,/* A B R[A] := not R[B] */
34: OP_LEN,/* A B R[A] := #R[B] (length operator) */
35: OP_CONCAT,/* A B R[A] := R[A].. ... ..R[A + B - 1] */
36: OP_CLOSE,/* A close all upvalues >= R[A] */
37: OP_TBC,/* A mark variable A "to be closed" */
38: OP_JMP,/* sJ pc += sJ */
39: OP_EQ,/* A B k if ((R[A] == R[B]) ~= k) then pc++ */
3A: OP_LT,/* A B k if ((R[A] < R[B]) ~= k) then pc++ */
3B: OP_LE,/* A B k if ((R[A] <= R[B]) ~= k) then pc++ */
3C: OP_EQK,/* A B k if ((R[A] == K[B]) ~= k) then pc++ */
3D: OP_EQI,/* A sB k if ((R[A] == sB) ~= k) then pc++ */
3E: OP_LTI,/* A sB k if ((R[A] < sB) ~= k) then pc++ */
3F: OP_LEI,/* A sB k if ((R[A] <= sB) ~= k) then pc++ */
40: OP_GTI,/* A sB k if ((R[A] > sB) ~= k) then pc++ */
41: OP_GEI,/* A sB k if ((R[A] >= sB) ~= k) then pc++ */
42: OP_TEST,/* A k if (not R[A] == k) then pc++ */
43: OP_TESTSET,/* A B k if (not R[B] == k) then pc++ else R[A] := R[B] (*) */
44: OP_CALL,/* A B C R[A], ... ,R[A+C-2] := R[A](R[A+1], ... ,R[A+B-1]) */
45: OP_TAILCALL,/* A B C k return R[A](R[A+1], ... ,R[A+B-1]) */
46: OP_RETURN,/* A B C k return R[A], ... ,R[A+B-2] (see note) */
47: OP_RETURN0,/* return */
48: OP_RETURN1,/* A return R[A] */
49: OP_FORLOOP,/* A Bx update counters; if loop continues then pc-=Bx; */
4A: OP_FORPREP,/* A Bx <check values and prepare counters>;
if not to run then pc+=Bx+1; */
4B: OP_TFORPREP,/* A Bx create upvalue for R[A + 3]; pc+=Bx */
4C: OP_TFORCALL,/* A C R[A+4], ... ,R[A+3+C] := R[A](R[A+1], R[A+2]); */
4D: OP_TFORLOOP,/* A Bx if R[A+2] ~= nil then { R[A]=R[A+2]; pc -= Bx } */
4E: OP_SETLIST,/* A B C k R[A][C+i] := R[A+i], 1 <= i <= B */
4F: OP_CLOSURE,/* A Bx R[A] := closure(KPROTO[Bx]) */
50: OP_VARARG,/* A C R[A], R[A+1], ..., R[A+C-2] = vararg */
51: OP_VARARGPREP,/*A (adjust vararg parameters) */
52: OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */
主执行循环在 luaV_execute,CALL指令的具体执行在 luaD_precall
和各种c写的解释器类似(之前见过的有2022 suctf mujs
),这里的CALL也分成几种(使用同一个opcode),light C和C closure都是直接调用原生C函数,内部使用函数指针直接执行,攻击解释器时可以通过修改编译出的bytecode触发该函数来getshell。
在调试中可以注意到,每个寄存器的地址为base+num*16,每个寄存器占用16字节的空间,前8字节是实际数据,后八字节存metadata,标记数据类型
下面来分析一下lua bytecode是如何在有PIE的情况下获取原生C函数地址的。
OP_GETTABUP: 0001011: 0xb
A: 0
B: 0
C: 0x19
OP_GETFIELD: 0x0e
A: 0
B: 0
C: 0x17
0b11001
OP_DIVK: 0x1b
A: 1
B: 0
C: 0x19
OP_LOADI: 0x01
A: 2
sBx: 0x10002
OP_DIVK: 0x1b
A: 3
B: 0
C: 9
OP_SHRI: 0100000
A: 3
B: 3
C: 0x17
OP_MMBINI:
A: 3
k: 0
sB: 0x17
C: 0x11
OP_BANDK: 0x1d
A: 3
B: 3
C: 0x18
OP_MMBINK: 0x30
A: 3
k: 0
B: 0x18
C: 0xD
CALL: 0x44
A: 0
B: 4 # 3 arg
C: 1 # 0 ret value