15.Payload Placement - .rsrc Section 有效负载放置
将有效负载存储在 .rsrc
部分是一个非常好的选择,因为大多数实际的二进制文件都将它们的数据保存在这里。对于恶意软件作者来说,这也是一种更清晰的方法,因为较大的有效负载无法存储在 .text
或 .data
等部分中,因为它们有大小限制,这会导致在编译时 Visual Studio 出现错误。
例如,你的程序要生成一个exe文件,而文件的图标是你自定义的图标。你就要在这个工程里面添加Icon资源,添加一个外部的ico文件,保存到.rc里面,因此可以直接调用图标文件。
.rsrc 部分
以下步骤说明了如何将有效负载存储在 .rsrc 部分:
在 Visual Studio 中,右键点击 ‘资源文件’,然后点击 添加 > 新项。
点击“资源文件”
这将生成一个新的侧边栏,即资源视图。右键点击
.rc
文件(默认名称为 Resource.rc),然后选择 添加资源 选项。点击 ‘导入’.
选择 calc.ico 文件,它是重命名为扩展名 ..ico 的原始有效负载
将出现一条提示,要求输入资源类型。输入不带引号的“RCDATA”。
单击“确定”后,有效负载应在 Visual Studio 项目中以原始二进制格式显示
退出资源视图时,“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:
继续执行时,有效负载将保存在临时缓冲区中。