写点什么

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:031972

评论

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

百度开放3000+实习机会,AI相关岗位占比87%

科技大数据

如何利用科学的预算管理为企业释放更多价值

智达方通

数字化转型 全面预算管理 财务管理 财务规划

智能体爆发元年,谁在“无人区”绘制地形图?

脑极体

AI

TiDB 观测性解读(一)丨索引观测:快速识别无用索引与低效索引

TiDB 社区干货传送门

Techub 财报解读:Circle 冲刺 IPO,但收入增长难掩利润困局

TechubNews

卫浴“家电化”:一场科技驱动洗牌赛拉开序幕

Alter

TiDB × AI :DeepSeek 时代你需要什么样的数据基座

TiDB 社区干货传送门

TiDB 可观测性解读(二)丨算子执行信息性能诊断案例分享

TiDB 社区干货传送门

Centos系统云主机中nvme盘不可用解决方法

天翼云开发者社区

云主机

AI框架不牢,模型地动山摇

脑极体

AI

掌握 JSON 到表格转换:全面指南

数据追梦人

【超详细】Mac读取移动硬盘速度很慢的原因及解决方法

阿拉灯神丁

文件存储 磁盘管理 Mac软件 苹果电脑 Tuxera NTFS2024

AI 浪潮下企业身份管理:特点凸显,安全挑战升级

TechLead Studio

AI 企业身份安全

SD-WAN能为医疗信息化升级提供哪些帮助?

Ogcloud

SD-WAN SD-WAN组网 SD-WAN厂商 sd-wan专线 SD-WAN厂家

AI框架不牢,模型地动山摇

白洞计划

AI

Apipost vs Apifox:高效API协作的差异化功能解析

数据追梦人

GraalVM 24 正式发布阿里巴巴贡献重要特性 —— 支持 Java Agent 插桩

阿里巴巴云原生

阿里云 云原生

Rust 如何轻松实现 RTMP 流媒体推送?深入解析直播推流场景与解决方案

Yeauty

rust ffmpeg Video RTMP media

LLM 不断提升智能下限,MCP 不断提升创意上限

阿里巴巴云原生

阿里云 微服务 云原生 LLM

高可靠架构+智能运维,华为云会议“始终在线”!

平平无奇爱好科技

线性判别分析(LDA):降维与分类的完美结合

不在线第一只蜗牛

机器学习

Kyutai 推出 Moshi 语音模型微调工具包;语音智能体平台 Vapi 2.0 上线,已提供 4400 万通电话服务丨日报

声网

东京 Voice AI Agent 工作坊!1 小时让你的 AI 能听能说

声网

Python + 腾讯云,多页PDF发票识别一键搞定!

程序员晚枫

开源

数据库选型指南:TiDB 与 MySQL 全方位对比清单新鲜出炉!

TiDB 社区干货传送门

【解决方案】多租户技术架构设计入门(二)

量贩潮汐·WholesaleTide

数据库 多租户

NineData云原生智能数据管理平台新功能发布|2025年3月版

NineData

阿里云可观测 2025 年 3 月产品动态

阿里巴巴云原生

阿里云 云原生 可观测

什么时候您需要升级SD-WAN解决方案?11个预警提示!

Ogcloud

SD-WAN组网 SD-WAN厂商 sd-wan专线 SD-WAN厂家

CAD裁剪块方法

极客天地

人工智能+牙科:临床应用中的几个问题

算AI

人工智能 算法 医疗AI 视觉模型

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