PE文件结构//
自修改代码
Self-Modifying Code
自修改代码(Self-Modifying Code),指在一段代码执行前对它进行修改。把代码以加密的形式保存在可执行文件中(或静态资源中),然后在程序执行的时候进行动态解密。这样我们在采用静态分析时,看到的都是加密的内容,从而减缓甚至阻止静态分析。
原理与示例
SMC思路:
1 2 3 4 5 6 7
| if (运行条件满足) { DecryptProc(Address of Check)
Check();
EncryptProc(Address of Check) }
|
SMC静态分析对抗示例:
正常程序:
1 2 3 4 5 6 7 8 9 10
| #include <stdio.h> int check(int in) { return in == 12345; } int main() { int input; scanf("%d", &input); if (check(input)) printf("good!"); }
|
在 VS 中 Release 32 位下编译(关闭随机基址),放入 IDA 中查看:
1 2 3 4 5 6 7
| unsigned char _check__YAHH_Z[] = { 0x55, 0x8B, 0xEC, 0x51, 0x81, 0x7D, 0x08, 0x39, 0x30, 0x00, 0x00, 0x75, 0x09, 0xC7, 0x45, 0xFC, 0x01, 0x00, 0x00, 0x00, 0xEB, 0x07, 0xC7, 0x45, 0xFC, 0x00, 0x00, 0x00, 0x00, 0x8B, 0x45, 0xFC, 0x8B, 0xE5, 0x5D, 0xC3 };
|
现在我们想把 check 函数保护起来,先把其机器码摘出来进行加密:
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include <stdio.h> unsigned char check[] = { 0x55, 0x8B, 0xEC, 0x33, 0xC0, 0x81, 0x7D, 0x08, 0x39, 0x30, 0x00, 0x00, 0x0F, 0x94, 0xC0, 0x5D, 0xC3 }; int main() { for (int i = 0; i < sizeof(check); i++) { check[i] ^= 0x90; printf("%02x ", check[i]); } }
|
加密后:
1
| c5 1b 7c c1 11 ed 98 a9 a0 90 90 e5 99 57 d5 6c 91 90 90 90 7b 97 57 d5 6c 90 90 90 90 1b d5 6c 1b 75 cd 53
|
修改初始代码:
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 <windows.h> int check(int in) { return in == 12345; }
void decrypt() { DWORD old; VirtualProtect(check, 4096, PAGE_EXECUTE_READWRITE, &old); for (int i = 0; i < 17; i++) { *((char*)check + i) ^= 0x90; } VirtualProtect(check, 4096, old, NULL); } int main() { int input; scanf("%d", &input); decrypt(); if (check(input)) printf("good!"); }
|
重新编译后,在 16 进制编辑器中找到并修改 check 函数的机器码为加密后的数据
一个具备 SMC 属性的可执行文件已经构造完成了,现在再用 IDA 进行分析:
发现原始逻辑已经被很好地隐藏了。
另一种常见的实现方法是通过新增一个具备 RWX 属性的程序段,将需要保护的代码书写在其中,这样就可以避免调用 VirtualAlloc / VirtualProtect / mprotect 这类 API 来暴露 SMC 的意图。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <stdio.h> #pragma code_seg(".qaq") int check(int in) { return in == 12345; } #pragma code_seg() #pragma comment(linker, "/SECTION:.qaq,ERW")
void decrypt() { for (int i = 0; i < 17; i++) *((char*)check + i) ^= 0x90; } int main() { int input; scanf("%d", &input); decrypt(); if (check(input)) printf("good!"); }
|
可以在 IDA 中看到新增了一个段:
对抗思路
能动态调试最好直接动态调试,因为在程序运行的某一时刻,它一定是解密完成的,这时也就暴露了,使用动态分析运行到这一时刻即可过掉保护。
其次是根据静态分析获得解密算法,写出解密脚本提前解密这段代码。
解密得到的机器码可以通过 IDAPython 的 patch_byte 接口很方便地写回。