18-有效负载加密 - RC4
简介
RC4 是一种速度快且高效的流密码,它也是一种双向加密算法,可以使用同一个函数进行加密和解密。市面上有许多 RC4 的 C 语言实现,但本模块将演示三种执行 RC4 加密的方法。
请注意,本模块的目的不是深入探讨 RC4 算法的工作原理,也不需要完全深入理解它。相反,目标是加密有效载荷以规避检测。
RC4 加密方法 1
该方法使用在 此处 找到的 RC4 实现,因为该实现稳定且代码编写良好。共有两个函数 rc4Init
和 rc4Cipher
,它们分别用于初始化 rc4context
结构并执行 RC4 加密。
typedef struct
{
unsigned int i;
unsigned int j;
unsigned char s[256];
} Rc4Context;
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 数组
for (i = 0; i < 256; i++)
{
context->s[i] = (unsigned char) 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;
}
}
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 流对输入数据进行异或运算
*output = *input ^ s[(s[i] + s[j]) % 256];
// 增加数据指针
input++;
output++;
}
// 剩余要处理的字节数
length--;
}
// 保存上下文
context->i = i;
context->j = j;
}
RC4 加密
下面的代码展示了 rc4Init
和 rc4Cipher
函数是如何用来加密数据的。
// 初始化
Rc4Context ctx = { 0 };
// 加密用密钥
unsigned char* key = "maldev123";
rc4Init(&ctx, key, strlen(key));
// 加密 //
// plaintext - 待加密的数据
// ciphertext - 用来存储加密后数据的缓冲区
rc4Cipher(&ctx, plaintext, ciphertext, sizeof(plaintext));
RC4 解密
以下代码演示了如何使用 rc4Init
和 rc4Cipher
函数来解密有效负载:
// 初始化
Rc4Context ctx = { 0 };
// 用于解密的密钥
unsigned char* key = "maldev123";
rc4Init(&ctx, key, strlen(key));
// 解密 //
// ciphertext - 要解密的加密有效负载
// plaintext - 用于存储输出的明文数据的缓冲区
rc4Cipher(&ctx, ciphertext, plaintext, sizeof(ciphertext));
RC4 加密 - 方法 2
未记录在案的 Windows NTAPI SystemFunction032
提供了 RC4 算法的一种更快、更小的实现。有关此 API 的更多信息,请参阅此 Wine API 页面。
SystemFunction032
文档页面声明函数 SystemFunction032
接受两个 USTRING
类型的参数。
NTSTATUS SystemFunction032
(
struct ustring* data,
const struct ustring* key
)
USTRING 结构
很遗憾,由于这是个未公开文档的 API,USTRING
的结构未知。但是,通过其他研究,可以在 wine/crypt.h 中找到 USTRING
结构定义。结构如下所示。有关此结构的更深入信息,请访问 Geoff Chappell。
typedef struct
{
DWORD Length; // 要加密/解密的数据的大小
DWORD MaximumLength; // 要加密/解密的数据的最大大小,尽管此大小通常与 Length 相同(USTRING.Length = USTRING.MaximumLength = X)
PVOID Buffer; // 要加密/解密的数据的基地址
} USTRING;
在了解到 USTRING
结构后,就可以使用 SystemFunction032
函数了。
获取 SystemFunction032 地址
要使用 SystemFunction032
,必须先检索其地址。由于 SystemFunction032
是从 advapi32.dll
导出的,因此必须使用 LoadLibrary
将 DLL 加载到进程中。该函数调用的返回值可直接用于 GetProcAddress
。
一旦成功检索到 SystemFunction032
地址,就应该将其类型转换为与 Wine API 页面 上引用的定义匹配的函数指针。但是,可以从 GetProcAddress
直接转换返回的地址。所有这些都展示在下方的代码段中。
fnSystemFunction032 SystemFunction032 = (fnSystemFunction032)GetProcAddress(LoadLibraryA("Advapi32"), "SystemFunction032");
SystemFunction032
的函数指针定义为 fnSystemFunction032
数据类型,如下所示:
typedef NTSTATUS(NTAPI* fnSystemFunction032)(
struct USTRING* Data, // 存储要加密/解密的缓冲区信息的 USTRING 类型结构
struct USTRING* Key // 存储加密/解密期间使用的密钥信息的 USTRING 类型结构
);
SystemFunction032 用法
以下代码片段提供了通过 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
};
fnSystemFunction032 SystemFunction032 = (fnSystemFunction032)GetProcAddress(LoadLibraryA("Advapi32"), "SystemFunction032");
if ((STATUS = SystemFunction032(&Data, &Key)) != 0x0) {
printf("[!] SystemFunction032 失败,错误:0x%0.8X \n", STATUS);
return FALSE;
}
return TRUE;
}
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
};
fnSystemFunction033 SystemFunction033 = (fnSystemFunction033)GetProcAddress(LoadLibraryA("Advapi32"), "SystemFunction033");
if ((STATUS = SystemFunction033(&Data, &Key)) != 0x0) {
printf("[!] SystemFunction033 调用失败,错误代码:0x%0.8X \n", STATUS);
return FALSE;
}
return TRUE;
}
加密/解密密钥格式
本模块和其他加密模块中的代码片段都使用一种有效的方法来表示加密/解密密钥。但是,密钥可以用多种不同的方式表示,这一点很重要。
请注意,将明文密钥硬编码到二进制文件中被认为是一种不良做法,并且在分析恶意软件时很容易被提取出来。未来的模块将提供解决方案,以确保无法轻松获取密钥。
// 方法 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'
};