将有效负载存储在 .rsrc 部分是一个非常好的选择,因为大多数实际的二进制文件都将它们的数据保存在这里。对于恶意软件作者来说,这也是一种更清晰的方法,因为较大的有效负载无法存储在 .text.data 等部分中,因为它们有大小限制,这会导致在编译时 Visual Studio 出现错误。

例如,你的程序要生成一个exe文件,而文件的图标是你自定义的图标。你就要在这个工程里面添加Icon资源,添加一个外部的ico文件,保存到.rc里面,因此可以直接调用图标文件。

.rsrc 部分

以下步骤说明了如何将有效负载存储在 .rsrc 部分:

  1. 在 Visual Studio 中,右键点击 ‘资源文件’,然后点击 添加 > 新项。

  2. 点击“资源文件”

  3. 这将生成一个新的侧边栏,即资源视图。右键点击 .rc 文件(默认名称为 Resource.rc),然后选择 添加资源 选项。

  4. 点击 ‘导入’.

  5. 选择 calc.ico 文件,它是重命名为扩展名 ..ico 的原始有效负载

  6. 将出现一条提示,要求输入资源类型。输入不带引号的“RCDATA”。

  7. 单击“确定”后,有效负载应在 Visual Studio 项目中以原始二进制格式显示

  8. 退出资源视图时,“resource.h”头文件应该可见,并根据步骤 2 中的 .rc 文件命名。该文件包含一个定义语句,引用资源部分中有效负载的 ID (IDR_RCDATA1) 。这对于稍后能够从资源部分检索有效负载非常重要。


编译后,有效负载现在将存储在该部分中,但无法直接访问。相反,必须使用多个 WinAPI 来访问 .rsrc

  • FindResourceW - 获取存储在资源部分的指定数据的位置,该数据由传入的特殊 ID 指定(在头文件中定义)。

  • LoadResource - 检索资源数据的句柄。此句柄可用于获取指定资源在内存中的基地址。返回类型为 HGLOBAL

  • LockResource - 从指定资源句柄中获取指向资源部分数据的指针。

  • SizeofResource - 获取资源部分中指定数据的大小。

下面的代码片段将利用上述 Windows API 来访问资源部分,并获取有效载荷的地址和大小:

#include <Windows.h>
#include <stdio.h>
#include "resource.h"

int main() {

	HRSRC		hRsrc                   = NULL;
	HGLOBAL		hGlobal                 = NULL;
	PVOID		pPayloadAddress         = NULL;
	SIZE_T		sPayloadSize            = NULL;

	
	// 获取存储在 .rsrc 中的数据位置,通过其 ID *IDR_RCDATA1*
	hRsrc = FindResourceW(NULL, MAKEINTRESOURCEW(IDR_RCDATA1), RT_RCDATA);
	if (hRsrc == NULL) {
		// 如果函数失败
		printf("[!] FindResourceW 调用失败,错误码: %d \n", GetLastError());
		return -1;
	}

	// 获取 HGLOBAL,即指定资源数据的句柄,稍后需要调用 LockResource
	hGlobal = LoadResource(NULL, hRsrc);
	if (hGlobal == NULL) {
		// 如果函数失败
		printf("[!] LoadResource 调用失败,错误码: %d \n", GetLastError());
		return -1;
	}

	// 获取 .rsrc 部分中有效载荷的地址
	pPayloadAddress = LockResource(hGlobal);
	if (pPayloadAddress == NULL) {
		// 如果函数失败
		printf("[!] LockResource 调用失败,错误码: %d \n", GetLastError());
		return -1;
	}

	// 获取 .rsrc 部分中有效载荷的大小
	sPayloadSize = SizeofResource(NULL, hRsrc);
	if (sPayloadSize == NULL) {
		// 如果函数失败
		printf("[!] SizeofResource 调用失败,错误码: %d \n", GetLastError());
		return -1;
	}
	
	// 打印指针和大小到屏幕上
	printf("[i] pPayloadAddress 变量: 0x%p \n", pPayloadAddress);
	printf("[i] sPayloadSize 变量: %ld \n", sPayloadSize);
	printf("[#] 按 <Enter> 键退出...");
	getchar();
	return 0;
}

编译并运行上述代码后,有效载荷的地址及其大小将被打印到屏幕上。需要注意的是,这个地址位于资源部分,该部分是只读内存,任何尝试在其中修改或编辑数据的操作都将导致访问冲突错误。如果要编辑有效载荷,必须分配一个与有效载荷大小相同的缓冲区,并将数据复制到该缓冲区。所有的修改(如解密有效载荷)都应在这个新缓冲区中进行。

更新 .rsrc Payload

由于有效载荷不能直接从资源部分进行编辑,因此必须将其移动到一个临时缓冲区。为此,可以使用 HeapAlloc 分配与有效载荷大小相同的内存,然后通过 memcpy 将有效载荷从资源部分移动到临时缓冲区

// 使用 HeapAlloc 调用分配内存
PVOID pTmpBuffer = HeapAlloc(GetProcessHeap(), 0, sPayloadSize);
if (pTmpBuffer != NULL){
	// 将有效载荷从资源部分复制到新的缓冲区
	memcpy(pTmpBuffer, pPayloadAddress, sPayloadSize);
}

// 打印缓冲区的基地址 (pTmpBuffer)
printf("[i] pTmpBuffer 变量: 0x%p \n", pTmpBuffer);

由于现在 pTmpBuffer 指向一个可写的内存区域,并且该区域保存了有效载荷,因此可以对有效载荷进行解密或执行任何更新操作。

下面的图片显示了存储在资源部分的 Msfvenom shellcode:

继续执行时,有效负载将保存在临时缓冲区中。

⬆︎TOP