Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
ShellGhost — 一种基于内存的逃避技术,使shellcode从进程启动到结束都不可见。 | Kitploit
工具/GitHubGitHub/lem0nsec/shellghost
加密/解密工具内存取证漏洞利用Shellcode二进制分析红队Payload 开发
GitHublem0nsec/shellghost

ShellGhost

一种基于内存的逃避技术,使shellcode从进程启动到结束都不可见。

查看仓库
1.2k1402年前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

ShellGhost

一种基于内存的规避技术,使 shellcode 从进程开始到结束均不可见。


动机

我想分享这个 shellcode 自注入 POC,以展示一些可能对红队有用的 AV/EDR 规避概念。就在几周前,我想出了一种自定义的内存规避技术,我称之为 ShellGhost。这项技术源于对 一段从进程开始到结束执行‘不可见’ shellcode 的代码 的需求。


处理线程执行流

ShellGhost 依赖于向量异常处理与软件断点的结合,以循环方式停止线程执行,用 RC4 加密的 shellcode 指令替换已执行的断点,解密指令,并在恢复内存保护至 RX 后恢复执行。当后续的 EXCEPTION_BREAKPOINT 触发时,异常处理器将前一条 shellcode 指令替换为新的断点,从而使内存分配永远不会泄露未加密的完整 shellcode。这一切发生在一个初始标记为 READ/WRITE 的私有内存页中。拥有 RW PRV 内存分配不会被诸如 PE-Sieve 和 Moneta 之类的内存扫描器视为“入侵指标”。当分配变为 RX 且页面被扫描时,只会发现断点。这发生在 shellcode 实际执行的同时。下图显示了一个反向 shell 正在运行,但 Moneta 未发现任何 IOC(除了二进制文件未签名外)。

尝试使用 Pe-Sieve 扫描进程会得到更好的结果:


Shellcode 映射

Shellcode 映射是 ShellGhost 的核心功能。这种策略使线程能够间歇性地执行指令,同时永远不在内存中暴露完整的 shellcode。这是可能的,因为线程执行的每条 shellcode 指令的位置对应于分配内存页内某个断点的位置。ShellGhost 通过计算从线程 RIP 到分配内存页基址的相对虚拟地址 (RVA) 并将其加到加密 shellcode/加密指令的基地址来解析此位置。被替换的断点数量并不总是相同,而是取决于每条指令需要正确生成和解释的操作码数量(QUOTA)。例如,指令 'POP RBP' 等于 '5D',这意味着只替换一个断点。相反,指令 'JMP RAX' 需要操作码 'FF E0',因此将替换两个断点。为此,我创建了以下 C 数据结构。

root@kitploit:~
typedef struct CRYPT_BYTES_QUOTA {

	DWORD RVA;		// offset to encrypted instruction 
	DWORD quota;	// number of opcodes that generate the instruction

} CRYPT_BYTES_QUOTA, * PCRYPT_BYTES_QUOTA;

断点不会立即替换为对应的指令。这是因为指令在执行前需要经过解密例程。这就是 DWORD quota 发挥作用的地方。ShellGhost 依赖于现在流行的 'SystemFunction032' 来执行 RC4 解密。与 XOR 不同,RC4 不是单字节加密方案。这意味着 shellcode 不能一次性加密和解密。这也是每条指令单独处理的另一个原因。替换断点后,SystemFunction032 所需的缓冲区长度将等于'指令配额',后者再次表示特定指令由多少个操作码组成。例如,考虑以下代码片段。

root@kitploit:~

CRYPT_BYTES_QUOTA instruction[200];
instruction[5].quota = 2

USTRING buf = { 0 }; 	// will contain the buffer to be decrypted and its length
USTRING key = { 0 }; 	// will contain the RC4 key and length

buf.Length = 2 		// buffer length, or length of the instruction to be decrypted

我们知道 shellcode 指令 5 由 2 个操作码组成,因此将向 SystemFunction032 传递缓冲区长度 2。这一点很重要,因为尝试通过一次 SystemFunction032 调用解密整个 shellcode 会完全破坏它。

Shellcode 映射如何执行?

shellcode 需要在编译前使用 ShellGhost_mapping.py 进行映射。该脚本提取每条单独指令,并将其视为一个小的独立 shellcode。指令逐一加密,并以 C 格式全部打印为 unsigned char。结果可以硬编码到 C 代码中。以下是一个加密的 MSF shellcode 指令(用于 calc.exe)的示例。

该 shellcode 有 98 条指令,因此声明了 98 个 CRYPT_BYTES_QUOTA 结构体。代码执行时,必须用正确的指令 RVA 和 QUOTA 填充这些结构体。'-1' 参数指示映射脚本打印执行此操作的代码片段。

调整 Winapi 参数

Metasploit x64 shellcode 通常将 winapi 字符串参数存储在指令之间。也就是说,调用 Winexec 的 MSF x64 shellcode 不会推送一系列以空字节结尾的字节以将第一个参数字符串放在栈上。相反,RCX 寄存器(第一个参数)是 shellcode 内部的一个指针,如下图所示。

这意味着与字符串相关的断点永远不会被解析,因为 RIP 永远不会触及该位置。实际上,此代码解析的是 RIP 经过的实际 shellcode 指令,而不是像指令那样永远不会执行的参数。为了解决这个问题,我注意到 MSF shellcode 总是将所调用 winapi 的指针存储在 RAX 寄存器中,然后跳转到该寄存器本身。因此,当 ShellGhost VEH 检测到解析的断点是 'JMP RAX' 且 RCX 寄存器包含指向 shellcode 内部某个位置的指针时,它会尝试同时解析 RCX 指向的内容。随后,执行不会返回到分配的内存。相反,RAX(winapi 地址)被复制到 RIP,线程执行从 winapi 恢复,从而覆盖 'JMP RAX' 并保持分配的内存为 RW。这对于调用 WaitForSingleObject 的反向 shell 是必需的,否则线程会在 'JMP RAX' 之后休眠,只要 shell 存活就会将内存保持在 RX 状态。以下代码片段包含两个必须同时满足的条件,以便 ShellGhost 在 RCX 寄存器包含 winapi 参数字符串时调整 RCX,并允许 MSF shellcode 正确发出函数调用(此示例中为 WinExec)。

root@kitploit:~
<snip>
	
if (*(PWORD)exceptionData->ContextRecord->Rip == 0xe0ff) // if RIP is 'JMP RAX'

<snip>

if ((contextRecord->Rcx >= (DWORD_PTR)allocation_base) && (contextRecord->Rcx <= ((DWORD_PTR)allocation_base + sizeof(sh)))) // if RCX is inside the allocation

<snip>

RDX、R8 和 R9(第二、第三和第四参数)尚未覆盖。

与其他技术的异同

ShellcodeFluctuation 是一个非常相似的内存规避概念。与它一样,这里分配的内存从 RW '波动' 到 RX。相比之下,ShellGhost 引入了以下改进:

  • RC4 加密加 'Shellcode 映射',而非单字节 XOR
  • 无需挂钩函数
  • 支持 Metasploit shellcode

然而,ShellGhost 远非完美技术。它仍然存在所有这些技术最大的缺点,即 在执行的某个时刻需要私有可执行内存。更先进的技术(如 Foliage)已经找到了解决方法。此外,充满软件断点的内存分配可能被 YARA 规则检测到。下图显示 Moneta 正确检测到 RX PRV 分配的 IOC。

在规避 EDR 解决方案时,内存扫描只是更大图景的一部分。完全不存在 IOC 并不一定意味着使用此技术的二进制文件能有效对抗特定的 EDR。据我所知,我经历过解决方案甚至不允许你以当前方式启动二进制文件的情况。另一面是,IOC 并不总是精确的指标,其中一些可能最终是误报。话虽如此,这只是一个原始的技术和灵感,希望读者能欣赏。红队成员知道,就像 EDR 的组件一样,内存规避只是引擎的一个组件。

备注

编译需要禁用增量链接。此 VS 项目已设置所有编译器/链接器选项。

下载工具