SMC技术

本文最后更新于:2023年10月12日 下午

什么是SMC

SMC,即Self Modifying Code,动态代码加密技术,指通过修改代码或数据,阻止别人直接静态分析,然后在动态运行程序时对代码进行解密,达到程序正常运行的效果,而计算机病毒通常也会采用SMC技术动态修改内存中的可执行代码来达到变形或对代码加密的目的,从而躲过杀毒软件的查杀或者迷惑反病毒工作者对代码进行分析。通常来说,SMC使用汇编去写会比较好,因为它涉及更改机器码,但SMC也可以直接通过C、C++来实现。

注意,如果使用VS20XX来实现SMC,需要将禁用优化,开启固定基址,关闭随机基址,关闭数据保护,并且选择release X64去编译

以下的伪代码演示了一种SMC技术的典型应用:

1
2
3
4
5
6
IF .运行条件满足
CALL DecryptProc (Address of MyProc);对某个函数代码解密
........
CALL MyProc ;调用这个函数
........
CALL EncryptProc (Address of MyProc);再对代码进行加密,防止程序被Dump

PE文件

在程序中使用SMC最简单的方法就是修改(或加密)整个数据段或代码段,而想要修改段,我们就要了解PE文件结构, Microsoft为它的32位Windows系统设计了一种全新的可执行文件格式,被成为“Portable Executable”,也就是PE格式

img

位于文件最开始部位的是一个MS-DOS头部和一段DOS stub代码,在PE文件中保留这一部分是为了DOS和Windows系统共存那一段时期设计的,当程序运行在DOS系统时,DOS系统按照DOS可执行文件的格式调用DOS stub代码,一个典型的DOS stub代码就是在控制台上输出一行提示:“This program cannot be run in MS-DOS mode”,当然不同的编译器产生的DOS stub代码也各不相同。曾经有一段时间很流行一种既可以在DOS系统上运行,又可以在Windows上运行的程序,其原理就是人为地替换这段DOS stub代码。紧跟在DOS stub代码之后的就是PE文件的内容了,首先是一个PE文件标志,这个标志有4个字节,也就是“PE/0/0”。这之后紧接着PE文件头(PE Header)和可选头部(Optional Header,也可以理解为这个PE文件的一些选项和参数),这两个头结构存放PE文件的很多重要信息,比如文件包含的段(Sections)数、时间戳、装入基址和程序入口点等信息。这些之后是所有的段头部,段头部之后跟随着所有的段实体。PE文件的尾部还可能包含其它一些混杂的信息,包括重分配信息、调试符号表信息、行号信息等等,这些信息并不是一个PE文件必须的部分,比如正常发布的Release版本的程序就没有调试符号表信息和行号信息。

PE文件的段,是可以新增的,我们可以通过如下代码去新增一个段:

1
2
3
4
5
6
7
8
9
10
11
#pragma code_seg(".ddd")
void abc()
{
cout << "WIN";
}
void d()
{
;
}
#pragma code_seg()
#pragma comment(linker, "/SECTION:.ddd,ERW")

值得注意的是,段必须要设置成可读写的情况,这样我们才能去修改它

在新增了一个段后,我们要想加密它,就得先找到它,即寻址,网上告诉了我们很多寻址操作,但最简单的莫过于直接指针赋值

1
2
3
4
5
6
7
8
9
10
11
12
char* b1 = (char*)abc;
char* c1 = (char*)d;
int i = 0;
for (; b1 < c1; b1++)
{
i++;
}
void* a1 = (char*)abc;
for (int i = 0; i < 32; i++)
{
*((BYTE*)a1 + i) ^= key;
}

直接指针赋值可以找到它的地址,然后我这里的加密选择的是最简单无脑的异或,当我们异或后再跳转到该函数是,会发生报错

img

我们可以查看一下为什么会有异常,分别将异或前和异或后的机器码输出出来看看,我们会发现:img

机器码发生了变化,这直接导致代码被改变,无法被识别而报错

明白了这个,我们就可以去写一个简单的SMC了

代码

直接上代码:

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

using namespace std;

#pragma code_seg(".ddd")
void abc()
{
cout << "WIN";
}
void d()
{
;
}
#pragma code_seg()
#pragma comment(linker, "/SECTION:.ddd,ERW")


int main()
{
int key;
cout << "input you key:" << endl;
cin >> key;
char* b1 = (char*)abc;
char* c1 = (char*)d;
int i = 0;
for (; b1 < c1; b1++)
{
i++;
}
void* a1 = (char*)abc;
for (int i = 0; i < 32; i++)
{
*((BYTE*)a1 + i) ^= key;
}
abc();
system("PAUSE");
}

这个代码编译出来的程序,是还未经加密的,所以直接解密无法运行,我们要手改一下他的机器码,把他的机器码加密一下

打开StudyPE+,查看段地址

img

然后在winhex中寻找到相应的地址,将机器码改掉,我这里选择的是与0x2D异或,这样,0x2D就是我的key

img

img

修改完数据后保存,打开程序,输入key,发现程序正常运行了

img
整挺好

本文始发于微信公众号(看雪学院):SMC技术浅析


SMC技术
https://huajien.gitee.io/2023/8a4de02f/
作者
HUAJI
发布于
2023年10月10日
许可协议