欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]Lua脚本反编译入门之一

[原创]Lua脚本反编译入门之一

随着越来越多的游戏,软件采用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类:

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                                */
OP_GETTABUP,/*        A B C        R(A) := UpValue[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 := 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 编译出来的格式

原始的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 版本。
感觉编辑的好痛苦,我还是直接贴我的比较图算了,看起来比较舒服

函数的头描述
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"为结束的字符串

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

***********************给大家发一点福利,矫正虚拟机指令的函数**************************************
//矫正虚拟机指令
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
}

上传的附件 Lua指令类型.png
QQ图片20140418154703.jpg
header.jpg
header2.jpg

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功