花指令与SMC

软件保护措施

一般许多软件有很多保护措施,如静态保护(花指令,加密,加壳,混淆),动态保护(反调试,反虚拟机)

花指令

反汇编原理

1,线性扫描算法

2,递归行进算法

工具 反汇编算法
OllyDbg 线性扫描算法/递归行进算法(按住“Ctrl +A”组合键时)
SoftICe 线性扫描算法
WinDBG 线性扫描算法
W32Dasm 线性扫描算法
IDA pro 递归行进算法

​ 常见反汇编工具所使用的反汇编算法

而在一些指令中加入一个字符通常会引起错误的反汇编

1
2
3
4
5
6
7
8
9
10
11
12
13
__asm {


push eax
xor eax, eax
jmp label1
__emit 0xe9
label1:
pop eax
mov ebx, 1
add eax, ebx
ret
}

_emit是直接插入一个数的写法,而在汇编指令中,e9常常会被认为是jmp的前缀,而反汇编遇到时会将e9以及后面4个字节一共五个字节当作jmp指令处理

而在这个新插入的字节却不会影响原本的程序运行,因为jmp label1为无条件跳转,所以cpu永远不会执行到__emit 0xe9但却会影响到反汇编,这也起到了保护程序正常运行的目的

在反汇编窗口可以看见反编译器将其错误的解析为jmp指令

而对于这种情况,使用递归行进算法的ida可以完美识别

可以看到ida成功的将e9识别为数据

然而这只是最简单的花指令,要骗过ida,因为ida是根据分支跳转,可以把jmp替换为两条互补的条件跳转指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
	__asm {


push eax
xor eax,eax
mov eax,1
jz label1
jnz label1
__emit 0xe9
label1:
pop eax
mov ebx, 1
add eax, ebx
ret

}

修改完之后我们再放入ida中看看

可以看到这次ida没有处理成功,因为ida是根据控制指令,去到不同的分支,在不同的分支中再采用线性算法,当遇到jz,jnz时,两种情况ida都需要处理,当ida不跳转时,并紧接着来到了E9的位置并将E9当作jmp指令

SMC技术

SMC全程Self Modifying Code技术,代码自修改技术,也就是说,在一段代码执行之前,可以对其进行修改,一般用来加密核心功能逻辑,也是加壳技术的基础

比如有如下两段代码一段为初始化代码而另一段为加密后代码,用ida打开蓝色代码可以正常看到,而红色部分在ida中则是加密后的16进制数据

如如下一段代码

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include "stdafx.h"
#include <windows.h>

__declspec(naked)
void important_fun() {
__asm {

mov eax, 0x76D7AE40
push 0
push 0x6E617579
push 0x6E617578
mov ebx, esp

push 0
push ebx
push ebx
push 0
call eax
add esp, 0x0C

ret
}
}

void important_fun2() {
printf("hello, world!\n");
}


void encrypt_fun() {
unsigned char encryped_code[32] = {0};
unsigned char* ptr = (unsigned char*)important_fun;
for (int i = 0; i < 31; i++) {
encryped_code[i] = ptr[i] ^ 0x11;
printf("0x%02X, ", encryped_code[i]);
if ((i + 1) % 8 == 0) {
printf("\n");
}
}

printf("\n======================================\n");

for (int i = 0; i < 31; i++) {
printf("0x%02X, ", ptr[i]);
if ((i + 1) % 8 == 0) {
printf("\n");
}
}
}


void decrypt_fun(PVOID address) {
unsigned char* ptr = (unsigned char*)address;
for (int i = 0; i < 31; i++) {
ptr[i] = ptr[i] ^ 0x11;
printf("0x%02X, ", ptr[i]);
if ((i + 1) % 8 == 0) {
printf("\n");
}
}
}


unsigned char g_code[] = {
0xF8, 0x38, 0x12, 0x11, 0x11, 0xF8, 0x93, 0x39,
0x11, 0x11, 0xF8, 0x48, 0x39, 0x11, 0x11, 0xF8,
0x17, 0x39, 0x11, 0x11, 0xF8, 0xB4, 0x12, 0x11,
0x11, 0xF8, 0xAD, 0x16, 0x11, 0x11, 0xF8
};


void smc_test() {

PVOID codePage = VirtualAlloc(NULL, 4096, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (codePage) {
ZeroMemory(codePage, 4096);
memcpy(codePage, g_code, sizeof(g_code));
decrypt_fun(codePage);

__asm {
call codePage

}
}
}

int _tmain(int argc, _TCHAR* argv[])
{
MessageBoxA(NULL, "start", "start", MB_OK);
important_fun();
//encrypt_fun();

//smc_test();

//important_fun2();
return 0;
}

其中有一个函数encrypt_fun()

1
2
3
4
5
6
7
8
9
10
11

void encrypt_fun() {
unsigned char encryped_code[32] = {0};
unsigned char* ptr = (unsigned char*)important_fun;
for (int i = 0; i < 31; i++) {
encryped_code[i] = ptr[i] ^ 0x11;
printf("0x%02X, ", encryped_code[i]);
if ((i + 1) % 8 == 0) {
printf("\n");
}
}

这一个函数可以对important_fun进行一个加密操作

运行之后

上面为加密后的字节码,下面为加密前的字节码,而函数smc_test()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void smc_test() {

PVOID codePage = VirtualAlloc(NULL, 4096, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (codePage) {
ZeroMemory(codePage, 4096);
memcpy(codePage, g_code, sizeof(g_code));
decrypt_fun(codePage);

__asm {
call codePage

}
}
}

这段函数通过VirtualAlloc分配了一段内存,会将加密的这段指令拷贝至内存中,接着利用decrypt_fun函数进行一个解密工作,再通过call指令直接调用这段代码