Lua脚本反编译入门之一

来源:互联网 发布:网络主播工资表 编辑:程序博客网 时间:2024/06/18 13:06
随着越来越多的游戏,软件采用Lua来实现业务逻辑,
想搞黑产的同学,时常便会遇见lua脚本,可惜大部分都是编译过的lua脚本,而且还是自定义的。
便难倒了很多菜鸟,lua 的实现机制,那可是虚拟机技术,非常难于调试。
本教程,便来普及lua 的虚拟机指令及其反编译lua脚本,成为文本形式的脚本

1.Lua的虚拟机指令,5.2 的有40条

Lua的指令使用一个32bit的unsigned integer表示。所有指令的定义都在lopcodes.h文件中(可以从Lua 官方网站下载),使用一个enum OpCode代表指令类型。在lua5.2中,总共有40种指令(id从0到39)。根据指令参数的不同,可以将所有指令分为4类:

点击图片以查看大图图片名称:Lua指令类型.png查看次数:13文件大小:7.9 KB文件 ID :88439

typedef enum {
/*----------------------------------------------------------------------
name    args  description

------------------------------------------------------------------------*/
OP_MOVE,/*  A B  R(A) := R(B)          */
OP_LOADK,/*  A Bx  R(A) := Kst(Bx)          */
OP_LOADKX,/*  A   R(A) := Kst(extra arg)        */
OP_LOADBOOL,/*  A B C  R(A) := (Bool)B; if (C) pc++      */
OP_LOADNIL,/*  A B  R(A), R(A+1), ..., R(A+B) := nil    */
OP_GETUPVAL,/*  A B  R(A) := UpValue[B]        */

OP_GETTABUP,/*  A B C  R(A) := UpValue[B][RK(C)]      */
OP_GETTABLE,/*  A B C  R(A) := R(B)[RK(C)]        */

OP_SETTABUP,/*  A B C  UpValue[A][RK(B)] := RK(C)      */
OP_SETUPVAL,/*  A B  UpValue[B] := R(A)        */
OP_SETTABLE,/*  A B C  R(A)[RK(B)] := RK(C)        */

OP_NEWTABLE,/*  A B C  R(A) := {} (size = B,C)        */

OP_SELF,/*  A B C  R(A+1) := R(B); R(A) := R(B)[RK(C)]    */

OP_ADD,/*  A B C  R(A) := RK(B) + RK(C)        */
OP_SUB,/*  A B C  R(A) := RK(B) - RK(C)        */
OP_MUL,/*  A B C  R(A) := RK(B) * RK(C)        */
OP_DIV,/*  A B C  R(A) := RK(B) / RK(C)        */
OP_MOD,/*  A B C  R(A) := RK(B) % RK(C)        */
OP_POW,/*  A B C  R(A) := RK(B) ^ RK(C)        */
OP_UNM,/*  A B  R(A) := -R(B)          */
OP_NOT,/*  A B  R(A) := not R(B)        */
OP_LEN,/*  A B  R(A) := length of R(B)        */

OP_CONCAT,/*  A B C  R(A) := R(B).. ... ..R(C)      */

OP_JMP,/*  A sBx  pc+=sBx; if (A) close all upvalues >= R(A) + 1  */
OP_EQ,/*  A B C  if ((RK(B) == RK(C)) ~= A) then pc++    */
OP_LT,/*  A B C  if ((RK(B) <  RK(C)) ~= A) then pc++    */
OP_LE,/*  A B C  if ((RK(B) <= RK(C)) ~= A) then pc++    */

OP_TEST,/*  A C  if not (R(A) <=> C) then pc++      */
OP_TESTSET,/*  A B C  if (R(B) <=> C) then R(A) := R(B) else pc++  */

OP_CALL,/*  A B C  R(A), ... ,R(A+C-2) := R(A)(R(A+1), ... ,R(A+B-1)) */
OP_TAILCALL,/*  A B C  return R(A)(R(A+1), ... ,R(A+B-1))    */
OP_RETURN,/*  A B  return R(A), ... ,R(A+B-2)  (see note)  */

OP_FORLOOP,/*  A sBx  R(A)+=R(A+2);
      if R(A) <?= R(A+1) then { pc+=sBx; R(A+3)=R(A) }*/
OP_FORPREP,/*  A sBx  R(A)-=R(A+2); pc+=sBx        */

OP_TFORCALL,/*  A C  R(A+3), ... ,R(A+2+C) := R(A)(R(A+1), R(A+2));  */
OP_TFORLOOP,/*  A sBx  if R(A+1) ~= nil then { R(A)=R(A+1); pc += sBx }*/

OP_SETLIST,/*  A B C  R(A)[(C-1)*FPF+i] := R(A+i), 1 <= i <= B  */

OP_CLOSURE,/*  A Bx  R(A) := closure(KPROTO[Bx])      */

OP_VARARG,/*  A B  R(A), R(A+1), ..., R(A+B-2) = vararg    */

OP_EXTRAARG/*  Ax  extra (larger) argument for previous opcode  */
} OpCode;

**********************************************************
                                  虚拟机指令(2) MOVE & LOAD

OP_MOVE  A  B 
OP_MOVE用来将寄存器B中的值拷贝到寄存器A中,由于Lua是基于寄存器虚拟机,大部分的指令都是直接对寄存器进行操作,而不需要对数据进行压栈和弹栈。OP_MOVE 指令的作用 是将一个Local变量复制给另一个local变量.
例子:
local a = 10;  
local b = a;  
编译出来的结果
1   [1] LOAD        0 1;1代表的是常量表的项,这里代表的是10  
2   [2] MOVE        1 0 
所代表的二进制为
                     B                 A        OP_Code 
Load    0  1  =  100000000 000000000 00000000   000001   =  0x80000001 ,也就是说, 0x80000001 的二进制所代表的指令为  Load  0  1,这里B中的最高位为1,表示的B为常量表的序号,而不是寄存器

MOVE   1  0 =   000000000 000000000 00000001  000000   =  0x40

*****************华丽分割线***********************************************
1.lua 的二进制格式,官方的luac.exe 编译出来的格式
名称:  QQ图片20140418154703.jpg查看次数: 10文件大小:  78.7 KB

原始的lua 脚本为
local a = 10
local b = a
print(b)


下面介绍格式文件,介绍每个字段的意思.当然啦,这种格式是官方的,各个游戏公司可能会做一些改动,但是万变不离其宗。个个字段已经用颜色标明了
在lua 的源文件中,前面四个字节  1b 4c 75 61  也就是 \033Lua , 标识的是lua文件的特有的标示符数据格式,代表是lua 
#define LUA_SIGNATURE  "\033Lua"  033时八进制  = 0x1b ,很多那些反编译工具判断这四个字节的值,来判断是否能反编译,很多公司都会偷偷的去掉或者用其他的值来替换,以迷惑菜鸟。呵呵

52  第五个字节,表示的是,当前lua 的目标版本,这里指的是5.2 版本。
感觉编辑的好痛苦,我还是直接贴我的比较图算了,看起来比较舒服

点击图片以查看大图图片名称:header.jpg查看次数:30文件大小:57.2 KB文件 ID :88548


函数的头描述
linedefined   =    00 00 00 00   ;函数定义开始处的行号
linedefined   =    00 00 00 00     ; 函数定义结束处的行号 ;顶级函数开始和结束行号都是为00
numparams  =    00          ;固定参数的数目 number of fixed parameters 
is_vararg      =    01            ;可变参数标识符
                                            • 1=VARARG_HASARG
                                            • 2=VARARG_ISVARARG
                                             • 4=VARARG_NEEDSARG
maxstacksize  =  03         ;调用函数所需要的堆栈空间指令段
sizecode         =   06 00 00 00  ; 函数中 指令的数目,缓存区的大小 = sizecode * sizeof(Instruction),每四个字节为一条指令
code               =  02 00 00 00 41 00 00 00 87 40 40 00 c1 00 80 00 a0 40 00 01 1e 00 80 00
                    
常量列表 保存着函数中引用的常量的列表 (常量池) 
Constant.sizek    =  02 00 00 00    ;常量列表的大小 ,缓存区的大小  = Constant.sizek * sizeof(TValue) = 2 * 8 = 16,每项为8个字节,
TValue *               =                                                                             03 00 00 .
                                           00 00 00 00 24 40 04 06 00 00 00 70 72 69 6e 74  ....$@.....print
Constant list 数据结构   保存着函数中引用的常量的列表 (常量池) 
Integer 常量列表的大小 (sizek)
[
    1 byte 常量类型 (value in parentheses):  • 0=LUA_TNIL, 1=LUA_TBOOLEAN,• 3=LUA_TNUMBER, 4=LUA_TSTRING
     Const 常量本身: 如果常量类型是0这个域不存在;如果类型是1,这个是0或1;如果类型是3这个域是 Number;如果类型是4 这个域是String。
]
这里的String 是包含"0"为结束的字符串
点击图片以查看大图图片名称:header2.jpg查看次数:40文件大小:58.6 KB文件 ID :88549

为什么上传图片以后,图片都变小了,而且不清晰呢?

***********************给大家发一点福利,矫正虚拟机指令的函数**************************************
//矫正虚拟机指令
DWORD Rectify(DWORD Source);
{
    DWORD Instruction = Source;
    BYTE  Source_OpCode =  Instruction & 0x3F;
    switch(Source_OpCode)
    {
  case OP_MOVE:
    Source_OpCode  = Target_OpCode; 
       break;
         ...
    }
   Instruction = ((Instruction & 0xFFFFFFC0) | Source_OpCode);
   return Instruction
}
0 0