之前我们的例子还没有分析完成,由于 算法分析部分会单独成一个系列,所以就不继续了,该系列的核心还是在于IDA的使用。
本篇再介绍一些其他的IDA使用技巧,有些技巧在特定的地方会很好用。
数据转换
我们在文本视图里面,经常会看到这样的东西:
有没有好奇过,这个 db 是什么意思呢?
byte / char / uint8 / int8 -> db 1个字节
word / short / uint16 / int16 -> dw 2个字节
dword / int / uint … -> dd 4个字节 (32位指针)
qword / int64 … -> dq 8个字节 (64位指针)
大部分情况下,整数以小端序存储在内存,比如 0xA1B2C3D4 存储到内存的顺序为 D4 C3 B2 A1。
我们看上图中的字符串,它们其实储存的方式也是一个一个的字节,只不过 ida 帮我们显示的漂亮了,可以算一下两个字符串之间的地址距离。
我们在字符串那一行,按一下 D 键,效果如下:
可以看到,每个字符都拆成了一个一个的字节,每个字节都是其 ASCII 码。
我们继续按 D,可以将该处的字节解释为 双字节/四字节/八字节/单字节 等,这个在动态调试的时候用处很大,因为对于C这种可以一个指针大天下的语言来说,你不动态调一下,根本不知道那个变量里面放的是什么东西,所以掌握D键非常的重要。
花指令处理
https://ctf-wiki.org/reverse/obfuscate/junk-code/
花指令(junk code)是一种专门用来迷惑反编译器的指令片段,这些指令片段不会影响程序的原有功能,但会使得反汇编器的结果出现偏差,从而使破解者分析失败。比较经典的花指令技巧有利用 jmp
、call
、ret
指令改变执行流,从而使得反汇编器解析出与运行时不相符的错误代码。
一般情况下,我们正常写程序是不会出现花指令的,但是有心人就不一样了,比如,我们可以在 C 代码里面写汇编:
//源码
#include<stdio.h>
#include<windows.h>
int main(int argc, char* argv[])
{
int a = MessageBox(NULL,"Hello","main",MB_OK);
int b,c,d,e;
//这部分为花指令部分
__asm{_emit 0xe8}
//花指令结束
b=1;
c=2;
d=3;
e=4;
return 0;
}
对于这样的程序,就会出现花指令。
当然,这么小儿科的花指令是难不倒IDA,但是总有人能写出骗过IDA的花指令。因为反汇编也是使用的算法来实现的,算法不可能做到完美。
我们看一个例子:
红色的部分,IDA 提示分析失败了,我们空格键切到文本模式看看:
发现code指令里面存在这种奇怪的指令,jz与jnz构成了一个绝对跳转,也就是无论 zf flag 的值是啥,都会跳转到 loc_405AF6。同时观察到,没有别的线条指向这些指令的中间位置,也就是说中间的指令不会被用到,我们可以将这些指令替换成 nop 指令。
首先,要将花指令中间的指令解释成数据:
对这些指令按下U键:
然后,再将整段花指令替换成 nop 指令:
比较让我疑惑的是,为啥不直接替换成 nop 指令呢?我尝试了几次,直接替换反汇编会报错。等后面学习栈修复了之后,再回来思考这个问题吧,现在猜想是ida本身自己有一个分析过程,直接替换是打乱了它的分析步骤???
注意要多选中一条指令,因为 Fill with NOPs 选项是不会替换选中的最后一条指令的。替换完成的效果如下:
继续往下看,还有很多这种指令,我们按照上面的方法全部替换掉。替换完成后,我们需要让ida来重新分析这个函数,先对这个函数取消定义,找到这个函数的头,按快捷键U或者右键:
效果如下:
ida将这块函数暂时解释成一个一个的 byte,我们再按 C 键,将这块内容解释为代码:
往下看下整个函数是否都解释完成了,如果没有,会有一些数据指令,在未解释的地方继续按 C 键,强制解释就行,如下图:
整个横线下面的就是没有解释的部分,我们再这里按下 C 键,继续解释就行。
全部弄完后,我们再按tab键将这个函数反汇编一下,IDA会提示:
如果没有出现这个提示,大概率是fill nops 搞错了,再重新来一次吧。弹这个窗是因为我们还需要为这块代码创建一个函数,按 P 键,或者右键选择创建函数:
创建完成后,再按tab键,就能看到反汇编代码了:
网上也有很多介绍如何去掉花指令的文章,可以自行搜索然后学习。特别是一些脚本的编写与使用,比人力要方便的多。
函数调用图
这里一个to,一个 from,还是很容易理解反的。to 的意思是谁调用了我。From 的意思是我调用了谁。
先看下 to 的效果:
由于 start 函数,已经是最上头的函数了,所以没人调用它。
看下 From 的效果:
这就非常恐怖了,但是我们可以放大了看:
可以看到 start 调用了3个方法。在你没啥思路的时候,这个图也许会有奇效。
常数搜索
菜单 [Search] → [Immediate Value],弹出一个窗,勾选“Find all occurrences”:
什么时候会需要搜索常数呢?一般是在你找到了某个特征的时候,比如我想看有系统调用的地方,那么我们可以搜索,0x81,为啥是这个值呢?可以去看 x86 汇编相关知识。
搜索到的结果,我们只看 text 部分即可,代码都是放在 text 段的。点一个进去看看是不是系统调用:
发现,确实是的。
指令文本搜索
菜单 [Search] → [Text],弹出一个窗,勾选“Find all occurrences”:
比如,我们可以使用 异或 的指令来搜索加密算法。
可以搜索 SVC 指令,这个相当于 x86 里面的 0x81 指令。
字节序列搜索
搜索满足匹配 HEX 串的序列。
菜单 [Search] → [Sequence of bytes],弹出一个窗,勾选“Find all occurrences”:
这个的用处就自行发散吧,主要是基于自身对逆向目标的了解程度。
IDA 的 patch
直接修改数据:数据窗口 F2,这种方法不推荐,
插件修改数据:LazyIDA,修改位置,右键,[Paste Data],修改大段数据。
修改汇编代码:Keypatch(得保持指令字节不变,不然so格式就乱了)。
注意,修改完成之后,需要将修改应用到文件:菜单项 [Edit] → [Patch program] → [Apply Patches to …]。
如果 apply 了多次,想撤回修改是不可能的(每次 patch 都是基于上一次 apply 的结果),所以每次 patch 完得自己存一个备份文件。
LazyIDA 数据提取
这个就是一个插件的功能,右键看一下就明白了。
点击后,会在窗口输出:
[+] Dump 0x4B8389 - 0x4B8395 (13 bytes) :
[0x55, 0x54, 0x50, 0x55, 0x54, 0x5F, 0x43, 0x48, 0x41, 0x52, 0x53, 0x45, 0x54]
还是非常好用的。
BinDiff
有需要再单独开。贴个网址:https://www.zynamics.com/bindiff.html