IDA的逆向分析水平就是一个逆向工程师的水平;
7.1 IDA与OD的联合使用
一般使用OD下断点,找到关键代码位置,然后使用IDA静态看流程。
需要注意的是IDA需要与OD基地址对齐,这样才方便在IDA中查找代码位置,具体方式如下:
Edit ---> Segment ----- Rebase Program : 设置基地址;
在WINdbg中基地址在“符号”中查看;在OD中基地址在Model中查看;
IDA中G + 地址,即可找对对应位置;
7.2 数据不小心转换成为函数,如何处理?
如果不小心使用P将数据识别成了函数,可以使用D,转换成数据,再用C转换成为代码;
毕竟IDA不存在撤销功能;
7.3 IDA如何创建结构体?
7.3.1 结构体的创建
IDA中结构体成员一般出现在:类初始化的地方(XX.dll);
根据成员的多少,我们创建结构体的大小,注意虚表vt占4个字节;一般有三个地方有助于我们创建结构体:初始化函数成员列表、memcpy函数的使用、数据库字段;
在初始化函数中看见最后一个成员为V1[239],可以 使用“Reset Point Type” 可以改为:v1-> 1036,这样就能看到成员在的偏移,就可以设置结构体大小为1036 + 4.。
创建了结构体后,structure窗口中可以N改写字段的名称;
7.3.2 结构体的使用
创建好结构体后,我们就可以使用Y来修改分析的数据类型;
7.3.3 结构体的导入和导出使用
有时我们需要在一个数据库中创建结构体,在另外的一个数据库中使用,具体方法:
在数据库A的localtype视图中选中需要导出的数据类型,右键 --- “export to header”,如果是多个结构体写到同一个文件,请选择追加方式(Append),导出到一个.h文件;
接下来是导入到另外一个数据库,具体方法:
Ctrl + F9 :”选择一个C文件头去解析”,选择需要导入的一个文件,这样我们就会可以在LocalType中查看我们自建的结构体;
7.3.4 创建结构体 例子
一般方法构建结构体(方法1):在structure窗口,Insert 一个结构,自己命名(如struct_4),在end处使用D添加结构体数据,在成员处D 切换占有的字节数(db、dw、dd),在成员处使用Y来改变识别类型(int、float、double、LPVOID、BYTE field_60[1024]、)
使用: 在相应的rdata处,使用Y改变当前位置的识别类型为struct_4;
例如:
创建结构体数组:
00000000 struc4 struc ; (sizeof=0x28, mappedto_194)
00000000 ; XREF: .data:stru_A47A220/r
00000000 field_0 dw ?
00000002 field_2 db ?
00000003 field_3 db ?
00000004 field_4 dd ?
00000008 field_8 dd ?
0000000C field_C db ?
0000000D field_D db ?
0000000E field_E db ?
0000000F field_F db ?
00000010 field_10 dd ?
00000014 field_14 dd ?
00000018 field_18 dd ?
0000001C field_1C dd ?
00000020 field_20 dd ?
00000024 field_24 dd ?
00000028 struc4 ends
例子: struc4 stru_A47A220[77]
数据区的 stru_A47A220 struc4 <0FC39h, 0, 0, 100h, 0, 0, 0, 0, 0, 1.0, 1.0, 0, 0, 1, 1>....
7.4 IDA如何创建虚表
虚表的创建方式与结构体很类似,关键是要找到虚表的位置,需要使用 OD来进行跟踪,虚表的地址一般存在于EAX中,对应找过去就能知道是那个库里面的函数集合;
虚表的获取: 都是先得到this 指针,然后得到虚表指针,再成员函数调用需要传递this指针到ecx,最后间接调用,具体:
mov ecx, dword ptr[ebp - 14h] ; ecx 得到this指针
mov edx, dword ptr [ecx] ; 得到虚表地址,this指针指向的第一个4字节就是虚表地址; edx 就是虚表地址;
mov esi,esp
mov ecx, dword ptr [ebp -14h] ;成员函数调用,需要ecx保存this指针;
call dword ptr [edx + 4] ;间接调用 虚函数;
可以直接写一个虚函数的调用例子,使用IDA来分析;
7.4.1 创建虚表 例子
建立虚表:
00000000 functionVT struc ; (sizeof=0x34, align=0x4, copyof_192)
00000000 sub_10001110 dd ?
00000004 sub_10002210 dd ?
00000008 sub_100010D0 dd ?
0000000C sub_10001000 dd ?
00000010 sub_10001090 dd ?....
functionVT ends
上面这个函数的虚表来自于rdata 中的虚函数列表:
.rdata:0A46F570 ??_7CJ2190_PID_Decoder@@6B@ dd offset sub_A411110
.rdata:0A46F570 ; DATA XREF: sub_A452B00+C
.rdata:0A46F574 dd offset f_baseFuncton_sub_E282210 ;
.rdata:0A46F578 dd offset sub_A4110D0
.rdata:0A46F57C dd offset sub_A411000
.rdata:0A46F580 dd offset sub_A411090
…..
建立类:
00000000 basestructure struc ; (sizeof=0x51C, align=0x4, mappedto_193)
00000000 VT dd ? ; offset
00000004 field_4 dd ?
00000008 field_8 dd ?
basestructure…ends
将:VT 识别为:functionVT* 就可以得到basestructure的虚表;
例如:
result =(*(unsigned __int8 *)(*(_DWORD *)a1 + 0x20))(a1);
将a1定义为 basestructure * 类型,就能识别成为:
result = a1->VT->sub_1000CAD0(a1);
优点:看到调用的函数,方便静态分析;
7.4.2 快捷添加虚表结构体的快捷的方式(方法二)
我们知道编译后虚表存在数据区,找到虚表头就可以看到当前类中的所有虚函数列表;这样我们在LocalType中 创建一个结构体,XXXVT ,将所有的虚表名称off_XXX 都赋值到结构体中,添加上类型int ,确定,然后双击结构体名,此时,会提示是否导入结构体到数据库,YES,然后再structure 窗口中就可以看到定义的虚表结构体了,这样,就可以Y来识别虚表了;
这样通过C的方式导入结构体的方式比在structure窗口中逐个添加成员要快很多;
7.5 IDA如何确定数据是数组类型?
Data区中被引用的地方一般是数组;
见:《如何提取DLL中的数据》
见:《如何访问DLL的函数》
7.6 IDA 如何使用UTF-8 显示中文字符
Alt + A 可以改变字符显示 格式,在DLL中所有的data区都是使用十六进制的数值显示的;
Alt + A : 设置---- “ASCII String style ”-----“set default encoding”。
如果想要在Hex -rays的伪代码中显示出字符串,可以设置:“Edit”---plugins ---- Option----Analysis --- “Print only constant string literals”
7.7 IDA 的强大之处---- 交叉引用
X:一个地方引用另外一个地方;
体现:双击、X、subView-Cross ref
在IDA-View-A 模式下,Xrefs graph to :表示该函数被其他哪些函数调用的信息;Xrefs graph from :函数调用其他函数的信息;
字符串窗口和交叉引用 是IDA的强大之处,常常用于代码片段定位,非常关键;
7.8 IDA 反编译失败1--- Decomplication failure
IDA经常会解析错误,我们要学会如何处理这些错误,才能得到伪代码;
可能原因1:函数约定的错误;
可能原因2:函数参数识别错误;
解决方法:分析函数的参数个数和调用约定,Y重新定义函数;
如果判断不出是哪种约定,可以D成数据,再用tab进行转成伪代码,可以看到反编译后成为了Jumpout(xxxxx),屏蔽了细节,这种方式,只是用来随便看看代码;
7.9 IDA 反编译失败2--- SP- analysis failed
使用IDA -- Options ---Disassembly ---Display disassenmly Line Parts ---- Stack Pointer,勾上可以看到SP,正常情况下SP的值在函数头和尾都是0000 ,表示堆栈是平衡的;
如果函数识别错误,Alt + p: 可以指定函数的头尾;
7.10 IDA 反编译失败3--- Positive sp value has been found
有些汇编指令无函数名,表示是虚函数,IDA很容易出错;
如: call dword ptr[eax + 4]
V解决:ALT + K,设置SP当前为0x4X,这里X表示的是Call 之前有几个push。这样做能识别代码,能分析,但是会引起堆栈不平衡。
7.11 IDA 自动优化: Debug 和Release版本的不同
常量、简单算法,在生成Release 版本的时候被优化; 相对而言, debug版本中有很多的无关逻辑的代码,例如:判断越界的函数等;
7.12 IDA 可以识别当前文件的类型
C#: Mircosoft.net assembly [peldw]
7.13 IDA 如何修改数据库
方式一、找到需要修改的位置,Edit---Patch pragram ----Assemble ;加入汇编语言,例如:给函数的返回值直接修改为2,反回;
mov eax,2
retn
如果是stdCal 的话,需要使用retn 4
修改后,在函数头P,函数对齐后,当前函数直接返回 2,而原先的逻辑都被注释掉了;
注意,此时修改并不会更新原始程序文件,实际只是修改了IDA的项目文件 idb。
最后,使用Edit---Patch pragram ----Apply patch to input file,这样就能修改原始文件。
方式二、使用UE打开,直接找到需要修改的函数的字节码,修改字节码也是一样的,例如 0x90 ----nop;
再使用Edit--- patch program ----Apply patches to input file ,OK 即可;
7.14 IDA Send Data 的识别
从Write File回溯,SendData一般被很多的地方调用;
7.15 IDA 取一个富含意义的名称,有助于理解。
7.16 IDA 跟踪技巧:注释+ 改名 : 雁过留声 很重要...
7.17 IDA 变量静态分析
7.17.1 X : type = W/O 时可以找到当前变量写的地方,可以直接看到关键逻辑,进而进行猜测;
7.17.2 数组的静态分析
dword_1002FEBE8 = HeapAlloc(hHeap, 8u, 8*g_count);
表示:分配内存8*count,每个元素有8字节,分配的内存往往与数据库对应;
*(_BYTE*)(dword_10031AC4 + 8*V7 + 1) = g_DataArry[V8].size
表示:元素8字节,V7是偏移元素个数,1表示元素内偏移,_BYTE 表示取出的是一个字节,赋值给size;
7.17.3 函数的静态分析
1、看懂函数流程后确定函数的意义;
2、如果调用dll中的函数,GetProAddress,函数名往往都是直接可以看到的;
7.18 根据lib文件制作sig文件(参考IDA权威指南)
Sig文件能使得IDA识别更多的符号,便于分析,F5刷新;
1、使用IDA打开lib,可以看出lib里面有多少个obj文件;
2、使用link.exe 将lib中的obj文件导出来(这个link.exe 来自于哪里?)
命令:link -lib /extract:testCplusplusCollect.obj testCplusplusCollect.lib
得到的是testCplusplusCollect.obj
没找到Link.exe ,
其实,这个obj文件在lib文件编译生成时会在debug目录生成(我是直接拷过来的哈);
听说VC里面的Lib.exe 也有这个功能。听说可以直接使用pcf.exe [lib文件名].lib ,会生成[lib文件名].pat 。
没试过,读者可以试试;
3、使用pcf.exe 将obj文件制作成为pat文件。
从IDA安装目录中找到pcf.exe,拷贝到testCplusplusCollect.lib 同级目录下;
命令:pcf testCplusplusCollect.lib
在同级目录得到了testCplusplusCollect.pat。
4、使用sigmake.exe将PAT文件转换成为sig文件。
IDA_65_Utils\flair65\bin\win\sigmake.exe
命令:sigmake.exe testCplusplusCollect.pat testCplusplusCollect.sig
在同级目录得到了testCplusplusCollect.sig。
5、在IDA中去加载sig文件
将testCplusplusCollect.sig 拷贝到IDA的sig目录,再使用FILE -> LoadFile àFILTER signature File;
7.19 IDA反编译失败:too big Funtion
解决方法: MAX_FUNCSIZE 改大即可;
7.20 结构体:如果知道了结构体的C表示,IDA如何添加
在TypeLocal(shift + F1 )的界面,insert 跳出输入对话框,在里面复制结构体就行了。
双击添加后的类型,提示“是否加入当前的数据库”,选择可以,那么在structure试图中就能得到我们想要的类型,可以在反编译试图中使用Y设置此类型;
7.30 如果将全局的字符串,显示在代码中?
Edit ---plugins --- Hex-Rays decomplier -Option -
Print only constant string literals ”把这个勾去掉就能,将全局的字符串给显示出来了。
7.31 打标签与修改名字
修改名字:N
打标签:JUMP/Mark postion 中“标记当前的位置”然后可以使用Ctrl + M 来快速跳转到标签的位置;
以前我一直这么做的: 分析到了哪个函数,就将函数名字N一下,改成YY开头,这样我就可以在FUnctionList中快速找到我分析过的函数;
7.32 如何解决IDA key黑名单检测
提示:“sorry, this database has been created by a pirate version of IDA”
这是因为IDA的key 黑名单检测,解决方法如下:十六进制工具将ida.dll 或 ida64.dll的地址0x277560 ~ 0x277750 的数据改为0 即可通过检测;这部分是所有的黑名单Key的MD5值。
7.33 如何使得IDA将所有的数值都用十六进制表示;
插件: Edit ---Plugins --- Hex-Rays Decompiler ---- options ---“Default radix” 填上16即可;
7.34 如何在伪代码中将全局的dword_454670 改为字符串显示?
将数据A转换为c-typle 字符串;然后:在伪代码中F5 就能将dword_454670 转成:
7.35 F5 反编译后提示: “Out of memory”
有时候函数太大,会这样提示,打开Hex-ray的选项,提示我们可以在 HexXXX.cfg中修改max_funcszie.
好像只有7.0之后的IDA配置文件中有这个属性。
7.36 F5 反编译的代码分支太多,代码太长,如何做?
将分析过的分支缩进,方法: 右键--collapse item 就行,如果想看里面的内容,一样操作选uncollapse;
7.37 F5 的伪代码加上注释后,有时候注释不见了;
当伪代码很大的时候,你走过一遍流程,写下了很多的注释(特别是多行的注释),当分析清除一个数据结构体,改变代码的数据类型的是,就会发现字节写下的注释,莫名其妙的就没了,而且IDA不提供撤销功能。
原因:可能是识别为多行的伪代码,改变数据结构类型后,引起行数的变化,一般为缩减了很多行,此时会导致注释被掩住;
解决方法:没有想到好的方法,当需要逆向分析代码量大的时候,可以每做一个步骤,到处.c备份一次。
7.36 can't load file
当打开idb的时候,显示不能加载数据,那么,可能有两个原因:
1、IDA是低版本的,idb是高版本生成的; 听说是有解决这个问题的插件;
2、IDA的路径不能为中文,改为英文目录;
7.38 在汇编处按Tab键发现反汇编的C预研与汇编指令对不上。
原因:汇编指令没有识别成函数,所以反汇编不会显示对应的C伪代码。未识别的汇编指令段地址是暗红色的,识别出来的是黑色(颜色是默认色,可以设置);
7.38 arm 与 thumb 之间指令转换
按快捷键 Alt+g, Value 填 0,切换为 arm, Value 填 1,切换为 thumb。