关注天宇实验室官方微信公众号,发送“逆向技巧”即可获取本文完整解题脚本、附加附录及附件~
NO.1
在各种逆向工程项目和CTF竞赛题中,都会用到很多开源库,比如集成的Lua解释器,以及zlib、zlib等常用库。
许多库都有非常复杂的逻辑。 逆向过程中的详细分析会增加很多工作量,很多逻辑会用到数学和密码学知识。 如果没有这些理论知识,基本上就没有办法逆转这个过程。 这些程序。
这时,各种库函数的识别就会变得非常重要。 本文主要介绍两种识别开源库函数的方法。
想法
NO.2
首先,我们来谈谈总体思路。 现在我们找到了一个开源库,程序中使用的很多函数的代码逻辑与库中的基本相同。
也就是说,我们得到了程序的部分源代码,但是我们不知道这些源代码对应的是程序的哪一部分。
如果我们能找到对应的函数,我们可以通过查阅开源库的源代码、函数名(符号)、开发文档来了解该函数的作用。 本文要介绍的两种方法就是基于这个思想而发展起来的。
自动分析-
NO.3
它是一个用于比较IDA数据库中功能代码相似度的自动化工具。 识别出的代码相似度越高,实现相同逻辑的可能性就越大。 换句话说,它是一个将程序中函数名与函数体进行匹配的工具。
环境安装
Java SE 套件
首先,您需要安装JDK。 笔者这里使用的是JDK 11,可以从官网下载安装。
您可以根据自己的需求选择对应的版本:
IDA专业版
通过IDA生成的数据库进行匹配。 IDA Pro产品需要购买后才能使用。 它是一个非常强大的逆向工具。 笔者这里使用的是IDA 7.6版本。
官网地址:
官方下载地址在这里,根据你的系统版本和环境选择即可:
注意:安装过程中会要求选择IDA的路径。 这时,你必须选择你的IDA工具(ida.exe/ida64.exe)所在的目录。
指示
要使用该工具,您需要提供另一个idb数据库进行比较。
这里我们首先使用IDA打开被分析程序的数据库,然后在File->中选择要对比的idb数据库
等待一段时间后,即可在 中得到匹配结果。
左边的未命名函数可以这样找到。
例子
以下是使用 Final 2021 - Barb Metal 的示例。
本题使用库来实现虚拟机,并对虚拟机的代码进行加密。 该题需要找到虚拟机代码中的漏洞来获取flag。
这里我们只匹配相关的库。
第一步是找到库的特征,比如库中的字符串、编译时添加的信息等,通过这些信息来定位使用的是哪个开源库。
直接在IDA中搜索字符串会得到以下结果,其中包含大量代码路径信息。
直接去搜索,编码路径信息。
获取以下三个开源库,全部托管于:
()
()
()
第二步,将开源库编译成相同架构的二进制库文件。 这里是Linux下的x86架构程序
这里有两点需要注意。 第一点就是必须在32位环境下编译(如果在64位环境下需要给CFLAG添加-m32参数),因为我们要逆向的文件是32位的。 第二点,如果想在更方便的情况下使用,需要将静态链接库(ar)转换为动态链接库(so),因为静态链接库本质上是一个压缩包,里面有很多编译过的东西其中的库。 文件,那么每个库文件都需要一个idb来存储其代码信息。 动态链接库只需要一个idb,因此我们尝试使用动态链接库生成二进制文件。
修改文件,例如
make后的结果是.so动态链接库文件
以此类推,我们继续编译和建库,最终得到三个so文件
导入IDA分别生成对应的idb,使用进行分析。
但对于他来说,就有点鸡肋了。
通过这个,我们的分析就会变得非常简单,比如下面的函数
同时也明确
这就是介绍的全部内容。 这个自动化工具非常容易使用,对于识别各种开源库非常有帮助。 当然,开源库版本的选择也很重要。 最好使所有库版本与要逆向的程序保持一致。 版本相同,提高匹配度。
手动分析-对比源码
NO.4
如果这个自动化工具在功能匹配上不是很有效,那么我们就只能通过手动对比源码来分析各个功能的作用了。
以刚才的例子来说,我们能获取到的信息包括源代码所在的目录和文件,甚至是这一行的代码或者信息。 有些函数可能会直接将函数名信息写入其中。 该信息用于定位源代码的函数位置,可用于匹配函数符号。
不过这种情况在CTF逆向工程中比较少见(除非问题的重点不是逆向工程)。 这时候程序本身的逻辑就会变得非常重要。 无论函数名称或源代码如何更改,函数的逻辑都不会改变。 通过人工直接分析功能逻辑,在信息不足的情况下可以定位到该功能。 接下来我们通过函数本身的特点来演示手动分析的方法。
例子
这道题基本没有什么可查的信息(提问者甚至删除了函数本身的一些可以追溯到源码的功能),所以我们先从main函数开始吧。
您可以简单浏览一下伪代码。 程序首先打印出密钥,然后加密data.txt并将其存储在enc.dat中,然后用字符串FLAG将其拆分,并写入转换后的密钥。
但进入任何函数时,逻辑都很复杂,没办法理解。
所以我们从能看到的常数值开始,先从上到下看,先进入。 里面有两条数据,但是遗憾的是搜遍了里面的数据后,并没有任何信息表明是哪种算法。
然后输入,可以看到简单的逻辑,再输入看看它做了什么改造。
将a1的类型改为*,因为根据前面的分析,a1是一个整型数组。
0x312 0x212 0x12 0x112 这些值看起来很特别,所以我们来搜索一下这些值并尝试一下。
我找到了加密算法的来源,是 DS的算法。
根据源码对比,可以得到如下函数映射
->
->
至于上面的两个数据集,如果你仔细分析源码和程序逻辑,你会发现它们只是将自己的key写入到一个sbox中,后续的很多数据甚至根本没有被使用。 这两个数据集实际上是对我们逆向工程的干扰。
返回main函数,找到加密逻辑
它实际上是什么并没有任何意义,因为v15是我们的加密数据,对其没有任何影响。 但根据逻辑比较,这个函数实际上是将刚刚加密的数据解密回来了。 。 。 我不知道提问者为什么要添加这个功能。
下图中的两个函数交换了int的四个字节的big-和-顺序,这实际上就是上面源码的函数逻辑。
下一步是非常明显的。 首先想办法解密密钥,然后转储生成的sbox,然后解密。
解密逻辑如下。
def crypt_64bit_down(x, y):
for i in range(0x11, 1, -1):
z = sbox[i] ^ x
x = sbox[0x012 + ((z>>24)&0xff)];
x = sbox[0x112 + ((z>>16)&0xff)] + x;
x = sbox[0x212 + ((z>> 8)&0xff)] ^ x;
x = sbox[0x312 + ((z>> 0)&0xff)] + x;
x = y ^ x
y = z
x = x ^ sbox[1]
y = y ^ sbox[0]
return (x, y)
def byteswap32(a):
return (a >> 8) & 0xFF00 | (a << 8) & 0xFF0000 | (a << 24) & 0xFF000000 | (a >> 24) & 0xFF
for i in range(0, len(encbuf), 2):
b = encbuf[i], encbuf[i+1]
b = crypt_64bit_down(byteswap32(a), byteswap32(b))
a = byteswap32(a)
b = byteswap32(b)
i+2] = [a, b] :
接下来回到主函数,trace->,这个函数很大,但是有一些关键的变量可以让我们搜索
放入搜索即可得到图书馆的结果
我还找到一篇文章,专门讲如何识别库函数。 不幸的是,这个问题去掉了文章中提到的函数的特征,因此无法通过本文的思路来识别它们。
然后我们直接编译拖进去,可惜识别率出奇的低,而且没有办法引用。
不过,我们编译出来的so文件也不是没有用的。 用IDA打开它。 首先是搜索键值并获取函数的名称。 接下来我们对比符号表,将so文件对应的函数填入我们的逆向程序中。 中间
接下来跟进各个函数,根据判断条件、调用函数等函数逻辑继续对函数进行重命名。 比如这个函数用了这个值,就直接搜索,跟踪每个函数,自己看看代码相似度。
猜测是一个函数,以此类推,用X进行交叉引用,发现两个函数很相似,尤其是调用参数1
继续往下看,基本可以断定两个函数一模一样,命名,然后copy add sub等函数也识别出来了,以此类推继续识别函数
这里有一个小技巧。 现在我们已经确定了该函数,该函数在很多函数中被调用,但是调用次数很少,都在1-5次之间,我们可以通过交叉引用来确定它在程序中的哪个位置被调用。 度数和位置,然后看它的参数(比如这里的3)就可以确定这个函数的名字是什么。
例如,一个非常复杂的函数被调用两次,值分别为10和7。
查找一个使用参数 10 和 7 调用两次的函数
通过手动分析,基本可以断定是一个函数。
以此类推,分别恢复符号。
那么加密逻辑就是下面的结果
很简单的逻辑,然后继续回到main函数
经过查找和识别很明显算法是,字典改为
9876432*Flag{n0T-EA5y=to+f1Nd}BCDGHJKLMPQRSUVWXYZbcehijkmp
然后用下面的代码就可以得到key的值,只需将整数开3次平方根即可。
(这里使用了gmpy2工具,原文章附录中有解释)
STANDARD_ALPHABET = b'123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
CUSTOM_ALPHABET = b'9876432*Flag{n0T-EA5y=to+f1Nd}BCDGHJKLMPQRSUVWXYZbcehijkmp'
with open("./enc.dat.bak", "rb") as f:
buf = f.read()
encrypted, key = buf.split(b"FLAG")
key = base58.b58decode(key.translate(bytes.maketrans(CUSTOM_ALPHABET, STANDARD_ALPHABET)))
print("Key: " + int(gmpy2.iroot(int.from_bytes(key, "big"))[0]).to_bytes(8, "big").decode())
通过调试,改变key的值,程序生成sbox后dump得到sbox.bin
总结
NO.5
本文主要介绍两种识别开源库的方法。 这种方法在逆向工程中更为重要。 希望对大家有所帮助。