写点什么

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

评论

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

简单了解InnoDB底层原理

leonsh

MySQL 数据库 innodb

深入理解Java虚拟机-HotSpot

华章IT

Java JVM 虚拟机

架构实战营 模块2 课后作业

༺NPE༻

ElasticSearch 如何使用 TDigest 算法计算亿级数据的百分位数?

程序员历小冰

大数据 elasticsearch 近似算法 TDgigest

浅谈JVM和垃圾回收

leonsh

Java JVM JVM虚拟机原理 垃圾回收算法

为什么我愿意持续做这样一件看似没有价值的事情

帅安技术

坚持 持续写作 长期价值

面试4轮字节Java研发岗,最终拿下Offer(原题复盘)

码农之家

编程 程序员 互联网 面试 字节

ARTS- Week 7

steve_lee

Lombok初始使用及遇到的问题

风翱

lombok 4月日更

为什么微服务一定要有 API 网关?

xcbeyond

微服务 api 网关 4月日更

Python 爬虫实战(一) 爬取自如网租房信息

U2647

python 爬虫 4月日更

【提纲】专访融云CTO杨攀 | 技术型人才的自我修炼

Python研究所

调查采访能力考核

朱嘉明:算力产业正面临着一个十年的长周期

CECBC

数字经济

领域驱动设计 101- 上下文与持续集成

luojiahu

领域驱动设计 DDD

翻译:《实用的Python编程》09_03_Distribution

codists

Python

使用Agora SDK开发React Native视频通话App

声网

RTC React Native 声网 RTE

奇绩创坛2021秋季创业营开始报名

奇绩创坛

斗智亦斗棋,零售云市场的“楚河汉界”突围赛

脑极体

python 变量作用域和列表

若尘

变量 Python编程 作用域

《采访彩食鲜 CTO 乔新亮:IT 团队从 100 到 10000 的管理心得》(采访提纲)

程序员历小冰

调查采访能力考核

百度大脑3月新品推荐:EasyDL视频目标追踪全新发布

百度大脑

百度大脑 EasyDL

精通比特币:为什么它对自由、财务和未来至关重要(上篇)

CECBC

比特币

对话声网 Agora 首席科学家钟声 :5G时代到来前景下RTE实时互动技术的应用与发展

麦洛

Java

Excelize 2.4.0 正式版发布, 新支持 152 项公式函数

xuri

GitHub 开源 Excel Go 语言 Excelize

【AI全栈二】视频流多目标多类别无延迟高精度高召回目标追踪

cv君

音视频 目标检测 视频跟踪 引航计划

对话声网 Agora 首席科学家钟声 :声网的未来规划和人才建议

小诚信驿站

采访 调查采访能力考核

如何从零开始学Python:(3)划重点:使用IDLE创建列表时需要注意的地方

广之巅

Python 4月日更

访谈阿里巴巴安全科学家吴翰清

容光

专访 阿里吴翰清 最新网络安全

浅谈在探索数分之路上的“数据思维”论述

小飞象@木木自由

数据分析 数据分析体系 数据思维 数据分析方法论

融合趋势下基于 Flink Kylin Hudi 湖仓一体的大数据生态体系

Apache Flink

flink

关于数字人民币、加密货币,央行前行长周小川、副行长李波博鳌论坛发声

CECBC

数字货币

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