使用IDA逆向安卓so库的一些经验
开始逆向工程前的准备
基本的程序编译、链接、加载知识。除了在c语言课上学到的基本知识外,你还需要清楚段
的概念,比如text
data
bss
段各自的作用。还有elf
文件格式的基本知识。
对一些C语言特性的熟悉。IDA Pro能够将反编译出的汇编代码转换成类似c语言的伪代码
,这大大降低了逆向工程的工作强度,但是你仍然需要掌握一些在通常情况下比较少用到的c语言特性。比如复杂的强制类型转换:
这里的(int (*)(void))
就是一个“将待转换对象转换成一个函数指针,该函数的参数为void,返回值为int”的强制类型转换器,如果你发现你无法理解这些强制类型转换,建议阅读《c陷阱与缺陷》。又比如指针与数值的相加操作:
这里的v15是char *
类型的指针,所以加4之后v15指向其之后4个byte的位置,如果v15是int *
类型的指针,加4之后v15就应该指向其之后16个byte的位置,以此类推。
清楚不同语言之间的差异。如果你要使用非c语言的其他语言去重新实现伪代码的逻辑,你需要注意你使用的语言和c语言之间的区别,比如在c语言中,两整数相除时,小数部分的舍入“总是向着0的方向进行”,而在python里小数部分的舍入是“向着负无穷的方向进行”,如-5/2
在python中就会得到-3
。这样一个小小的特性区别可能会让你白白浪费数个小时。
IDA Pro使用技巧
使用高版本的IDA Pro。高版本提供更多新的特性,而且有时候低版本的IDA Pro会反编译错误,比如在分析的过程中,我碰到了这样一些伪代码:
6.4版本将这一部分理解成了if
语句,可实际上,这里应该被理解成switch
语句,_gnu_thumb1_case_sqi(v5);
也暗示了这一点。为了搞清楚这里的逻辑,我花了半天的时间做了很多的猜测,写了一些IDC
脚本来观察变量的变化规律,可仍然徒劳无功。最后,我切换到了6.8版本,惊喜的发现高版本的反编译器给出了正确的结果:
熟悉IDA基本操作。首先是基本的快捷键:
键名 | 动作 |
---|---|
F5 | 打开Pseudocode界面 |
TAB | 在IDA View和Pseudocode之间切换 |
g | 跳转到指定地址 |
Alt + g | 指定32位(ARM)/16位(Thumb)指令集 |
然后,你还需要需要知道IDA Pro中的一些默认不会打开的视图,通过View=>Open subviews
你可以查看它们,其中最有用的有Names
Strings
Segments
Structures
,这些视图有时候会向你提供非常有用的信息。
通过变量重命名和添加注释来理解伪代码。IDA Pro生成的伪代码质量很高,几乎不存在逻辑错误,但毕竟是由机器从二进制码生成出来的,理解难度很大。其中变量名很多都是以v+数字
的形式出现,而一个函数里有时候会有上百个这样的函数名,大概只有机器会喜欢阅读这样的代码。不过,你可以在变量名上点击鼠标右键,选择Rename lvar
来给变量取一个好理解的名字。同时,你也可以在每一行代码的最后点击鼠标右键选择Edit comment
来给代码添加注释。通过这两个方法,理解伪代码就不再是遥不可及的事情了。顺便一提的时,在IDA Pro里很多地方都可以点击鼠标右键调出相应的选项,当你对一个东西有疑问时,请试着右击一下。
动态调试程序。IDA Pro支持多种平台程序的动态调试,对于安卓APP,需要将安装目录下的dbgsrv/android_server
拷贝到安卓设备中,然后在设备中执行./android_server
并设置好端口转发(具体操作参见文末的参考文章)。需要注意的是,如果你的安卓设备版本高于5.0,android_server
可能无法正常运行,请更换成其他低版本,或者按照这里的方法来解决。在动态调试页面你可以设置断点并使用F7/F8
进行单步调试。
编写IDC/IDAPython脚本。在动态调试的时候,有时你需要记录一些寄存器的数值并与你自己的预测值或程序输出值进行比对,当数据量很大时难以手工完成这些工作。此时你可以借助IDC/IDAPython
脚本来使不可能变得可能,以下是一个设置断点的IDC脚本示例:
#include<idc.idc>
static breakpoint_point_bank(){
auto file,fname;
fname="/home/wz/x2";
file=fopen(fname,"a+");
fputc(R0%256,file);
fputc(R0/256%256,file);
fputc(R0/256/256%256,file);
fputc(R0/256/256/256%256,file);
return 1==0;
}
static main(){
auto addr;
addr=0x981fe8de;
AddBpt(addr);
SetBptCnd(addr,"breakpoint_point_bank()");
}
这个脚本在0x981fe8de处设置一个断点,每当程序执行到该位置时,就会将R0寄存器的内容输出到文件当中,返回false表示不中断程序的执行,如果返回true,程序就会在断点处停下来。关于IDC/IDAPython
脚本以及IDA Pro的其他使用方法,建议阅读《The IDA Pro Book》。
适应IDA Pro的输出。IDA Pro将二进制程序转换成可以理解的伪代码,为我们提供了巨大的帮助,但IDA Pro的“代码风格”有时是很糟糕的,比如你可能会看到类似这样的伪代码块:
if ( (v4 = 21, (1 << v5) & 0x57EC34) || (v4 = 15, (1 << v5) & 0x80) )
{
--v3;
}
else
{
v4 = 6;
--v3;
}
注意不要忽略了条件里的逗号语句,v4=21
和v4=15
虽然对表达式的结果没有影响,却实实在在的改变了变量v4
的值,不要认为看上去不合理的语句是无用的,又比如这样的代码:
if ( (unsigned int)(v18 - 49) <= 1511 )
注意v18-49
的值被转换成了unsigned int
类型,所以当v18
小于49时,根据补码的特点,得到的最终结果会是一个很大的正数,条件表达式为假。也就是说,这里更容易理解的等价代码是:
if ( 49 <= v18 && v18 <= 1600 )
最让我头疼的是循环语句,IDA Pro总是倾向于将循环语句翻译成while
加goto
的形式,即使嵌套层数不深也难以理清各个循环分支之间的关系,克服这一点就需要耐心和时间了。所以逆向工程做到最后,我认为仍然是一种体力活。
最后,这里给出一些参考资料,在学习逆向和IDA Pro的过程中,这些资料给了我极大的帮助,希望对你也有用: