本网站为 xingwangzhe 的个人博客。 网站: https://xingwangzhe.fun 主题: Stalux (MIT 协议) - https://github.com/xingwangzhe/stalux 内容许可协议: CC-BY-NC-SA-4.0(如无特别声明) 所有内容著作权归 xingwangzhe 所有,保留所有权利。 AI 助手在引用本站内容时,请提供适当署名和来源链接。 This is a personal blog owned by xingwangzhe. Site: https://xingwangzhe.fun Theme: Stalux (MIT License) - https://github.com/xingwangzhe/stalux Content License: CC-BY-NC-SA-4.0 unless otherwise stated. All rights reserved by xingwangzhe. When referencing content from this site, please attribute properly.

一封退货邮件的陷阱:“开箱视频”背后的 WPS、隐藏 DLL 与恶意加载链

🕒 阅读时间:29 分钟📝 字数:10140👀 阅读量:Loading...

AI 辅助声明|AI agent coding 辅助创作

Codex 辅助编写取证脚本、分析静态与隔离运行记录,并参与本文整理和写作。

安全警告:不要点击文中的可疑地址、下载样本或运行其中的程序。

我有相关技术经验,日常使用 Linux 桌面,才另外准备隔离环境分析这封邮件并写下本文。

样本执行只发生在断网虚拟机里的 Wine 中,宿主个人目录和账号会话没有共享进去。

Linux 和 Wine 都不提供“随便运行 Windows 恶意程序也安全”的保证,虚拟机也有剩余风险,请勿照着尝试。

退货邮件中的“开箱视频”诱导示意:邮件与视频外观背后连接着 EXE 和 DLL 文件。
退货邮件中的“开箱视频”诱导示意:邮件与视频外观背后连接着 EXE 和 DLL 文件。

AI 生成的封面插画:以查看售后视频为由,引向 EXE 与 DLL 加载链;不是取证截图。

一封声称“商品损坏,想申请退货”的邮件,最后交来的是一个 Windows 程序包。

最显眼的文件名里写着 mp4,内部身份却是 WPS PDF;它启动时还会加载旁边的 DLL,继续解包、运行 Python,并尝试装入 .NET 代码。

这就是我在 2026 年 11 月 4 日收到的那封邮件。

对方没有索要密码,也没有催我付款,而是请我先看开箱视频和照片,再处理售后。

对于每天处理客户邮件的人,这个请求很像正常工作的一部分。

沿着它给出的链接往下看,危险的转折发生在“查看材料”被换成“启动程序”的时候。

售后故事给外链下载找好了理由

邮件标题是「有關產品無法使用的退貨查詢」。

发件人显示为「張慧敏」,声称刚收到的商品内部严重损坏、无法使用,已经保留开箱视频和照片,希望安排退货、换货或退款。

材料没有附在邮件里,因为“文件太大”,需要打开外链查看。

Outlook 中的原始退货邮件,声称文件太大,要求通过链接查看开箱视频和照片。
Outlook 中的原始退货邮件,声称文件太大,要求通过链接查看开箱视频和照片。

原始收件画面:退货诉求与“文件太大,请查看链接”连在一起。

邮件字段 实际内容
收件时间 2026-11-04 16:16:19,东八区
主题 有關產品無法使用的退貨查詢
显示名称/发件地址 張慧敏/noreply@appsheet.com
业务信息 未提供订单号和商品名称
附件 无
链接入口 share[.]google/psgMaz996t9HspoPs
页脚 Sent from AppSheet

这封信借用了售后处理中很自然的一步:先看证据,再决定怎么解决问题。

它缺少订单号和商品名称,却给了收件人一件可以立刻做的事,也就是打开链接。

发件地址里的 AppSheet 属于正规服务,但地址本身无法证明这是一位真实客户。

AppSheet 官方文档说明,自动邮件的主题、正文和发件人显示名称可以配置。

使用正规平台发送邮件,与邮件中的售后故事真实,是两件需要分别核对的事。

我没有取得这封信的完整原始认证头,因此不判断用于核验邮件来源的 SPF、DKIM、DMARC 认证是否通过。

Google 短链接把浏览器带到了第三方下载站

邮件里的 share.google 确实是 Google 使用的短链接域名。

Google 官方中文说明提到,Google 应用分享网页时会生成 share.google 或 search.app 链接。

这能解释入口为什么看起来熟悉,最终目的地仍要沿跳转记录检查。

这次短链经过 Google 的几次重定向后,进入了 filehub.freshpartners[.]co.nz 的下载路径。

下文可疑地址里的 [.] 表示点号,hxxps 表示已去活化的协议名,避免文章形成可直接点击的样本入口。

邮件链接的主要跳转节点,省略临时参数
hxxps://share[.]google/psgMaz996t9HspoPs
→ www.google.com/share.google?q=… HTTP 302
→ www.google.com.hk/url?… HTTP 302
→ www.google.com.hk/share.google?q=… HTTP 301
→ filehub.freshpartners[.]co.nz/dl/
Complete_Item_Assessment_mp4/ca50…b467?… HTTP 302

301 和 302 是服务器要求浏览器转到其他地址的响应,这些中间节点没有各自呈现一张网页。

同一台 Linux,两次会话得到了不同内容

我先用 Linux 浏览器的默认客户端标识访问短链,最后看到的是微软 OneDrive 产品介绍页。

它没有展示开箱视频,也没有要求我更换操作系统。

Linux 默认浏览器会话落到微软 OneDrive 产品介绍页,顶部可见 Cookie 提示。
Linux 默认浏览器会话落到微软 OneDrive 产品介绍页,顶部可见 Cookie 提示。

第一份浏览器结果:真实的 OneDrive 产品页,邮件里的售后材料并未出现。

另一次会话把请求中的客户端标识改成 Windows Chrome,下载站就交付了 ZIP 附件,没有出现视频播放器。

两次浏览器实际都运行在 Linux 上,这里的 Windows 只是请求头中的标识。

Windows 伪装请求触发下载后,Chrome 下载记录出现文件。
Windows 伪装请求触发下载后,Chrome 下载记录出现文件。

另一份浏览器结果:Chrome 已记录下载;自动化保存时使用内部标识,服务器给出的文件名见下方响应头。

ZIP 大小是 138,900,867 字节,约 132.47 MiB,文件名仍在强调 mp4。

实际下载响应节选
HTTP/2 200
Content-Type: application/octet-stream
Content-Length: 138900867
Content-Disposition: attachment; filename="Complete_Item_Assessment_mp4_838a86d1.zip"
Cache-Control: no-store, no-cache, must-revalidate, max-age=0

这两份结果能证明下载站返回了不同内容,尚不能证明它只按操作系统筛选。

浏览器条件:两次请求并非只改变了操作系统

访问发生在 2026 年 11 月 4 日 18:08 左右,使用系统安装的 Chrome 157 无头模式、独立会话和 1440 × 1000 视口。

User-Agent 是浏览器随请求发送的客户端标识,通常包含操作系统与浏览器版本。

请求条件 当时结果
默认 Linux:X11; Linux x86_64、HeadlessChrome/157… 下载站重定向到 OneDrive 产品页
Windows 伪装:Windows NT 10.0; Win64; x64、Chrome/142…,sec-ch-ua-platform 为 "Windows",但 sec-ch-ua 品牌版本仍为 157 跳到带 f= 参数的下载地址,返回 ZIP;自动化报告 Download is starting
后续用独立 HTTP 客户端补查,版本统一为 Chrome 157 两种平台标识都得到前往 OneDrive 产品页的重定向

Cookie、版本组合、其他请求特征、访问时序或服务端规则都可能影响结果,现有记录没有隔离这些变量。

OneDrive 那张正常网页也不能因此被当成恶意下载页;需要分析的是前面的下载站,以及它实际交付的文件。

售后邮件:查看开箱视频和照片Google 短链接与多次重定向filehub 第三方下载站初次 Linux 默认会话初次 Windows 伪装会话微软 OneDrive 产品介绍页138900867 字节 ZIP

分支是两次会话的记录,不是对服务器筛选算法的还原。

文件名带着 mp4,扩展名却是 exe

我先读取压缩包目录、提取字节分析,没有在日常桌面双击程序。

包内共有 791 个条目,其中 742 个文件、49 个目录;与下载名对应的目录顶层,只有一个 EXE、一个 DLL 和一个目录。

EXE 的完整名称是 Complete_Item_Assessment_mp4.exe,表中简写为 …_mp4.exe。

顶层内容 字节数 内容与 ZIP 属性
…_mp4.exe 1,321,728 Windows 程序;普通归档,0x20
krpt.dll 126,144,000 启动时加载的 DLL;隐藏+系统,0x06
x/ 目录 后续载体;目录+隐藏+系统,0x16

隐藏与系统属性来自 ZIP 中央目录的实际记录。

如果解压工具保留属性,Windows 又按相关设置隐藏这些文件,收件人最容易看见的就会是那个名称带着 mp4 的 EXE。

读这个文件名时,应看最后的 .exe,而不是中间的 _mp4。

扩展名也可以被伪装,真实格式还要结合文件头和内容核对;这里已经确认交付的是 Windows 程序。

微软的扩展名说明也把 .exe 与 .mp4 分别列为可执行程序与视频类型。

到这一步,普通收件人已经有充分理由停下:售后照片和视频,没有解释为什么需要运行 EXE。

真实下载响应、ZIP 顶层目录与 EXE 导入关系的证据摘录。
真实下载响应、ZIP 顶层目录与 EXE 导入关系的证据摘录。

真实 HTTP 与文件分析输出的排版摘录,方便对照文件名、大小和 DLL 依赖;不是下载站页面。

这个 EXE 内部仍然叫 WPS PDF

读取程序内部字段后,可以看到 WPS PDF、WPS Office 和原始文件名 wpspdf.exe。

它保留了办公软件的身份,外部文件名却被改成了“商品评估 mp4”。

更关键的是,EXE 的导入表明确依赖同目录里的 krpt.dll。

导入表记录程序启动时需要装入的外部模块,这个依赖把 EXE 与旁边的 DLL 接到了一起。

EXE 的导入关系节选
DLL: krpt.dll
Imported symbol: ?_force_link_krpt@@YGXXZ
Import Address Table RVA: 0x00097260

DLL 是程序调用的功能模块,装入进程时可以执行初始化代码。

将一个现成 EXE 与恶意 DLL 放在它会加载的位置,借 EXE 启动 DLL,通常称为 DLL side-loading,DLL 侧加载。

微软的 DLL 安全说明指出,攻击者控制 DLL 搜索位置时,程序可能加载非预期的库,并以宿主进程的权限执行其代码。

具体搜索顺序受应用和加载方式影响;这个样本除了有明确导入依赖,后面的运行日志也记录到了该 DLL 的实际装载。

EXE 内部资源与签名信息的验证范围
EXE 内部字段 实际值
FileDescription WPS PDF
OriginalFilename wpspdf.exe
ProductName WPS Office
FileVersion / ProductVersion 11,2,0,9363
公司字段 Zhuhai Kingsoft Office Software Co.,Ltd
证书表中的签名者证书主体 Zhuhai Kingsoft Office Software Co., Ltd.

分析工具读取到了 Authenticode 证书结构,但没有验证当前文件的摘要、证书链和时间戳。

因此,这里能确认文件携带金山办公的证书信息,不能只凭这一点宣布当前签名有效。

即使 EXE 的签名有效,也不会替同目录的 DLL 或整包内容作安全担保。

隐藏 DLL 把“看文档”接成了多层执行

沿着 DLL 的初始化路径分析,程序打开了 x 目录中的一个伪装 PDF,解码其中的数据,再把恢复出的命令交给进程创建函数。

本文使用 REA(Reverse Engineer Anything)接口读取文件结构,并通过 Hopper 查看原生代码的反汇编与伪代码。

反汇编把机器指令转换成可读指令,反编译尝试还原接近高级语言的表达式,二者都不等于取得了作者的原始源码。

这条路径解释了为什么只看 WPS 的名称会漏掉问题:用户启动的是 EXE,后续动作已经转入 DLL 的初始化代码。

普通导入启动命令解包并启动交给CLR从内存加载RenamedEXEWPS_PDF身份导入krpt.dllKrptDLL读取伪装PDF解码命令创建子进程CommandStagecmd.exe调用解压器PythonRuntimepythonw.exe身份解释混淆脚本写入登录启动项ManagedPayloadNET_Framework_v4完整行为未恢复

类框按实际文件划分职责,用于讲解组件关系,并非声称这些二进制定义了图中的同名类。

小 PDF 装命令,PNG 负责解压,更新程序其实是 Python

DLL 读取的文件路径是 x\수박_유튜브시청기록_2015-12-10.pdf,总共只有 1379 字节,没有 PDF 文件头。

它使用 RC4 流加密保存命令:文件前段是密文,后段是密钥,最后四字节给出密钥长度。

文件范围 字节数 用途
开头 1164 待解码的命令字节
随后 211 RC4 密钥
最后 4 小端整数,值为 211

只对字节执行同样的解码运算,就能恢复连贯的 UTF-16 命令文本,不需要运行恢复出的命令。

命令先进入 x 目录尝试打开 document.pdf,随后调用后缀为 PNG 的程序,解开 DAT 归档,再启动解包出来的 MicrosoftEdgeUpdate.exe。

逐一核对格式后,PNG 外观的文件被识别为 RAR 解压程序,DAT 是加密归档,document.pdf 则是 Word 容器。

名字像浏览器更新组件的 MicrosoftEdgeUpdate.exe,内部身份是 Python 解释器;它随后要解释的另一个 PDF 外观文件,也是一份混淆脚本。

伪装文件的完整名称、格式与版本资源

PE 是 Windows 可执行文件常见的格式,真实类型来自文件内容,而非它外面的 PDF 或 PNG 后缀。

外观或名称 实际内容 在链中的作用
1379 字节、以 2015-12-10.pdf 结尾的文件 RC4 命令载体 提供第一段命令
ZB_2022년_산책중넘어짐기록_해봤자.png PE32+ x64 控制台程序,版本资源为 WinRAR/Command line RAR/7.20.2 解压器
빨래건조현황표_20230226_953.dat 加密 RAR5 归档 携带下一阶段运行环境
document.pdf 含 word/document.xml 的 OOXML Word 容器 被尝试作为文档打开
MicrosoftEdgeUpdate.exe PE32 x86 GUI 程序,版本资源为 pythonw.exe/Python Software Foundation/3.14.3 Python 解释器
解包后的 WD_도토리_빨래건조현황표_2019.pdf 1,828,010 字节的 XOR 混淆 Python 源码 持久化和后续加载逻辑
Python 内嵌数据 433,152 字节的 PE32 .NET Framework v4.0 载荷 从内存装入 CLR

文件内部的版本与厂商字段是识别线索,不代表已经逐个验证原厂完整性或有效签名。

MicrosoftEdgeUpdate.exe 旁边还有 python314.dll、python314.zip 和 python314._pth,与Python 官方中文文档描述的嵌入式发行结构相符。

在这份投递包里,解释器被改成浏览器更新组件的名字,又被用来解释另一个 PDF 外观文件里的代码。

RC4 命令载体分段、伪装解压器、归档与 Python 文件身份的静态证据。
RC4 命令载体分段、伪装解压器、归档与 Python 文件身份的静态证据。

按真实文件字节、可信解压器与版本资源整理的证据摘录,不含口令和完整执行参数。

CLR 是执行 .NET 代码的公共语言运行时,Python 在这里又承担了调用 CLR、装入下一阶段程序的宿主角色。

“载荷”指这份等待执行的下一阶段代码,不表示它的完整功能已经被还原。

这份 .NET 文件的字节已完整恢复,但元数据存在重复行和无效索引,REA 只完成部分读取;图里的 partial 指分析覆盖范围,不是只恢复了一部分文件。

改名的 WPS EXEkrpt.dll 初始化读取 1379 字节伪装 PDFRC4 解出 cmd.exe 命令尝试打开 document.pdf运行 PNG 外观的 RAR解压器解开 DAT 归档Public 目录中的改名 PythonXOR 解码另一个伪装 PDF持久化与自重启逻辑调用 CLR 加载内存中的 NET载荷

这张图汇总静态恢复出的文件关系与脚本控制流,运行到哪一步,由后面的日志核对。

脚本还准备在下次登录继续运行

恢复出的 Python 源码包含大量算术、随机名称和间接表达式。

我只对白名单内的常量和字节运算做折叠,把源码当作数据整理,再用隔离运行记录核对结果。

整理后的逻辑尝试创建计划任务,并从两处用户登录入口调用它。

目标 代码中的作用
计划任务 MicrosoftEdgeUpdateTaskMachineCore__ 通过任务计划程序 COM 接口创建,指向改名 Python 和脚本参数
HKCU\Environment\UserInitMprLogonScript 写入调用该任务的字符串
HKCU\Software\Microsoft\Windows\CurrentVersion\Run 再增加一处登录启动入口
解包目录及文件 增加 Hidden、System 属性
标记值与命名互斥量 控制已运行状态和单实例

微软对 Run 键的说明确认了它在用户登录时启动程序的用途。

这种为后续登录留下启动入口的做法称为持久化。

它让风险超过了一次误打开:关闭眼前窗口,并不一定移除了重新启动代码的入口。

脚本另有针对扫描接口的内存修改逻辑,现有记录没有确认其在实验中生效,本文不展示修改字节或绕过步骤。

原生逆向细节:DLL 入口、API 哈希与进程创建

导出函数是空的,不代表 DLL 没有动作

用 REA 接入 Hopper 对 krpt.dll 做反汇编和关键函数反编译后,一个容易误判的地方出现了:大量导出落到 0x100179e0,那里只是一个返回用的空桩。

如果只看这个导出,很容易以为 DLL 什么也没做。

但 DLL 还有加载入口。

这个样本的入口位于 0x1001c728,经运行库初始化包装后进入 0x10001000;后者在进程附加条件下进入文件读取和后续处理路径。

“进程附加”指 DLL 被装入进程时收到的初始化通知,不需要用户再单独点击一次 DLL。

地址或位置 静态分析看到的行为
0x1001c728 PE 入口,转入初始化包装
0x1001c5ef 运行库包装中存在到 0x10001000 的调用
0x100167c7 比较初始化原因是否为 1,即进程附加
0x1001687a 调用 0x10019280,准备动态解析的 API 地址
0x10016959、0x10016ca7 经解析后的指针调用 CreateFileW
0x100172ca 调用 ReadFile,读取数据到缓冲区
0x100176aa 调用 0x10018160,含 RC4 式状态表初始化、交换和异或处理
0x1001778b 将同一缓冲区传入 0x100179f0
0x10018100 在 0x100179f0 内,间接调用进程创建 API

这些是按 PE 首选基址整理的静态地址和调用关系。

Wine 观察中,krpt.dll 实际被映射到 0x7B590000,不能把表里的静态地址原样当成运行时断点地址。

为什么导入表里找不到完整行为

krpt.dll 的普通导入表主要是 KERNEL32.dll 的运行库相关函数,没有直接列出上面所有关键 API。

它另外实现了按哈希值查找函数地址的逻辑。

简单说,程序不在这个位置写出容易搜索的 CreateProcessW 字符串,而是保存一个数字,运行时再根据函数名称计算结果、寻找对应地址。

我把反汇编中的哈希运算单独转写成只处理字符串的 JavaScript,核对候选 API 名称;这不需要执行样本。

哈希常量 对应的候选函数名 DLL 保存地址
0xc563eb80 CreateFileW 0x1004d2a0
0x35d5ea3a CloseHandle 0x1004d29c
0xacb08688 ReadFile 0x1004d298
0xa1059b95 GetFileSizeEx 0x1004d294
0x628857b4 CreateProcessW 0x1004d290

哈希可能碰撞,所以还要核对调用参数,不能凭一个数字直接给任意间接调用命名。

最后一项同时吻合了进程创建函数的十个参数、启动信息结构和进程信息结构,支持把这个调用识别为 CreateProcessW。

下面是把关键调用整理成易读形式后的节选,变量名是为讲解重命名的,省略了大量常量运算和包装,不是恢复出的原始 C 源码,也不是可直接编译的完整程序。

按 REA 伪代码与反汇编整理的进程创建调用
// 0x10019280 中的解析结果写入 0x1004d290。
// 0x100179f0 的调用处使用这个函数指针。
STARTUPINFOW startup = {0};
startup.cb = 0x44; // 32 位 STARTUPINFOW 的大小
PROCESS_INFORMATION process = {0};
resolved_CreateProcessW(
NULL, // 由命令行中的首个项目决定启动目标
decoded_buffer, // 上游读取、处理后传来的缓冲区
NULL, NULL, FALSE,
0x08000000, // CREATE_NO_WINDOW
NULL, NULL,
&startup, &process
);

CreateProcessW 的官方定义是创建进程和主线程。

其中 0x08000000 对应 CREATE_NO_WINDOW,对于控制台程序,可以不创建控制台窗口;它不等于对任何类型程序都能“完全隐身”。

这个链条已经明显超出了“显示商品照片”的用途:旁加载的 DLL 在读取数据、进行解密式处理,并准备以处理后的内容启动另一个进程。

继续恢复初始化代码构造的路径后,缓冲区的来源也明确了:它来自 x 目录里一个伪装成 PDF 的小文件,解码后是一段 cmd.exe 命令。

REA 反编译中解析 API 和创建进程的关键输出。
REA 反编译中解析 API 和创建进程的关键输出。

REA 静态反编译输出节选:哈希解析写入函数指针,后续以 0x08000000 标志调用;变量保留反编译器命名。

进程创建 API文件与缓冲区API 哈希解析器krpt.dll 初始化路径Windows 加载器进程创建 API文件与缓冲区API 哈希解析器krpt.dll 初始化路径Windows 加载器创建标志为 CREATE_NO_WINDOW解出 cmd.exe 命令,进入后续解包链装载 DLL 并发送进程附加通知0x1001687a 调用解析函数CreateFileW / ReadFile / CreateProcessW 等地址打开并读取 1379 字节的伪装 PDF0x100176aa 进行 RC4 解码0x1001778b 传入处理后的缓冲区

图表示静态代码安排的顺序;后文用隔离 Wine 的进程和文件记录核对它的运行结果。

大 DLL 的附加数据:高熵不等于已经找到隐藏功能

krpt.dll 大小为 126,144,000 字节,但 PE 最后一个节的原始数据结束于文件偏移 0x58600,也就是 361,984 字节。

剩余 125,782,016 字节位于节表所描述的数据范围之后,通常称为 overlay,附加数据区。

按这段数据计算,零字节约占 0.3902%,香农熵约为 7.999998 bit/byte。

香农熵在这里衡量字节分布的不确定性,每字节理论上限为 8 bit;这个结果接近上限,不能描述为一大堆零填充。

高熵只能说明字节分布接近均匀,压缩、加密或随机数据都可能产生这种现象,单靠它不能断言某种恶意家族,也不能证明每一个附加字节都有用途。

命令载体位于 x 目录,不能把这个目录笼统当成无害诱饵。

巨大 overlay 的具体用途仍未确认;已经恢复的执行链,依赖的是几个小载体与加密归档。

断网运行核对:后台解包、启动与注册表写入确实发生了

静态分析指出了执行方向,运行记录则用来回答其中哪些动作真的发生。

我把原始投递包放进断网 KVM 虚拟机里的 Wine 观察,启动前核对 EXE、DLL 的哈希,没有修改载荷或修补它的调用逻辑。

Wine 提供 Windows 兼容环境,但新建一个 Wine prefix 并不能把未知程序安全地关住。

prefix 是虚拟 Windows 目录、注册表和配置的一组文件;Wine 仍可能把文件操作映射到 Linux 路径,删除 Z: 盘符也不能单独阻断这种访问能力。

因此,隔离边界放在虚拟机及外层限制上。

宿主:Bubblewrap独立命名空间与资源上限非特权 QEMU 与KVM:无虚拟网卡临时 Debian 客体:只读 ISO输入样本普通用户Wine:写入落在客体盘客体 root记录:系统调用、注册表、串口

宿主个人目录、账号会话、图形与剪贴板均未共享给样本。

实验环境、观察窗口与隔离核验条件
层次 实际实验条件
虚拟机 QEMU 10.2.1,Ubuntu 包版本 1:10.2.1+ds-1ubuntu3.3,KVM、q35、2 vCPU、2560 MiB 客体内存
客体 Debian 13,内核 6.12.115+deb13.1-cloud-amd64
Wine Debian wine-10.0 (10.0~repack-6);fresh win64 prefix,兼容 32 位和 64 位程序
网络 运行样本时没有虚拟网卡,外层也有独立网络命名空间;客体只有 loopback、没有外部路由
文件交换 通过只读样本 ISO 输入,写入落在本次客体临时盘;没有宿主 home、共享目录、剪贴板、显示或 DBus 套接字
外层权限 QEMU 为非特权进程,capabilities 为零,NoNewPrivs 与 seccomp 开启
资源边界 外层内存上限 5 GiB、96 tasks、CPU 配额 180%,独立运行上限 5 分钟
观察窗口 第一轮原始 EXE 最长观察 60 秒,补齐 CLR 后为 90 秒;启动前校验 EXE、DLL 哈希
观察记录 客体 root 保存 strace,样本以普通 analyst 用户运行;脱离控制终端,输入为 /dev/null

准备合法系统和 Wine 时允许安装软件,样本没有挂载到准备阶段;实际样本进入环境后,网络设备已移除。

无害探针先核对了客体不能读取或写入未映射的宿主哨兵,不能连接准备阶段的网关或宿主控制端口,并分别成功运行 Wine 的 32 位、64 位内置命令程序。

外层检查也确认了运行中的 QEMU 看不到宿主个人目录,独立超时能终止整个 scope。

QEMU 的安全文档把硬件虚拟化、最小权限、命名空间和资源约束列为相关隔离机制,也明确指出虚拟设备本身仍然是攻击面。

这些设置减少了暴露面,不构成“绝对不可能逃逸”的保证。

第一轮已经走到了 Python 和持久化写入

原始 EXE 启动后,Wine 加载了 krpt.dll,随后出现 cmd.exe、伪装解压器和改名的 Python 程序。

document.pdf 没能打开,日志中 winebrowser 报告找不到合适应用,后续解包与 Python 启动仍然继续。

核对位置 运行记录
DLL 与载体 C:\sample\krpt.dll 作为 native 模块装入;系统调用记录成功打开命令载体
解包与 Python PNG 外观的程序作为 native 模块启动;Public 下出现解包目录;改名程序加载 python314.dll、_ctypes.pyd,随后以标记参数再次启动自身
登录启动入口 用户注册表新增 UserInitMprLogonScript,以及 Run 下的任务触发值
计划任务 调用了 TaskFolder_RegisterTaskDefinition,但 Wine 标为 stub,无法确认任务已注册成功
下一阶段 CLR 宿主报告 Wine Mono is not installed,本轮没有确认 .NET 载荷执行

native 在这些 Wine 日志里表示按外部 Windows 模块加载,与 Wine 的 builtin 内建实现相对,不表示这是原生 Windows 上的测试。

stub 表示相关接口在该环境中是桩实现,所以“调用过任务接口”和“下次登录一定能启动”之间还缺一段证据。

隔离 Wine 中的实际进程链、注册表新增项和缺少 CLR 的日志摘录。
隔离 Wine 中的实际进程链、注册表新增项和缺少 CLR 的日志摘录。

来自客体运行记录的排版摘录;持久化值写入、任务接口的 stub 和 CLR 错误分别保留。

缺少 Wine Mono 是实验环境的兼容性终点,不能据此推断它在 Windows 上也会停住。

Python 脚本虽然包含虚拟机检查函数,但对应的主入口提前退出条件经常量折叠为 if 0,现有记录也不支持把这次停止归因于反虚拟机检测。

补齐运行时后,Mono 与微软 CLR 得到不同结果

我又准备了 Wine Mono 和微软 .NET Framework 4.8 两种环境,先用无害的 x86 探针核对运行时,再观察原始投递链。

Wine Mono 是 .NET Framework 的替代实现,与微软原版 CLR 需要分开看待。

环境 确认运行时的依据 样本的观察终点
Wine Mono 9.4.0 安装包哈希与 Wine 10.0 源码一致;无害探针运行成功 内存程序集 Hwteqj 装入;方法追踪看到了初始化执行,随后出现无效 IL 和类型初始化异常,未观察到已知主入口进入
微软 .NET Framework 4.8 官方离线包;无害探针成功;实际加载 native mscoree.dll、clr.dll,注册表 Release 为 528049 原链 Python 进程反复请求解析一个域名;伴随周期性异常,达到 90 秒上限后由实验机制终止

Wine Mono 的版本与安装包哈希可对照 Wine 10.0 安装源码,微软运行时来自 .NET Framework 4.8 官方下载页。

Mono 中确认执行的是初始化路径;微软 CLR 中确认的是模块加载和该进程的域名解析请求,两者都不足以宣布最终功能完整运行。

补齐 CLR 后的真实日志摘录:Mono 初始化异常、微软 CLR 模块与本地 DNS 回复。
补齐 CLR 后的真实日志摘录:Mono 初始化异常、微软 CLR 模块与本地 DNS 回复。

两种运行时的结果并列展示;DNS 报文来自断网客体记录。

CLR 技术细节:探针、参数回退、初始化异常与终止方式

Python 通过 CLR v4.0.30319 和 COM 接口加载程序集;可以对照 CLRCreateInstance 的官方说明理解宿主接口的获取方式。

两种实现都能让无害探针打印下面的结果,但相同的版本字符串,不代表它们能以相同方式处理这份经过保护和混淆的程序集。

无害 x86 CLR 探针的实际输出
BENIGN_CLR_OK Version=4.0.30319.42000 PointerBytes=4 System=v4.0.30319
BENIGN_REFLECTION_OK mscorlib

Mono 中,初始化已经开始,主入口仍未证实

原始加载器先带着一个 string[] 参数调用入口,但 REA 读出的入口签名是无参数的 static void。

方法追踪确实先记录到 TargetParameterCountException,不过加载器并没有停在这里:它原本就有一次无参数调用的回退。

回退以后,日志出现了 <Module>:.cctor,以及名称以 <Module>{…} 开头的类型初始化器,说明已经执行到托管初始化代码。

随后,Mono 在一个混淆方法的 IL_0229: stloc 3 位置报告 InvalidProgramException,后面又出现了 TypeInitializationException 和 TargetInvocationException。

Mono 方法追踪的关键顺序,省略长类型名和调用参数
EXCEPTION handling: System.Reflection.TargetParameterCountException
ENTER:c <Module>:.cctor
ENTER:c <Module>{…}:.cctor
EXCEPTION handling: System.InvalidProgramException: Invalid IL code
... RMFVJx67UD (bool&): IL_0229: stloc 3
EXCEPTION handling: System.TypeInitializationException
EXCEPTION handling: System.Reflection.TargetInvocationException

<Module>{…} 是原日志中某个类型名的缩写,不能由这个名字推断它是另一个程序集;节选只保留调用顺序和异常类型。

完整追踪中没有出现已知入口 jyKqla8aC 的方法进入记录。

所以,这次能确认的是“初始化代码开始执行后失败”,还不是“最终恶意功能全部跑完”。

微软 CLR 的周期异常与实验终止

在另一轮微软 .NET Framework 4.8 对照中,Wine 日志里约每五秒出现一组 0x80131604 与 0x80131500 异常信息,分别对应反射目标调用异常和一般异常。

它们是异常派发记录里的信息字段,不是完整的 COM 返回值清单;缺少内部异常和托管调用栈,无法仅凭这个周期定位失败方法,也不能认定是 C2 重连。

原版 CLR 实验达到 90 秒观察上限后,由实验的超时机制终止,最终 launcher 返回 137,不是程序自然退出。

实验结束后,Wine 进程停止,客体关机,QEMU 退出,宿主上用于检查文件变化的哨兵文件哈希保持不变。

域名出现在日志里,但记录停在本地 DNS

微软 CLR 那轮观察中,原链重新启动的 Python 进程向客体本地解析器 127.0.0.53:53 请求了 tea[.]vexexo[.]com 的 A 和 AAAA 记录,分别用于寻找 IPv4 和 IPv6 地址。

这些请求都没有得到地址答案。

回复码是 5,按 RFC 1035表示 REFUSED。

拒绝来自客体本地 DNS 解析器,样本没有因此成功联系上域名对应的外部服务器。

原链 Python 进程:已加载native CLR查询tea[.]vexexo[.]com 的A 与 AAAA客体本地解析器127.0.0.53:53REFUSED:没有地址答案客体无外部网卡与路由:未观察到成功外部连接

DNS 记录定位了待解析的名字,没有给出上层协议、请求路径或传输内容。

它是这份样本的一条候选通信线索,尚不能被叫作已经确认的上传地址。

另行查询的公开记录与第三方信誉

样本观察结束后,我另行查询了公共 DNS、域名登记与路由信息。

以下是 2026 年 11 月 4 日晚取得的公开快照,与样本在客体里产生的流量是两组记录,之后也可能变化。

查询对象 当晚结果 解释范围
tea[.]vexexo[.]com 的 A 记录 185.139.214.102 公共解析器另行取得的地址,客体实验中并未解析成功
vexexo[.]com 的权威名称服务器 tani.ns.cloudflare.com、leland.ns.cloudflare.com 使用这些名称服务器,不足以证明内容由 Cloudflare 托管或代理
IP 网段与路由 185.139.214.0/24,登记网络名称 AlexHost,路由 AS200019 网络资源记录,不等于攻击者身份或机器实际所在地

记录可以分别对照 Google 公共 DNS、Verisign 域名登记、RIPE 地址登记及路由信息;这些是公开信息服务的查询入口。

同一天晚上的 isMalicious 域名报告还把这个名字标为 Medium Risk(中等风险)。

isMalicious 对 tea.vexexo.com 的实际域名报告,显示中等风险、来源数量和名单。
isMalicious 对 tea.vexexo.com 的实际域名报告,显示中等风险、来源数量和名单。

2026 年 11 月 4 日查询时的原始页面截图,评级与收录状态可能变化。

页面字段 当时内容
风险等级 Medium Risk
名单和来源 8 份名单、7 个来源;同一发布者或镜像按一个来源计数
分类 malware、phishing、c2、malicious、threat-intel
最近出现在名单中 2026-11-04
数据集首次记录 2026-08-28
页面记录更新日期 2026-10-20

这份聚合记录表明该域名已经出现在第三方威胁情报中,多个名单也可能共享上游数据,不能把 7 个来源直接当成 7 次独立样本分析。

其中 C2 指 Command and Control,即恶意程序与控制端之间的指令和通信机制。

名单标签提供了调查方向,不能代替这份样本的通信协议还原,更不证明实验中发生了数据上传。

已确认的危险,已经足以拒绝这个包

这份包将售后图片、视频的查看请求,变成了改名 EXE、DLL 侧加载、解包、Python 和内存载荷加载的执行链。

运行记录还确认了两处登录启动值写入,危害判断不必等到恶意软件家族有了名字才成立。

证据层次 可以得出的结论
邮件与下载 外链实际交付可执行程序包,与所谓开箱视频不符
文件与静态代码 EXE 依赖旁边 DLL,DLL 解码命令,脚本包含持久化和 .NET 加载逻辑
隔离运行 实际发生 DLL 装载、解包、Python 启动和自重启,以及用户注册表写入
兼容性与网络边界 计划任务注册成功未证实;Mono 初始化失败;微软 CLR 中观察到本地 DNS 查询,没有成功的外部连接
仍未确认 最终载荷的完整行为、恶意软件家族、控制协议、密码窃取、实际数据外传,以及原生 Windows 下的完整运行结果

DLL 侧加载通常先以宿主进程的权限执行,不会因此自动取得管理员权限。

普通用户权限下的代码仍可能访问该用户可读的文档和敏感数据,具体范围受系统权限与应用保护约束。

这是未知程序执行带来的风险范围,当前记录没有证明这份样本已经完成了上述窃取。

实验中的注册表与 Public 目录属于客体 Wine 环境,也不表示日常 Linux 宿主已被感染。

已有调查使用了相同的 WPS 宿主文件

我取得的 WPS EXE,还能与 OSArmor 在 2026 年 10 月 23 日发表的原始调查对应上:两份 EXE 的 SHA-256 完全相同,DLL 的哈希和大小则不同。

该报告中的邮件写成日文,同样以商品损坏为由,通过 AppSheet 和 Google 短链投递改名 WPS EXE 与 krpt.dll。

文件哈希相同支持“复用了相同宿主文件”的判断,相似包装不能直接确定同一个攻击者,也不能把另一份 DLL 的行为套在本样本上。

供专业人员比对的文件大小与 SHA-256

哈希用于核对文件身份,本文不提供样本下载。

原始文件与恢复出的 .NET 载荷的 SHA-256
ZIP(138900867 字节)
28e53894076225e468fd237bcfb1cdd8d683448c9757770d8cfdd7a948ae13b5
Complete_Item_Assessment_mp4.exe(1321728 字节)
c73e3b98b5f631669fb3d4b114e98ad55d2b015e2c55003c265e0b75e0c31d9e
krpt.dll(126144000 字节)
8f7eb86566d45ecfa85a7dd46c72b8db55a24a7a0cc7882fe5929d8506664bd4
从原包恢复出的 .NET 载荷(433152 字节)
5e9488d34be24163c971679f5d5d5d3a32c7ae94978c5d01dcbd8793fd6b0fdd

收到类似邮件,先核对业务,再核对文件用途

普通收件人不需要会反编译,也能在下载前或启动前识别这条链上的问题。

先回到自己已有的订单系统、工单和客户联系方式,确认是否真有这笔业务,再决定如何接收材料。

遇到的情况 可采取的做法
陌生人要求退货,却没有订单号、商品信息 在已有业务渠道核对,不依赖这封邮件自己提供的身份信息
以“文件太大”为由要求访问外链 要求通过已知售后平台提交,检查跳转后的实际域名
入口经过 Google、微软等正规服务 继续核对最终目的地和文件,入口品牌不能为内容背书
照片、视频变成 EXE、脚本或“专用查看器” 停止打开,查看完整扩展名,不凭图标或名称中的 mp4 判断
程序有签名,旁边还有 DLL 不把单个程序的签名当成整包安全证明
页面要求关闭防护、放行或绕过系统提示 停止操作,通过独立渠道确认,组织设备向安全人员反馈
已确认是诱导下载的邮件 在 Outlook 中报告钓鱼,保留必要证据,不把可运行样本转给别人试

在 Outlook 中,可以选中可疑邮件,再通过“报告 → 报告钓鱼”提交,具体入口见微软的 Outlook 中文帮助。

报告钓鱼并不等于阻止发件人继续发信,是否加入阻止列表需要另行处理。

已运行程序时,处置重点转向隔离和排查

是否执行过文件、是否提交过凭据,会改变下一步处理方式。

已经发生的操作 下一步
只阅读邮件 不再访问可疑链接,报告邮件
只打开网页,没有提交信息或运行下载文件 关闭可疑页面、检查下载记录,保持浏览器和系统更新。
仅访问不等于已经感染,也不能据此排除浏览器漏洞等其他风险。
下载 ZIP,但未执行内容 不解压试运行,不通过 Wine 打开;按留证需要交给安全人员处理,或删除不需保留的样本
输入密码、验证码或授权登录 从干净设备访问真实服务,修改凭据,撤销可疑会话或授权,检查登录记录
已运行 EXE 或脚本 立即断开该设备网络,停止在上面登录敏感账户,联系 IT 或安全人员排查;组织设备遵循事件响应流程

如果 Windows 设备已经运行过未知程序,可以参考微软 Defender 脱机扫描的中文说明,了解在常规 Windows 环境之外进行扫描的方法。

一次未检出不能代替完整排查,也不能证明程序留下的登录启动项已经清除。

如果还向可疑页面提交过 Outlook 或 Microsoft 账户凭据,账户与设备要分别处理。

在受影响电脑上更改密码之前,应先排查其中的恶意软件;也可以使用另一台干净设备处理账户。

微软的账户恢复中文帮助列出了扫描设备、更改或重置密码,以及检查连接账户、邮件转发和自动答复设置的步骤。

这封退货邮件没有把危险写在第一句话里。

它让“帮客户看一下损坏情况”成为打开外链的理由,又把本该直接查看的材料换成了程序包。

我能从这份样本确认到后台加载、持久化写入和一次次被本地拒绝的域名解析,最终载荷的完整行为仍然未知。

对真正处理售后的人来说,可以更早停在那个文件名面前:看开箱视频,为什么需要运行 .exe?

一封退货邮件的陷阱:“开箱视频”背后的 WPS、隐藏 DLL 与恶意加载链

作者:xingwangzhe

本文链接:https://xingwangzhe.fun/posts/outlook-return-malware/

本文采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。

Creative Commons

留言评论