免杀专题
28、花指令的剔除
1. 课程目标
学习如何识别和剔除花指令,这是逆向分析的重要技能。
2. 识别花指令
2.1 常见特征
| 特征 | 说明 |
|---|---|
| NOP序列 | 连续的0x90 |
| 无效跳转 | 跳转到下一条指令 |
| 自抵消操作 | push/pop, add/sub相同值 |
| 死代码 | 跳转后的不可达代码 |
| 重复指令 | 相同操作重复多次 |
2.2 识别方法
1. 控制流分析
- 找出永远不执行的代码块
- 识别无条件跳转后的代码
2. 数据流分析
- 找出对结果无影响的操作
- 识别自抵消的指令序列
3. 模式匹配
- 已知花指令模式库
- 正则匹配识别
3. 手动剔除
3.1 使用IDA Pro
1. 找到花指令区域
2. Edit → Patch program → Change byte
3. 用NOP(0x90)替换花指令
4. 或直接删除并重新分析
3.2 使用x64dbg
1. 定位花指令
2. 右键 → Binary → Fill with NOPs
3. 或手动修改为有效指令
4. 自动剔除脚本
4.1 IDA Python脚本
# IDA Python花指令剔除
import idaapi
import idc
def remove_junk_code(start, end):
"""移除指定范围内的花指令"""
addr = start
while addr < end:
# 获取指令
mnem = idc.print_insn_mnem(addr)
# 检查NOP
if mnem == "nop":
print(f"Found NOP at {hex(addr)}")
# 检查无效跳转 (jmp next_instruction)
if mnem == "jmp":
target = idc.get_operand_value(addr, 0)
next_addr = idc.next_head(addr)
if target == next_addr:
print(f"Found useless jmp at {hex(addr)}")
# Patch为NOP
idc.patch_byte(addr, 0x90)
idc.patch_byte(addr + 1, 0x90)
addr = idc.next_head(addr)
# 使用
remove_junk_code(0x401000, 0x402000)
4.2 通用识别模式
# 常见花指令模式
JUNK_PATTERNS = [
# push reg; pop reg (无效操作)
[0x50, 0x58], # push eax; pop eax
[0x51, 0x59], # push ecx; pop ecx
# xor reg, reg; add reg, val; sub reg, val
# ... 更多模式
]
def detect_junk(data, offset):
for pattern in JUNK_PATTERNS:
if data[offset:offset+len(pattern)] == bytes(pattern):
return len(pattern)
return 0
5. 对抗花指令剔除
5.1 使用有意义的花指令
// 看起来像真实代码的花指令
volatile int dummy = 0;
dummy = GetTickCount();
dummy = dummy * 2;
if (dummy > 0) {
dummy = 0;
}
// 实际dummy从未被使用
5.2 交织真实代码
int result = 0;
int junk = GetTickCount(); // 花指令
result = CalculateValue(); // 真实
junk ^= junk; // 花指令
result += 10; // 真实
junk++; // 花指令
return result; // 真实
6. 课后作业
6.1、作业1:手动识别(必做)
- 使用IDA分析一个加花的程序
- 手动识别出花指令位置
6.2、作业2:脚本剔除(进阶)
- 编写IDA Python脚本自动识别花指令
- 测试脚本效果
7. 下一课预告
下一课我们将学习动态Patch函数技术。