QCon北京「鸿蒙专场」火热来袭!即刻报名,与创新同行~ 了解详情
写点什么

Lua 程序逆向之 Luac 文件格式分析(下)

  • 2019-11-29
  • 本文字数:5147 字

    阅读完需:约 17 分钟

Lua程序逆向之Luac文件格式分析(下)

点击 010 Editor 菜单 Templates->New Template,新建一个模板,会自动生成如下内容:


//------------------------------------------------//--- 010 Editor v8.0 Binary Template////      File: //   Authors: //   Version: //   Purpose: //  Category: // File Mask: //  ID Bytes: //   History: //------------------------------------------------
复制代码


File 是文件名,010 Editor 使用.bt 作为模柏树的后缀,这里取名为 luac.bt 即可。


Authors 是作者信息。


Version 是当前模板的版本,如果将最终的模板文件上传到 010 Editor 的官方模板仓库,010 Editor 会以此字段来判断模板文件的版本信息。


Purpose 是编写本模板的意图,内容上可以留空。


Category 是模板的分类,010 Editor 中自带了一些内置的分类,这里选择 Programming 分类。


File Mask 是文件扩展名掩码,表示当前模板支持处理哪种文件类型的数据,支持通配符,如果支持多种文件格式,可以将所有的文件扩展名写在一行,中间使用逗号分开,这里设置它的值为“*.luac, *.lua”。


ID Bytes 是文件开头的 Magic Number,用来通过文件的开头来判断是否为支持处理的文件,这里的取值为“1B 4c 75 61”。


History 中可以留空,也可以编写模板的更新历史信息。


最终,Luac.bt 的开头内容如下:


//------------------------------------------------//--- 010 Editor v8.0 Binary Template////      File: luac.bt//   Authors: fei_cong(346345565@qq.com)//   Version: 1.0//   Purpose: //  Category: Programming// File Mask: *.luac, *.lua//  ID Bytes: 1B 4c 75 61//   History: //      1.0   fei_cong: Initial version, support lua 5.2.//// License: This file is released into the public domain. People may //          use it for any purpose, commercial or otherwise. //------------------------------------------------
复制代码


010 Editor 模板与 C 语言一样,支持 C 语言的宏、数据类型、变量、函数、代码语句、控制流程等,还支持调用常见的 C 语言函数。


数据类型上,支持的非常丰富,官方列出 BS 的支持的数据类型如下:


- 8-Bit Signed Integer - char, byte, CHAR, BYTE- 8-Bit Unsigned Integer - uchar, ubyte, UCHAR, UBYTE- 16-Bit Signed Integer - short, int16, SHORT, INT16- 16-Bit Unsigned Integer - ushort, uint16, USHORT, UINT16, WORD- 32-Bit Signed Integer - int, int32, long, INT, INT32, LONG- 32-Bit Unsigned Integer - uint, uint32, ulong, UINT, UINT32, ULONG, DWORD- 64-Bit Signed Integer - int64, quad, QUAD, INT64, __int64- 64-Bit Unsigned Integer - uint64, uquad, UQUAD, UINT64, QWORD, __uint64- 32-Bit Floating Point Number - float, FLOAT - 64-Bit Floating Point Number - double, DOUBLE - 16-Bit Floating Point Number - hfloat, HFLOAT - Date Types - DOSDATE, DOSTIME, FILETIME, OLETIME, time_t (for more information on date types see Using the Inspector)
复制代码


在编写模板时,同一数据类型中列出的类型,使用上是一样,如下面的代码片断:


local int a;local int32 a;local long a;
复制代码


表示的都是一个 32 位的整型变量,这三种声明方式表达的含义是相同的。声明变量时,需要在前面跟上 local 关键字,如果没有跟上 local,则表明是在声明一个占位的数据字段。所谓占位的数据字段,指的 010 Editor 在解析模板中的变量时,会对占位的数据部分使用指定的数据类型进行解析,如下面的代码:


typedef struct {    GlobalHeader header;    Proto proto;} Luac;Luac luac;
复制代码


010 Editor 在解析这段代码时,会按照 Luac 中所有的占位数据字段信息解析当前的二进制文件。GlobalHeader 与 Proto 的声明也中如此,没有加上 local 的数据字段,都会被 010 Editor 解析并显示。


除了支持基本的 C 语言格式结构体 struct 外,010 Editor 模板语法还加入了一些特性,比如字段注释与格式、结构体压缩与处理函数。看如下的结构体信息:


typedef struct {    uint64 varname_size <format=hex>;    char varname[varname_size];    uint32 startpc <format=hex, comment="first point where variable is active">;    uint32 endpc <format=hex, comment="first point where variable is dead">;} LocVar <read = LocVarRead, optimize = false>;
复制代码


这是按照前面介绍的 LocVar 结构体信息,按照 010 Editor 模板语法处理过后的效果。为字段后添加 format 可以指定它的输出格式为十六进制 hex,默认是 10 进制;为字段后添加 comment 可以指定它的注释信息,这两个字段可以同时存在,在中间加入一个逗号即可;可以为结构体指定 read 来指定它的类型读取函数,也可以指定 write 来指定它的类型写入函数,read 与 write 有着自己的格式,如下所示:


string LocVarRead(LocVar &val) {    return val.varname;}
复制代码


所有的 read 与 write 返回值必须为 string,参数必须为要处理的结构体类型的引用。注意:010 Editor 模板语法不支持指针,但支持引用类型,但引用类型不能作为变量与函数的返回值,只能作为参数进行传递,在编写模板代码时需要注意。


除了以上的基础类型外,010 Editor 模板还支持字符串类型 string,这在 C 语言中是不存在的!它与 char[]代表的含义是相同的,而且它支持的操作比较多,如以下字符串相加等操作:


local string str = "world";local string str2 = "hello " + str + "!\n";
复制代码


010 Editor 模板中的宏有限制,并不能解析那些需要展开后替换符号的宏,只支持那些能够直接计算的宏。如下面的 BITRK 与 ISK 宏:


#define SIZE_B9#define BITRK(1 << (SIZE_B - 1))#define ISK(x)((x) & BITRK)
复制代码


前者可以直接解析并计算出来,010 Editor 模板就支持它,而对于 ISK 宏,并不能在展开时计算出它的值,因此,010 Editor 模板并不支持它。


010 Editor 模板支持 enum 枚举,与 C 语言中的枚举的差别是,在定义枚举时可以指定它的数据类型,这样的好处是可以在 010 Editor 模板中声明占位的枚举数据。如下所示是 Luac.bt 中用到的 LUA_DATATYPE 类型:


enum <uchar> LUA_DATATYPE {    LUA_TNIL=     0,    LUA_TBOOLEAN=  1,    LUA_TLIGHTUSERDATA =  2,    LUA_TNUMBER=     3,    LUA_TSTRING=     4,    LUA_TTABLE=     5,    LUA_TFUNCTION=     6,    LUA_TUSERDATA=     7,    LUA_TTHREAD=     8,    LUA_NUMTAGS     =    9,};
复制代码


010 Editor 模板中支持调用常见的 C 语言库函数,如 strlen()、strcat()、print()、sprintf()、strstr(),不同的是,函数名上有些差别,这些可调用的函数在 010 Editor 模板中首字母是大写的,因此,在调用时,它们分别是 Strlen()、Strcat()、Print()、Sprintf()、Strstr()。更多支持的字符串操作的函数可以查看 010 Editor 的帮助文档“String Functions”小节,除了“String Functions”外,还有“I/O Functions”、“Math Functions”、“Tool Functions”、“Interface Functions”等函数可供模板代码使用。


接下来看下代码结构部分,010 Editor 模板支持 C 语言中的 for/while/dowhile 等循环语句,这些语句可以用来组成到 010 Editor 模板的函数与代码块中。一点细微的差别是 010 Editor 模板的返回类型只能是上面介绍过的基础类型,不支持自定义类型与数组结构,这就给实际编写代码带来了一些麻烦,遇到这种函数场景时,就需要考虑更改代码的结构了。

编写 luac.bt 文件格式模板

了解了 010 Editor 模板语法后,就可以开始编写 Luac.bt 模板文件了。编写模板前,需要找好一个 Luac 文件,然后边写边测试,生成一个 Luac 文件很简单,可以编写好 hello.lua 后,执行下面的命令生成 hello.luac:


$ luac -o ./hello.luac ./hello.lua
复制代码


生成好 Luac 文件后,就是编写一个个结构体进行测试,这是纯体力活了。luadec 提供了一个 ChunkSpy52.lua,可以使用它打印 Luac 的文件格式内容,可以参考它的输出进行 Luac.bt 的编写工作,实际上我也是这么做的。


首先是 GlobalHeader,它的定义可以这样写:


typedef struct {    uint32 signature <format=hex>;   //".lua"    uchar version <format=hex>;    uchar format <comment = "format (0=official)">;    uchar endian <comment = "1 == LittleEndian; 0 == BigEndian">;    uchar size_int <comment = "sizeof(int)">;    uchar size_size_t <comment = "sizeof(size_t)">;    uchar size_Instruction <comment = "sizeof(Instruction)">;    uchar size_lua_Number <comment = "sizeof(lua_Number)">;    uchar lua_num_valid <comment = "Determine lua_Number whether it works or not, It's usually 0">;    if (version == 0x52) {        uchar luac_tail[0x6] <format=hex, comment = "data to catch conversion errors">;    }} GlobalHeader;
复制代码


这种定义的方式与前面介绍的 LocVar 一样,具体就不展开讨论了。下面主要讨论编写过程中遇到的问题与难点。


首先是输出与 ChunkSpy52.lua 一样的 function level,也就是函数的嵌套级别,定义结构体时可以传递参数,这一点是 C 语言不具备的,但这个功能非常实用,可以用来传递定义结构时的信息,如这里的 function level 就用到了该特性。这是 Protos 的定义:


typedef struct(string level) {    uint32 sizep <format=hex>;    local uint32 sz = sizep;    local uint32 i = 0;    local string s_level;    while (sz-- > 0) {        SPrintf(s_level, "%s_%d", level, i++);        Proto proto(s_level);    };} Protos <optimize=false>;
复制代码


为结构体加上一个 string 类型的 level 参数,初始时传值“0”,然后往下传递时,为传递的值累加一,这样就做到了 function level 的输出。


然后是 Constant 常量信息的获取,由于 TValue 支持多种数据的类型,因此在处理上需要分别进行处理,这里参考了 luadec 的实现,不过在细节上还是比较麻烦。luadec 使用 DecompileConstant()方法实现,它的代码片断如下:


···char* DecompileConstant(const Proto* f, int i) {    const TValue* o = &f->k[i];switch (ttype(o)) {case LUA_TBOOLEAN:return strdup(bvalue(o)?"true":"false");case LUA_TNIL:return strdup("nil");#if LUA_VERSION_NUM == 501 || LUA_VERSION_NUM == 502case LUA_TNUMBER:{char* ret = (char*)calloc(128, sizeof(char));sprintf(ret, LUA_NUMBER_FMT, nvalue(o));return ret;}case LUA_TSTRING:        return DecompileString(o);default:return strdup("Unknown_Type_Error");}}···
复制代码


bvalue 与 nvalue 是 Lua 提供的两个宏,这在编写模板时不能直接使用,需要自己实现,由于宏的嵌套较多,实际测试时编写了 C 语言代码展开它的实现,如 nvalue 展开后的实现为:


((((((o))->tt_) == ((3 | (1 << 4)))) ? ((lua_Number)(((((o)->value_).i)))) : (((o)->value_).n))));
复制代码


于是编写替换代码 number2str 函数,实现如下:


string number2str(TValue &o) {    local string ret;    local string fmt;    if (get_inst_sz() == 4) {        fmt = "(=%.7g)";    } else if (get_inst_sz() == 8) {        fmt = "(=%.14g)";    } else {        Warning("error inst size.\n");    }    local int tt = o.value_.val.tt_;    //Printf("tt:%x\n", tt);    local lua_Integer i = o.value_.i;    local lua_Number n = o.value_.n;    SPrintf(ret, "%.14g", ((tt == (3 | (1 << 4))) ? i : n));    return ret;}
复制代码


然后为 Constant 编写 read 方法 ConstantRead,代码片断如下:


string ConstantRead(Constant& constant) {    local string str;    switch (constant.const_type) {        case LUA_TBOOLEAN:        {            SPrintf(str, "%s", constant.bool_val ? "true" : "false");            return str;        }        case LUA_TNIL:        {            return "nil";        }        case LUA_TNUMBER:        {            return number2str(constant.num_val);        }        case LUA_TSTRING:        {            return "(=\"" + constant.str_val + "\")";        }        ......        default:            return "";    }}
复制代码


DecompileConstant 中调用的 DecompileString 方法,原实现比较麻烦,处理了非打印字符,这里简单的获取解析的字符串内容,然后直接返回了。


最后,所有的代码编写完成后,效果如图所示:



luac.bt 的完整实现可以在这里找到:https://github.com/feicong/lua_re


本文转载自公众号 360 云计算(ID:hulktalk)。


原文链接:


https://mp.weixin.qq.com/s/EXMqlYe6C8MEWsz063wHGA


2019-11-29 15:031680

评论

发布
暂无评论
发现更多内容

为什么我选择清晨阅读和学习

胡迪伦

学习 效率 自学编程 早起 晨型人

iOS面试梳理 - 2020年8月初

iOSer

ios 面试

超详细超级细B站视频爬取

海绵宝宝re儿

python 爬虫 多进程

小码农也有大目标,最新BAT大厂Java面试总结

Java架构师迁哥

架构师训练营大作业

方堃

Skywalking Php注册不上问题排查

心平气和

php Skywalking 全链路追踪

第一周总结

睁眼看世界

极客大学架构师训练营

有了数据湖探索服务,企业决策“新”中有数

华为云开发者联盟

Serverless 数据湖 数据分析 云原生 华为云

“锟斤拷”的前世今生

Java旅途

Unicode 编码 ASCII 锟斤拷

架构师训练营大作业二

qihuajun

防止重复点击2.0

老菜鸟

Vue

架构师训练营大作业一

qihuajun

【性能优化】小伙伴问我性能指标监控怎么做,这次我安排上了!!

冰河

tomcat 性能优化 监控 性能调优 jvm调优

你有没有想过为什么交易和退款要拆开不同的表?

程序员小航

设计 开发 交易 拆表

极客大学架构师训练营 0 期 结课作业

chun1123

架构师 架构师技能

食堂就餐卡系统设计

天天向上

极客大学架构师训练营

一个敏捷教练成长必备的8项技能

华为云开发者联盟

程序员 敏捷 敏捷开发 敏捷教练 技术技能树

第一周学习心得

alpha

极客大学架构师训练营

食堂就餐系统 UML 图

睁眼看世界

极客大学架构师训练营 食堂就餐系统

使用枚举的正确姿势

Java旅途

Java 单例 枚举

架構師訓練營第1期-第01周總結

Panda

10大高性能开发宝石

码界西柚

开发者说:愿为你点亮“懂环境知冷暖”智能的灯

华为云开发者联盟

人工智能 物联网 NB-IoT 路灯 华为IoT平台

第一周总结

黄立

架构师 架构总结

互联网架构师能力图谱

dony.zhang

架构师 架构师技能

食堂就餐卡系统设计

Gosling

极客大学架构师训练营

Golang领域模型-聚合根

奔奔奔跑

架构 微服务 领域驱动设计 DDD Go 语言

拖动旋转的 3D 骰子效果

Clloz

CSS transform rotate3d

SpringCloud Gateway 身份认证

Barry的异想世界

微服务 nacos 权限系统 Gateway spring cloud alibaba

Spring-技术专题-重试机制Retry机制

码界西柚

解构 Dubbo-go 的核心注册引擎 Nacos

apache/dubbo-go

dubbogo

Lua程序逆向之Luac文件格式分析(下)_文化 & 方法_安全客资讯平台_InfoQ精选文章