二进制程序对比

Published: 2022年10月07日

In Reverse.

文章

https://www.cnblogs.com/LittleHann/p/13451724.html

函数指纹匹配

在逆向分析时,一般都遇不到有符号表的程序,但它们可能使用了开源项目,此时可使用开源项目生成特征来识别符号,此类插件评判主要看两点:

  1. 效率高:内存占用要低,时间要快,算法不好时程序稍微变大(10M以上)就无法工作了。
  2. 准确:尽可能多的匹配,尽可能少的误报,当然后者更重要,因为我们会假定有名称的函数名称代表了它的含义,就不会再分析它了。

Flirt

此时可直接先将程序编译为静态库文件,之后先使用pelf等生成pat文件,再使用sigmake生成sig文件,将其命名后放入ida的ids/pc目录后,使用File->load file->flirt选择即可,详细步骤如下:

# 根据目标类型选择工具生成pat文件,如.a文件用pelf
betamao@DESKTOP:~/flair75/bin/linux$ ./pelf ~/myapache/lib/libapr-1.a libapr.pat
~/myapache/lib/libapr-1.a: skipped 0, total 76

# 再将pat转换为.sig
betamao@DESKTOP:~/flair75/bin/linux$ ./sigmake libapr.pat libapr.sig

如果顺利的话这就结束了,但一般都会报错,那是因为有多个函数有同样的签名,此时它会生成一个exc文件,需要通过编辑该文件进行取舍,之后再重新执行sigmake,详见帮助文档。从它的官方文档可见,它使用通过如下算法生成的函数签名:

  1. 取函数前32字节,若里面包含可变数据(如重定位数据)将以.进行通配
  2. 之后是CRC32校验和
  3. 接下来会以^XXXX FuncName表示它在偏移XXXX处调用了函数FuncName,这个可以重复多次

此处推荐个库sig-database,它里面有一些ubuntu和windows下的C库,打CTF分析静态链接文件时可用(逃

idb2pat

有些项目比较复杂,不便于修改为库,此时依然可以先生成普通文件,使用ida生成idb信息,再由idb2pat.py生成pat,之后的方法同时,只是需要注意,idb2pat使用python2编写,若需要在ida7.5(默认使用python3.8)上使用,需要做如下修改:

diff --git a/python/flare/idb2pat.py b/python/flare/idb2pat.py
index c1633f0..300a4c8 100644
--- a/python/flare/idb2pat.py
+++ b/python/flare/idb2pat.py
@@ -35,7 +35,8 @@ def zrange(*args):
         raise RuntimeError("Invalid arguments provided to zrange: {:s}".format(str(args)))
     if end < start:
         raise RuntimeError("zrange only iterates from smaller to bigger numbers only: {:d}, {:d}".format(start, end))
-    return iter(itertools.count(start).next, end)
+    return itertools.takewhile(lambda x:x<end, itertools.count(start, 1))


 def get_ida_logging_handler():
@@ -255,7 +256,7 @@ def make_func_sig(config, func):
         else:
             sig += "%02X" % (get_byte(ea))

-    sig += ".." * (32 - (len(sig) / 2))
+    sig += ".." * (32 - (len(sig) // 2))

     if func.end_ea - func.start_ea > 32:
         crc_data = [0 for i in zrange(256)]
@@ -292,7 +293,7 @@ def make_func_sig(config, func):

         sig += public_format % (public - func.start_ea, name)

-    for ref_loc, ref in refs.iteritems():
+    for ref_loc, ref in refs.items():
         # TODO: what is the first arg?
         name = get_true_name(0, ref)
         if name is None or name == "":
@@ -460,14 +461,14 @@ def main():
     sigs = make_func_sigs(c)

     if c.pat_append:
-        with open(filename, "ab") as f:
+        with open(filename, "a", encoding='ascii') as f:
             for sig in sigs:
                 f.write(sig)
                 f.write("\r\n")
             f.write("---")
-            f.write("\r\n")
+            f.write("\n")
     else:
-        with open(filename, "wb") as f:
+        with open(filename, "w", encoding='ascii') as f:
             for sig in sigs:
                 f.write(sig)
-            f.write("\r\n")
+            f.write("\n")

另外它生成的部分数据可能有误,导致sigmake运行失败,此时可使用-vvvv参数增加输出详细度,这样能快速定位到错误行。

Diaphora

没有仔细分析过它的原理,看它的官网和代码量感觉应该很厉害,使用时,先对开源库生成一个sqlite数据库文件,它不是插件,直接把下载的源码包解压后,按打开运行脚本窗口,选择diaphora.py并执行: image.png 选择导出的地址(1)确认即可。之后在待分析的目标文件里按同样的方式打开,选择导出的sqlite文件地址(1),以及上一步开源库导出的sqlite文件的地址(2)后就会开始比较,比较后结果会按相似度排列,此时可以选择一个阈值右键直接进行函数名迁移。 ​

rizzo

该项目下有多个插件和脚本,主要关注的就是rizzo,它能生成函数的签名,并在另一个项目中导入此签名,其实它和idb2pat作用类似,都是通过IDB生成签名信息,之后在另一个项目中用签名识别函数并为其重命名,但是它们实现的方式不同,Rizzo没有使用Flirt机制,它完全用idapython实现,并使用了如下几种识别方式:

o "Formal" signatures, where functions must match exactly o "Fuzzy" signatures, where functions must only resemble each other in terms of data/call references. o String-based signatures, where functions are identified based on unique string references. o Immediate-based signatures, where functions are identified based on immediate value references.

安装时,可以直接下载整包,之后执行plugins里的install.py脚本进行安装:

python ./install.py --install -d /path/to/your/ida/install/directory

不过它有很多插件可能实际上并不想用,所以这里手动把它里面的rizzo.py文件和shims文件夹移到插件目录即可:

bm@Desktop: ~/IDA7.5SP3/plugins$ tree

├── rizzo.py
├── shims
│   └── ida_shims.py

使用时,先打开已分析过的文件,打开File->Produce File-> Rizzo signature file...生成签名文件,再打开待分析的文件,打开File->Load File-> Rizzo signature file...image.png 在运行结束后,会在Output窗口输出统计信息,并把识别成功的函数属性改为FUNC_LIB(函数窗口看,函数名称变为浅蓝色)。事实上它的匹配效果较差,适合用在升级文件的符号迁移,而在同文件间做迁移,直接写脚本效率更高,可写如下脚本:

from ida_funcs import get_func_name, get_func
from ida_nalt import get_input_file_path
from ida_kernwin import ask_file, ask_buttons
from ida_name import set_name
from idautils import Functions
from json import dump, load

input_file_path = get_input_file_path()


def export_map():
    out_path = ask_file(True, input_file_path + '.map', '要导出的文件路径')
    func_addr_name = {}
    for func_addr in Functions():
        func_name = get_func_name(func_addr)
        if func_name.startswith(('sub_',)):
            continue
        func_addr_name[func_addr] = func_name

    with open(out_path, 'w', encoding='utf8') as f:
        dump(func_addr_name, f, indent=4)


def import_map():
    in_path = ask_file(False, input_file_path + '.map', '要导入的文件路径')
    with open(in_path, 'r', encoding='utf8') as f:
        func_addr_name = load(f, object_hook=lambda x:{int(k):v for k,v in x.items()})
    for func_addr in Functions():
        func_name = get_func_name(func_addr)
        if not func_name.startswith(('sub_',)):  # 已有的名字就不处理了
            continue
        if func_addr not in func_addr_name:  # 原项目也没有名字
            continue
        func = get_func(func_addr)
        new_func_name = func_addr_name[func_addr]
        print('ori name: ', func_name, 'new name:', new_func_name)
        set_name(func.start_ea, new_func_name)


if __name__ == '__main__':
    answer = ask_buttons('export', 'import', 'cancel', 1, '你要干啥?')
    if answer == 1:
        export_map()
    elif answer == 0:
        import_map()
    else:
        ...

bindiff

作为炒鸡牛批的二进制对比工具,能进行函数识别很合情合理吧!它提供了很多种匹配算法,但是缺少文档看不懂是啥意思...它提供了一篇论文,描述了其中一种基于调用关系的匹配方式。 ​

finger

阿里云开发的,没看过代码,按描述是云端收集了大量样本,建立了一个很庞大的指纹库,使用时它会把本地数据(至少是签名)上传到云端进行匹配,按描述在识别公共库时应该很有用,我自己试了下某特殊应用效果不理想,安装时,先安装sdk:

pip install finger-sdk

之后再把插件下载到plugins目录重启,在函数窗口右键选择Finger可尝试识别函数: image.png

Karta

用于识别程序中的开源组件

Pigaios

源码与二进制间比较,并将源码的信息移植到分析数据库中

其他

fingermatch还没看过不知道效果如何...