社区应用 最新帖子 精华区 社区服务 会员列表 统计排行
  • 21阅读
  • 1回复

[分享]秒盗账号钱包!伪装Electron程序暗藏后门窃取加密数据

楼层直达
z3960 
级别: FLY版主
发帖
875580
飞翔币
132575
威望
325957
飞扬币
4001582
信誉值
8

近期,火绒威胁情报中心针对SeanPalia样本的分析发现,这是一款伪装为正常 Electron桌面程序运行的信息窃取木马。样本启动后,会通过main.js加载经过bytenode 编译的decrypted_payload.jsc,并以运行时恢复方式释放主payload。随后,程序会先结束抓包、调试和逆向分析工具,并批量关闭浏览器进程以释放数据库文件锁;之后通过系统性扫描浏览器、Discord、桌面钱包与浏览器钱包扩展等高价值目标,读取Login Data、Web Data、Cookies、History、Bookmarks、Local Storage/leveldb 等本地敏感数据,同时恢复Local State中的Chromium密钥并解密受保护的数据。
进一步分析显示,该样本还会额外下载并装载第二阶段payload2,用于处理Chromium App-Bound等更难直接提取的受保护数据。在完成本地窃取后,样本还会调用Discord API富化账号资料,补充支付方式、礼品码、好友和 guild信息,并收集公网 IP、主机硬件、系统信息以及游戏 / 工具存在性等受害者画像。最终,所有窃取结果会被加密、压缩、归档并上传到多个临时文件平台,同时将下载链接、压缩包密码和受害者摘要回传至攻击者控制端,形成一条完整的信息窃取、画像评估与集中出网链路,最终造成个人隐私泄露、账号被盗、数字钱包资产损失等严重后果。目前,火绒安全产品已实现对该恶意样本的拦截与查杀。
查杀图
[font=-apple-system, BlinkMacSystemFont, &quot]技术背景
Electron 是一种基于 Chromium 与 Node.js 的跨平台桌面应用框架,凭借开发成本低、生态完善以及可直接调用系统能力等特点,被大量桌面软件采用。但与此同时,Electron 应用通常会携带完整的 Node.js 运行环境,并通过 ASAR、JavaScript、V8 字节码或原生模块加载业务代码,这也使其逐渐成为攻击者投毒和隐藏恶意逻辑的载体。
SeanPalia 正是这类风险的典型样本。该程序表面上伪装为正常 Electron 应用,实际在启动后通过入口脚本加载恶意 V8 字节码载荷,并进一步执行浏览器数据窃取、Discord 信息窃取、加密货币钱包扫描、受保护数据解密、数据打包以及多通道上传等恶意行为。综合其执行链路与业务目标判断,该样本属于 Electron 形态的信息窃取木马。
流程图如下:
[font=-apple-system, BlinkMacSystemFont, &quot]一、样本分析
1. 初始阶段
初始化时期目的:样本在启动阶段会准备 Electron 与 Node.js 执行环境,并通过入口脚本加载核心 payload,从而让后续恶意模块能够在受害者系统中执行。
Electron 基础结构:Electron 由 Chromium 与 Node.js 组成,开发者可以使 HTML、CSS JavaScript 构建跨平台桌面程序。一个基础 Electron 应用通常包含 package.json、main.js 和页面文件,并由 Electron 可执行文件负责启动 。
Electron 的优势主要包括:
● 兼容性较好:应用绑定指定版本 Chromium,开发者无需额外处理大量浏览器兼容问题。
● Node.js 能力完整:前端页面或主进程可调用文件系统、进程、网络、系统信息等 Node.js API。
● 网络访问灵活:可直接使用 Node.js 网络模块 npm 依赖发起请求。
● 扩展能力强:可通过 npm 模块或原生扩展调用更多系统能力。
Electron 投毒原理:Electron 应用通常会将业务代码打包到 resources 目录下的 ASAR 文件中。攻击者可通过解包 ASAR 后篡改源码或依赖、替换入口脚本、植入加载器,或者利用调试模式注入恶意代码,从而实现命令执行、持久化、数据窃取或后门控制。Electron 程序带有可信数字签名,若投毒过程未改动外层主程序可执行文件,签名状态不受破坏,则极易降低用户安全警惕。
Electron 主程序图:
resources 下的 ASAR 文件图:
解包后得到 main.js、配置文件和被引用模块图:
分析 main.js 后发现,其主要作用并非正常业务逻辑,而是作为 payload 加载器继续引入后续恶意代码:
payload是由9Bgr5Cugh6G3UuU1.enc aes解密后得到的V8 Bytecode文件
9Bgr5Cugh6G3UuU1.enc图:
V8 Bytecode jsc文件图:
[font=-apple-system, BlinkMacSystemFont, &quot]二、V8 Bytecode 反汇编 / 反编译过程
V8 是什么?
V8是由Google 开发的高性能 JavaScript 引擎,最初用于 Chrome 浏览器,现在也被 Node.js 使用。
它的主要职责是把 JavaScript 源码执行成机器能理解的指令,让浏览器或服务器快速运 JS 代码:
Bytecode 执行:源码可以被编译为 V8 bytecode,别人拿到文件后难以直接看懂或运行。
怎么反编译?
先把 V8 序列化出来的字节码结构、常量池 BytecodeArray 打印出来,再据此做进一步反混淆和语义恢复。对 SeanPalia 这类 Electron/Node 样本,这一步的价值主要有四点:
● 确认样本使用的 V8 版本,保证后续字节码解析口径一致。
● 直接拿到 SharedFunctionInfo、常量池、对象模板等运行时结构。
● 绕过 bytenode/JSC 只给缓存字节码、不带原始源码的问题。
● 可以配合[原创]V8 Bytecode反汇 反编译不完全指南-逆向工程-看雪安全社区|专业技术交流与安全研究论坛配合阅读。(点击文章底部参考链接)。
2.1 总体思路
对这类 .jsc 文件,最稳的做法通常是:
1. 确认 Electron / Node / V8 版本
2. 拉取对应版本的 V8 源码
3. 在 V8 内部打印 SharedFunctionInfo、BytecodeArray、FixedArray 等结构
4. 编译一个最小的字节码加载器,把目标 .jsc 当作 CachedData 喂给 V8
5. 收集反汇编输出,再进入后续脚本化清洗、重命名和业务还原
这一步更准确地说是“反汇编 + 结构化打印”,而不是传统意义上的完整反编译。
2.2 拉取对应版本的 V8 源码
先安装最基本的构建依赖:

apt-get install ninja-build clang pkg-config
安装 depot_tools:

mkdir ~/v8toolscd ~/v8toolsgit clone https://chromium.googlesource.com/chromium/tools/depot_tools.gitexport PATH=~/v8tools/depot_tools:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$PATH
拉取 V8 源码:

mkdir ~/v8cd ~/v8fetch v8cd v8
切换到与目标字节码匹配的版本。本文样本分析时使用的是:

git checkout refs/tags/10.4.132.24gclient sync -D
这里版本必须尽量对齐目标 Electron/Node 所携带 V8。版本不匹配时,即使能绕过部分校验,输出结构也很容易错位。
2.3 修改 V8 以打印字节码结构
V8 内部本来就具备 Disassemble()、对象打印和 SharedFunctionInfoPrint() 能力,但默认这些能力主要服务于调试构建和源码调试。为了让它直接消费 .jsc 并吐出结构,我们需要做几处定点修改:
1. 放宽反序列化检查并打印 SharedFunctionInfo
修改 src/snapshot/code-serializer.cc:
● CodeSerializer::Deserialize 成功拿到结果后打印 SharedFunctionInfo
● SerializedCodeData::SanityCheck() 中临时直接返回成功,绕过一部分缓存校验。
插入的核心打印逻辑如下:

std::cout << "nStart SharedFunctionInfon";       result->SharedFunctionInfoPrint(std::cout);       std::cout << "nEnd SharedFunctionInfon";       std::cout << std::flush;
这里 Start SharedFunctionInfo / End SharedFunctionInfo 最好保持稳定,方便后续脚本按标记切分输出。
2. 打印 BytecodeArray
修改 src/diagnostics/objects-printer.cc,在 SharedFunctionInfo::SharedFunctionInfoPrint(std::ostream& os) 中:
● 注释 PrintSourceCode(os);
● 在合适位置补上 BytecodeArray 打印
核心逻辑如下:

os << "nStart BytecodeArrayn";       this->GetActiveBytecodeArray().Disassemble(os);       os << "nEnd BytecodeArrayn";       os << std::flush;
这样输出里就不只是函数壳,还能直接拿 Ignition 字节码指令流。
3. 打印常量池和对象模板
修改 src/objects/objects.cc,重点是 HeapObject::HeapObjectShortPrint 里几个常见结构分支:
● FIXED_ARRAY_TYPE
● FIXED_DOUBLE_ARRAY_TYPE
● OBJECT_BOILERPLATE_DESCRIPTION_TYPE
● SHARED_FUNCTION_INFO_TYPE
可以给这些分支加上带分隔符的打印,例如:

os << "nStart FixedArrayn";       FixedArray::cast(*this).FixedArrayPrint(os);       os << "nEnd FixedArrayn";
以及:

os << "nStart ObjectBoilerplateDescriptionn";     ObjectBoilerplateDescription::cast(*this).ObjectBoilerplateDescriptionPrint(os);   os << "nEnd ObjectBoilerplateDescriptionn";
通过该方式开展后续逆向分析时,就能把常量池、对象字面量模板和函数元数据一起还原出来,而不是只看到一串指令。
4. 避免字符串打印被截断
修改 src/objects/string.cc String::StringShortPrint,把过长字符串的截断逻辑临时关掉。否则很多 URL、混淆字符串表、脚本片段只会显示前半截,后面还需额外回补。
5. 需要时放宽 magic number 校验
如果样本字节码和本地 V8 版本只接近、不完全一致,可以进一步修 src/snapshot/deserializer.cc 中的 magic number 检查。常见做法是临时去掉:

CHECK_EQ(magic_number_, SerializedData::kMagicNumber);
2.4 最小字节码加载器
改完 V8 后,还需要一个极小的加载器把目标 .jsc 输入到 ScriptCompiler::CachedData。示例:

#include <fstream>#include <iostream>#include <vector>#include "libplatform/libplatform.h"#include "v8.h"using namespace v8;static Isolate* isolate = nullptr;static void loadBytecode(uint8_t* bytecodeBuffer, int length) {  ScriptCompiler::CachedData* cachedData =      new ScriptCompiler::CachedData(bytecodeBuffer, length);  ScriptOrigin origin(isolate, String::NewFromUtf8Literal(isolate, "code.jsc"));  ScriptCompiler::Source source(      String::NewFromUtf8Literal(isolate, ""dummy""),      origin,      cachedData);  ScriptCompiler::CompileUnboundScript(      isolate,      &source,      ScriptCompiler::kConsumeCodeCache);}static void readAllBytes(const std::string& path, std::vector<char>& buffer) {  std::ifstream input(path, std::ios::binary);  input.seekg(0, input.end);  size_t length = input.tellg();  input.seekg(0, input.beg);  if (length > 0) {    buffer.resize(length);    input.read(buffer.data(), length);  }}int main(int argc, char* argv[]) {  V8::SetFlagsFromString("--no-lazy --no-flush-bytecode");  V8::InitializeICU();  std::unique_ptr<Platform> platform = platform::NewDefaultPlatform();  V8::InitializePlatform(platform.get());  V8::Initialize();  Isolate::CreateParams createParams;  createParams.array_buffer_allocator =ArrayBuffer::Allocator::NewDefaultAllocator();  isolate = Isolate::New(createParams);  Isolate::Scope isolateScope(isolate);  HandleScope handleScope(isolate);  std::vector<char> data;  readAllBytes(argv[1], data);  loadBytecode(reinterpret_cast<uint8_t*>(data.data()), static_cast<int>(data.size()));}
这段程序本身不负责“反混淆”,仅负责将.jsc文件安全传入经篡改的V8引擎,并触发前面加进去的结构化打印。
2.5 编译 V8 与加载器
先生成构建目录:

./tools/dev/v8gen.py x64.release
然后编辑 out.gn/x64.release/args.gn,常用参数如下:

dcheck_always_on = false       is_component_build = false       is_debug = false       target_cpu = "x64"       use_custom_libcxx = false       v8_monolithic = true       v8_use_external_startup_data = false       v8_static_library = true       v8_enable_disassembler = true       v8_enable_object_print = true
如果目标 Node/Electron 携带的 V8,必要时还要根据版本处理压缩指针或沙箱相关配置。例如某些环境下需要:

v8_enable_pointer_compression = false
再编译:

ninja -C out.gn/x64.release v8_monolith
最后把前面的最小加载器链接到编译产物上,执行方式就是:

./v8dasm target.jsc > disasm.txt
2.6 这一阶段的实际产出
完成后,通常可以得到这些材料:
● SharedFunctionInfo
● BytecodeArray
● FixedArray 常量池
● ObjectBoilerplateDescription
● 长字符串、URL、对象模板、函数引用关系
再通过 v8View 对前面导出的 disasm.txt 做后处理,就能得到对应的 .js 文件。使用方式只需要理解成三步:
1. 先用修改后的 V8 导出 disasm.txt
2. 再把 disasm.txt 交给 v8View
3. v8View 输出可继续分析的 .js 文件
[font=-apple-system, BlinkMacSystemFont, &quot]三、decrypted_payload.jsc
decrypted_payload.jsc反编译后是一个装载层,它装载 BAVSItA 这一大段混淆的 js 代码业务模块。
从当前分析结果看,decrypted_payload.jsc 本身更像恢复器 / 装载层,它的作用不是直接承载完整恶意业务,而是把真正的主逻辑 body 恢复出来,再通过 new Function("BAVSItA", "deps", body) 动态构造入口函数。这里的 BAVSItA 才是当前分析到的那份大块混淆主文件,也是后续所有浏览器、Discord、钱包、上传和 payload2 逻辑真正挂载的地方。
代码:

// 非可执行伪代码:只用于说 decrypted_payload.jsc 的装载关系       (function bootstrapRecoveredJsc() {         const BAVSItA = { /* 当前混淆主文件在运行时对应的配置/上下文对象 */ };         const deps = { /* fs / path / crypto / dpapi / http / child_process 等依赖 */ };                  // jsc 里恢复出来的是一整块 body 文本         const body = "/* recovered body text */";                  // decrypted_payload.jsc 自己不直接展开主业务,         // 而是只负责把恢复出的 body 交给 new Function 组出入口         const recoveredEntry = new Function("BAVSItA", "deps", body);                  // 真正进入主逻辑的是 BAVSItA,而不是 decrypted_payload.jsc 自己         return recoveredEntry(BAVSItA, deps);       })();  
这里需要特别区分两层:
● decrypted_payload.jsc
○ 负责恢复 body
○ 负责调用 new Function("BAVSItA", "deps", body)
○ 负责把依赖注入进去
● BAVSItA
○ 才是当前那份真正承载主业务逻辑的混淆文件
○ 内部再继续展开反分析、目标发现、浏览器数据采集、钱包采集、Discord 富化、payload2 下载装载、归档上传与 C2 回传。
因此,对 SeanPalia 来说,decrypted_payload.jsc 更准确的定位不是“完整恶意逻辑本体”,而是恶意主逻辑的恢复与装载入口;真正需要持续反混淆和语义分析的核心对象,是传入 new Function(...) BAVSItA 主体。
[font=-apple-system, BlinkMacSystemFont, &quot]四、恶意行为详解:BAVSItA 混淆主体
前置说明: 反混淆对应文件
导出BAVSItA得到业务混淆文件图:
文件内容图:
后面的业务代码都是反混淆后的再进行修饰的代码,这里以钱包总入口为例:
反混淆过的原始代码:

async function collectBrowserWalletTokenBundle(...e67guKE){  var xIlSGc={    Psj1cL(...e67guKE){return collectChromiumLoginDataByTarget(...e67guKE)},    Mo7Wwi(...e67guKE){return _SHQR5(...e67guKE)},    eA6n_l6(...e67guKE){return PX8GL2B(...e67guKE)},    PemkRM(...e67guKE){return collectDiscoveredDiscordTokenFiles(...e67guKE)}  };
这个 xIlSGc 可以先理解成一个局部“结构体 / 转发对象”。把浏览器登录数据、桌面钱包、浏览器钱包扩展、token 文件这几类子流程统一挂载后,再传给下游函数处理。
字段对应关系:
● Psj1cL
○ 类型:局部转发方法名
○ 实际调用:collectChromiumLoginDataByTarget(...)
○ 最终业务名:collectChromiumLoginDataByTarget
● Mo7Wwi
○ 类型:局部转发方法名
○ 实际调用:_SHQR5(...)
○ 说明:_SHQR5 这一支处理的是桌面钱包目标路径
○ 最终业务名:collectDesktopWalletArtifacts
● eA6n_l6
○ 类型:局部转发方法名
○ 实际调用:PX8GL2B(...)
○ 说明:PX8GL2B 这一支处理的是浏览器钱包扩展目标路径
○ 最终业务名:collectBrowserExtensionWalletArtifacts
● PemkRM
○ 类型:局部转发方法名
○ 实际调用:collectDiscoveredDiscordTokenFiles(...)
○ 最终业务名:collectDiscoveredDiscordTokenFiles
前者只是当前局部对象里的槽位名,后者才是真正被调用的下游函数。后面的语义名,则是根据这些下游函数实际处理的目标对象总结出来的。
整条推导链可以按下面这条顺序理解:
1. 先从外层 wrapper 看调用关系Mo7Wwi -> _SHQR5eA6n_l6 -> PX8GL2B
2. 再看这两个真实下游函数分别处理什么目标集合_SHQR5 处理桌面钱包目标路径PX8GL2B 处理浏览器钱包扩展目标路径
3. 最后按它们实际处理的对象,给出业务语义名_SHQR5 -> collectDesktopWalletArtifactsPX8GL2B -> collectBrowserExtensionWalletArtifacts
4. 再把局部转发名替换成可读函数名,得到整理后的版本

async function collectBrowserWalletTokenBundle(...e67guKE){  var xIlSGc={    collectChromiumLoginDataByTarget(...e67guKE){return collectChromiumLoginDataByTarget(...e67guKE)},    collectDesktopWalletArtifacts(...e67guKE){return collectDesktopWalletArtifacts(...e67guKE)},    collectBrowserExtensionWalletArtifacts(...e67guKE){return collectBrowserExtensionWalletArtifacts(...e67guKE)},    collectDiscoveredDiscordTokenFiles(...e67guKE){return collectDiscoveredDiscordTokenFiles(...e67guKE)}  };        return _hBIlH(e67guKE, xIlSGc);}
1. 钱包与扩展资产采集
这一部分在代码内部主要做三件事:先枚举硬编码的钱包路径和浏览器扩展路径,再把命中的目录、配置文件和相关资产复制进归档区,最后对少数高价值钱包继续做更深一层的数据恢复。像 Exodus 这类目标,代码不只是判断目录是否存在,还会继续读 seed.seco、passphrase.json 一类文件,并调用助记词/密码恢复逻辑,尽量把“目录存在”提升成“可直接利用的钱包材料”
1.1 钱包总入口
功能解析:
1. 统一协调多类高价值资产来源
2. 不只拿钱包目录,也顺带纳入浏览器凭据 token 文件:
3. 是“钱包与资产归档”主入口
运行结果:
● 形成桌面钱包、浏览器钱包扩展、凭据与 token 文件的统一资产归档
动态参数:
● 入口动态依赖:
○ 浏览器登录数据归档结
○ 桌面钱包目录
○ 浏览器钱包扩展目
○ 已发 token 文件
参数:
● collectChromiumLoginDataByTarget
○ 浏览器登录数据采集入
● collectDesktopWalletArtifacts
○ 桌面钱包目录采集
● collectBrowserExtensionWalletArtifacts
○ 浏览器钱包扩展采
● collectDiscoveredDiscordTokenFiles
○ token 文件补充采集
代码:

/*** 组合浏览器登录数据、桌面钱包、浏览器钱包扩展、token 文件四类来源*/// 浏览器凭据与钱包资产会被统一合并进一条高价值资产收集链async function collectBrowserWalletTokenBundle(...e67guKE){  var xIlSGc={    collectChromiumLoginDataByTarget(...e67guKE){return collectChromiumLoginDataByTarget(...e67guKE)},    collectDesktopWalletArtifacts(...e67guKE){return collectDesktopWalletArtifacts(...e67guKE)},    collectBrowserExtensionWalletArtifacts(...e67guKE){return collectBrowserExtensionWalletArtifacts(...e67guKE)},    collectDiscoveredDiscordTokenFiles(...e67guKE){return collectDiscoveredDiscordTokenFiles(...e67guKE)}  };  return _hBIlH(e67guKE, xIlSGc);}
1.2 Exodus 助记词 / 密码恢复
功能解析:
1. 检查 Exodus 是否存在
2. 不只复制目录,还尝试深入恢复 mnemonic和password
3. 恢复成功后再组装成上报内容
运行结果:
● 可能得到 mnemonic
● 可能得到 password
● 也可能无结果返回
读取目标 / 请求目标:

C:UsersAdministratorAppDataRoamingexodusexodus.walletseed.secopassphrase.json
动态参数:
● 关键动态依赖:
○ seco-file
○ bitcoin-seed
○ bip39
参数:
● readPassphraseJson
○ 读取钱包相关配置
● collectPasswordIfNeeded
○ 必要时收集口
● decodeMnemonic
○ 尝试恢复助记
代码:

/**        * 检 Exodus 钱包并尝试恢 mnemonic / password          */       async function extractExodusMnemonicAndPassword(deps) {         try {           // 读取 Exodus  passphrase 配置或相关钱包元信息           const passphraseInfo = await deps.readPassphraseJson();                    // 某些情况下需要先拿到用户口令,才能继续解密内部内             const password = await deps.collectPasswordIfNeeded();                    // 尝试把钱包内部数据还原成助记             const mnemonic = await deps.decodeMnemonic(passphraseInfo, password);           if (!mnemonic && !password) return null;                    return { mnemonic: mnemonic ?? undefined, password: password ?? undefined };         } catch {           return null;         }       }  
2. 反分析 / 反调试
这一部分在代码内部会先准备一长串目标进程名,然后循环调用 taskkill /IM "<name>" /F /T 去结束抓包、调试、逆向、监控和部分浏览邮件客户端进程。实现上它不会因为单个结束失败就停下,而是继续处理下一个目标,并在每轮之间插入短延时。这样做一方面是为了降低被分析到的概率,另一方面也是为了释放浏览器数据库、配置文件和会话文件的占用锁。
2.1 结束目标进程
功能解析:
1. 构造一大批目标进程名列表
2. 对每个进程执 taskkill /IM "<name>" /F /T
3. 即使单个结束失败,也不会中断整体流程
4. 每轮之间加入短延时,避免系统调用过密
运行结果:
● 强制关闭浏览器、邮件客户端及部分分析相关程序
● 释放数据库和配置文件锁
读取目标 / 请求目标:

taskkill /F /T /IM "HTTP Toolkit.exe"       taskkill /F /T /IM "HTTPDebuggerUI.exe"       taskkill /F /T /IM "HTTPDebuggerSvc.exe"       taskkill /F /T /IM "HTTPDebuggerPro.exe"       taskkill /F /T /IM "HTTP Debugger Pro.exe"
动态参数:
● 关键命令
○ taskkill /IM "<processName>" /F /T
● 关键运行条件
○ processName
○ throttleMs
参数:
● processNames
○ 一大批目标进程名数
● execCommand
○ 实际执行 taskkill 的命令调用函数
● delay
○ 每次结束进程后的节流等待函数组
● throttleMs
○ 节流等待时间
代码:

/**        * 对一大批目标进程执行 taskkill /F /T          */       async function terminateTargetProcesses(deps) {         for (const processName of deps.processNames) {           try {             // 对每个目标进程执行强制结               await deps.execCommand(`taskkill /IM "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$${processName}" /F /T`);           } catch {             // 单个失败不影响整体清             }                    // 每轮之后稍作等待,避免系统调用过于密             await deps.delay(deps.throttleMs);         }       }  
3. 目标发现与 Discord 数据窃取
这一部分在代码内部先做目标枚举,再做 token 提取,最后做账号富化。它会按硬编码路径检 Discord 安装目录、浏览器 profile、Local State Local Storage\leveldb,把存在的目标组织成后续采集列表;随后在 .ldb/.log 文本里匹配普通的 Discord 用户认证 token dQw4w9WgXcQ 前缀密文 token,并在需要时结合 Local State 恢复出的密钥做解密。拿 token 后,代码还会继续调 Discord API 拉用户资料、支付方式、礼品码、好友和 guild 信息,把原始 token 变成更有价值的账号画像。
3.1 发现 token
功能解析:
1. 遍历 Discord / 浏览器本地存 LevelDB
2. 正则匹配经典 token、MFA token、宽匹配 token
3. 若命中新格式密文 token,再结合 Local State key 解密
4. 去重后汇总为 token 集
运行结果:
● 产出后续所 Discord API 利用所需 token
读取目标 / 请求目标:

    <Discord or Browser>Local Storageleveldb*.ldb       <Discord or Browser>Local Storageleveldb*.log       <Discord>Local State
动态参数:
● 已确 Discord 客户端路径:
○ C:UsersAdministratorAppDataRoamingdiscord
○ C:UsersAdministratorAppDataRoamingdiscordcanary
○ C:UsersAdministratorAppDataRoamingdiscordptb
○ C:UsersAdministratorAppDataRoamingdiscorddevelopment
● 关键动态参数:
○ 普 token regex
○ dQw4w9WgXcQ 前缀密文 token
○ Local State 中恢复出的解 key
参数:
● targets
○ LevelDB 路径和可 Local State 路径
● listLevelDbFiles
○ 枚举 .ldb/.log
● readText
○ 读文件文本
● regexFindAll
○ 匹配 token 模式
● readLocalStateMasterKey
○ 读取解密 key
● decryptDiscordToken
○ 解新格式 token
代码:

    </blockquote><blockquote style="color: rgb(51, 51, 51); font-family: 微软雅黑, Tahoma, Helvetica, SimSun, sans-serif;">/*扫描 .ldb / .log,匹配普 token;如果是 Discord 安装根,还尝试解密新格式 token */    </blockquote><blockquote style="color: rgb(51, 51, 51); font-family: 微软雅黑, Tahoma, Helvetica, SimSun, sans-serif;">    async function discoverDiscordTokensAcrossTargets(targets: Array<{</blockquote><blockquote style="color: rgb(51, 51, 51); font-family: 微软雅黑, Tahoma, Helvetica, SimSun, sans-serif;">levelDbPath:       string; localStatePath?: string }>, deps) {    </blockquote><blockquote style="color: rgb(51, 51, 51); font-family: 微软雅黑, Tahoma, Helvetica, SimSun, sans-serif;">      const discoveredTokens = new Set<string>();    </blockquote><blockquote style="color: rgb(51, 51, 51); font-family: 微软雅黑, Tahoma, Helvetica, SimSun, sans-serif;">         </blockquote><blockquote style="color: rgb(51, 51, 51); font-family: 微软雅黑, Tahoma, Helvetica, SimSun, sans-serif;">      // 真实实现会遍历每 LevelDB 文件,分别匹配普 token 和加 token    </blockquote><blockquote style="color: rgb(51, 51, 51); font-family: 微软雅黑, Tahoma, Helvetica, SimSun, sans-serif;">      // 如果目标 Discord 客户端目录,还会读取 Local State  dQw4w9WgXcQ 前缀 token 解密    </blockquote><blockquote style="color: rgb(51, 51, 51); font-family: 微软雅黑, Tahoma, Helvetica, SimSun, sans-serif;">      return [...discoveredTokens];    </blockquote><blockquote style="color: rgb(51, 51, 51); font-family: 微软雅黑, Tahoma, Helvetica, SimSun, sans-serif;">    }
3.2 账号富化
功能解析:
1. token users/@me 确认账号可用
2. 并发补全支付 FA、礼品码、好友、guild 管理权限等信息
3. 把“一 token”升级成“一个高价值账号画像”
运行结果:
● 产出可直接用于筛选高价 Discord 账户的情报
读取目标 / 请求目标:

GET https://discord.com/api/v9/users/@me       GET https://discord.com/api/v9/users/@me/billing/payment-sources       GET https://discord.com/api/v9/users/@me/outbound-promotions/codes       GET https://discord.com/api/v9/users/@me/relationships       GET https://discord.com/api/v9/users/@me/guilds?with_counts=true
动态参数:
● 入口动态参数:
○ 已发现的 Discord token 列表
● 关键请求头:
○ Authorization: <stolen token>
参数:
● tokens
○ 已发现的 Discord token 列表
● fetchCurrentUser
○ 拉用户基本资料
● fetchBillingSummary
○ 拉支付信息
● fetchMfaStatus
○ 2FA 状态
● fetchGiftCodes
○ 拉促销/礼品码
● collectDiscordFriends
○ 拉好友列表
● fetchDiscordHighValueGuilds
○ 拉高价 guild
代码:

       /**        *  token  users/@me,再并发 billing FA、礼品码、好友和高价 guild          */       async function enrichDiscordUserProfile(tokens: string[], deps) {         const profiles: any[] = [];                  for (const token of tokens) {           // 先用 users/@me 校验 token 是否有效,并拿到基础账号资料           const currentUser = await deps.fetchCurrentUser(token);           if (!currentUser) continue;                    // 真实实现还会并发拉支付方式 FA、礼品码、好友和高价 guild           profiles.push(currentUser);         }                  return profiles;       }  
4. 无头浏览器辅助执行链
这一部分在代码内部会先解码内置的浏览器可执行文件路径表,确认 Chrome、Brave、Edge、ChromeBeta 等目标是否存在,然后拼出一组很长的 headless 启动参数去实际拉起浏览器。参数里会主动关闭扩展、同步、站点隔离、后台联网、Web 安全等能力,因此这条链的目的不是正常自动化测试,而是尽量在一个受控、低安全限制的浏览器环境里执行后续辅助动作。
4.1 Chrome / Edge 头模式启动链
功能解析:
1. 从内置浏览器可执行文件映射表里解 Chrome / Edge 等目标路径
2. 检查目标浏览器是否存在
3. 组装一组带大量安全禁用参数 headless 启动命令
4. 将这些任务交给后 helper 实际拉起
运行结果:
● 形成 Chrome / Edge 的头模式浏览器启动任务
● 可能用于后续会话注入、页面访问或 session 窃取辅助
读取目标 / 请求目标:

chrome.exe --headless=new --no-sandbox --disable-setuid-sandbox --disable-gpu         --disable-software-rasterizer --disable-webgl --disable-web-security         --disable-client-side-phishing-detection --disable-domain-reliability         --disable-site-isolation-trials --disable-sync --disable-translate         --disable-notifications --disable-permissions-api --disable-extensions         --disable-default-apps --disable-component-update         --disable-background-timer-throttling --disable-renderer-backgrounding         --disk-cache-size=1 --media-cache-size=1 --no-first-run  
动态参数:
● 调用链:
○ qT2G09
○ tVcLge
○ launchOrSpawnDecodedBrowserExecutable
○ S1roXJN
● 浏览器路径来源:
○ browserExecutableMapBase64
● 关键运行条件
○ 可执行文件存在性检
○ 浏览器路 Base64 解码
● 关键启动标志
○ --headless=new
○ --disable-web-security
○ --disable-extensions
○ --no-sandbox
参数:
● browserTarget
○ 浏览器目标描述对象
● decodeBrowserExecutableMapEntries
○ 解码内置浏览器路径映射表格
● pathExists
○ 检查浏览器可执行文件是否存在
● spawnBrowserTask
○ 实际拉起浏览器任务
代码:

/**        * 解码浏览器路径映射,检查可执行文件是否存在,并构 headless 启动任务          */       async function launchOrSpawnDecodedBrowserExecutable(browserTarget: { browserName: string; executableKey: string }, deps) {         // 从内 Base64 映射表恢复出浏览器可执行文件路径         const executableMap = deps.decodeBrowserExecutableMapEntries(deps.browserExecutableMapBase64);         const executablePath = executableMap[browserTarget.executableKey];         if (!executablePath) return [];                  // 目标浏览器不存在就直接结           if (!(await deps.pathExists(executablePath))) return [];                  // 组装头模式浏览器启动参数,重点是关闭一大批安全与隔离功           const args = [           "--headless=new",           "--no-sandbox",           "--disable-setuid-sandbox",           "--disable-gpu",           "--disable-software-rasterizer",           "--disable-webgl",           "--disable-web-security",           "--disable-client-side-phishing-detection",           "--disable-domain-reliability",           "--disable-site-isolation-trials",           "--disable-sync",           "--disable-translate",           "--disable-notifications",           "--disable-permissions-api",           "--disable-extensions",           "--disable-default-apps",           "--disable-component-update",           "--disable-background-timer-throttling",           "--disable-renderer-backgrounding",           "--disk-cache-size=1",           "--media-cache-size=1",           "--no-first-run",         ];                  // 真实链路会把任务继续交给更低 helper 或直 spawn         return [await deps.spawnBrowserTask(executablePath, args)];       }  
4.2 动态命中与主线作用
功能解析:
1. 运行时会先从 browserExecutableMapBase64 中恢复出少数硬编码浏览器可执行文件路径
2. 这条链当前不是“所有被扫描浏览器都启动 headless”,而是优先尝试 Chrome / Brave / Edge / ChromeBeta 这一小组可执行文件
3. 动态证据已经证明它实际命中 Edge,通过附加大量安全削弱参数拉起进程
4. 因此这条链更像浏览器侧辅助执行器,用来借本地浏览器环境完成后续页面访问、会话复用或注入型操作,而不是普通本地数据库读取逻辑
运行结果:
● 已确认主程序当前运行中能真实拉起 msedge.exe
● 说明这条 headless 链不是纯静态死代码
读取目标 / 请求目标:

browserExecutableMapBase64:       - Chrome       - Brave       - Edge       - ChromeBeta                动态命中:       C:Program Files (x86)MicrosoftEdgeApplicationmsedge.exe
动态参数:
● Stage14 主文件内置浏览器路径表:
○ Chrome
○ Brave
○ Edge
○ ChromeBeta
● 动态存在性检查命中:
○ C:Program Files (x86)MicrosoftEdgeApplicationmsedge.exe
● 动态事件:
○ FS_EXISTS
○ SPAWN
● 已捕获启动参数除核心标志外,还包含:
○ --disable-webgl2
○ --disable-accelerated-2d-canvas
○ --disable-accelerated-video-decode
○ --mute-audio
○ --hide-scrollbars
○ --disable-background-networking
参数:
● browserExecutableMapBase64
○ 内置 Base64 编码浏览器路径字典
● decodedExecutablePath
○ 解码后的浏览器可执行文件路径
● spawnArgs
○ 一组带 headless 与降安全语义的启动参数
● spawn
○ 实际执行浏览器启动
代码:

        /**        * 动态日志已经证明这条链实际命中 Edge          * 它的意义不是直接读本地数据库,而是借浏览器本体执行浏览器侧辅助动作          */       async function runHeadlessBrowserAssistChain(deps) {         const executableMap = deps.decodeBrowserExecutableMapEntries(deps.browserExecutableMapBase64);         const edgePath = executableMap.Edge;         if (!edgePath) return false;                  if (!(await deps.pathExists(edgePath))) return false;                  await deps.spawn(edgePath, [           "--headless=new",           "--no-sandbox",           "--disable-web-security",           "--disable-extensions",           "--disable-background-networking",           "--disable-site-isolation-trials",         ]);                  return true;       }  
5. 浏览器数据采集主线
这一部分在代码内部会围绕各类 Chromium / Gecko profile 逐个检查关键文件是否存在,然后分别打开 SQLite 数据库或 JSON 文件,把不同类型的数据拆开导出。对 Login Data,它会查 logins 表并解密 password_value;对 Web Data,它会取 autofill、credit_cards、local_stored_cvc;对 Cookies,它会读 encrypted_value 并按浏览器版本处理; History Bookmarks,它会提取下载记录、访问痕迹和书签树。最终这些内容会被分类写入归档目录,形成浏览器侧的主体战果。
5.1 Chromium 登录数据
功能解析:
1. 定位每个 Chromium profile 下的 Login Data
2. 查询 origin_url / username_value / password_value
3. 使用 Local State 恢复 key 解密保存密码
4. 按浏览器维度写入 Passwords/*.txt
运行结果:
● 产生浏览器保存账号密码的明文导出
读取目标 / 请求目标:

<Browser>User Data<Profile>Login Data  
动态参数:
● 目标数据库路径模式:
○ <Browser>User Data<Profile>Login Data
● 已确认命中路径:
○ C:UsersAdministratorAppDataLocalGoogleChromeUser DataDefaultLogin Data
○ C:UsersAdministratorAppDataLocalMicrosoftEdgeUser DataDefaultLogin Data
● 关键动态依赖:
○ SQLite logins 表
○ Local State 恢复出的 masterKey
本帖最近评分记录: 1 条评分 飞扬币 +50
爱我中华 飞扬币 +50 25分钟前 社区因为有您的参与更精彩!
我不喜欢说话却每天说最多的话,我不喜欢笑却总笑个不停,身边的每个人都说我的生活好快乐,于是我也就认为自己真的快乐。可是为什么我会在一大群朋友中突然地就沉默,为什么在人群中看到个相似的背影就难过,看见秋天树木疯狂地掉叶子我就忘记了说话,看见天色渐晚路上暖黄色的灯火就忘记了自己原来的方向。
级别: FLY版主
发帖
695281
飞翔币
237554
威望
4141
飞扬币
3465740
信誉值
0

只看该作者 1 发表于: 10分钟前
安全第一