一、什么是花指令
1)定义
花指令又名垃圾代码、脏字节,英文名是junk code。花指令就是在不影响程序运行的情况下,往真实代码中插入一些垃圾代码,从而影响反汇编器的正常运行;或是起到干扰逆向分析人员的静态分析,增加分析难度和分析时间。
2)分类
花指令分为不可执行花指令、可执行花指令
可执行花指令 顾名思义,可以执行的花指令,这部分垃圾代码会在程序运行的时候执行,但是执行这些指令没有任何意义,并不会改变寄存器的值,同时反汇编器也可以正常的反汇编这些指令。目的是为了增加静态分析的难度,加大逆向分析人员的工作量。
不可执行花指令 不可以执行的花指令,这类花指令会使反编译器在反编译的时候出错,反汇编器可能错误的反汇编这些指令。根据反汇编的工作原理,只有花指令同正常指令的前几个字节被反汇编器识别成一组无用字节时,才能破坏反汇编的结果。因此,插入的花指令应当是一些不完整的指令,被插入的不完整指令可以是随机选择的。
为了能够有效迷惑反汇编器,同时又确保代码的正确运行,花指令必须满足两个基本特征,即:
- 垃圾数据必须是某个合法指令的一部分。
- 程序运行时,花指令必须位于实际不可执行的代码路径。
3)原理:反汇编算法的设计缺陷
常用的两类反汇编算法:
1.线性扫描算法:逐行反汇编(无法将数据和内容进行区分)
2.递归行进算法:按照代码可能的执行顺序进行反汇编程序。
通过构造必然条件或者互补条件,使得反汇编出错。
简单的花指令 0xe8是跳转指令,可以对线性扫描算法进行干扰,但是递归扫描算法可以正常分析。
两个跳转一个指向无效数据,一个指向正常数据来干扰递归扫描算法。
二、花指令实现
1.简单jmp
这是最简单的花指令。
这种jmp单次跳转只能骗过线性扫描算法,会被IDA识别(递归下降)。
__asm{
jmp label1
db junkcode
label1:
}
2.多层跳转
本质上和简单跳转是一样的,只是加了几层跳转。显然无法干扰ida
start://花指令开始
jmp label1
DB junkcode
label1:
jmp label2
DB junkcode
label2:
jmp label3
DB junkcode
label3
和单次跳转一样,这种也会被IDA识别。
为了骗过IDA,我们将上面的花指令改写一下,
__asm {
_emit 0xE8
_emit 0xFF
//_emit 立即数:代表在这个位置插入一个数据,这里插入的是0xe8
}
查看反汇编后的结果
可以看到IDA错误的识别loc_411877处的代码,成功的实现了花指令的目的。那么我们知道了如何构造,自然也就明白了如何去除,只需要将插入的立即数nop掉即可,点击0xe8和0xff,点击右键,选择patching->change byte
也可以使用一个idapython脚本添加一个快捷键,
from idaapi import *
from idc import *
def nopIt():
start = get_screen_ea()
patch_byte(start,0x90)
refresh_idaview_anyway()
add_hotkey("ctrl-N",nopIt)
这个快捷键可以将选中的地方直接nop掉。
3.jnx和jx条件跳转
利用jz和jnz的互补条件跳转指令来代替jmp。骗过OD
_asm{
jz label1
jnz label1
db junkcode
label1:
}
__asm {
jz Label;
jnz Label;
_emit 0xC7;
Label:
这种混淆去除方式也很简单,特征也很明显,因为是近跳转,所以ida分析的时候会分析出jz或者jnz会跳转几个字节,这个时候我们就可得到垃圾数据的长度,将该长度字节的数据全部nop掉即可解混淆。
4.永真条件跳转
通过设置永真或者永假的,导致程序一定会执行,由于ida反汇编会优先反汇编接下去的部分(false分支)。也可以调用某些函数会返回确定值,来达到构造永真或永假条件。ida和OD都被骗过去了
__asm{
push ebx
xor ebx,ebx
test ebx,ebx
jnz label1
jz label2
label1:
_emit junkcode
label2:
pop ebx//需要恢复ebx寄存器
}
__asm{
clc
jnz label1:
_emit junkcode
label1:
}
确保一个支路永远跳转
在另一个不跳转的支路填充垃圾代码
__asm {
push ebx;
xor ebx, ebx;
test ebx, ebx;
jnz LABEL7;
jz LABEL8;
LABEL7:
_emit 0xC7;
LABEL8:
pop ebx;
}
先对ebx进行xor之后,再进行test比较,zf标志位肯定为1,就肯定执行jz LABEL8,也就是说中间0xC7永远不会执行。
解混淆的时候也需要稍加注意,需要分析一下哪里是哪里是真正会跳到的位置,然后将垃圾数据nop掉,本质上和前面几种没什么不同。
5.call&ret构造花指令
这里利用call和ret,在函数中修改返回地址,达到跳过thunkcode到正常流程的目的。可以干扰ida的正常识别
__asm{
call label1
_emit junkcode
label1:
add dword ptr ss:[esp],8//具体增加多少根据调试来
ret
_emit junkcode
}
call指令:将下一条指令地址压入栈,再跳转执行
ret指令:将保存的地址取出,跳转执行
6.汇编指令共用opcode
jmp的条指令是inc eax的第一个字节,inc eax和dec eax抵消影响。这种共用opcode确实比较麻烦
三、清除花指令
手动清除
找到所有的花指令,重新设置数据和代码地址。或者将花指令设置为nop(0x90)
在0x401051设置为数据类型(快捷键D),在0x401052设置为代码类型(快捷键C)
这里用一个ida python脚本添加ALT+N快捷键来将指令的第一个字节设置为NOP
from idaapi import *
from idc import *
def nopIt():
start = get_screen_ea()
patch_byte(start,0x90)
refresh_idaview_anyway()
add_hotkey("alt-N",nopIt)
参考文章
CTF逆向Reverse 花指令介绍 and NSSCTF靶场入门题目复现_ctf 花指令-优快云博客