花指令简单总结

j1ya Lv5

背景

  • 线性扫描算法:逐行反汇编(无法将数据和内容进行区分)
  • 递归下降算法:根据一条指令是否被另一条指令引用来决定是否对其进行反汇编(难以准确定位)

正是因为这两种反汇编的机制和缺陷,所以才导致花指令的诞生

花指令简单说就是在代码中混入一些垃圾数据阻碍静态分析

常见指令

  • 0xE8 call + 4字节偏移地址
  • 0xE9 jmp + 4字节偏移地址
  • 0xEB jmp + 2字节偏移地址
  • 0xFF15 call + 4字节地址
  • 0xFF25 jmp + 4字节地址
  • 0xcc int 3
  • 0xe2 loop
  • 0x74 jz
  • 0x75 jnz

快捷键

下面是与花指令相关的IDA快捷键

U:undefine,把代码转化为数据

C:把数据转化为代码

P:创建函数

常规类型

1.jx+jnx(x可为e,z,l)

jnz实际上是fake的,因为jz这个指令让ida认为jz下面的是另外一个分支

2.call + add [esp], n + retn

这里call指令,其实本质就是jmp&push 下一条指令的地址,但是这里只需要jmp指令,push这条指令是多余的,后续的add指令又会修改下一条指令的地址,造成爆红

易语言自带的花指令

1
2
3
4
004010BF   .  E8 00000000   call 1111.004010C4
004010C4 /$ 830424 06 add dword ptr ss:[esp],0x6
004010C8 \. C3 retn
004010C9 B9 db B9

只需要将下面的特征码patch掉:E80000000083042406C3??

3.jmp XXX

题目练习:https://www.nssctf.cn/note/set/2970

这也是一种常见的花指令,虚拟地址不可能那么大,实际是jmp指令对应的e9在搞鬼,ida会默认将e9后面的4个字节当成地址

4.stx+jx

clc是清除EFlags寄存器的carry位的标志,而jnb是根据cf==0时跳转的,然而jnb这个分支指令,ida又将后面的部分当成了另外的分支

5.干扰栈分析

这条花指令会使 IDA 误以为 0x116B 处的指令可能会执行,导致 IDA 的栈分析出现错误

可以修改 ida 对栈的分析结果

在Options -> General菜单中勾上Stack pointer选项可以查看每行指令执行之前的栈帧大小

Alt + K 可以修改某条指令对栈指针的影响,从而消除这条花指令对反编译的影响。

6.XOR加密代码字节

根据_main猜测是main,开头三个字节和push ebp;mov ebp,esp对应的16进制数55 8B EC异或一下刚好是7,所以猜测特定函数xor 7之后就能还原

IDA Options->Disassembly窗口下可以修改显示的opcode

如何去除

nop单字节(E8/E9)

练习题目:https://www.nssctf.cn/problem/2313

在0x401051设置为数据类型(快捷键D),将call 转成硬编码 E8 再将光标放到 db 0E8上 将E8改成 nop(90) 再次按C键(转化为代码类型)点yes 将硬编码修复成代码

然后向下逐⼀修复 将光标放置在黄色的行上 按C修复 直到没有黄色地址

最后全选函数,按P生成函数

nop多余汇编

jz指令指向下一条指令中间

这个时候让jz正常分析,也就是把中间的nop

如果后面有数据没被分析为code,需要继续操作

nop部分连续汇编

一般去菜单中的Edit->Patch program->Change byte修补单字节

像下面这种红色标志离原函数有一定距离又是call+retn组合加上400f64又没什么用,可以都nop,这是一种暴力方法

nop完后看到有%lld,删除函数,修补函数即可反编译

xchg很少用到,后面还有retn,主打不想要的直接全部nop

也可以先可以小范围的尝试,把call及下面一行先nop;发现不行,再从头把关键数据之前(D7,flag is上面那一行)也给nop掉,发现可以了

代码自动去花

出现大量花指令没有统一方法去除

集成脚本

下面脚本主要去除call相关花指令,jx + jnx花指令和stx + jx花指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
import regex as re
from idc import *
import idc
from keystone import *
from capstone import *

asmer = Ks(KS_ARCH_X86, KS_MODE_32)
disasmer = Cs(CS_ARCH_X86, CS_MODE_32)

def disasm(machine_code, addr=0):
l = ""
for i in disasmer.disasm(machine_code, addr):
l += "{:8s} {};\n".format(i.mnemonic, i.op_str)
return l.strip('\n')

def asm(asm_code, addr=0):
l = b''
for i in asmer.asm(asm_code, addr)[0]:
l += bytes([i])
return l

def check_call_to_jmp(call_insn_addr):
call_target = idc.get_operand_value(call_insn_addr, 0)
if call_target not in range(start_ea, end_ea): return None
idc.create_insn(call_target)
if ida_bytes.get_bytes(call_target, idc.get_item_size(call_target)) == asm('add esp, 4', call_target):
return call_target + idc.get_item_size(call_target)
if idc.print_insn_mnem(call_target) == 'pop':
return call_target + idc.get_item_size(call_target)
insn = disasm(ida_bytes.get_bytes(call_target, idc.get_item_size(call_target)), call_target)
if '[esp],' in insn and ('add' in insn or 'sub' in insn) and idc.get_operand_type(call_target, 1) == o_imm:
idc.create_insn(call_target + idc.get_item_size(call_target))
if idc.print_insn_mnem(call_target + idc.get_item_size(call_target)) == 'retn':
return (call_insn_addr + 5 + (1 if idc.print_insn_mnem(call_target) == 'add' else -1) * idc.get_operand_value(call_target, 1)) & 0xFFFFFFFF
return None

def check_jcc_to_jmp(jcc_insn_addr):
code1 = ida_bytes.get_bytes(jcc_insn_addr, idc.get_item_size(jcc_insn_addr))
next_insn_addr = jcc_insn_addr + idc.get_item_size(jcc_insn_addr)
idc.create_insn(next_insn_addr)
code2 = ida_bytes.get_bytes(next_insn_addr, idc.get_item_size(next_insn_addr))
if abs(code1[0] - code2[0]) == 1 and min(code1[0], code2[0]) % 2 == 0 and idc.get_operand_value(jcc_insn_addr, 0) == idc.get_operand_value(next_insn_addr, 0):
return idc.get_operand_value(jcc_insn_addr, 0)
code = ida_bytes.get_bytes(jcc_insn_addr, 12)

print("bbbbb")
pattern_list = [
re.compile(rb"(?s)\x7C\x03\xEB\x03.\x74\xFB"),
re.compile(rb"(?s)\xEB\x07.\xEB\x01.\xEB\x04.\xEB\xF8."),
re.compile(rb"(?s)\xEB\x01.")
]

for pattern in pattern_list:
match = re.match(pattern, code)
if match != None and match.span()[1] != 0:
return jcc_insn_addr + match.span()[1]

return None

st_mnem_map = {'clc': ['jnb'], 'stc': ['jb']}

def check_st_to_jmp(st_insn_addr):
st_mnem = idc.print_insn_mnem(st_insn_addr)
next_insn_addr = st_insn_addr + idc.get_item_size(st_insn_addr)
idc.create_insn(next_insn_addr)
if idc.print_insn_mnem(next_insn_addr) in st_mnem_map[st_mnem]:
return idc.get_operand_value(next_insn_addr, 0)
return None

start_ea = 0x401000
end_ea = 0x4B9CD0

ea = start_ea
while ea < end_ea:
print("aaa: " + hex(ea))
for i in range(ea, ea + 0x10): idc.del_items(i)
if idc.create_insn(ea) == 0:
# idc.patch_byte(ea, 0x90)
ea += 1
continue
mnem = idc.print_insn_mnem(ea)
if mnem == 'call':
jmp_target = check_call_to_jmp(ea)
if jmp_target != None:
assert jmp_target > ea
print("call: " + hex(ea))
print("jmp target: " + hex(jmp_target))
if jmp_target > ea and abs(jmp_target - ea) <= 0x80:
ida_bytes.patch_bytes(ea, b"\x90" * (jmp_target - ea))
ea = jmp_target
else:
code = asm('jmp 0x%x' % (jmp_target), ea)
ida_bytes.patch_bytes(ea, code)
ea += len(code)
continue
elif mnem[0] == 'j':
jmp_target = check_jcc_to_jmp(ea)
if jmp_target != None:
print("jcc: " + hex(ea))
assert jmp_target > ea
if jmp_target > ea and abs(jmp_target - ea) <= 0x80:
ida_bytes.patch_bytes(ea, b"\x90" * (jmp_target - ea))
ea = jmp_target
else:
code = asm('jmp 0x%x' % (jmp_target), ea)
ida_bytes.patch_bytes(ea, code)
ea += len(code)
continue
elif mnem in st_mnem_map:
jmp_target = check_st_to_jmp(ea)
if jmp_target != None:
print("st: " + hex(ea))
assert jmp_target > ea
if jmp_target > ea and abs(jmp_target - ea) <= 0x80:
ida_bytes.patch_bytes(ea, b"\x90" * (jmp_target - ea))
ea = jmp_target
else:
code = asm('jmp 0x%x' % (jmp_target), ea)
ida_bytes.patch_bytes(ea, code)
ea += len(code)
continue

ea += idc.get_item_size(ea)

for _ in range(start_ea, end_ea):
idc.del_items(_)

idc.jumpto(0x004B8DE4)

print("finish")

jx+jnx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from ida_bytes import get_bytes,patch_bytes
start= 0x401000
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")

jmp花指令

分析main函数可以看到是杂乱字节,观察0x1144可以发现存在jmp db这种类型的花指令,因此可以写idapython脚本来解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import ida_bytes
import ida_ida
def patch(ea,num=1):
for i in range(num):
ida_bytes.patch_byte(ea+i,0x90)
return
print("-----")
hexStr="EB FF C0 BF ?? 00 00 00 E8"
bMask = bytes.fromhex(hexStr.replace('00', '01').replace('??', '00'))
bPattern = bytes.fromhex(hexStr.replace('??', '00'))
signs=ida_bytes.BIN_SEARCH_FORWARD| ida_bytes.BIN_SEARCH_NOBREAK| ida_bytes.BIN_SEARCH_NOSHOW
print(bMask,bPattern)
begin_addr=0x1135
end_addr=0x3100
while begin_addr<end_addr:
ea=ida_bytes.bin_search(begin_addr,end_addr,bPattern,bMask,1,signs)
if ea == ida_idaapi.BADADDR:
break
else:
print(hex(ea))
patch(ea,3)
begin_addr=ea+8

参考链接:

https://blog.csdn.net/m0_51246873/article/details/127167749

https://www.cnblogs.com/YenKoc/p/14136012.html

https://www.xjx100.cn/news/40167.html?action=onClick

https://mp.weixin.qq.com/s/MUth1Qw-Fl2a5OrLw_2_0g

[https://skyi23.github.io/2024/11/11/IDA%20Python%20%E4%BD%BF%E7%94%A8%E6%80%BB%E7%BB%93/](https://skyi23.github.io/2024/11/11/IDA Python 使用总结/)

  • 标题: 花指令简单总结
  • 作者: j1ya
  • 创建于 : 2024-10-22 11:36:26
  • 更新于 : 2026-09-18 11:04:15
  • 链接: https://redefine.ohevan.com/2024/10/22/花指令/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论