花指令总结

花指令总结

为什么花指令会干扰正常反汇编

简单的花指令 0xe8是跳转指令

image-20250401094746989

两个跳转一个指向无效数据,一个指向正常数据来干扰递归扫描算法。

0xE8CALL 指令的操作码

image-20250401094807308

花指令通过跳转的汇编语言来干扰程序反汇编,让ida误认为是执行语句而分析出错

1.常见类型

ida能正常反汇编

1.jmp 到垃圾指令处

image-20250401095424119

这里的jmp到垃圾代码处

2.jnx和jx条件跳转 ida无法正常反汇编

利用jz 和jnz的互补条件跳转

image-20250401095756556

为什么互补就可以干扰ida反汇编

1.构造死代码路径

jz(跳转如果零)和 jnz(跳转如果非零)通常是互斥的指令。

在正常情况下,它们应该分别指向不同的代码路径,但这里它们指向相同的目标 loc_40104C+1,这意味着无论跳转与否,程序最终都会执行 loc_40104C+1 处的代码。

这让 IDA 产生困惑,因为通常一个分支跳转后,另一条路径应该包含不同的代码,而这里却是个“无用的跳转”。

2.欺骗静态分析

IDA 在解析时,会尝试确定哪些代码路径是有效的。当 jzjnz 都跳转到 loc_40104C+1,IDA 可能无法正确推导出哪些指令是真正可执行的。

由于 IDA 依赖于基本块分析,它可能会因为这种“伪条件跳转”而误解析代码块的边界,导致后续的 call 指令或其他代码无法正确反汇编。

3.跳转到偏移量+1 破坏对齐

loc_40104C+1 这个跳转地址很关键,它意味着代码实际上会跳转到 loc_40104C 的第二个字节,而不是指令的起始位置,这可能导致 CPU 误解指令流,执行错误的操作。

反汇编工具通常假设指令是按字节对齐的,但如果跳转到一个“非法的”字节上,IDA 可能会解析错误,导致反汇编结果混乱。

4.影响代码流分析

由于 jzjnz 让 IDA 认为两条路径都可执行,但实际执行路径并不符合常规逻辑,这会导致 IDA 误识别函数结构,可能让后续的 calldb 伪造数据变得不可读。

3.永真条件跳转

通过设置永真或者永假的,导致程序一定会执行,由于ida反汇编会优先反汇编接下去的部分(false分支)。也可以调用某些函数会返回确定值,来达到构造永真或永假条件。

image-20250401201905596

这里的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”一般指的是一段中间过渡代码

image-20250401202836650

就是先call 再ret 循环执行没有意义的代码,干扰正常反汇编

2.如何去除花指令

image-20250401203052786

把这里的jnz 到 call 再到 add的汇编指令全部nop 可以用 alt+N的快捷键

例题 [HZNUCTF 2023 final]虽然他送了我玫瑰花

看题目能看出来是花

jz+jnz的永恒跳转

image-20250401204652724

因为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个加密函数

image-20250401205240048

一个一个看

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

最后的意思就是

posted @ 2025-05-30 10:56  ethan——1231  阅读(140)  评论(0)    收藏  举报