IDA实例教程详解.docx
《IDA实例教程详解.docx》由会员分享,可在线阅读,更多相关《IDA实例教程详解.docx(17页珍藏版)》请在冰豆网上搜索。
IDA实例教程详解
IDA实例教程详解
作者:
笨笨雄(转载)
1软件环境
静态分析有很多好处,例如加壳的程序(尽管对于高手来说这并不会耗费太多时间),我们不需要寻找OEP,也不需要解除自校验,只要修复IAT,DUMP下来就可以动手分析了。
假如你需要修改程序,可以使用内存补丁技术。
动态与静态,调试器与反汇编器结合可以简化分析任务,帮助我们理解代码。
因此掌握一种反汇编器是非常必要的。
IDA可以说是这方面的首选工具,它为我们提供了丰富的功能,以帮助我们进行逆向分析。
这从IDA复杂的工作界面便可以知道。
种类繁多的工具栏
在分辨率不高的情况,这些工具栏与反汇编窗口挤在小屏幕里,看起来不爽。
我一般把它关闭(查看=>工具栏=>主工具栏)以获得更好的视觉效果。
当我们需要这些功能的时候,直接使用快捷键就可以了。
下面是常用快捷键的清单:
快捷键
功能
注释
C
转换为代码
一般在IDA无法识别代码时使用这两个功能整理代码
D
转换为数据
A
转换为字符
N
为标签重命名
方便记忆,避免重复分析。
;
添加注释
R
把立即值转换为字符
便于分析立即值
H
把立即值转换为10进制
Q
把立即值转换为16进制
B
把立即值转换为2进制
G
跳转到指定地址
X
交叉参考
便于查找API或变量的引用
SHIFT+/
计算器
ALT+ENTER
新建窗口并跳转到选中地址
这四个功能都是方便在不同函数之间分析(尤其是多层次的调用)。
具体使用看个人喜好
ALT+F3
关闭当前分析窗口
ESC
返回前一个保存位置
CTRL+ENTER
返回后一个保存位置
在工具栏下面的便是工作窗口。
主要的窗口分页有“IDAView-A”、“Name”、“Strings”、“Exports”和“Imports”。
对于后面3项相信大家都不会陌生了,它们分别是字符参考,输出函数参考和输入函数参考。
Name是命名窗口,在那里可以看到我们命名的函数或者变量。
这四个窗口都支持索引功能,可以通过双击来快速切换到分析窗口中的相关内容,使用起来十分方便。
简单输入几个字符即可定位目标
IDAView-A是分析窗口,支持两种显示模式,除了常见的反汇编模式之后,还提供图形视图以及其他有趣的功能。
IDA的反汇编窗口
一般我们在分析的时候,并不关心程序的机械码,所以IDA为我们自动隐藏了这些信息。
如果你有需要,可以通过以下步骤来设置:
选项=>常规=>反汇编=>显示反汇编行部分=>机械码字节数=>修改为你允许显示的大小
现在让我们以论坛脱壳版块置顶帖的那个经典为例,看看图形视图的表现。
首先我们到以下连接下载:
你能通过图形视图及其缩略图快速找到壳的出口吗
如图所示,标签40EA0E便是壳的出口代码的地址。
在OD中直接跳到该地址,下断点,然后运行到该处,再单步便能看到OEP了。
假如希望通过跳转法找OEP,相信图形视图比你在OD一个一个跳转跟随,要快得多。
再来看看这个壳的另类脱法。
直接运行该程序,DUMP下来,再使用IMPORTREC的IATAutoSearch功能修复输入表。
用IDA打开修复了输入表的DUMP文件。
在IMPORT窗口随便选一个API,随便通过交叉参考跳转到一个函数的代码。
此处为文件输入表的位置
我选了RegQueryValueExA,通过交叉参考,来到Sub_402488处的函数代码。
用鼠标拖动缩略图中的虚线框到上方,便能看到该CALL的头部了。
然后按下图指示操作:
在函数标记上点击鼠标右键
处于最上层的函数,便是OEP了,使用PE工具修改文件入口为10CC。
现在函数可以正常工作了。
这个方法的原理是通常我们写程序都有如下流程:
Mainproc
","打开IAT符号文件");
CustEa=AskAddr(0,"目标IAT地址");
filehandle=fopen(fileName,"r");
for(ea=CustEa;zcount<2;ea=ea+4){
if(Dword(ea)!
=0){
Sbuffer=readstr(filehandle);
if(strlen(Sbuffer)<2){
00404C2C00404C2C00404C00404C404C6A00404C00404C00404C00404C00404C00404C00404C00404C00404C4A00404C00404C4F00404C4F00404C00404C00404C404C
4560F
0F
0F
0A0C8F5A5A83C
0A0C8F5A5A83C0a0a
0045639F004563A004563A004563A004563A004563C004563C004563C4563C4563C004563C004563F004563F4563F004563F004563F004563F004563F4563F0045640A0045640A45640A0045640Aseg005:
0045640A;Resendthelasttransmission
seg005:
0045640A
seg005:
0045640D;seg005:
0045640D
修复之后的代码
除了“subeax,[esp-8+arg_4]”(实际上是subeax,[esp])看起来有点怪之后,一切正常。
作为一个壳,在解决了花指令之后,剩下的问题便只有反调试代码和解密(解压缩)代码了。
例如上面列出的代码是通过时间校验检查调试器,一旦检查到,便使用特权级指令,让程序发生异常,无法继续运行下去。
当然,我们在静态的环境下,反调试技巧对于我们来说,毫无意义。
尽管如此,我们仍然需要知道程序会在什么时候运行到什么地方,最常见的利用系统的机制莫过于SEH了,现在来看看下面代码:
seg005:
00456A9Bcall$+5
seg005:
00456AA0adddwordptr[esp+0],136Fh
seg005:
00456AA7pushlargedwordptrfs:
0
seg005:
00456AAEmovlargefs:
0,esp
设置SEH的代码
“call$+5”指令后堆栈里的内容便是它的下一条指令在内存中的地址。
这是病毒常用的重定位技巧。
shift+/输入0x00456AA0+0x136F便能计算出异常处理函数的地址(457E0F)了。
seg005:
0045745Cxoreax,eax
seg005:
0045745Emovzxeax,byteptr[eax]
产生异常的代码
现在我们应该跳到457E0F继续分析。
我想你已经了解如何在静态环境下跟踪程序的流程,现在就让我们跟着程序的流程把解密相关的代码找出来。
seg005:
00459191pushecx
seg005:
00459192xorecx,ecx
seg005:
00459194call$+5
seg005:
00459199popedi
seg005:
0045919Aaddedi,9C4h
seg005:
004591A0popedx
seg005:
004591A1addedx,15h
seg005:
004591A4loc_4591A4:
;CODEXREF:
sub_459149+6Bj
seg005:
004591A4movzxeax,byteptr[ecx+edi]
seg005:
004591A8xoreax,edx
seg005:
004591AAmov[ecx+edi],al
seg005:
004591ADincecx
seg005:
004591AEcmpecx,93h
seg005:
004591B4jbshortloc_4591A4
解密代码
容易看出这就是解密代码,在循环之中,且有修改内存的指令。
至于解密的KEY,其实就是00459191处ECX的值+15h。
我希望你还记得到达这里之前曾经看过下面代码:
seg005:
004587B6moveax,[esp+0Ch]
seg005:
004587BAxorecx,ecx
seg005:
004587BCxorecx,[eax+4]
seg005:
004587BFxorecx,[eax+8]
seg005:
004587C2xorecx,[eax+0Ch]
seg005:
004587C5xorecx,[eax+10h]
这一段是检查硬件断点的代码,假如没有设置硬件断点,那么ECX的结果应该是0。
假如你不能理解为什么,我建议你看看SEH以及关于反硬件断点的一些文章。
在知道解密代码的所有关键要素之后,就可以开始动手写脚本了。
#include""
staticmain(){
autoStartAddr,cKey,Cbuffer,Counter;
StartAddr=0x00459199+0x9c4;
cKey=0x15;
for(Counter=0;Counter<0x93;Counter++){
Cbuffer=Byte(StartAddr)^cKey;
00461F00461F00461F
0046200F0046204C
0046206A0046206F0046207A
0046206A
00461F9C
0046206F
lse语句。
if(CF==1){
CF=0;
while(ECX!
=0){
PatchByte(DeCodeAddr,Byte(EDX));
EDX++;
DeCodeAddr++;
ECX--;
}
}
else{
while(Counter!
=1){
PatchDword(DeCodeAddr,Dword(EDX));
EDX=EDX+4;
DeCodeAddr=DeCodeAddr+4;
if(ECX<=4){
ECX=ECX-4;
break;
}
ECX=ECX-4;
}
DeCodeAddr=DeCodeAddr+ECX;
}
//反汇编代码的循环入口(4528DE)与我们转换的循环入口不同(4528E9)
//跟开始的时候一样,入口之前的代码放到循环外面。
IsNotZero=EBX&0x7FFFFFFF;
if(IsNotZero==0){
CF=1;
EBX=Dword(MyAddr);
MyAddr=MyAddr+4;
}
HigtBitflat=EBX&0x;
EBX=EBX+EBX;
EBX=EBX+CF;
CF=0;
}
}
至此,我们成功将004528D0到004529A1处的代码转换成C代码。
在完成如此复杂的代码还原之后,004529A6到004529D8处的反汇编代码只是小菜一碟。
里面的代码也很好理解,将符合E801和E901的机械码解密。
位移指令可以通过借用程序中的一个闲置的Dword,使用IDC提供的Pactch系列指令来模拟,详见。
在完成最后的解密代码后,便是IAT的修复了。
现在看看下面代码:
004529DAleaedi,[esi+50000h]
004529E0loc_4529E0:
004529E0moveax,[edi]
004529E2oreax,eax
004529E4jzshortloc_452A22
004529E4
004529E6movebx,[edi+4]
004529E9leaeax,[eax+esi+549B0h]
004529F0addebx,esi
004529F2pusheax
004529F3addedi,8
004529F6calldwordptr[esi+54A3Ch]
004529FCxchgeax,ebp
004529FDloc_4529FD:
004529FDmoval,[edi]
004529FFincedi
00452A00oral,al
00452A02jzshortloc_4529E0
00452A02
00452A04movecx,edi
00452A06pushedi
00452A07deceax
00452A08repnescasb
00452A0Apushebp
00452A0Bcalldwordptr[esi+54A40h]
00452A11oreax,eax
00452A13jzshortloc_452A1C
00452A13
00452A15mov[ebx],eax
00452A17addebx,4
00452A1Ajmpshortloc_4529FD
在分析该处代码之前,显然应该先把ESI的值计算出来。
鼠标点击ESI,以高亮显示该寄存器,向上滚动反汇编窗口,发现从004529A6popesi处开始,ESI便没有被修改过,而该处对应于:
seg005:
0046206Fmovesi,offsetunk_447000
seg005:
00462074leaedi,[esi-46000h]
seg005:
0046207Apushedi
可见ESI=0x401000,容易计算出004529F6和00452A0B处CALL的地址分别为455A3Ch和455A40h。
跳转到该地址:
显然,这里便是壳填充IAT的地方了。
那么004529DAleaedi,[esi+50000h]中,EDI便是保存API名字的数据表。
做脱壳机的任务就留给读者作课后练习,正如前面介绍的那样,只需要API的名字为相关IAT地址重命名,便能分析了。
也就是说00452A0B处,调用GetProcAddress,跟踪它的参数lpProcName(00452A06pushedi),以及它的返回值(00452A15mov[ebx],eax),当然这里的跟踪,可以象刚才那样手动确认,也可以通过与调试器配合快速得出结果。
不难得出下面脚本:
#include""
staticmain(){
autoESI,EDI,EAX,EBX,Counter,cBuffer,BufLen,straa;
ESI=0x447000-0x46000;
EDI=ESI+0x50000;
Counter=MaxEA()-MinEA();
MakeUnknown(MinEA(),Counter,1);//将整个程序标记未分析
AnalyzeArea(MinEA(),MaxEA());//分析整个程序
Counter=0;
while(Counter!
=1){
EAX=Dword(EDI);
if(EAX==0)break;
EBX=Dword(EDI+4);
EBX=EBX+ESI;
EDI=EDI+8;
while(Counter!
=1){
EAX=Byte(EDI);
EDI++;
if(EAX==0)break;
cBuffer=GetString(EDI,-1,ASCSTR_C);
straa=cBuffer+"_";//IDA不允许重复命名,加上“_”避免重复
MakeNameEx(EBX,straa,SN_AUTO);
EBX=EBX+4;
EDI=EDI+strlen(cBuffer);
EDI++;
}
}
}
注意解密后,必须将整个程序标记为未分析,并重新分析,然后才能进行重命名。
程序的OEP
到此,静态脱壳完毕。
从这个例子也可以知道,对于掌握反汇编器的人来说,除非反调试机制与解密KEY关联,否则根本就没有强度可言。
然而,IDA博大精深,还有更多强大的功能,本文也只是抛砖引玉而已。
下面给出几个链接,方便大家更进一步学习:
IDA的官方网站:
看雪论坛9月翻译专题:
&threadid=31023
IDAPro的插件开发SDK:
&threadid=31441
IDA逆向工程入门:
&threadid=40765
IDA简易教程: