首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
逆向工程
发新帖
10
5
[原创]实战还原 V8 bytenode 保护 JS(V8 字节码分析记录)
发表于: 2026-3-7 18:38
15376
[原创]实战还原 V8 bytenode 保护 JS(V8 字节码分析记录)
Dorimu
2026-3-7 18:38
15376
> V8 字节码分析,简单写写在前辈们的基础上,又遇到些什么问题~(绝对不是我水不出很长的文章的问题)~ > (如有错漏,敬请指正,因为是在弄完后过了很久才写的) > 我的blog:<a href="elink@c47K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2k6r3!0J5K9h3#2#2i4K6u0W2j5$3&6Q4x3V1j5J5x3o6t1#2i4K6u0r3x3o6y4Q4x3V1j5K6x3q4)9J5c8X3A6K6j5#2)9J5k6r3c8W2j5$3!0E0M7r3W2D9j5i4c8A6L8$3&6Q4x3V1j5`.">blog.dorimu.cn</a> --- ## 0x00 前言 拿到一个需要逆向分析的 JS `start.js`。 目标环境: - Node.js:`16.14.0` - 对应 V8:`9.4.146.24-node.20`(flag hash `ed0ab240`) 核心代码如下: ```javascript const vm = require('vm'); const v8 = require('v8'); const zlib = require('zlib'); const fs = require('fs'); const path = require('path'); const Module = require('module'); v8.setFlagsFromString('--no-lazy'); v8.setFlagsFromString('--no-flush-bytecode'); global.generateScript=function(cachedData, filename) { cachedData = zlib.brotliDecompressSync(cachedData); fixBytecode(cachedData); const length = readSourceHash(cachedData); let dummyCode = ''; if (length > 1) { dummyCode = '"' + '\u200b'.repeat(length - 2) + '"'; } const script = new vm.Script(dummyCode, { cachedData, filename }); if (script.cachedDataRejected) { throw new Error(''); } return script; } global.compileCode = function(javascriptCode, compress) { const script = new vm.Script(javascriptCode, { produceCachedData: true }); let bytecodeBuffer = (script.createCachedData && script.createCachedData.call) ? script.createCachedData() : script.cachedData; if (compress) bytecodeBuffer = zlib.brotliCompressSync(bytecodeBuffer); return bytecodeBuffer; }; global.fixBytecode = function(bytecodeBuffer) { const dummyBytecode = compileCode(''); dummyBytecode.subarray(12, 16).copy(bytecodeBuffer, 12); }; global.readSourceHash = function(bytecodeBuffer) { return bytecodeBuffer.subarray(8, 12).reduce((sum, number, power) => sum += number * Math.pow(256, power), 0); }; try { Module._extensions['.jsc'] = function(fileModule, filename) { const data = fs.readFileSync(filename, 'utf8') const bytecodeBuffer = Buffer.from(data, 'base64'); const script = generateScript(bytecodeBuffer, filename); function require(id) { return fileModule.require(id); } require.resolve = function(request, options) { return Module._resolveFilename(request, fileModule, false, options); }; if (process.main) { require.main = process.main; } require.extensions = Module._extensions; require.cache = Module._cache; const compiledWrapper = script.runInThisContext({ filename: filename, lineOffset: 0, columnOffset: 0, displayErrors: true }); const dirname = path.dirname(filename); const args = [ fileModule.exports, require, fileModule, filename, dirname, process, global ]; return compiledWrapper.apply(fileModule.exports, args); }; } catch (ex) { console.error('xrequire:' + ex.message); } require("${codeScript}") ``` 经过搜索资料发现: 这就是**V8 cachedData / bytenode** 方案。 --- ## 0x01 第一次尝试:View8 定位到 bytecode 后,我先上了 `View8`: <mark class="encrypted">b73K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6K6N6h3I4W2M7X3q4E0i4K6u0r3g2X3W2W2N6K6R3`.</mark> 然后配套 `9.4.146.24.exe` 去跑反编译。 发现问题: - 输出一点代码后自动崩溃退出。 - View8 因 d8 崩溃只导出 23 个外围函数,关键的函数基本丢失。 当时第一反应是项目太久没维护,又去github找别的项目。 --- ## 0x02 第二次尝试:jsc2js 又看了 `jsc2js`: <mark class="encrypted">495K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6^5M7i4V1J5x3o6l9$3i4K6u0r3K9Y4y4U0x3X3A6K6</mark> 这个仓库新一些,也有 patch + CI 体系。 把 patch 套到 `v8 9.4.146.24`,结果仍然和第一轮差不多。 这时候基本就炸毛了~(先躺一会)~: 字节码本来就不太好 hook,现成工具又不稳定。 于是查阅相关资料: - <mark class="encrypted">88fK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2S2P5h3&6S2K9$3g2&6j5g2)9J5k6h3y4G2L8g2)9J5c8X3q4J5N6r3W2U0L8r3g2K6i4K6u0r3j5%4c8X3i4K6u0r3j5g2)9J5k6s2q4#2K9h3y4C8i4K6u0V1k6%4g2A6k6r3g2Q4x3X3c8@1L8#2)9J5k6r3c8A6M7$3q4K6M7$3g2E0j5X3I4W2i4K6u0V1N6U0S2Q4x3X3c8T1P5i4c8W2j5$3!0V1k6g2)9J5c8R3`.`.</mark> - <mark class="encrypted">e04K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6J5j5$3g2Q4x3X3g2E0L8$3g2Q4x3V1j5J5x3o6t1#2i4K6u0r3x3o6q4Q4x3V1j5H3y4#2)9J5c8Y4j5^5i4K6u0V1j5Y4W2@1k6h3y4G2k6r3g2Q4x3X3c8V1k6h3y4G2L8i4m8A6L8r3g2J5i4K6u0r3</mark> > V8 bytecode 就是 V8 自己序列化的一段内部数据。 > 想稳定拿结果,必须回到 V8 源码层改输出逻辑。 > 不同 V8 版本在字节码层差异很大,尤其是 opcode、参数语义、寄存器布局。 --- ## 0x03 第三次尝试:拉 V8 仓库 ```bat @echo off set PATH=E:\Dev\SDKs\depot_tools;%PATH% set DEPOT_TOOLS_WIN_TOOLCHAIN=0 mkdir v8_941 cd v8_941 echo solutions = [{ > .gclient echo "name": "v8", >> .gclient echo "url": "https://chromium.googlesource.com/v8/v8.git@9.4.146.24", >> .gclient echo "deps_file": "DEPS", >> .gclient echo "managed": False, >> .gclient echo "custom_deps": {}, >> .gclient echo }] >> .gclient git clone --depth=1 --branch 9.4.146.24 https://chromium.googlesource.com/v8/v8.git v8 gclient sync -D --no-history ``` --- ## 0x04 patch + 编译参数 先patch,再单独构建 d8: ```bat cd /d <dir>\v8_941\v8 python ..\..\apply_patches_v8_94.py . gn gen out/release ninja -C out/release d8 ``` 构建参数: ```gn dcheck_always_on = false is_clang = 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 treat_warnings_as_errors = false v8_enable_pointer_compression = false v8_enable_31bit_smis_on_64bit_arch = false v8_enable_lite_mode = false v8_enable_i18n_support = true v8_enable_webassembly = true ``` --- ## 0x05 改动阶段 > 真男人就要硬刚v8,部分diff我就不贴出来了,把问题和思路贴一下,欸嘿~ --- ## 0x06 问题一:`cachedData` 反序列化被拒绝 `CodeSerializer::Deserialize` 默认会严检 magic/version/flags/hash/checksum/source hash。 如果任何一项没通过,它会直接 reject 掉这份缓存,返回空对象。 `src/snapshot/code-serializer.cc`: ```diff @@ SerializedCodeData::SanityCheck - SanityCheckResult result = SanityCheckWithoutSource(); - if (result != CHECK_SUCCESS) return result; - ... - return CHECK_SUCCESS; + return SerializedCodeData::SanityCheckResult::CHECK_SUCCESS; @@ SerializedCodeData::SanityCheckWithoutSource - if (this->size_ < kHeaderSize) return INVALID_HEADER; - uint32_t magic_number = GetMagicNumber(); - if (magic_number != kMagicNumber) return MAGIC_NUMBER_MISMATCH; - ... - if (Checksum(ChecksummedContent()) != c) return CHECKSUM_MISMATCH; - return CHECK_SUCCESS; + return SerializedCodeData::SanityCheckResult::CHECK_SUCCESS; ``` `src/snapshot/deserializer.cc`: ```diff @@ Deserializer<IsolateT>::Deserializer - CHECK_EQ(magic_number_, SerializedData::kMagicNumber); + /* + CHECK_EQ(magic_number_, SerializedData::kMagicNumber); + */ @@ ReadSingleBytecodeData + std::fprintf(stderr, "[FATAL] Unknown serializer bytecode: 0x%02x\n", data); ``` --- ## 0x07 问题二:反汇编/打印阶段栈溢出 > 这里就是之前view8打印不出来的主要问题: 1. `BytecodeArray::Disassemble` 2. 打常量池 3. 命中 `SharedFunctionInfo` 4. `SharedFunctionInfoPrint` 5. 再次 `Disassemble` 6. 深度叠加,最终栈爆 - 改动:TLS guard + SEH `src/diagnostics/objects-printer.cc`: ```diff +thread_local int g_in_bytecode_disasm = 0; ... + ++g_in_bytecode_disasm; + hbc->Disassemble(*(c->os)); + --g_in_bytecode_disasm; @@ SharedFunctionInfoPrint - PrintSourceCode(os); + // PrintSourceCode(os); + int exc = SehWrapCall(DoBcDisasm, &ctx); + if (exc != 0) { os << "<BytecodeArray Disassemble CRASHED ...>"; } ``` `src/objects/objects.cc`: ```diff +extern thread_local int g_in_bytecode_disasm; +void SafePrintSharedFunctionInfo(...); +void SafePrintFixedArray(...); ... case SHARED_FUNCTION_INFO_TYPE: + if (g_in_bytecode_disasm > 0) { break; } + SafePrintSharedFunctionInfo(shared, os); case FIXED_ARRAY_TYPE: + SafePrintFixedArray(FixedArray::cast(*this), os); ``` - 对应:d8 入口改成 BFS 平铺 `src/d8/d8.cc`: ```diff +void Shell::LoadBytecode(...) +std::deque<i::Handle<i::SharedFunctionInfo>> queue; +std::unordered_set<i::Address> seen; +while (!queue.empty()) { ... } +global_template->Set(isolate, "loadBytecode", + FunctionTemplate::New(isolate, LoadBytecode)); ``` --- ## 0x08 稳定性 ### 修 Handle 生命周期和字节码迭代稳定性 ```diff - i::HandleScope inner_scope(isolateInternal); + // No inner HandleScope here — child handles stored in queue/all_sfis + // must survive across iterations. outer_scope keeps them all alive. ... - i::BytecodeArray handle_storage = *hbca; - i::Handle<i::BytecodeArray> handle( - reinterpret_cast<i::Address*>(&handle_storage)); - i::interpreter::BytecodeArrayIterator iterator(handle); + // Use hbca directly — it's a proper Handle rooted in print_scope. + i::interpreter::BytecodeArrayIterator iterator(hbca); ... + // Re-derive base_address each iteration (GC-safe) + i::Address base_address = hbca->GetFirstBytecodeAddress(); ``` ### 调试可见性 + SFI 入队条件 ```diff + printf("[DBG] root SFI ptr = 0x%p\n", reinterpret_cast<void*>(root->ptr())); + printf("[DBG] root HasBytecodeArray = %d\n", root_has_bc); ... + printf("[DBG] cp[%d] raw=0x%p smi=%d\n", cp_index, + reinterpret_cast<void*>(obj.ptr()), obj.IsSmi()); ... - if (obj.IsSharedFunctionInfo()) { + if (!obj.IsSmi() && obj.IsSharedFunctionInfo()) { ``` --- ### 常量池可读性增强 ```diff +const int kMaxLiteralElementsToPrint = 1024; +std::function<void(i::Object, int)> print_compact_obj; ... +if (value.IsArrayBoilerplateDescription()) { ... } +if (value.IsFixedArray()) { ... } +if (value.IsFixedDoubleArray()) { ... } ... +print_compact_obj(obj, 0); ``` --- ### 其它 `src/objects/string.cc`: ```diff -if (len > kMaxShortPrintLength) { +// if (len > kMaxShortPrintLength) { ... -accumulator->Add("%c", c); +accumulator->Add("\\u%04x", c); ``` --- ## 0x09 将反编译结果初步还原成可读js - 喂给 `jsc2js/View8`(这里你可能要手动改一下,懒得贴了),我记得好像还要处理一下常量池? --- 来来回回折腾了三天 hhhhhhh 写完跑路
登录后可查看完整内容
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
#调试逆向
收藏
・
10
点赞
・
5
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
wx_晨梦
感谢你的积极参与,期待更多精彩内容!
2026-7-16 05:48
git_56009war9
为你点赞!
2026-3-12 10:56
深山樵夫
你的分享对大家帮助很大,非常感谢!
2026-3-10 16:19
我的小拇指啊
为你点赞!
2026-3-7 22:03
东方玻璃
为你点赞!
2026-3-7 20:52
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
2
)
mb_ldbucrik
雪 币:
6
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
691
粉丝
7
关注
私信
mb_ldbucrik
2
楼
感谢分享
2026-3-7 20:19
0
Imxz
雪 币:
112
活跃值:
(9420)
能力值:
( LV2,RANK:10 )
在线值:
发帖
6
回帖
752
粉丝
9
关注
私信
Imxz
3
楼
tql
2026-3-7 21:43
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
Dorimu
2
发帖
0
回帖
10
RANK
关注
私信
他的文章
[原创]面向 CTF / 流量分析 / 信息收集的网络基础总览
5112
[原创]实战还原 V8 bytenode 保护 JS(V8 字节码分析记录)
15376
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部