菜鸡的花语就是菜就多练

☝️🤓

🙌🙌🙌🙌

感觉自己是啥都了解一点但是确实啥也不会

什么是花指令?

花指令在我的理解就是垃圾指令,在不影响真实代码中加入一些垃圾代码从而影响反汇编的正常运行,主要是用来影响逆向分析人员的的静态分析的。

简单的花指令

花指令分为两种

  • 可执行花指令
  • 不可执行花指令

可执行花指令

顾名思义就是可以执行的花指令,在程序运行时执行,并且不会改变寄存器的值,目的增加静态分析的难度。

不可执行花指令

不可执行的花指令通常情况下会反编译的时候报错,这种情况时插入了一些不完整的汇编指令,导致反编译失败。

花指令必须满足的两个特征

  • 垃圾数据必须是合法指令的一部分
  • 程序运行时,花指令必须位于实际不可执行的代码路径

原理

反汇编算法可以分为两类:

  • 递归下降算法、

从程序入口开始进行反汇编,然后对整个代码段进行扫描,反汇编扫描过程中每个遇到的每条指令。

缺点:在芬诺伊曼系结构下。无法区分数据和代码,从而导致代码段嵌入的数据解释为指令的操作码,从而导致反编译失败。

  • 线性扫描算法

强调控制流的概念,控制流根据一条指令是否被另一条指令应用决定是否对它进行反汇编,遇到非控制流的指令时顺序进行反汇编,而遇到控制流的转移的指令从转移地址开始进行反汇编,通过构造必然条件或者互补条件,使反汇编失败。

花指令的构造

永恒跳转

最简单的jump指令

1
2
3
jmp LABEL1
db junk_code;
LABEL1:

这种jump单次跳转,只能骗过线性扫描算法,会被ida识别(递归下降)

多次跳转

1
2
3
4
5
6
7
8
9
10
11
12
13
__asm {
jmp LABEL1;
_emit 68h;
LABEL1:
jmp LABEL2;
_emit 0CDh;
_emit 20h;
LABEL2:
jmp LABEL3;
_emit 0E8h;
LABEL3:
}

这种和单次一样也会被识别,可以这样改写

1
2
3
4
5
__asm {
_emit 0xE8
_emit 0xFF
//_emit 立即数:代表在这个位置插入一个数据,这里插入的是0xe8
}

其他构造类型

jnz和jz的互补跳转

1
2
3
4
5
6
 __asm {
jz Label;
jnz Label;
_emit 0xC7;
Label:

跳转指令构造花指令

1
2
3
4
5
6
7
8
9
10
11
__asm {
push ebx;
xor ebx, ebx;
test ebx, ebx;
jnz LABEL7;
jz LABEL8;
LABEL7:
_emit 0xC7;
LABEL8:
pop ebx;
}

call&ret构造花指令

1
2
3
4
5
6
7
8
9
__asm {
call LABEL9;
_emit 0x83;
LABEL9:
add dword ptr ss : [esp] , 8;
ret;
__emit 0xF3;
}

image-20250112120235482

解题方法

没有什么特别的好方法,就是区分垃圾数据就行。然后就nop掉就行

还有就是动调,我的理解就是找到最后的判断结果那下断点,然后进行F9即可。

例题

复现一下咯!

donntyousee

2025-全国大学生软件创新大赛软件系统安全赛

怎么说呢?

那道题发现是道ELF文件先常规看一下架构

1
2
3
4
5
6
7
8
bbq@ubuntu:~$ checksec chall
[*] '/home/bbq/chall'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
bbq@ubuntu:~

amd64,ida打开

image-20250111173647033

打开后发现时看见有一个ret指令,把他nop掉之后

image-20250112163601494

可以看见mian函数

image-20250112163810449

这里看一下这两个函数

这里不直接看反汇编建议,直接看流程图,这里调用了r8寄存器里的地址,我们跟踪过去看看

image-20250112164150890

但是遇到同样的ret指令,我们把它去掉就行了

image-20250112164743840

然后我们跟踪一下这个函数

image-20250112164913978

看这个函数和上面的rc4也很明显,找到密钥和密文即可,最下面的sub_405CAA函数是主要逻辑

image-20250112165037829

看见密文了

简单提取一下

image-20250112171815156

1
data=[0x25,0xCD,0x54,0xAF,0x51,0x1C,0x58,0xD3,0xA8,0x4B,0x4F,0x56,0xEC,0x83,0x5D,0xD4,0xF6,0x47,0x4A,0x6F,0xE0,0x73,0xB0,0xA5,0xA8,0xC3,0x17,0x81,0x5E,0x2B,0xF4,0xF6, 0x71,0xEA, 0x2F, 0xFF, 0xA8, 0x63, 0x99, 0x57]

image-20250112165108111

异或也是常见的了

最主要的就是找密钥了

找到在这

image-20250112170335218

去花之后,看见

image-20250112170356186

密钥存在

image-20250112170406826

但是这里有一个踩坑点,这里有一个反调试,这里看见的密钥不是最终的,而是静态编译下的密文

image-20250112170548567

这样的话,密文就找到了

1
KEY=  [0x92, 0x1C, 0x2B, 0x1F, 0xBA, 0xFB, 0xA2, 0xFF, 0x07, 0x69,0x7D, 0x77, 0x18, 0x8C]

然后正常解就行了。

这里是用了厨子

image-20250112175901750

dart{y0UD0ntL4cKg0oD3y34T0F1nDTh3B4aUtY}

怎么说呢?

Rafflesia-pcb2024

鹏程杯-2024

这是道花指令的题

由于好久没逆了,本来就菜结果跟不会了,还是跟着复现了一手

打开之后很明显的花指令

so,我们nop一手

image-20241113123537797

反编译成功后是有个base编码和加了个异或0x18的问题

但是问题肯定不会这么简单

SO,在我们动调的时候就发现了,肯定是还有东西的

函数栏里找到callback函数

image-20241113123757982

在nop一手

image-20241113134417171

遇到JZ的时候换一手就可以了

导航图动调一手就可以发现有一个换表的过程

image-20241113134240903

最后直接换表就行了

img

ko0h

2024年春秋杯网络安全联赛冬季赛

32位,无壳,打开ida

看了一眼字符串

image-20250117181900352

明显的换表,看一眼表

image-20250117181940457

但是是假的

看见函数,存在花指令

image-20250117182012763

看一眼汇编主要存在的花就是

image-20250117182059899

全部nop掉就可以发现主要的加密逻辑在下面

image-20250117182326215

跟进去之后就会发现同样存在花指令,去掉之后

image-20250117182458555

然后双击

image-20250117182558483

很明显的密文,找一下加密逻辑

image-20250117182823240

RC4

image-20250117182805435

本以为密钥是

image-20250117182859582

但是我们跟踪过去发现真正的密钥

image-20250117182947058

这里注意都是存在花指令的,用于去除即可

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
#include<stdio.h>

/*
RC4初始化函数
*/
void rc4_init(unsigned char* s, unsigned char* key, unsigned long Len_k)
{
int i = 0, j = 0;
char k[256] = { 0 };
unsigned char tmp = 0;
for (i = 0; i < 256; i++) {
s[i] = i;
k[i] = key[i % Len_k];
}
for (i = 0; i < 256; i++) {
j = (j + s[i] + k[i]) % 256;
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
}

/*
RC4加解密函数
unsigned char* Data 加解密的数据
unsigned long Len_D 加解密数据的长度
unsigned char* key 密钥
unsigned long Len_k 密钥长度
*/
void rc4_crypt(unsigned char* Data, unsigned long Len_D, unsigned char* key, unsigned long Len_k) //加解密
{
unsigned char s[256];
rc4_init(s, key, Len_k);
int i = 0, j = 0, t = 0;
unsigned long k = 0;
unsigned char tmp;
for (k = 0; k < Len_D; k++) {
i = (i + 1) % 256;
j = (j + s[i]) % 256;
tmp = s[i];
s[i] = s[j];
s[j] = tmp;
t = ((s[i] + s[j]) % 256);
Data[k] = Data[k] + s[t] ;
}
}
int main()
{
// 密钥:key ='WangDingCUPKEY!!'
//密文:src=[0xC632A2F05BD9371D,0x3AA73E7E508CA730,0x1C6B85816B58C0BA,0x9742C18A7C80F54C]
//字符串密钥
unsigned char key[] = "DDDDAAAASSSS";
unsigned long key_len = sizeof(key)-1;
//数组密钥
// unsigned char key[] = {0x92, 0x1C, 0x2B, 0x1F, 0xBA, 0xFB, 0xA2, 0xFF, 0x07, 0x69,0x7D, 0x77, 0x18, 0x8C};
// unsigned long key_len = sizeof(key);

//加解密数据
unsigned char data[] = {0x18,0x9c,0x47,0x3d,0x3b,0xe1,0x29,0x27,0x9f,0x34,0x83,0xd5,0xed,0xb5,0x6e,0x59,0x7f,0xde,0x47,0xd7,0x65,0x3f,0x7a,0x33,0x5b,0x64,0xb6,0xfa,0x94,0x55,0x87,0x42,0x20,0x6,0xc,0x69,0xfe,0x72,0xa9,0xe4,0xd1,0x7c};
//加解密
rc4_crypt(data, sizeof(data), key, key_len);

for (int i = 0; i < sizeof(data); i++)
{
printf("%c", data[i]);
}
printf("\n");
return 0;
}

参考文献:

CTF逆向Reverse 花指令介绍 and NSSCTF靶场入门题目复现_ctf逆向工程基础题-CSDN博客