什么是花指令

反汇编过程中存在几个关键问题,其中之一是数据与代码的区分问题。反汇编算法必须对汇编指令长度,多种多样的间接跳转实现形式进行适当处理,从而保证反汇编结果的正确性。机器的一般格式为:指令+数据。而反汇编的大致过程是:首先会确定指令開始的首地址,然后依据这个指令字推断是哪个汇编语句,然后再将后面的数据反汇编出来。由此,我们能够看到,在这一步的反汇编过程中存在漏洞:如果有人有益将错误的机器指令放在了错误的位置,那反汇编时,就有可能连同后面的数据一起错误地反汇编出来,这样,我们看到的就可能是一个错误的反汇编代码。
巧妙构造代码和数据,在指令流中插入很多“数据垃圾”,干扰反汇编软件的判断,使其错误地确定指令的起始位置,让破解者无法清楚正确地反汇编程序的内容,迷失方向。经典的是,目标位置是另一条指令的中间,这样在反汇编的时候便会出现混乱。这就是“花指令”,简而言之,花指令是利用了反汇编时单纯依据机器指令字来决定反汇编结果的漏洞。
以下为一段汇编源程序。
carbon.svg
对源程序进行编译,然后用W32Dasm进行反汇编,结果如下。
carbon (1).svg
Linear Sweep式反汇编软件是逐行反汇编的,代码中的垃圾数据“0E8h”干扰了其工作,因此错误地确定了指令的起始位置,导致反汇编的一些跳转指令所跳转的位置无效。就像这里的地址00401009h不再是一条指令的起始地址,而是在指令的内部。所以,如果在反汇编一个程序时发现这样的特征,就可以断定该程序中使用了花指令。
OD在打开文件时使用线性扫描算法,在分析代码功能(“Ctrl+A”组合键)时使用递归行进算法。用OD代开实例并进行分析,生成的反汇编代码完全正确,“0E8h”字节没有被反汇编,也就是说,此类花指令无法迷惑OD,代码如下:
carbon (3).svg
在递归行进算法中,一个十分重要的假设是:对任意一条控制转移指令,都能确定其后继(即转移)的目的地址。想要迷惑这类反汇编工具,只要让其难以确定跳转的目的地址即可。
创建一个指向性无效数据的跳转指令的代码,具体如下。
carbon (5).svg
使用OD打开编译好的实例,这次汇编代码的识别出错了。OD认为垃圾数据“0E8h”所在的地址00401008h是有效的,故将其作为指令的起始地址,导致后面的指令识别出现了错误,具体如下。
carbon (4).svg
“无用的字节”干扰了反汇编工具对指令的起始位置的判断,从而导致反汇编的结果错误。如果能让反汇编工具正确地识别指令的起始地址,就能达到去除花指令的目的。例如,可以把那些无用的字节都替换成单字节指令。最常见的一种替换方法是把无用字节替换成nop指令,即十六进制数90h,示例如下。
carbon (6).svg
OD的一个花指令去除插件就是利用这个原理来去除花指令的。它根据收集的花指令特征码,将垃圾数据替换为nop指令,从而使反汇编工具正常工作。

一些简单花指令的解析

第一种花指令:

特征:先有一个push 操作,然后jmp到一个Call,Call后面跟一个POP
56 EB 27 2B A6 F1 CC 7E 25 31 96 23 39 7D B0 18 5A 1B D7 5E CB 5E FF E6 2B A6 F1 CC 7E 25 31 96 23 39 7D B0 18 5A 1B D7 5E CB E8 E6 FF FF FF 5E
花指令 - 图7
原理:
push D(寄存器) 1.先push esi(使用一个参数,保留环境,这里也可以push eax-edi等寄存器)
jmp C(偏移) 2.通过EB xx的jmp偏移指令,跳转至精心构造的偏移xx处,也就是Call
call B() 3.特定E8的Call指令回退调用(Call => 1.push 下一条指令的地址A 2.jmp 函数地址B)
pop D,jmp D 4.此时pop esi 就把地址A从堆栈中push出来,然后jmp esi就跳转至地址A
pop D 5.再次pop esi 的目的在于还原环境
解决方案:单步跟踪时,只需要F8即可过掉。(想在IDA内看结构,就把它们全部NOP掉就好了)
变通点:B C D都可以变换,导致特征码不好定位,暂时没想到好的批处理方法

第二种花指令:

特征:
无效指令:如CLC,CLD,FNOP。
无效跳转:
mov 寄存器,A;
cmp 寄存器,A;
JNZ 迷惑跳转;
花指令 - 图8
解决方案:这种比较简单,迷惑跳转不用管,直接nop填充即可,调试跟踪的时候也可以当当乐子

第三种花指令:

特征:
push ss
pop ss
待执行指令
有效OPCODE 16 17
花指令 - 图9
push ss;向堆栈压入16字节内容

花指令 - 图10
pop ss;

花指令 - 图11
原理:在执行push ss,pop ss语句后,会自动执行下一条语句,当下一条为jmp或着Call是就会跑飞/关键功能丢失

花指令 - 图12
解决方案:如果pop ss后面跟的是Call,在Call入口下断即可。如图:

花指令 - 图13

第四种花指令(来源于GrandCrab5.3的一个样本):

花指令 - 图14
1.mov ecx,0x1C3D2(给ecx赋值) 有效OPCODE:B9

花指令 - 图15
2.Call XXxx(将下一条语句作为返回地址push入栈,然后跳转至偏移)有效OPCODE:E8 0B 00 00 00(Call XXxx)

花指令 - 图16
3.add [esp],0x12; 修改返回地址 有效OPCODE:83 04 24 12

花指令 - 图17
4.loopd short 循环跳转 ,然后cmp ecx,XXXX(由于之前赋值的是较小数一定会跳转)最后retn返回至被修改过的函数返回地址,结束 有效OPCODE:E2 EF(loopd short) 81 F9 XXxx( cmp ecx,XX) 78 F0(JS short) C3(retn)

花指令 - 图18
解决方案:
利用010去花指令:花指令特征码为B9 D2 C3 01 00 E8 0B 00 00 00 81 F9 58 FF FE 01 78 F0 79 F8 E9 83 04 24 12 E2 EF E8 替换为EB 1A,后面随便填充即可,如:EB 1A 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
去花对比:
花指令 - 图19
去花后:

花指令 - 图20
总结:
1.前三种花指令都是来自于最近看的一个VB样本的ShellCode内,没能太大阻碍分析过程。第四个来源于很早之前看的一个GrandCrab5.3勒索病毒,解决花指令能有效利用IDA对其快速分析,帮助还是挺大的(当OD分析花指令时候一定要灵活利用ctrl+↑和ctrl+↓手动矫正反编译结果)。
2.花指令就是一个消耗分析人员耐心的行为(主要考验分析人员的汇编功底),分析的多了自然就熟练了(一般加花的话,像上面的有效OPCODE就很难变动)写下此贴目的之一就是告诫自己要耐心耐性
3.我们也可以通过上面的有效指令,构造属于我们的花指令(具体怎么操作就靠大家发散了),然后添加到代码的任意位置,略微增大分析难度

参考资料:
《加密与解密》
https://www.52pojie.cn/thread-1068444-1-1.html
https://www.baidu.com