跳至主要內容

18-有效负载加密 - RC4

Maldevacademy大约 6 分钟安全开发

简介

RC4 是一种速度快且高效的流密码,它也是一种双向加密算法,可以使用同一个函数进行加密和解密。市面上有许多 RC4 的 C 语言实现,但本模块将演示三种执行 RC4 加密的方法。

请注意,本模块的目的不是深入探讨 RC4 算法的工作原理,也不需要完全深入理解它。相反,目标是加密有效载荷以规避检测。

RC4 加密方法 1

该方法使用在 此处open in new window 找到的 RC4 实现,因为该实现稳定且代码编写良好。共有两个函数 rc4Initrc4Cipher,它们分别用于初始化 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 加密

下面的代码展示了 rc4Initrc4Cipher 函数是如何用来加密数据的。

	// 初始化
	Rc4Context ctx = { 0 };

	// 加密用密钥
	unsigned char* key = "maldev123";
	rc4Init(&ctx, key, strlen(key));

	// 加密 //
	// plaintext - 待加密的数据
	// ciphertext - 用来存储加密后数据的缓冲区
	rc4Cipher(&ctx, plaintext, ciphertext, sizeof(plaintext));

RC4 解密

以下代码演示了如何使用 rc4Initrc4Cipher 函数来解密有效负载:

    // 初始化
    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 页面open in new window

SystemFunction032

文档页面声明函数 SystemFunction032 接受两个 USTRING 类型的参数。

 NTSTATUS SystemFunction032
 (
  struct ustring*       data,
  const struct ustring* key
 )

USTRING 结构

很遗憾,由于这是个未公开文档的 API,USTRING 的结构未知。但是,通过其他研究,可以在 wine/crypt.hopen in new window 中找到 USTRING 结构定义。结构如下所示。有关此结构的更深入信息,请访问 Geoff Chappellopen in new window

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 页面open in new window 上引用的定义匹配的函数指针。但是,可以从 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'
};