IDA实例教程

更新时间:2023-11-26 16:40:01 阅读量: 教育文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

IDA实例教程

作者:笨笨雄

邮箱:nemo314@gmail.com

1 软件环境

静态分析有很多好处,例如加壳的程序(尽管对于高手来说这并不会耗费太多时间),我们不需要寻找OEP,也不需要解除自校验,只要修复IAT,DUMP下来就可以动手分析了。假如你需要修改程序,可以使用内存补丁技术。动态与静态,调试器与反汇编器结合可以简化分析任务,帮助我们理解代码。因此掌握一种反汇编器是非常必要的。IDA可以说是这方面的首选工具,它为我们提供了丰富的功能,以帮助我们进行逆向分析。这从IDA复杂的工作界面便可以知道。

种类繁多的工具栏

在分辨率不高的情况,这些工具栏与反汇编窗口挤在小屏幕里,看起来不爽。我一般把它关闭(查看=>工具栏=>主工具栏)以获得更好的视觉效果。当我们需要这些功能的时候,直接使用快捷键就可以了。下面是常用快捷键的清单:

快捷键 C D A N ; R H Q B G X SHIFT+/ ALT+ENTER ALT+F3 ESC

功能 转换为代码 转换为数据 转换为字符 为标签重命名 添加注释 把立即值转换为字符 把立即值转换为10进制 把立即值转换为16进制 把立即值转换为2进制 跳转到指定地址 交叉参考 计算器 便于查找API或变量的引用 便于分析立即值 方便记忆,避免重复分析。 注释 一般在IDA无法识别代码时使用这两个功能整理代码 新建窗口并跳转到选中地址 这四个功能都是方便在不同函数之间分析(尤其是多层次的调关闭当前分析窗口 用)。具体使用看个人喜好 返回前一个保存位置 CTRL+ENTER 返回后一个保存位置 在工具栏下面的便是工作窗口。主要的窗口分页有“IDA View-A”、“Name”、“Strings”、“Exports”和“Imports”。对于后面3项相信大家都不会陌生了,它们分别是字符参考,输出函数参考和输入函数参考。Name是命名窗口,在那里可以看到我们命名的函数或者变量。这四个窗口都支持索引功能,可以通过双击来快速切换到分析窗口中的相关内容,使用起来十分方便。

简单输入几个字符即可定位目标

IDA View-A是分析窗口,支持两种显示模式,除了常见的反汇编模式之后,还提供图形视图以及其他有趣的功能。

IDA的反汇编窗口

一般我们在分析的时候,并不关心程序的机械码,所以IDA为我们自动隐藏了这些信息。如果你有需要,可以通过以下步骤来设置:

选项=>常规=>反汇编=>显示反汇编行部分=>机械码字节数=>修改为你允许显示的大小

现在让我们以论坛脱壳版块置顶帖的那个经典为例,看看图形视图的表现。首先我们到以下连接下载:http://bbs.pediy.com/upload/bbs/unpackfaq/notepad.upx.rar

你能通过图形视图及其缩略图快速找到壳的出口吗?

如图所示,标签40EA0E便是壳的出口代码的地址。在OD中直接跳到该地址,下断点,然后运行到该处,再单步便能看到OEP了。假如希望通过跳转法找OEP,相信图形视图比你在OD一个一个跳转跟随,要快得多。 再来看看这个壳的另类脱法。直接运行该程序, DUMP下来,再使用IMPORTREC的IAT AutoSearch功能修复输入表。用IDA打开修复了输入表的DUMP文件。在IMPORT窗口随便选一个API,随便通过交叉参考跳转到一个函数的代码。

此处为文件输入表的位置

我选了RegQueryValueExA,通过交叉参考,来到Sub_402488处的函数代码。

用鼠标拖动缩略图中的虚线框到上方,便能看到该CALL的头部了。然后按下图指示操作:

在函数标记上点击鼠标右键

处于最上层的函数,便是OEP了,使用PE工具修改文件入口为10CC。现在函数可以正常工作了。这个方法的原理是通常我们写程序都有如下流程:

Main proc //代码 CALL FUN1 //代码 CALL FUN2 //代码 END proc 所以处于函数调用最上层的便是MAIN函数了。当然这个方法局限性很大,这里只是对该功能的一种介绍。我们留意到图表功能有两个选项,在上面的例子中,我们使用的是“交叉参考到”。我想细心的朋友大概能通过“交叉参考来自”左边的小图标猜出它的用途了。该功能可以显示目标函数调用了什么函数,当然也包括API。这样除了观察函数的输入参数来判断是否关键CALL之外,又多了一个参考途径。

2 强大的IDC

有时我们需要分析一些非文件格式的代码,例如ShellCode,远线程注入和病毒。这些代码的特点便是动态获取API,这给静态分析带来困难。尽管IDA支持分析2进制文件,但是缺少IAT的情况下,分析起来跟不方便。频繁的切换调试器查看并不是一个好方法。IDC是IDA的脚本语言,它功能强大,为我们提供了另一条与调试器交互的途径。

如何使调试器获得IDA分析得出的符号? IDA提供多种文件格式输出,调试器可以通过解释这些文件获得一些符号。你可以通过文件菜单中的“创建文件”获得更多的信息。 以OD为例,它的GODUP插件支持解释MAP文件(还能加载IDA的SIG)。在IDA中使用如下步骤: 菜单: 文件=>创建文件=>创建MAP文件 即可创建MAP文件,然后切换到OD,使用如下步骤便能获得符号了: 菜单: 插件=>GODUP Plugin=>Map Loader=>Load labels 仍然以那个经典的UPX加壳的NOTEPAD为例子,这次我们用OD打开,在到达OEP之后DUMP下来,不修复输入表,直接用IDA载入后看到下图:

丰富的文件载入选项

需要注意的是Make imports segment是PE文件特有的选项,该选项会隐藏输入表区域的所有数据,同时你获得的好处便是能在图表功能中看到API的调用。假如你希望查看在输入表的范围内的代码或者数据,你需要使用从菜单中选择“编辑”=>“区段”以删除遮挡数据的部分区段。

为了更真实的模拟从内存中截取代码的情况,在这里选择Binary file,载入偏移量选400000(根据实际代码在内存中的基址来选择),然后IDA就开始尝试分析可能存在于该文件中的代码了。对照OD中的OEP地址,在IDA中可以看到以下代码:

seg000:004010CC push ebp seg000:004010CD mov ebp, esp seg000:004010CF sub esp, 44h seg000:004010D2 push esi seg000:004010D3 call ds:dword_4063E4 seg000:004010D9 mov esi, eax seg000:004010DB mov al, [eax] seg000:004010DD cmp al, 22h seg000:004010DF jnz short loc_4010FC OEP处的部分代码

OD中对应的显示:

004010D3 FF15 E4634000 call dword ptr [4063E4] ; kernel32.GetCommandLineA

使用以下ollyscript (附件中的ollyGetSym.txt)提取IAT的符号:

var ea

var Ecount var oFile

ask \请输入IAT起始地址\cmp $RESULT, 0 je ECancel

mov ea, $RESULT ask \输出文件?\cmp $RESULT, 0 je ECancel

mov oFile, $RESULT

TryGetSym: GN [ea] cmp $RESULT,00000000 je ETest

WRTA oFile,$RESULT_2

//0分隔号的记数器

//获取该地址的符号

//OLLYSCRIPT是区分00000000和0的

mov Ecount,0 add ea,4 jmp TryGetSym ECancel: msg \无效输入\ret ETest: cmp Ecount,1 je Send add Ecount,1 add ea,4 jmp TryGetSym SEnd: Ret

//不同模块的地址以0分隔 //若存在两个DWORD的0则认为是末尾 使用下面IDC脚本获取符号并对相应地址重命名:

#include

static main() {

auto Sbuffer,ea,zcount,filehandle,fileName,CustEa; fileName = AskFile (0,\打开IAT符号文件\CustEa = AskAddr(0,\目标IAT地址\filehandle = fopen(fileName,\

for (ea = CustEa; zcount < 2; ea = ea + 4){ if (Dword(ea) !=0){ Sbuffer = readstr(filehandle); if(strlen(Sbuffer) < 2){ //ollyscript的输出文件存在无效字符 Sbuffer = readstr(filehandle); //如果字符无效则再取一次字符 } MakeNameEx (ea,Sbuffer,SN_AUTO ); //为对应DWORD改名 zcount = 0; } else{ zcount = zcount + 1; } } fclose(filehandle) ; }

GetSym.idc

正如ollyscript接近于ASM,IDC的函数及其语法也近似于C语言(详见IDA的帮助),在编写几个脚本之后,便能轻松掌握它的用法。

seg000:004010CC push ebp seg000:004010CD mov ebp, esp seg000:004010CF sub esp, 44h seg000:004010D2 push esi seg000:004010D3 call ds:GetCommandLineA_ seg000:004010D9 mov esi, eax seg000:004010DB mov al, [eax] seg000:004010DD cmp al, 22h seg000:004010DF jnz short loc_4010FC 现在可以正常显示函数调用的API了

下面来看看另外一个例子中IDC的表现。附件中的Exvirus.v是一个木马程序。当然这

里并不是要分析这个木马,更不会运行它,在静态分析的环境下,很安全。

几乎都是乱码的窗口

加密了的字符,总要在使用之前解密。也就是说可以通过加密字符的交叉引用定位解密代码。

lea edx, [ebp+var_4] mov eax, offset s_XsXQqSxUsSq ; \抿遽翦燥镬桢祓巢宇狃箬雉\call sub_404BEE 通过交叉引用定位的函数

由字符参考中的“SOFTWARE\\Borland\\Delphi\\RTL”可以判断该木马是用Delphi编写的(也可从函数的参数传递约定判断)。在详细分析之前,先在菜单中进行如下步骤的操作:

文件=>加载文件=>加载FLIRT签名文件=>Delphi7 RTL/VCL/CLX

现在IDA将会根据Delphi的函数特征识别出一些库函数,这样可以减少很多工作量。 CODE:00404C2C mov [ebp+var_8],1 //已处理字符记数器 CODE:00404C2C CODE:00404C33

CODE:00404C33 loc_404C33: ; CODE XREF: sub_404BEE+6A??j CODE:00404C33 mov eax, [ebp+var_4] CODE:00404C36 mov edx, [ebp+var_8] CODE:00404C39 mov bl, [eax+edx-1] //单字节取字符解密 CODE:00404C3D add bl, 80h

CODE:00404C40 lea eax, [ebp+var_C] CODE:00404C43 mov edx, ebx

CODE:00404C45 call @System@@LStrFromChar$qqrr17System@AnsiStringc CODE:00404C45

CODE:00404C4A mov edx, [ebp+var_C] CODE:00404C4D mov eax, edi

CODE:00404C4F call @System@@LStrCat$qqrv CODE:00404C4F

CODE:00404C54 inc [ebp+var_8]

CODE:00404C57 dec esi //字符长度=0跳出循环,解密完毕 CODE:00404C58 jnz short loc_404C33

函数较长,这里只列出关键代码。判断这部分为关键代码主要是因为整个函数就只有该处是循环。解密是对一定长度的数据进行运算,因此会有一个循环对字符中的数据逐一解密。然后从输入参数与寄存器或者堆栈的关联便可以理解函数的关键部分是如何工作的。由于IDA已经为我们识别出Delphi的库函数,所以这里很容易便知道解密的方便是对目标字符的每个字节都加上80h。下面来看看我如何使用IDC来完成解密字符的工作。

#include \

static main() {

auto ea,x,y,z,zbyte,SRange,TStrLen,DeCodeBuffer,DeCodeCounter,NotTarget;

x = 0x404bee;

for ( y=RfirstB(x); y != BADADDR; y=RnextB(x,y) ){ //通过交叉参考取得函数调用地址 for (SRange = 4; SRange < 0x50; SRange++){ z = y - SRange; zbyte = Byte(z); if (zbyte == 0xb8){ //mov eax,mem32的机械码是b8 zbyte = Dword(z + 1); ea = Dword(zbyte); if (ea != 0xFFFFFFFF){ //判断mem32是否有效,防止识别错指令 if (Byte(zbyte - 1) == 1){ //在字符指针前一个字节写入处理标记 break; //避免重复处理 } PatchByte (zbyte - 1,1); TStrLen = 0; while (TStrLen < 0x30){ //解密的循环 DeCodeCounter = zbyte + TStrLen; DeCodeBuffer = Byte(DeCodeCounter) + 0x80; if (DeCodeBuffer == 0x80) break; PatchByte (DeCodeCounter,DeCodeBuffer); TStrLen++; } MakeUnknown (zbyte,TStrLen,0); //取消IDA原来的分析结果 MakeStr (zbyte, DeCodeCounter); //把该位置标记为字符 break; } } }

} }

Decode.idc

既然可以通过加密字符定位目标函数,那么也可以通过加密函数定位加密字符。通过使用解密函数的交叉引用,往上搜索,解密第一条mov eax,mem32中的字符。当然这里个脚本写得有点简陋,并不能完全解决程序中的加密字符。这个就任务就留给读者来挑战吧。这里要注意的是我在编写IDC的过程中遇到很多BUG,这是因为IDA区分大小写(调试了很久才知道)。此外要转换数据类型得先把原来的分析结果取消才可以。最后要看到下图的窗口,在运行脚本后,你需要重新打开字符参考窗口(不会自动刷新)。

解密后的字符参考窗口

3 静态脱壳

上一节我们用IDC完成了字符解密的工作,既然脱壳的过程实际就是对源程序的解密,现在让我们来尝试在不运行壳的情况下把壳解决掉。首先到下面连接下载一个壳:

http://www.pediy.com/tools/PACK/Protectors/MSLRH/MSLRHv0.31a.rar

主页对这个壳的介绍是可以作为Unpackme练练手,现在就以该壳的主程序作为例子讲解如何静态脱壳。首先用IDA加载该壳的主程序。

seg005:004560FA loc_4560FA: ; CODE XREF: start:loc_4560F4??j seg005:004560FA call sub_456109 seg005:004560FA

seg005:004560FA start endp //入口函数的结尾 seg005:004560FA seg005:004560FF seg005:004560FF seg005:004560FF

seg005:004560FF sub_4560FF proc near ; CODE XREF: seg005:00456104??p seg005:004560FF ; sub_456109??p //红色 seg005:004560FF call sub_456DEF seg005:004560FF

seg005:004560FF sub_4560FF endp seg005:004560FF

seg005:00456104 call sub_4560FF seg005:00456104 seg005:00456109 seg005:00456109 seg005:00456109

seg005:00456109 sub_456109 proc near ; CODE XREF: start:loc_4560FA??p seg005:00456109 call near ptr sub_4560FF+1 //+1表示反汇编出现混乱

正常的交叉参考标记是绿色,当显示为红色时则证明与其他部分的反汇编代码产生冲突。另外在jcc,jmp和call后面出现“+X”的符号(X为任意数字),一般也为反汇编出现混乱。在正式分析之前,我们必须找到花指令的规律,编写脚本,除去它的影响。现在我们从最初产生影响的地方开始。点击地址4560FF,按D

seg005:004560FF byte_4560FF db 0E8h; CODE XREF: seg005:00456p seg005:00456100 unk_456100 db 0EBh ; ? ; CODE XREF: sub_456109??p seg005:00456101 db 0Ch seg005:00456102 db 0 seg005:00456103 db 0 seg005:00456104 call near ptr byte_4560FF 注意00456104处也是花指令之一,它的作用就是让IDA误以为004560FF处为有效指令。因此也在该位置上按D,将其转换为数据。而在00456100处按C转换为代码。

seg005:004560FA call sub_456109 seg005:004560FA

seg005:004560FA start endp seg005:004560FA

seg005:004560FA ; --------------------------------------------------------------------------- seg005:004560FF db 0E8h

seg005:00456100 ; --------------------------------------------------------------------------- seg005:00456100

seg005:00456100 loc_456100: ; CODE XREF: sub_456109??p seg005:00456100 jmp short loc_45610E seg005:00456100

seg005:00456100 ; --------------------------------------------------------------------------- seg005:00456102 db 0 seg005:00456103 db 0 seg005:00456104 db 0E8h seg005:00456105 db 0F6h ; ? seg005:00456106 db 0FFh seg005:00456107 db 0FFh seg005:00456108 db 0FFh seg005:00456109 seg005:00456109

seg005:00456109 sub_456109 proc near ; CODE XREF: start:loc_4560FA??p seg005:00456109 call loc_456100 seg005:00456109 seg005:0045610E

seg005:0045610E loc_45610E: ; CODE XREF: seg005:loc_456100??j seg005:0045610E add esp, 8

现在我们手动修正了一处被花掉的代码。我们知道OPCODE的E8和EB后面的实际是一个相对地址偏移,而不是地址编码(反汇编翻译成地址是便于分析)。因此可能你已经想到通过搜索内存中的相应指令序列,然后告诉IDA什么是代码,什么则不是。读者可以先试试自己找出壳中花指令的规律,然后对比一下结果。

经过手动整理之后,发现壳使用了下面4种花指令代码:

call label1 db 0E8h label2: jmp label3 db 0 db 0 db 0E8h db 0F6h ; db 0FFh db 0FFh db 0FFh label1: call label2 label3: add esp, 8 花指令1

Jz label1 Jnz label1 db 0EBh db 2 label1: jmp label2 db 81h label2: 花指令2

push eax call label1 db 29h db 5Ah label1: pop eax imul eax, 3 Call label2 db 29h db 5Ah label2: add esp, 4 pop eax 花指令3

Jmp label1 db 68h Label1: Jmp label2 db 0CDh, 20h Label2: Jmp label3 db 0E8h Label3: 花指令4

在知道花指令结构之后,容易写出下面脚本用NOP(0x90h)来代替干扰的反汇编器的数据:

static PatchJunkCode() {

auto x,FBin,ProcRange;

FBin = \// 花指令1的特征码

for (x = FindBinary(MinEA(),0x03,FBin);x != BADADDR;x FindBinary(x,0x03,FBin)){

x = x +5;

PatchByte (x,0x90); x = x + 3 ;

PatchByte (x,0x90); x++;

PatchWord (x,0x9090); x =x +2 ;

PatchDword (x,0x90909090); }

FBin = \// 花指令2的特征码

for (x = FindBinary(MinEA(),0x03,FBin);x != BADADDR;x FindBinary(x,0x03,FBin)){

x = x + 4;

PatchWord (x,0x9090); x = x + 4;

PatchByte (x,0x90); }

=

=

本文来源:https://www.bwwdw.com/article/e4lt.html

Top