模块 18 - Payload 加密 - RC4
恶意软件开发课程 - Payload 加密 - RC4
模块 18 - Payload 加密 - RC4#
[!IMPORTANT] 本知识库声明
本知识库由本人整理自互联网 MalDev Academy 泄露资源,并由本人手动翻译为中文,过程中增加了大量关键技术提示与实践心得。
- 内容完整性:并未修改任何核心代码与技术逻辑,仅做汉化与注释加强。
- 版权归属:原始知识产权归原作者/官方所有。
- 支持正版:本仓库仅供内部学习交流,如果您有经济能力,请务必支持正版课程。
- 权利申诉:如相关内容侵犯了您的权益,请联系我,我将立即核实并删除。
Payload 加密 - RC4#
简介#
RC4 是一种快速高效的流加密算法,也是一种双向加密算法,允许使用相同的函数进行加密和解密。网上有多种 RC4 的 C 语言实现,但本模块将演示三种执行 RC4 加密的方法。
需要注意的是,深入研究 RC4 算法的工作原理并不是本模块的目标,也不需要完全深入理解它。相反,目标是加密 payload 以规避检测。
RC4 加密 - 方法 1#
此方法使用这里 ↗找到的 RC4 实现,因其稳定性和代码质量良好而被选择。有两个函数 rc4Init 和 rc4Cipher,分别用于初始化 Rc4Context 结构和执行 RC4 加密。
RC4 上下文结构#
typedef struct
{
unsigned int i; // RC4 状态变量 i
unsigned int j; // RC4 状态变量 j
unsigned char s[256]; // RC4 的 S-box (置换表)
} Rc4Context;cRC4 初始化函数#
void rc4Init(Rc4Context* context, const unsigned char* key, size_t length)
{
unsigned int i;
unsigned int j;
unsigned char temp;
// 检查参数有效性
if (context == NULL || key == NULL)
return ERROR_INVALID_PARAMETER;
// 清空上下文
context->i = 0;
context->j = 0;
// 使用恒等排列初始化 S 数组
// S[0]=0, S[1]=1, S[2]=2, ..., S[255]=255
for (i = 0; i < 256; i++)
{
context->s[i] = i;
}
// 处理 S 数组 256 次迭代
for (i = 0, j = 0; i < 256; i++)
{
// 使用提供的密钥随机化排列
j = (j + context->s[i] + key[i % length]) % 256;
// 交换 S[i] 和 S[j] 的值
temp = context->s[i];
context->s[i] = context->s[j];
context->s[j] = temp;
}
}c[!IMPORTANT] 📚 知识扩展 - RC4 初始化过程 (KSA - Key Scheduling Algorithm)
RC4 初始化分为两个阶段:
阶段 1: 初始化 S-box
cfor (i = 0; i < 256; i++) s[i] = i; // S = [0, 1, 2, ..., 255]阶段 2: 密钥调度
cfor (i = 0, j = 0; i < 256; i++) { j = (j + s[i] + key[i % length]) % 256; swap(s[i], s[j]); // 交换 S[i] 和 S[j] }为什么要这样做?
- 将密钥的影响”扩散”到整个 S-box
- 即使密钥很短(如 8 字节),也能生成 256 字节的复杂状态
- 交换操作确保 S-box 成为 0-255 的一个随机排列
示例 (简化):
plaintext密钥: [0xAA, 0xBB, 0xCC] 初始 S: [0, 1, 2, 3, 4, ...] 第 1 步: j = (0 + 0 + 0xAA) % 256 = 170, 交换 S[0] 和 S[170] 第 2 步: j = (170 + 1 + 0xBB) % 256 = 76, 交换 S[1] 和 S[76] ... 最终 S: [170, 76, 219, ...] (完全随机化的排列)
RC4 加密/解密函数#
void rc4Cipher(Rc4Context* context, const unsigned char* input, unsigned char* output, size_t length){
unsigned char temp;
// 恢复上下文状态
unsigned int i = context->i;
unsigned int j = context->j;
unsigned char* s = context->s;
// 加密循环
while (length > 0)
{
// 调整索引
i = (i + 1) % 256;
j = (j + s[i]) % 256;
// 交换 S[i] 和 S[j] 的值
temp = s[i];
s[i] = s[j];
s[j] = temp;
// 输入和输出都有效?
if (input != NULL && output != NULL)
{
// 将输入数据与 RC4 流进行 XOR
*output = *input ^ s[(s[i] + s[j]) % 256];
// 递增数据指针
input++;
output++;
}
// 处理剩余字节
length--;
}
// 保存上下文状态
context->i = i;
context->j = j;
}cRC4 加密示例#
以下代码展示了如何使用 rc4Init 和 rc4Cipher 函数加密 payload。
// 初始化 RC4 上下文结构
Rc4Context ctx = { 0 };
// 用于加密的密钥
unsigned char* key = "maldev123";
rc4Init(&ctx, key, strlen(key)); // 使用密钥初始化
// 加密 //
// plaintext - 要加密的 payload
// ciphertext - 用于存储输出加密数据的缓冲区
rc4Cipher(&ctx, plaintext, ciphertext, sizeof(plaintext));cRC4 解密示例#
以下代码展示了如何使用 rc4Init 和 rc4Cipher 函数解密 payload。
// 初始化 RC4 上下文结构
Rc4Context ctx = { 0 };
// 用于解密的密钥
unsigned char* key = "maldev123";
rc4Init(&ctx, key, strlen(key)); // 使用相同的密钥初始化
// 解密 //
// ciphertext - 要解密的加密 payload
// plaintext - 用于存储输出明文数据的缓冲区
rc4Cipher(&ctx, ciphertext, plaintext, sizeof(ciphertext));cRC4 加密 - 方法 2#
未公开的 Windows NTAPI SystemFunction032 提供了 RC4 算法的更快、更小的实现。有关此 API 的更多信息可以在这个 Wine API 页面 ↗上找到。
[!IMPORTANT] 📚 知识扩展 - 未公开的 Windows API
什么是未公开 API?
- Windows 包含许多未公开的函数,不在官方 MSDN 文档中
- 这些函数被操作系统内部使用,但没有公开给开发者
SystemFunction032就是这样一个函数为什么使用未公开 API?
- ✅ 更小的二进制文件 - 不需要包含第三方库
- ✅ 更快的性能 - 由微软优化的原生实现
- ✅ 隐蔽性 - 安全工具可能不熟悉这些 API
风险:
- ❌ 未来 Windows 版本可能删除或修改
- ❌ 行为可能在不同 Windows 版本间变化
- ❌ 缺乏官方文档和支持
SystemFunction032 函数签名#
文档页面指出,函数 SystemFunction032 接受两个 USTRING 类型的参数。
NTSTATUS SystemFunction032
(
struct ustring* data, // 要加密/解密的数据
const struct ustring* key // 加密/解密密钥
)cUSTRING 结构#
不幸的是,由于这是一个未公开的 API,USTRING 的结构定义是未知的。但通过额外的研究,可以在 wine/crypt.h ↗ 中找到 USTRING 结构定义。该结构如下所示。有关此结构的更深入信息可以在 Geoff Chappell ↗ 找到。
typedef struct
{
DWORD Length; // 要加密/解密的数据大小
DWORD MaximumLength; // 数据的最大大小,通常与 Length 相同 (USTRING.Length = USTRING.MaximumLength = X)
PVOID Buffer; // 要加密/解密的数据的基地址
} USTRING;c现在知道了 USTRING 结构,就可以使用 SystemFunction032 函数了。
获取 SystemFunction032 的地址#
要使用 SystemFunction032,必须首先获取其地址。由于 SystemFunction032 从 advapi32.dll 导出,因此必须使用 LoadLibrary 将 DLL 加载到进程中。函数调用的返回值可以直接在 GetProcAddress 中使用。
成功获取 SystemFunction032 的地址后,应将其类型转换为与之前引用的 Wine API 页面 ↗上找到的定义匹配的函数指针。但是,返回的地址可以直接从 GetProcAddress 进行转换。所有这些都在下面的代码片段中演示。
fnSystemFunction032 SystemFunction032 = (fnSystemFunction032) GetProcAddress(LoadLibraryA("Advapi32"), "SystemFunction032");cSystemFunction032 的函数指针定义为 fnSystemFunction032 数据类型,如下所示。
typedef NTSTATUS(NTAPI* fnSystemFunction032)(
struct USTRING* Data, // USTRING 类型的结构,包含要加密/解密的缓冲区信息
struct USTRING* Key // USTRING 类型的结构,包含加密/解密时使用的密钥信息
);cSystemFunction032 使用方法#
以下代码片段提供了一个工作代码示例,利用 SystemFunction032 函数执行 RC4 加密和解密。
typedef struct
{
DWORD Length;
DWORD MaximumLength;
PVOID Buffer;
} USTRING;
typedef NTSTATUS(NTAPI* fnSystemFunction032)(
struct USTRING* Data,
struct USTRING* Key
);
/*
调用 SystemFunction032 的辅助函数
* pRc4Key - 用于加密/解密的 RC4 密钥
* pPayloadData - 要加密/解密的缓冲区的基地址
* dwRc4KeySize - pRc4Key 的大小(参数 1)
* sPayloadSize - pPayloadData 的大小(参数 2)
*/
BOOL Rc4EncryptionViaSystemFunc032(IN PBYTE pRc4Key, IN PBYTE pPayloadData, IN DWORD dwRc4KeySize, IN DWORD sPayloadSize) {
NTSTATUS STATUS = NULL;
// 初始化数据结构
USTRING Data = {
.Buffer = pPayloadData, // 指向要加密的数据
.Length = sPayloadSize, // 数据长度
.MaximumLength = sPayloadSize // 缓冲区大小
};
// 初始化密钥结构
USTRING Key = {
.Buffer = pRc4Key, // 指向密钥
.Length = dwRc4KeySize, // 密钥长度
.MaximumLength = dwRc4KeySize // 密钥缓冲区大小
};
// 动态获取 SystemFunction032 函数地址
fnSystemFunction032 SystemFunction032 = (fnSystemFunction032)GetProcAddress(LoadLibraryA("Advapi32"), "SystemFunction032");
// 调用函数进行加密/解密
if ((STATUS = SystemFunction032(&Data, &Key)) != 0x0) {
printf("[!] SystemFunction032 FAILED With Error: 0x%0.8X \n", STATUS);
return FALSE;
}
return TRUE;
}c[!IMPORTANT] 📚 知识扩展 - SystemFunction032 的特点
与方法 1 的对比:
特性 方法 1 (自实现) 方法 2 (SystemFunction032) 代码量 ~100 行 ~30 行 依赖 无 advapi32.dll 性能 良好 优秀(原生实现) 可控性 完全可控 Windows 控制 隐蔽性 中等 较高(非常见 API) 重要提示:
SystemFunction032会就地加密/解密(直接修改Data.Buffer)- 加密和解密使用同一个函数
- 不需要维护
Rc4Context结构
RC4 加密 - 方法 3#
实现 RC4 算法的另一种方式是使用 SystemFunction033,它接受与之前展示的 SystemFunction032 函数相同的参数。
typedef struct
{
DWORD Length;
DWORD MaximumLength;
PVOID Buffer;
} USTRING;
typedef NTSTATUS(NTAPI* fnSystemFunction033)(
struct USTRING* Data,
struct USTRING* Key
);
/*
调用 SystemFunction033 的辅助函数
* pRc4Key - 用于加密/解密的 RC4 密钥
* pPayloadData - 要加密/解密的缓冲区的基地址
* dwRc4KeySize - pRc4Key 的大小(参数 1)
* sPayloadSize - pPayloadData 的大小(参数 2)
*/
BOOL Rc4EncryptionViSystemFunc033(IN PBYTE pRc4Key, IN PBYTE pPayloadData, IN DWORD dwRc4KeySize, IN DWORD sPayloadSize) {
NTSTATUS STATUS = NULL;
// 初始化密钥结构
USTRING Key = {
.Buffer = pRc4Key,
.Length = dwRc4KeySize,
.MaximumLength = dwRc4KeySize
};
// 初始化数据结构
USTRING Data = {
.Buffer = pPayloadData,
.Length = sPayloadSize,
.MaximumLength = sPayloadSize
};
// 动态获取 SystemFunction033 函数地址
fnSystemFunction033 SystemFunction033 = (fnSystemFunction033)GetProcAddress(LoadLibraryA("Advapi32"), "SystemFunction033");
// 调用函数进行加密/解密
if ((STATUS = SystemFunction033(&Data, &Key)) != 0x0) {
printf("[!] SystemFunction033 FAILED With Error: 0x%0.8X \n", STATUS);
return FALSE;
}
return TRUE;
}
c加密/解密密钥格式#
本模块和其他加密模块中的代码片段使用了表示加密/解密密钥的一种有效方式。但是,重要的是要知道密钥可以用几种不同的方式表示。
请注意,将明文密钥硬编码到二进制文件中被认为是不好的做法,在分析恶意软件时很容易被提取。未来的模块将提供解决方案以确保密钥不会被轻易获取。
// 方法 1 - 字符串形式
unsigned char* key = "maldev123";
// 方法 2 - 十六进制字节数组
// 这是 'maldev123' 的十六进制表示
unsigned char key[] = {
0x6D, 0x61, 0x6C, 0x64, 0x65, 0x76, 0x31, 0x32, 0x33
};
// 方法 3 - 十六进制转义序列
// 这是 'maldev123' 的十六进制/字符串形式(十六进制转义序列)
unsigned char* key = "\x6D\x61\x6C\x64\x65\x76\x31\x32\x33";
// 方法 4 - 更好的方法(通过栈字符串)
// 这是 'maldev123' 的字符数组表示
unsigned char key[] = {
'm', 'a', 'l', 'd', 'e', 'v', '1', '2', '3'
};c[!WARNING] ⚠️ 密钥硬编码的安全风险
为什么硬编码密钥是危险的?
静态分析可见
plaintext使用 strings 命令: $ strings malware.exe | grep -i "maldev" maldev123 ← 密钥暴露!内存转储可见
- 调试器可以轻松找到字符串常量
- 内存取证工具可以提取密钥
逆向分析容易
- IDA Pro / Ghidra 会高亮显示字符串
- 交叉引用功能可快速定位密钥使用位置
更安全的替代方案:
✅ 栈字符串 (方法 4)
cunsigned char key[] = { 'm', 'a', 'l', 'd', 'e', 'v', '1', '2', '3' }; // 优势: 不会出现在 .rdata 段,稍微难找一些✅ 运行时解密密钥
cunsigned char encrypted_key[] = { 0xE7, 0xC3, 0xCF, ... }; // 在运行时用另一个算法解密这个密钥 decrypt_key(encrypted_key);✅ 从环境派生密钥
c// 基于计算机名、用户名等生成密钥 generate_key_from_computer_name();✅ 从网络获取密钥
c// 从 C2 服务器下载密钥 fetch_key_from_server();
三种 RC4 方法对比#
| 特性 | 方法 1 (自实现) | 方法 2 (SystemFunc032) | 方法 3 (SystemFunc033) |
|---|---|---|---|
| 代码量 | 大 (~100 行) | 小 (~30 行) | 小 (~30 行) |
| 性能 | 良好 | 优秀 | 优秀 |
| 依赖 | 无外部依赖 | advapi32.dll | advapi32.dll |
| 可移植性 | 高 | 仅 Windows | 仅 Windows |
| 隐蔽性 | 中等 | 较高 | 较高 |
| 维护性 | 完全可控 | 依赖 Windows | 依赖 Windows |
| 推荐度 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
总结#
本模块展示了三种实现 RC4 加密的方法:
- 自定义实现 - 完全可控,代码较长
- SystemFunction032 - Windows 原生,简洁高效 (推荐)
- SystemFunction033 - 与方法 2 类似的替代方案
RC4 比 XOR 更安全,比 AES 更简单,是恶意软件开发中的常见选择。然而,对于需要高安全性的场景,仍建议使用 AES256。
[!IMPORTANT] 下一步学习:
在下一个模块中,我们将学习 AES 加密,这是目前最安全的对称加密算法之一!
RC4 vs AES 预告:
算法 安全性 速度 代码复杂度 实战应用 RC4 ⭐⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐ 中等价值 payload AES ⭐⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐ 高价值 payload