← 文章 / 编程开发
Hacker News 25天前 · 2026-10-16 06:51:54 · 14 阅读

有人在种子站上传假剧集,Sonarr 中招抓下恶意 EXE

我在 NAS 上跑的是常规的 *arr 套件。平时都挺正常,直到我在仪表盘上看到一条警告。新一集《南方公园》卡在队列里,提示:NAS Panel 都 2026 年了,居然还有人信这个?

这是个 EXE

我把这事儿说清楚点。 有人拿《南方公园》某集的片名,在末尾加了 .exe,然后传到了公开 Tracker 上,种子数竟然高达 2,392。谁在拿 EXE 当剧集分发啊? 结果 Sonarr 还真把它抓下来了,它办事从不让人失望。之后它就一直待在那儿,每隔 90 秒就试一次导入: Copy
Import failed, path does not exist or is not accessible by Sonarr:
/data/downloads/torrents/South Park S29E03 1080p WEB H264-MeGusta.exe
连个带假 .mkv 和“编解码器安装器”的文件夹都懒得做,那是“旧时代”的做派了。 就一个 EXE。我是认真的。 qBittorrent 在添加两秒后就标记它为“完成”,且 total_downloaded: 0,所以实际上什么都没落盘。不知道为什么,也不在乎。谢了 qBit。 我在 Sonarr 里把它拉黑,这样会从 qBittorrent 中移除它,并阻止它再次抓取同样的垃圾恶意软件。 然后我故意又下载了一次。因为我现在很火,想知道里面到底有什么。

887 MB 的无意义填充

这是个真正的 PE,x64 GUI。实际程序大小只有 118 KB。 Copy
pe end 0x1ce00
overlay 886653736 bytes
另外那 886 MB 是粘在文件末尾的随机垃圾。为什么?因为很多扫描器遇到这么大的文件就直接放弃了。 版本信息写着: Copy
CompanyName Freeware Developers
FileDescription prepares application data
OriginalFilename SetupHelper.exe
“免费软件开发者”,呸,骗鬼。 我把前 118 KB 切下来丢进 IDA,因为我可不想把一整吉的废话加载进任何工具。然后我发现整个程序都被这种东西覆盖着: Copy
dword_14000C020 ^= 0xF2C2EAC0;
v34 = 0;
dword_14000C020 ^= 0xF2C2EAC0;
对一个全局变量做异或。再异或回去。啥也没干。重复几百次。 它的存在纯粹是为了让反编译器输出变得面目全非,而且它确实做到了,恭喜。 把这些全去掉,逻辑其实只有六步:
  1. 打开它自己的文件。
  2. 读取最后 64 个字节。
  3. 用这些字节在垃圾数据中找到并解密隐藏的载荷。
  4. 手动将其映射为内存中的 PE。
  • 把 PEB->ImageBaseAddress 指向它。
  • 跳进去执行。
  • 整个过程不落盘。为这六步加的全套混淆。

    文件尾

    文件最后 64 字节就是全部配置:
    偏移大小说明
    0x0032 字节key,与 .data 里的 32 字节 mask 异或
    0x2012 字节ChaCha20 nonce
    0x2C4 字节payload 大小 ^ fnv1a(mask)
    0x304 字节(flags | padding << 8) ^ fnv1a(mask)
    0x348 字节payload 大小 ^ fnv1a(mask)
    0x3C4 字节fnv1a(mask)
    用的是 ChaCha20。他们通过全局变量拼出常量来隐藏,但拼出来还是 expand 32-byte k。见过 ChaCha20 的人不可能认不出 ChaCha20。flags = 3 会开启反调试检查(PEB->BeingDebugged、查询调试端口,老一套)和一个反沙箱评分。任何一个被触发,程序就悄悄退出。沙箱看到的就是一个正常程序打开文件然后退出。挺聪明。但问题是,key 就明晃晃地放在文件里。(???)根本不需要运行它:pythonCopy
    mask = bytes.fromhex("56951de331cd7f254bf1931533dd82bc3c8bf542f0d1ed5736a40f394c51d0a1")
    h = fnv1a(mask)
    size = u64(footer, 0x34) ^ (h | h << 32)
    fl = h ^ u32(footer, 0x30)
    pad = fl >> 8
    key = xor(mask, footer[:32])
    nonce = footer[32:44]
    payload = chacha20(key, nonce, data[-64 - pad - size : -64 - pad])
    这样就解出了第二个 PE。929 KB,MSVC 编译,C++20,捆绑了 zlib。搞了一堆反分析的幺蛾子,结果十几行 Python 就把混淆扒干净了。

    不出所料,是个 Stealer

    字符串全加密,导入表只留 CRT,所有东西运行时才解析。结果 WinMain 直接把所有模块按名字明文列了出来: Copy
    osinfo
    browsers
    telegram
    wallets
    discord
    steam
    clipboard
    remoteaccess
    credentialmanager
    authy
    notes
    obs
    ssh
    email
    games
    filegrabber
    其余每个字符串都有专门的函数:cCopy
    v5 = _mm_xor_ps(v6, v5);
    v7 = { 1924987977, 1270327144, 877876327, 271431577 };
    v8 = { 641333755, -1942364857, ... };
    sub_140080F40(&v5, v7, v8, 0x20);
    两个常量异或生成密钥、IV 和密文长度。sub_140080F40 使用 AES,S-box 隐藏在 RC4 之后——显然一层加密还不够硬核。试了第一个模块的标准模式: Copy
    cbc  b'w\x1c\xe9\xc6\xa0y\x96\xa4...'
    ctr  b'guid=\x00\x00\x00...'
    
    AES-128-CTR,每个字符串独立一个密钥。672 处调用点。我绝不打算手动处理这些。于是我把所有调用点都从 IDA 里提取出来,在 Unicorn 中仅模拟每个调用点前几条指令,从寄存器中取出密钥、IV 和密文,再用 Python 解密。601 处解密结果清晰可读,剩下的藏在大函数里,但我根本不在乎——601 处已经足够了。

    它想要的一切

    • 浏览器:Chrome、Edge、Brave、Opera、Opera GX、Vivaldi、Firefox。包括密码、Cookie、自动填充,甚至 Chrome 新的应用绑定加密。
    • 加密货币钱包:Exodus、Electrum、Atomic、Guarda、Coinomi、Ledger Live、Trezor Suite、Wasabi、Sparrow、Monero、Zcash、Armory,以及浏览器扩展钱包。
    • 密码管理器:KeePass、KeePassXC、Bitwarden、1Password、NordPass、RoboForm、Windows 凭据管理器。
    • 账户信息:Telegram、Discord token、Steam、Roblox Cookie、Minecraft 账户、Authy。
    • 开发相关:.ssh、.aws、.azure、gcloud、.kube、.docker。
    • 远程访问:AnyDesk、TeamViewer、WinSCP、FileZilla、mRemoteNG、RDCMan。
    • 其他:Outlook、Thunderbird、VPN 配置、OBS 设置、便签,以及桌面文件。
    它甚至要抓你的便签——为了什么?!所有内容都会用内置的 zlib 压缩打包后外传。

    YouTube 频道

    没有硬编码服务器,而是解密后得到: Copy
    www.youtube.com/channel/UC18vFC1xgebddr_7NsvgBYA
    gist.githubusercontent.com/jasonterra69/48d9e264c05843f0cdc302143b4400c8/raw
    [[
    ]]
    ZhxchsdfhSGdfgsgdfAJsakdaksdk2364236AH
    
    一个 YouTube 频道、一个 GitHub Gist、两个花括号,还有一个明显是某人乱按键盘生成的密钥。这个 YouTube 频道名叫“bubblegym”。创建于 2026 年 9 月 14 日: 对 Base64 解码后的数据进行 XOR 运算,密钥就是那段键盘乱按,结果得到了以下地址: pythonCopy
    b = base64.b64decode("OA0dDQ0HFBQHJyIHEkkQCAlJLS0JTgwAAB8S")
    k = b"ZhxchsdfhSGdfgsgdfAJsakdaksdk2364236AH"
    bytes(x ^ k[i % len(k)] for i, x in enumerate(b))
    Copy
    beenetprotect[dot]com/lgz/gdata
    这个地址似乎还硬编码在二进制文件里作为后备方案??? 据我理解,dead drop(死信箱/中转站)的初衷应该是可以随时更换服务器,而不让地址直接暴露在恶意软件中。但你居然把地址直接写进了恶意软件里,那再从 Gist 和 YouTube 获取地址还有什么意义?

    Opsec 水平:Cloudflare 注册商

    Copy
    Domain Name: BEENETPROTECT.COM
    Creation Date: 2026-09-14T06:21:54Z
    Registrar: Cloudflare, Inc.
    Name Server: DONALD.NS.CLOUDFLARE.COM
    Name Server: JULISSA.NS.CLOUDFLARE.COM
    9 月 14 日注册。和那个“游戏频道”是同一天。构建日期是 10 月 6 日,10 月 8 日出现在 torrent 中。

    给 *arr 用户的 PSA(公共安全通告)

    如果你使用公共索引器运行 Sonarr 或 Radarr,这事肯定能撞上你。Sonarr 本身不能执行 exe,所以它不是问题所在。问题在于你的室友、你的父母,或者是凌晨 3 点你在下载文件夹里双击运行的那个(请勿执行这个垃圾恶意软件)你自己。 花五分钟做以下配置:
    • qBittorrent:将 *.exe、*.scr、*.lnk、*.bat、*.cmd 添加到“排除文件名”,它会直接拒绝下载这些文件。
    • Sonarr / Radarr:当看到这些文件时,使用带有“从客户端移除”功能的黑名单进行拦截。
    • Prowlarr:如果某个 tracker 总是提供这种垃圾,降低其优先级或直接移除它。
    至于制作这个恶意软件的人:你很可能花了数周时间或消耗大量 tokens,在 ChaCha20、AES-CTR、运行时 API 哈希、反沙箱评分、Chrome 应用绑定加密绕过上,结果做出来的却是个垃圾级 skid 恶意软件。 去正经找个班上吧。
    原始来源: Hacker News

    评论 (0)