IoT设备逆向工程中的函数识别

本文主要讨论IoT设备逆向工程中的函数识别、符号迁移问题,不考虑BinDiff、PatchDiff2等重型工具,有些场景也不太适用。函数识别技术主要涉及两大类,一是IDA自身的FLIRT技术,二是Craig的rizzo技术,本文介绍它们的基本原理、工程实践细节。另外附赠几个其他类型IDA插件的使用说明。

☆ 前言

本文主要讨论IoT设备逆向工程中的函数识别、符号迁移问题,不考虑BinDiff、PatchDiff2等重型工具,有些场景也不太适用。函数识别技术主要涉及两大类,一是IDA自身的FLIRT技术,二是Craig的rizzo技术,本文介绍它们的基本原理、工程实践细节。另外附赠几个其他类型IDA插件的使用说明。

☆ 函数识别/符号迁移

1) FLIRT

1.1) FLIRT简介

考虑如下场景,分析一个被strip过的、不包含调试信息、符号信息的ELF,此ELF静态链接过OpenSSL的libcrypto.a,而你关心ELF所用加密算法、HASH算法。findcrypt插件可用于这种需求,但不够。

FLIRT是”Fast Library Identification and Recognition Technology”的缩写。这是IDA自带的一种函数识别技术,用于前述需求。

参[1],作者介绍IDA 3.6中FLIRT技术的基本原理:

————————————————————————–
Each function is represented by a pattern. Patterns are first 32 bytes of a function where all variant bytes are marked.

A special signature file, called startup signature file is applied to the entry point of the disassembled program to determine the generating compiler.

For the sake of user’s convenience we attempted to recognize the main() function as often as it was possible. The algorithm for identifying this function differs from compiler to compiler and from program to program.

The signature file contains no byte from the original libraries, except for the names of the functions.
————————————————————————–

简单点说,为静态库中每个函数确定其特征字节流、特定调用关系、特定检查项等等,以此识别出对应函数。将各函数名及其识别特征按特定格式组织并存放在签名文件中,形成扩展名为.sig的签名文件。IDA在分析ELF时,可以加载.sig,自动识别ELF中静态链接进来的库函数。

签名文件对函数的识别标准比较复杂,并不是函数入口32字节这么简单,细节参[1]。

1.2) 签名文件

IDA自带一些签名文件:

IDA自带签名文件存放在:

<arch>是CPU架构,IoT设备逆向工程很可能面对arm。.sig的名字已经提供了一定信息,可以从中猜测该.sig源自什么,.sig中可能含有一条说明信息,用如下办法查看:

第二行输出即可能存在的说明信息,不是所有的.sig都有说明信息。

1.3) 应用签名

View->Open subviews->Signatures(Shift-F5)

此处看到当前使用的签名文件及靠它识别出来的函数个数

View->Open subviews->Signatures(Shift-F5)->右键菜单->Apply new signature(Ins)

File->Load file->FLIRT signature file

此处看到”<IDA PATH>\sig\<arch>\”下所有签名文件

《The IDA Pro Book》第2版第12章介绍FLIRT、FLAIR的使用。

“dummy name”形如”sub_…”。这即是说,binary被反汇编后尽可能早地应用签名文件,以免出现人工重命名干挠签名文件带来的重命名。

1.4) FLAIR简介

FLAIR是”Fast Library Acquisition for Identification and Recognition”的缩写。这是IDA自带工具集,用于定制自己的签名文件。

FLAIR工具集的基本使用方式:

.a通过pelf得到.pat,后者是静态库中各函数名及其识别特征的文本文件格式。

.pat通过sigmake得到.sig。一般情况下第一次执行sigmake会碰上冲突,就是说多个函数的识别特征是一样的,此时会产生.exc文件,必须手工处理.exc,解决冲突,再次执行sigmake得到.sig。

sigmake得到的.sig已经可以为IDA所用,可用zipsig对之压缩,得到扩展名仍然是.sig的压缩版,IDA同时支持非压缩、压缩版.sig。

FLAIR自带帮助文件:

1.5) EXC文件

这表示.pat中存在冲突,多个函数的识别特征一样;自动生成libcrypto_arm.exc,比如:

.exc最前面4行内容是固定的,它告诉你可以在后面的每行打头处增加+/-号,其意义是:

+ 使用该函数名
– 让该函数名以注释形式出现

后面是空行分隔的冲突分组。

手工处理.exc示例:

必须删掉.exc最前面4行,表示这个.exc已被手工处理过,否则第二次sigmake不会做动作。

对第1冲突分组(从1计)不做任何动作,该函数名被废弃。

对第2冲突分组采用函数名”EVP_dss”,其余两个函数名被废弃,这导致误报。

对第3冲突分组在匹配函数处增加注释”PROXY_CERT_INFO_EXTENSION_free”。

处理EXC文件时的三条基本原则:

————————————————————————–
a)

最简处理方式是删除EXC文件最前面(以分号打头的)4行内容,其余内容保持不变。这
表示冲突分组中所有函数名被废弃,重命名时不会用到它们。

b)

不要在同一个冲突分组内同时使用+/-,要么+要么-,且只出现一次。

c)

如果某冲突分组只有一个函数,不要对之使用+/-,保持原样,表示废弃该函数名。
我不清楚为什么会出现这种情形。
————————————————————————–

2) IDB2PAT

定制签名文件的一般套路是:

ELF -> PAT -> SIG

有可能出现:

IDB -> PAT -> SIG

考虑如下场景,分析过一个裸格式的IoT固件,已经重命名一批关键函数,某天需要分析另一个与前者高度接近的新版固件,此时可以用BinDiff、PatchDiff2进行二进制比较,但那过于重型,有无其他办法快速迁移函数名?

IDB2PAT是个IDA插件,顾名思义,从当前IDB导出PAT文件。然后sigmake生成SIG并应用到新IDB中。

最早IDB2PAT是C版插件,需要IDA SDK编译,IDA一升级就得重新编译。FireEye提供了Python版插件,参[6]。

Alt-F7加载idb2pat.py,提示选择输出PAT文件到哪里,确定即可。

idb2pat.py可用于IDA 7.2。为了在IDA 7.1中使用,需要做些修改。把所有的”get_name(“全文替换成”get_name(BADADDR,”,7.2版get_name()函数原型是”get_name(ea)”,7.1版get_name()函数原型是”get_name(from,ea)”。原实现中Config()使用缺省参数”mode=ALL_FUNCTIONS”,改成”NON_AUTO_FUNCTIONS”,否则会将”sub_…”一并导出到PAT文件,这没有意义。

3) lscan

参[7]。

考虑如下场景,用IDA分析某strip过的ELF,发现其中存在被静态链接进来的库函数,想快速知道该ELF用到哪些静态库,想快速重命名相应的静态库函数。假设你有大量现成的.sig,一个个试过去太累。

lscan可以解决上述需求。它不是IDA插件,是独立使用的Python脚本。lscan检查SIG与ELF的匹配程度,匹配百分比越高,ELF用到相应静态库的可能性越大。lscan可以一次性检查多个SIG与单个ELF的匹配度,然后在IDA中优先应用匹配度最高的SIG。

lscan不能使用经zipsig压缩过的SIG,只认非压缩版SIG。

“git clone”下回来406M,真正有用的就lscan.py一个文件,其他的都算测试集,其自带SIG没啥大用,所以不必”git clone”下载。

用lscan自带”arm/sig/”下的所有SIG去匹配FLIRTTarget:

lscan自带SIG没有适用于FLIRTTarget的。

用自制SIG去匹配FLIRTTarget:

fromidb.sig源自IDB2PAT,其余两个.sig源自自编译库文件。

从这个输出看出lscan有BUG,匹配百分比都超出100%了,我懒得去找原因。

4) rizzo

FLIRT的函数识别特征主要是机器码序列,而静态库函数的机器码序列受编译器、编译选项、源代码版本影响太大。Craig提出另一套启发式函数识别标准,参[8],他检查函数如下项:

a) 对特征字符串的引用
b) 对特征常量的引用
c) CFG
d) …

这些检查项受编译器、编译选项、源代码版本影响较小。

rizzo的工作流程是:

IDB A -> RIZ -> IDB B

rizzo和IDB2PAT都是从IDB出发。在IDB A中利用rizzo导出.riz文件,这相当于FLIRT的.sig文件。然后在IDB B中利用rizzo导入.riz文件,这相当于应用签名。

考虑如下场景,用IDA反汇编静态库libsome.a,用rizzo导出some.riz,用IDA反汇编怀疑用到libsome.a的ELF,用rizzo导入some.riz。

rizzo.py是IDA插件,将之复制到plugins目录,重启IDA即可。

导出.riz:

File->Produce file->Rizzo signature file

加载.riz:

File->Load file->Rizzo signature file

如果没看到这两项,说明插件有BUG。

.riz文件随便在哪儿,不要求放到”<IDA PATH>\sig\<arch>\”下。

5) 利用IDC迁移符号

如果两个IDB对应同一个ELF,通过导出IDC迁移符号、结构、注释即可。为什么会有这种需求,自己想去。

导出.idc:

File->Produce file->Dump database to IDC file

编辑some.idc,修改main(),只保留Bytes()、Functions()相关的函数。

加载.idc:

File->Load file->Additional binary file

6) syms2elf

参[10]。

假设某ELF是strip过的,用IDA对之进行分析,利用FLIRT、rizzo等技术重命名过一批函数,此时利用syms2elf生成一个包含符号信息的新ELF。IDA反汇编新ELF时、gdb调试新ELF时,符号信息自动生效。将syms2elf.py复制到plugins目录,重启IDA即可。可用于IDA 6.95至7.2,x86/x64均可。

Edit->Plugins->sym2elf

假设FLIRTTarget是旧ELF,FLIRTTarget_new是新ELF:

nm可以看到新ELF中已经包含重命名过的函数名。

syms2elf会将”sub_…”当成有效函数名写入新ELF,这不好,应该只写”sub_…”之外的其他函数名,我懒得改。

可用objcopy从新ELF中导出符号文件:

显然,syms2elf只能用于ELF格式的binary,不能用于裸格式的固件。

syms2elf构造新ELF时会去找旧ELF,如果旧ELF不在相应路径上,syms2elf会失败。

检查binary所在路径:

idaapi.get_input_file_path()

修改binary所在路径:

idaapi.set_root_filename( “…” )

修改成相对路径,比如只保留ELF文件名,不要目录结构,这样ELF与IDB在同一目录
即可。

7) 静态库

FLIRT、rizzo技术都依赖一个匹配度较高的静态库,有它才有SIG、RIZ。一般情况下分析ELF时很难知道它用到哪些静态库,更不要说获取编译时所用静态库文件本身。运气好的话,可能在ELF中看到一些相关字符串信息。

这可能表示编译FLIRTTarget时用的是gcc 3.5。在没有ldd的环境中查看ELF所依赖的动态库:

FLIRTTarget可能用到”OpenSSL 1.0.1l”ARM版静态库(libcrypto.a)。

如下命令查看libcrypto.a中的符号:

如下命令可知libcrypto.a的版本号:

如果有条件,还可以这样确定版本号及编译选项:

$ openssl version -a

上哪儿去找FLIRTTarget所用libcrypto.a呢?

7.1) 从源码编译OpenSSL

https://ftp.openssl.org/source/old/1.0.1/openssl-1.0.1l.tar.gz

版本是”openssl-1.0.1″,后面跟着小写的字母”l”。

交叉编译ARM版本:

arm-gcc是个示意写法,你得设法分析FLIRTTarget,找出一个最接近的交叉编译工具链。

我们只关心加密算法相关的函数,比如RSA_new(),指定no-zlib表示不关心zlib,否则还得交叉编译zlib,麻烦。

从源码编译OpenSSL是被逼无奈的选择,不知道当时所用编译器、编译选项等信息,只能瞎猜将就。

7.2) 生成SIG

sigmake很少能一次性成功,必须手工处理.exc解决冲突。

二次执行sigmake:

可用zipsig对.sig进行压缩,减少空间占用,但这不是必须的:

可用dumpsig查看.sig:

dumpsig不能还原出.pat,由.pat到.sig的过程有信息丢失,是单向过程,但dumpsig得到的.txt中仍能看到特征字节流信息。

新生成的.sig应该放到”<IDA PATH>\sig\<arch>\”下。

用IDA反汇编libcrypto.a,用rizzo导出libcrypto.riz。

7.4) SIG/RIZ实验

个人经验是优先应用RIZ,再应用SIG。

先在FLIRTTarget.idb中应用libcrypto.riz,成功识别出:

注意到FLIRTTarget.idb中RSA_new之后的函数未被识别。接着应用SIG,先Shift-F5,
再Ins,依次应用:

测试发现反复应用SIG可以多识别出一些函数,比如:

第二行表示该轮应用libcrypto_arm.sig时有2403处函数重命名发生。

从FLIRT技术原理看,应该多次应用SIG,直至不再新增被识别函数。

7.5) 其他

静态链接时,.o中所有函数都被链接到binary中。假设在FLIRTTarget.idb中识别出.o中某函数,其前后函数很可能对应.o中的布局,如果前后函数未被重命名,可手工检查之。

8) 社区维护的SIG库 vs 自编译源码

参[7],有一些社区维护的SIG库,不过没啥用,用脚趾头都能想像。实践表明,还得想办法自编译源码获取SIG/RIZ,从最终效果看,这样做有意义。

9) IDA Signsrch

参[4]。

“IDA Signsrch”功能类似过去的findcrypt.plw,能识别的算法更多,可用于IDA 7.2。将IDA_Signsrch.plw、IDA_Signsrch.p64、signsrch.xml、IDA_Signsrch.dll、IDA_Signsrch64.dll分别放入相应版本plugins目录,重启IDA即可。

Edit->Plugins->Signsrch->Continue

匹配结果在”Signsrch matches”窗口展示。

“IDA Signsrch”和findcrypt不能说没用,但用处有限,很多时候还不如自己肉眼识别特征常数并Google之。

10) 静态识别 vs 动态调试

用IDA静态识别加密函数只是手段之一,如能动态调试,可以通过观察in/out合理猜
测算法并验证之,二者可以结合着来。

☆ 反编译

1) Snowman

参[2]。

Snowman类似Hex-Rays,但远不如Hex-Rays。简单的”Hello World”反编译结果都惨不忍睹。号称支持x86、x64、ARM、ARM64。官方声称支持到IDA 7.0,但实际上7.2也可以用。

将snowman.plw、snowman.p64、snowman.dll、snowman64.dll分别放入相应版本plugins目录,重启IDA即可。在光标处按F3展示当前函数C代码。F3的展示窗口有两块区域,左侧是汇编,右侧是C/C++,可以在汇编区域选中代码片段,右键菜单中有”Decompile (Ctrl-E)”,对所选代码片段进行反编译,但这没有什么意义。

2) HexRaysCodeXplorer

参[5]。

将HexRaysCodeXplorer.plw、HexRaysCodeXplorer.p64、HexRaysCodeXplorer32.dll、HexRaysCodeXplorer64.dll分别放入相应版本plugins目录,重启IDA即可。

这是Hex-Rays的插件,在Hex-Rays的反编译窗口中通过右键菜单使用。插件生效时,右键菜单多出如下项:

IDA可以在”Local types (Shift-F1)”中自定义结构,HexRaysCodeXplorer的”REconstruct Type”就是自动干这事,这是该插件最有用的功能。基本用法是:

a) 在Hex-Rays反编译窗口中选中对象(结构)指针
b) 右键”Reset pointer type”,或手工将变量由指针类型变成整型
c) 右键”REconstruct Type”
d) 检查”Local types (Shift-F1)”

HexRaysCodeXplorer-2.1.zip与IDA 7.2 SDK不兼容,目前不能在IDA 7.2中使用。

☆ 其他

1) IdaRef

参[3]。

IdaRef的效果是,当光标停留在某条汇编指令上时,在”Instruction Reference”窗口实时显示该汇编指令的帮助信息。支持x86、x64、ARM、MIPS。

将idaref.py和archs目录复制到plugins目录,重启IDA即可。

这是个鸡肋插件,食之无味、弃之可惜。

一般来说,打开IDA的自动注释就够了。

☆ 参考资源

[1] IDA F.L.I.R.T. Technology: In-Depth
https://www.hex-rays.com/products/ida/tech/flirt/in_depth.shtml

[2] Snowman
https://github.com/yegord/snowman

[3] IdaRef
https://github.com/nologic/idaref

[4] IDA Signsrch
https://sourceforge.net/projects/idasignsrch/

[5] HexRaysCodeXplorer
https://github.com/REhints/HexRaysCodeXplorer

[6] IDB2PAT
https://github.com/fireeye/flare-ida/blob/master/python/flare/idb2pat.py

http://www.openrce.org/downloads/details/26/IDB_2_PAT
(C版本)

[7] lscan
https://github.com/maroueneboubakri/lscan

Community driven collection of IDA FLIRT signature
https://github.com/Maktm/FLIRTDB
https://github.com/push0ebp/sig-database
(没有arm的)

[8] https://github.com/devttys0/ida/tree/master/plugins/rizzo

A Code Signature Plugin for IDA – Craig [2014-10-11]

A Code Signature Plugin for IDA

[10]
syms2elf
https://github.com/danigargu/syms2elf

发表评论