V8 字节码反编译 | 还原bytenode保护的js代码
字数 2091 2025-08-22 12:23:19
V8 字节码反编译与还原bytenode保护的JS代码
前言
本文详细讲解如何分析和反编译由bytenode保护的Electron应用中的V8字节码,还原原始JavaScript代码。通过逆向分析一个实际案例,展示从字节码提取到最终反编译的完整流程。
1. 识别Electron应用的保护机制
1.1 寻找可疑文件
在分析Electron应用时,通常会寻找app.asar文件,但在这个案例中:
- 安装目录下没有找到任何ASAR文件
- 发现两个可疑的
.node文件:wrapper.node和major.node - 这些文件是PE格式,大小数十MB
- 文件内包含少量明文JS代码和有规律的JS文件名结构
1.2 识别bytenode保护
通过分析发现:
- 文件内组装了数百个
ByteCodeInfo结构体 - 结构体数组
[]ByteCodeInfo储存了V8字节码和JS文件/函数名的映射关系 - 文件头包含V8字节码魔数
?? ?? DE C0 - 这与开源的bytenode保护机制相同:直接加载V8字节码缓存而非原始JS代码
2. V8字节码结构分析
2.1 V8字节码生成过程
字节码在SerializedCodeData::SerializedCodeData中生成,主要步骤:
- 计算大小并分配内存
- 设置头部信息:
- 魔数(MagicNumber)
- 版本哈希(VersionHash)
- 源文件哈希(SourceHash)
- 标志哈希(FlagHash)
- 只读快照校验和
- 有效载荷长度
- 复制序列化数据
- 计算并设置校验和
2.2 V8字节码头部结构
V8字节码头部包含以下关键字段:
| 偏移量 | 字段名 | 描述 |
|---|---|---|
| 0x00 | MagicNumber | 魔数标识 |
| 0x04 | VersionHash | 版本哈希值 |
| 0x08 | SourceHash | 源文件哈希 |
| 0x0C | FlagHash | 标志哈希 |
| 0x10 | ReadOnlySnapshotChecksum | 只读快照校验和 |
| 0x14 | PayloadLength | 有效载荷长度 |
| 0x18 | Checksum | 校验和 |
2.3 魔数分析
魔数计算公式:
static constexpr uint32_t kMagicNumber = 0xC0DE0000 ^ ExternalReferenceTable::kSize;
魔数间接编码了ExternalReferenceTable::kSize信息,用于快速验证数据合法性。
2.4 版本哈希计算
版本哈希通过MurmurHash算法计算:
static uint32_t Hash() {
return static_cast<uint32_t>(base::hash_combine(major_, minor_, build_, patch_));
}
哈希计算函数实现:
func hashCombine(seed, value int64) int64 {
value = (value * 0xCC9E2D51) & 0xFFFFFFFF
value = ((value >> 15) | (value << (32 - 15))) & 0xFFFFFFFF
value = (value * 0x1b873593) & 0xFFFFFFFF
seed ^= value
seed = ((seed >> 13) | (seed << (32 - 13))) & 0xFFFFFFFF
seed = (seed * 5 + 0xE6546B64) & 0xFFFFFFFF
return seed
}
2.5 版本号爆破
通过暴力搜索匹配版本哈希:
for i := 0; i < 20; i++ {
for j := 0; j < 20; j++ {
for k := 0; k < 500; k++ {
for l := 0; l < 100; l++ {
result := versionHash64(i, j, k, l)
if result == myUint32 {
fmt.Println(i, j, k, l)
return
}
}
}
}
}
3. 构建V8反编译环境
3.1 获取V8源代码
- 安装依赖:
apt-get install ninja-build clang pkg-config
- 安装depot_tools:
mkdir ~/v8tools/
cd ~/v8tools/
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH=~/v8tools/depot_tools/:$PATH
- 拉取V8代码:
mkdir ~/v8
cd ~/v8
fetch v8
cd v8
- 切换到目标版本:
git checkout refs/tags/10.4.132.24
- 同步依赖:
gclient sync -D
3.2 修改V8引擎代码
关键修改点:
-
修改
src/snapshot/code-serializer.cc:- 在
CodeSerializer::Deserialize中插入打印代码 - 修改
SerializedCodeData::SanityCheck直接返回成功
- 在
-
修改
src/diagnostics/objects-printer.cc:- 在
SharedFunctionInfo::SharedFunctionInfoPrint中插入字节码反汇编调用
- 在
-
修改
src/objects/objects.cc:- 在
HeapObject::HeapObjectShortPrint中添加各种类型的打印支持
- 在
-
修改
src/objects/string.cc:- 移除字符串打印长度限制
-
(可选)修改
src/snapshot/deserializer.cc:- 注释掉魔数检查
CHECK_EQ(magic_number_, SerializedData::kMagicNumber)
- 注释掉魔数检查
3.3 编译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应用可能需要添加
v8_enable_pointer_compression = false
- 编译共享库:
ninja -C out.gn/x64.release v8_monolith
3.4 编译反汇编工具
使用修改后的V8引擎编译反汇编工具:
对于Electron应用:
clang++ v8dasm.cpp -g -std=c++17 -Iinclude -Lout.gn/x64.release/obj -lv8_libbase -lv8_libplatform -lv8_monolith -o v8dasm -DV8_COMPRESS_POINTERS -ldl -pthread
对于Node应用(不使用压缩指针):
clang++ v8dasm.cpp -g -std=c++17 -Iinclude -Lout.gn/x64.release/obj -lv8_libbase -lv8_libplatform -lv8_monolith -o v8dasm
4. 使用View8工具反编译
- 安装View8:
git clone https://github.com/j4k0xb/View8
cd View8
pip install -r requirements.txt
- 执行反编译:
python view8.py input_file output_file --path /path/to/disassembler
5. 完整流程总结
- 识别Electron应用的保护机制
- 提取V8字节码文件
- 分析字节码头部信息,确定V8版本
- 获取对应版本的V8源代码
- 修改V8引擎代码以支持反编译
- 编译修改后的V8引擎
- 编译反汇编工具
- 使用View8工具进行最终反编译
6. 注意事项
- 确保V8版本与目标字节码版本完全匹配
- 编译环境要求较高(建议16GB内存,SSD)
- 对于不同Electron/Node版本可能需要调整编译参数
- 反编译结果不是原始JS代码,而是字节码的伪代码表示
- 新版本V8(如12.x)可能需要额外修改代码位置和参数
通过以上步骤,可以有效地分析和还原由bytenode保护的Electron应用中的JavaScript代码。