一、基本概念
花指令:目的是干扰ida和od等软件对程序的静态分析。使这些软件无法正常反汇编出原始代码。
常用的两类反汇编算法:
1.线性扫描算法:逐行反汇编(无法将数据和内容进行区分)
2.递归行进算法:按照代码可能的执行顺序进行反汇编程序。
简单的花指令 0xe8是跳转指令,可以对线性扫描算法进行干扰,但是递归扫描算法可以正常分析。
两个跳转一个指向无效数据,一个指向正常数据来干扰递归扫描算法。
二、花指令实现
这里的实验基础代码是参考安全客上一个师傅代码进行的
//源码
#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;
}
1.简单jmp
这是最简单的花指令。OD还是能被骗过去。但是因为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
3.jnx和jx条件跳转
利用jz和jnz的互补条件跳转指令来代替jmp。竟然没有骗过OD(是因为吾爱的这个有插件吗)。但是ida竟然没有正常识别
_asm{
jz label1
jnz label1
db junkcode
label1:
}
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:
}
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确实比较麻烦
三、清除花指令
1.手动清除
找到所有的花指令,重新设置数据和代码地址。或者将花指令设置为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)
2.自动清楚花指令
上面有3个类别ida无法正常识别
- 互补条件跳转(比较好处理)
- 永真条件跳转 (各种永真条件比较难匹配)
- call&ret跳转(比较难处理)
所以就只对第一种jnx和jx的花指令进行自动化处理
所有的跳转指令,互补跳转指令只有最后一个bit位不同
70 <–> JO(O标志位为1跳转)
71 <–> JNO
72 <–> JB/JNAE/JC
73 <–> JNB/JAE/JNC
74 <–> JZ/JE
75 <–> JNZ/JNE
76 <–> JBE/JNA
77 <–> JNBE/JA
78 <–> JS
79 <–> JNS
7A <–> JP/JPE
7B <–> JNP/JPO
7C <–> JL/JNGE
7D <–> JNL/JGE
7E <–> JLE/JNG
7F <–> JNLE/JG
第一条指令跳转距离=第二条跳转距离+2。简单一点可以是\x03和\x01
抄的代码
from ida_bytes import get_bytes,patch_bytes
start= 0x401000#start addr
end = 0x422000
buf = get_bytes(start,end-start)
def patch_at(p,ln):
global buf
buf = buf[:p]+b"\x90"*ln+buf[p+ln:]
fake_jcc=[]
for opcode in range(0x70,0x7f,2):
pattern = chr(opcode)+"\x03"+chr(opcode|1)+"\x01"
fake_jcc.append(pattern.encode())
pattern = chr(opcode|1)+"\x03"+chr(opcode)+"\x01"
fake_jcc.append(pattern.encode())
print(fake_jcc)
for pattern in fake_jcc:
p = buf.find(pattern)
while p != -1:
patch_at(p,5)
p = buf.find(pattern,p+1)
patch_bytes(start,buf)
print("Done")
总结:
1.各种构造花式跳转。
2.也可以是将汇编进行等价替换。
3.增加无意义的汇编代码,前后除了改变EIP其他什么都不改变。
对于不同的程序可能有独特的花指令,自动生成的话,可以提取特定规则来方便自动化去花指令。
很多花指令的类型只能慢慢积累。
也可以用正则表达式的方法(感觉也不好用)。
很多时候需要记录运行时的条件(临近跳转jcc的几条汇编模拟执行判断jcc跳转是否一定会发生)(clc;jne|xor eax.eax;test eax,eax,jnz这种需要记录临近几条)
参考链接
花指令总结
https://www.anquanke.com/post/id/236490
逆向学习笔记之花指令
https://www.anquanke.com/post/id/208682
逆向-花指令去除(脚本)
https://blog.csdn.net/whklhhhh/article/details/88730934
《恶意代码分析实战》
Intel硬编码(一):Opcode Map、定长指令与指令前缀
https://blog.csdn.net/Apollon_krj/article/details/77508073
ida python函数查询
https://hex-rays.com/products/ida/support/idapython_docs/