花指令

0x00 概念

​ 花指令是企图隐藏掉不想被逆向工程的代码块(或其它功能)的一种方法,在真实代码中插入一些垃圾代码的同时还保证原有程序的正确执行,而程序无法很好地反编译,难以理解程序内容,达到混淆视听的效果。

0x01 分类

可执行花指令

[1] 花指令代码在程序的正常执行过程中会被执行

[2] 加大静态分析的难度,让你难以识别代码的真正意图。这种花指令可以破坏反编译的分析,使得栈指针在反编译引擎中出现异常。(当然我们知道栈指针实际上是没有问题的,只不过反编译引擎还有待完善的空间)

不可执行花指令

[1] 花指令代码在程序的正常执行过程中不会被执行

[2] 利用反汇编器线性扫描算法的缺陷使得静态分析的时候会看到一些错误的代码

例如,有不可执行花指令

image-20221102220706307

image-20221102221030274

0x02 最常见的花指令

image-20200616211207943

如何处理呢?

patch方法

我们首先将 0x00401D92 处的代码转换成数据(快捷键U)。

image-20200616211824836

然后将0x00401D94处的数据转换成代码(快捷键 E),再把0x00401D92,0x00401D93处的数据 nop 掉即可(“Edit”–“Patch program”–“Assemble”)。

知识点:E9是jmp指令对应的机器码,当反汇编器读取到E9时,接着会往下读取四个字节的数据作为跳转地址的偏移,所以才会看到错误的汇编代码。

编写方法

我们在写程序的时候嵌入_asm _emit 0E9,反编译器就会把下一条指令当做地址数据,不管下一条指令实际上的四个字节是地址数据还是操作指令。例如:

1
2
3
4
5
6
__asm { 
_emit 075h #jmp $+4
_emit 2h
_emit 0E9h
_emit 0EDh
}

​ 上面嵌入的4字节数据即可使得程序反汇编反编译出错,注意这里的75是jnz的机器码,所以要求程序执行到这里时Zflag=0。

0x03 具体例子

无花指令程序

​ 写了一个tea加密算法,我们用msvc编译,生成pdb文件方便分析,然后ida打开。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdint.h>

void encrypt(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1], sum = 0, i; /* set up */
uint32_t delta = 0x9e3779b9; /* a key schedule constant */
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3]; /* cache key */
for (i = 0; i < 32; i++) { /* basic cycle start */
sum += delta;
v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
} /* end cycle */
v[0] = v0; v[1] = v1;
}
int main() {
int a = 1;
uint32_t flag[] = {1234, 5678};
uint32_t key[] = {9, 9, 9, 9};
encrypt(flag, key);
printf("%d, %d", flag[0], flag[1]);
return 0;
}

知识点:与Linux系列操作系统不同,Windows原生环境不提供类似gcc,Clang的C/C++语言源程序编译运行工具链。运行在Windows上的IDE(集成开发环境),比如CodeBlocks之类,一般都使用MinGW(Minimalist GNU for Windows)配置模拟Linux下的开发环境来进行Windows下的开发。但是在Windows下,与开发环境以及code编辑器协同更好的还是MSVC(Microsoft Visual C/C++)编译器。对于灵活程度更高的code编辑器,我们可以将Microsoft的Visual C/C++编译器下载并集成到code中。MSVC编译器工具链主要由cl.exelink.exe构成。其中:

  1. cl.exe用于控制在 Microsoft C/C++的编译器和链接器
  2. link.exe 将通用对象文件格式 (COFF) 对象文件和库链接起来,以创建可执行 (.exe) 文件或动态链接库 (DLL)

​ 可以看到main函数和源代码差别不大。

image-20221102225324518

​ 可以看到 encrypt 函数也和源代码基本一样。

image-20221102225447090

加入花指令

加入两个花指令,分别为:

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

#define JUNKCODE __asm{
__asm jmp junk1
__asm __emit 0x12
__asm junk2:
__asm ret
__asm __emit 0x34
__asm junk1:
__asm call junk2
}

void encrypt(uint32_t* v, uint32_t* k) {
uint32_t v0 = v[0], v1 = v[1], sum = 0, i; /* set up */
uint32_t delta = 0x9e3779b9; /* a key schedule constant */
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3]; /* cache key */
for (i = 0; i < 32; i++) { /* basic cycle start */
JUNKCODE
sum += delta;
v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
} /* end cycle */
v[0] = v0; v[1] = v1;
}
int main() {
int a = 1;
uint32_t flag[] = { 1234,5678 };
uint32_t key[] = { 9,9,9,9 };
__asm {
_emit 075h
_emit 2h
_emit 0E9h
_emit 0EDh
}
encrypt(flag, key);
printf("%d,%d", flag[0], flag[1]);
return 0;
}

花指令1

​ 首先ida打开查看main函数:

image-20221102230737829

​ 虽然还是反编译成功了,但是可以看到内容已经完全错误,我们再来看main函数的汇编代码,也就是我们加入的第一个花指令。

image-20221102231002759

​ 可以看到这里出现的红色就是我们的第一处花指令,patch方法同上,我们主要看第二处花指令。

花指令2

​ F5反编译直接报错。

image-20221102231454933

​ 继续跟进到花指令的反汇编代码处:

image-20221102231750684

去除方法

​ 上述连续的可执行花指令的去除方法特别简单,直接整块nop掉即可。

​ 但是真正的复杂程序里这种花指令的数量很多,人工nop很耗时,同时极容易出错,所以我们真正应该掌握的是自动化的方法,编写脚本匹配花指令模板进行去除。

​ 以下是我们要去除的花指令模板:

1
2
3
4
5
6
7
8
9
#define JUNKCODE __asm{
__asm jmp junk1
__asm __emit 0x12
__asm junk2:
__asm ret
__asm __emit 0x34
__asm junk1:
__asm call junk2
}

​ 如下是 idapython 编写的 ida 匹配模板去除花指令脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def nop(addr, endaddr):
while addr < endaddr:
PatchByte(addr, 0x90)
addr += 1

def undefine(addr, endaddr):
while addr < endaddr:
MakeUnkn(addr, 0)
addr += 1

def dejunkcode(addr, endaddr):
while addr < endaddr:
MakeCode(addr)
# 匹配模板
if GetMnem(addr) == 'jmp' and GetOperandValue(addr, 0) == addr + 5 and Byte(addr+2) == 0x12:
next = addr + 10
nop(addr, next)
addr = next
continue
addr += ItemSize(addr)

dejunkcode(0x00411820, 0x00411957)
undefine(0x00411820, 0x00411957)
MakeFunction(0x00411820, -1)

重要函数解析

1
2
3
4
5
6
7
8
MakeCode(ea) #分析代码区,相当于ida快捷键C
ItemSize(ea) #获取指令或数据长度
GetMnem(ea) #得到addr地址的操作码
GetOperandValue(ea,n) #返回指令的操作数的被解析过的值
PatchByte(ea, value) #修改程序字节
Byte(ea) #将地址解释为Byte
MakeUnkn(ea,0) #MakeCode的反过程,相当于ida快捷键U
MakeFunction(ea,end) #将有begin到end的指令转换成一个函数。如果end被指定为BADADDR(-1),IDA会尝试通过定位函数的返回指令,来自动确定该函数的结束地址

运行脚本

​ 在ida中运行脚本,然后可以发现那段花指令已经成功nop掉了,按下f5反编译:

image-20221102233747892

注:上述都是某师傅博客中写的,自己本来想试试来着,但是由于visual studio 是2022版的搞内联汇编很麻烦(主要是懒,所以。。

特殊花指令

​ 还有一类较特殊的花指令,它不会影响反汇编和反编译,只是单纯的混淆视听。譬如我们程序需要将某个特定值(这里假设是0x12)压栈,正常操作应该是:

1
push    0x12

​ 加入花指令后,这个操作可以变成这样:

1
2
push    0x26
xor dword ptr ss:[esp], 0x34

​ 我们很容易可以看出来这两种写法是等效的,当我们要压栈的数据是一些很明显的特征值的时候,这种花指令可以很好的保护我们的特征值,防止算法特征被迅速识别。

[1] https://www.anquanke.com/post/id/208682

[2] https://blog.csdn.net/u011642058/article/details/114757503

留言

2022-11-02

© 2024 wd-z711

⬆︎TOP