花指令总结
花指令总结
为什么花指令会干扰正常反汇编
简单的花指令 0xe8是跳转指令
两个跳转一个指向无效数据,一个指向正常数据来干扰递归扫描算法。
0xE8 是 CALL 指令的操作码
花指令通过跳转的汇编语言来干扰程序反汇编,让ida误认为是执行语句而分析出错
1.常见类型
ida能正常反汇编
1.jmp 到垃圾指令处
这里的jmp到垃圾代码处
2.jnx和jx条件跳转 ida无法正常反汇编
利用jz 和jnz的互补条件跳转
为什么互补就可以干扰ida反汇编
1.构造死代码路径
jz(跳转如果零)和 jnz(跳转如果非零)通常是互斥的指令。
在正常情况下,它们应该分别指向不同的代码路径,但这里它们指向相同的目标 loc_40104C+1,这意味着无论跳转与否,程序最终都会执行 loc_40104C+1 处的代码。
这让 IDA 产生困惑,因为通常一个分支跳转后,另一条路径应该包含不同的代码,而这里却是个“无用的跳转”。
2.欺骗静态分析
IDA 在解析时,会尝试确定哪些代码路径是有效的。当 jz 和 jnz 都跳转到 loc_40104C+1,IDA 可能无法正确推导出哪些指令是真正可执行的。
由于 IDA 依赖于基本块分析,它可能会因为这种“伪条件跳转”而误解析代码块的边界,导致后续的 call 指令或其他代码无法正确反汇编。
3.跳转到偏移量+1 破坏对齐
loc_40104C+1 这个跳转地址很关键,它意味着代码实际上会跳转到 loc_40104C 的第二个字节,而不是指令的起始位置,这可能导致 CPU 误解指令流,执行错误的操作。
反汇编工具通常假设指令是按字节对齐的,但如果跳转到一个“非法的”字节上,IDA 可能会解析错误,导致反汇编结果混乱。
4.影响代码流分析
由于 jz 和 jnz 让 IDA 认为两条路径都可执行,但实际执行路径并不符合常规逻辑,这会导致 IDA 误识别函数结构,可能让后续的 call 或 db 伪造数据变得不可读。
3.永真条件跳转
通过设置永真或者永假的,导致程序一定会执行,由于ida反汇编会优先反汇编接下去的部分(false分支)。也可以调用某些函数会返回确定值,来达到构造永真或永假条件。
这里的jz 和jnz 构成花指令,jz跳转到call 这个指令,其实这是一个没有意义的函数,但干扰ida,误认为是函数的分支
4.call +ret 构造花指令
利用call 和 ret ,在函数中修改返回地址,达到跳过thunkcode 到正常流程的目的
__asm{
call label1
_emit junkcode
label1:
add dword ptr ss:[esp],8//具体增加多少根据调试来
ret
_emit junkcode
}
call指令:将下一条指令地址压入栈,再跳转执行
ret指令:将保存的地址取出,跳转执行
先用 call 调用当前函数(这样返回地址就会被压入栈中),然后在函数内部对返回地址做修改,再使用 ret 跳转到正确的流程入口。
“Thunk code”一般指的是一段中间过渡代码
就是先call 再ret 循环执行没有意义的代码,干扰正常反汇编
2.如何去除花指令
把这里的jnz 到 call 再到 add的汇编指令全部nop 可以用 alt+N的快捷键
例题 [HZNUCTF 2023 final]虽然他送了我玫瑰花
看题目能看出来是花
jz+jnz的永恒跳转
因为byte 数组也是jnz跳转的,一并nop掉,然后在main函数开头按P重建函数就行了
主函数
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // esi
int v4; // eax
char v6; // [esp+0h] [ebp-F8h]
char v7; // [esp+0h] [ebp-F8h]
char v8[100]; // [esp+Ch] [ebp-ECh]
__int128 v9; // [esp+70h] [ebp-88h]
int v10; // [esp+80h] [ebp-78h]
int v11; // [esp+84h] [ebp-74h]
int v12; // [esp+88h] [ebp-70h]
char v13; // [esp+8Ch] [ebp-6Ch]
char Arglist[100]; // [esp+90h] [ebp-68h] BYREF
sub_401020(Format, v6);
sub_401050("%s", (char)Arglist);
v10 = -171171450;
v11 = -669748952;
v12 = 1651994351;
v13 = -6;
v9 = xmmword_402170;
if ( strlen(Arglist) == 29 )
{
for ( i = 0; i < 29; ++i )
v8[i] = funcs_40117E[i % 5u](Arglist[i]);
v4 = 0;
while ( v8[v4] == *((_BYTE *)&v9 + v4) )
{
if ( ++v4 >= 29 )
{
sub_401020("Congratulations!!\n", v7);
return 0;
}
}
sub_401020("try again\n", v7);
}
else
{
sub_401020("wwwhhhaaattt???\n", v7);
}
return 0;
}
看主要加密函数 funcs_40117E
进去有5个加密函数
一个一个看
int __cdecl sub_401080(int a1)
{
return a1 ^ 0x19;
}
int __cdecl sub_401090(int a1)
{
return a1 + 18;
}
int __cdecl sub_4010A0(int a1)
{
return a1 - 16;
}
int __cdecl sub_4010B0(char a1)
{
return 2 * (a1 & 0x7F);
}
最后的意思是
(a1 >> 1) & 0xFF
2 * (a1 & 0x7F);
a1 & 0x7F:清除a1的最高位(最高位是第 7 位,0x7F = 0111 1111)。- 乘以 2 相当于左移 1 位 (
<< 1),但不会影响原本被清除的最高位。 - 作用:保留
a1的低 7 位,并将其值翻倍。
(a1 >> 1) & 0xFF;
a1 >> 1:将a1右移 1 位,相当于除以 2(向下取整)。& 0xFF:在 8 位运算环境下没有影响,通常用于确保结果仍然是 8 位的数。
int __cdecl sub_4010C0(int a1)
{
return a1 ^ ((unsigned __int8)a1 ^ (unsigned __int8)~(_BYTE)a1) & 0x80;
}
前4个都比较好理解
看看第五个
先查看运算符的优先级:~大于&大于^
举个例子:
~00000000=11111111
11111111^00000000=11111111
11111111&任意数=任意数
~(按位取反,Bitwise NOT)
-
作用:对一个数的二进制按位取反(0 变 1,1 变 0)。
-
示例:
int a = 5; // 5 的二进制是 00000101 int b = ~a; // 取反后变成 11111010(补码表示 -6) printf("%d", b); // 输出 -6
&(按位与,Bitwise AND) 同1才1
-
作用:对两个数的二进制逐位执行
AND操作(1 & 1 = 1,其他情况为 0)。 -
示例:
int a = 5; // 5 -> 00000101 int b = 3; // 3 -> 00000011 int c = a & b; // 结果 -> 00000001 (即 1) printf("%d", c); // 输出 1
^(按位异或,Bitwise XOR)
- 作用:对两个数的二进制逐位执行
XOR操作(相同为 0,不同为 1)。
int a = 5; // 5 -> 00000101
int b = 3; // 3 -> 00000011
int c = a ^ b; // 结果 -> 00000110 (即 6)
printf("%d", c); // 输出 6
最后的意思就是

浙公网安备 33010602011771号