什么是花指令?

花指令(也称为乱序执行指令)是计算机体系结构中的概念,它描述了处理器在执行指令时可能发生的一种现象。

在现代处理器中,为了提高性能,处理器通常采用乱序执行(out-of-order execution)的方式执行指令。这意味着处理器可以以任意顺序执行指令,并且在保持程序语义正确的前提下,尽可能地并行执行多个指令。

然而,由于某些指令之间存在数据依赖关系,即后续指令需要等待前面的指令完成才能执行,因此处理器需要进行指令重排序(instruction reordering)。在指令重排序的过程中,处理器可能会将一些后续指令提前执行,而不必等待前面的指令完成。这样的指令重排序和乱序执行可以提高处理器的效率和吞吐量。

然而,有些指令之间存在控制依赖关系,即后续指令的执行取决于前面的分支(如条件语句)的结果。在这种情况下,处理器可能会进行分支预测(branch prediction)来猜测分支的结果,并且在猜测正确的情况下继续执行后续指令。如果猜测错误,处理器需要将之前已执行的指令丢弃,并重新执行正确的指令路径。
总的来说,花指令就是一堆垃圾代码,虽然会增加cpu运行的负担,但是会误导ida的正常识别,从而达到混淆的目的,一般做题的解决方法就是通过汇编代码读取出junkcode,然后nop掉一些没必要的指令并且在不影响程序正常运行的前提下。

出题实验

实验原来的代码:

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
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
void get_xorkey(unsigned char xorkey[]) {
srand(1704038400); // 设置随机数种子为 1704038400
for (int i = 0; i < 52; i++) {
xorkey[i] = rand() % 256; // 生成 0 到 255 之间的随机数
}
}


int main() {
unsigned char flag[52] = {224,137,18,198,208,72,151,220,74,232,90,162,95,247,108,114,239,82,97,180,61,167,80,199,121,200,164,126,8,13,78,244,168,126,216,116,180,221,211,211,190,219,129,125,249,34,49,125,87,86,177,218};
int i;
unsigned char xor_key[52] = {0};
unsigned char input[100];
printf("please input:");
scanf("%s",input);
get_xorkey(xor_key);
for (int i = 0; i < 52; i++)
{
input[i]=input[i]^xor_key[i];
}
for (int i = 0; i < 52; i++)
{
if(flag[i]-input[i]!=0)
{
printf("oh my god you are error!");
return 0;
}
}
printf("yes, you are right!");

return 0;
}

实验环境:

一定是x86,64位不支持不支持不支持!!!内联汇编
(之前一直在拿64位的跑一直出问题…….0000)

image-20240126162725996

内联汇编代码

1
2
3
4
5
6
7
8
9
10
__asm {
push ebx
xor ebx, ebx
jnz sub1
jz sub2
sub1 :
_emit 0xe8
sub2 :
pop ebx
}

这是一段比较简单常见的内联汇编花指令,使用后的效果如下:
双击到添加花指令的函数时:
image-20240126164420330

看汇编界面就是很经典的jz,jnz无条件跳转:
image-20240126164522751

解决方案:

读一读汇编代码,nop掉没有用的就好了

  1. push ebx: 将 ebx 寄存器的值压入栈中,保存它的值。
  2. xor ebx, ebx: 将 ebx 寄存器与自身进行异或操作,实际上是将 ebx 寄存器的值设置为0。
  3. jnz sub1: 检查 ebx 寄存器的值是否为零(即上一条指令将其设置为零)。如果不为零,则跳转到标记为 sub1 的位置继续执行。
  4. jz sub2: 如果 ebx 寄存器的值为零,即上一条指令将其设置为零,则跳转到标记为 sub2 的位置继续执行。
  5. sub1:: 这是一个标记,用于标识一个代码块的起始位置。在这个例子中,它标识了一个名为 sub1 的代码块的开始位置。
  6. _emit 0xe8: 这是一个特殊的指令,用于在内联汇编中生成字节码。在这个例子中,它生成一个字节 0xe8,具体的含义取决于上下文。
  7. sub2:: 这是另一个标记,标识了一个名为 sub2 的代码块的开始位置。
  8. pop ebx: 弹出之前保存在栈中的值,恢复 ebx 寄存器的原始值。
    nop掉这些代码,然后ucp就可以了
    image-20240126174214162

可以正常分析了
当然以上只是一个简单的例子,后续的如果还有我出到了其他样式的题目,我也会继续写下面的。

完整代码:

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
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
void get_xorkey(unsigned char xorkey[]) {
__asm {
push ebx
xor ebx, ebx
jnz sub1
jz sub2
sub1 :
_emit 0xe8
sub2 :
pop ebx
}
int seed = 1704038400;
srand(seed);
for (int i = 0; i < 52; i++) {
xorkey[i] = rand() % 256;
}
}


int main() {
unsigned char flag[52] = { 224,137,18,198,208,72,151,220,74,232,90,162,95,247,108,114,239,82,97,180,61,167,80,199,121,200,164,126,8,13,78,244,168,126,216,116,180,221,211,211,190,219,129,125,249,34,49,125,87,86,177,218 };
int i;
unsigned char xor_key[52] = { 0 };
unsigned char input[100];
printf("please input:");
scanf("%s", input);
get_xorkey(xor_key);
for (int i = 0; i < 52; i++)
{
input[i] = input[i] ^ xor_key[i];
}
for (int i = 0; i < 52; i++)
{
if (flag[i] - input[i] != 0)
{
printf("oh my god you are error!");
return 0;
}
}
printf("yes, you are right!");

return 0;
}