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.nodemajor.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中生成,主要步骤:

  1. 计算大小并分配内存
  2. 设置头部信息:
    • 魔数(MagicNumber)
    • 版本哈希(VersionHash)
    • 源文件哈希(SourceHash)
    • 标志哈希(FlagHash)
    • 只读快照校验和
    • 有效载荷长度
  3. 复制序列化数据
  4. 计算并设置校验和

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源代码

  1. 安装依赖:
apt-get install ninja-build clang pkg-config
  1. 安装depot_tools:
mkdir ~/v8tools/
cd ~/v8tools/
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH=~/v8tools/depot_tools/:$PATH
  1. 拉取V8代码:
mkdir ~/v8
cd ~/v8
fetch v8
cd v8
  1. 切换到目标版本:
git checkout refs/tags/10.4.132.24
  1. 同步依赖:
gclient sync -D

3.2 修改V8引擎代码

关键修改点:

  1. 修改src/snapshot/code-serializer.cc

    • CodeSerializer::Deserialize中插入打印代码
    • 修改SerializedCodeData::SanityCheck直接返回成功
  2. 修改src/diagnostics/objects-printer.cc

    • SharedFunctionInfo::SharedFunctionInfoPrint中插入字节码反汇编调用
  3. 修改src/objects/objects.cc

    • HeapObject::HeapObjectShortPrint中添加各种类型的打印支持
  4. 修改src/objects/string.cc

    • 移除字符串打印长度限制
  5. (可选)修改src/snapshot/deserializer.cc

    • 注释掉魔数检查CHECK_EQ(magic_number_, SerializedData::kMagicNumber)

3.3 编译V8引擎

  1. 生成编译脚本:
./tools/dev/v8gen.py x64.release
  1. 修改编译参数(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
  1. 编译共享库:
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工具反编译

  1. 安装View8:
git clone https://github.com/j4k0xb/View8
cd View8
pip install -r requirements.txt
  1. 执行反编译:
python view8.py input_file output_file --path /path/to/disassembler

5. 完整流程总结

  1. 识别Electron应用的保护机制
  2. 提取V8字节码文件
  3. 分析字节码头部信息,确定V8版本
  4. 获取对应版本的V8源代码
  5. 修改V8引擎代码以支持反编译
  6. 编译修改后的V8引擎
  7. 编译反汇编工具
  8. 使用View8工具进行最终反编译

6. 注意事项

  1. 确保V8版本与目标字节码版本完全匹配
  2. 编译环境要求较高(建议16GB内存,SSD)
  3. 对于不同Electron/Node版本可能需要调整编译参数
  4. 反编译结果不是原始JS代码,而是字节码的伪代码表示
  5. 新版本V8(如12.x)可能需要额外修改代码位置和参数

通过以上步骤,可以有效地分析和还原由bytenode保护的Electron应用中的JavaScript代码。

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 魔数分析 魔数计算公式: 魔数间接编码了 ExternalReferenceTable::kSize 信息,用于快速验证数据合法性。 2.4 版本哈希计算 版本哈希通过MurmurHash算法计算: 哈希计算函数实现: 2.5 版本号爆破 通过暴力搜索匹配版本哈希: 3. 构建V8反编译环境 3.1 获取V8源代码 安装依赖: 安装depot_ tools: 拉取V8代码: 切换到目标版本: 同步依赖: 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引擎 生成编译脚本: 修改编译参数( out.gn/x64.release/args.gn ): 编译共享库: 3.4 编译反汇编工具 使用修改后的V8引擎编译反汇编工具: 对于Electron应用: 对于Node应用(不使用压缩指针): 4. 使用View8工具反编译 安装View8: 执行反编译: 5. 完整流程总结 识别Electron应用的保护机制 提取V8字节码文件 分析字节码头部信息,确定V8版本 获取对应版本的V8源代码 修改V8引擎代码以支持反编译 编译修改后的V8引擎 编译反汇编工具 使用View8工具进行最终反编译 6. 注意事项 确保V8版本与目标字节码版本完全匹配 编译环境要求较高(建议16GB内存,SSD) 对于不同Electron/Node版本可能需要调整编译参数 反编译结果不是原始JS代码,而是字节码的伪代码表示 新版本V8(如12.x)可能需要额外修改代码位置和参数 通过以上步骤,可以有效地分析和还原由bytenode保护的Electron应用中的JavaScript代码。