首页
社区
课程
招聘
[原创]实战还原 V8 bytenode 保护 JS(V8 字节码分析记录)
发表于: 11小时前 350

[原创]实战还原 V8 bytenode 保护 JS(V8 字节码分析记录)

11小时前
350

V8 字节码分析,简单写写在前辈们的基础上,又遇到些什么问题(绝对不是我水不出很长的文章的问题)

(如有错漏,敬请指正,因为是在弄完后过了很久才写的)

我的blog:blog.dorimu.cn

拿到一个需要逆向分析的 JS start.js

目标环境:

Node.js:16.14.0

对应 V8:9.4.146.24-node.20(flag hash ed0ab240

核心代码如下:

经过搜索资料发现:

这就是V8 cachedData / bytenode 方案。

定位到 bytecode 后,我先上了 View8

e5cK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6K6N6h3I4W2M7X3q4E0i4K6u0r3g2X3W2W2N6K6R3`.

然后配套 9.4.146.24.exe 去跑反编译。

发现问题:

输出一点代码后自动崩溃退出。

View8 因 d8 崩溃只导出 23 个外围函数,关键的函数基本丢失。

当时第一反应是项目太久没维护,又去github找别的项目。

又看了 jsc2js

92fK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6^5M7i4V1J5x3o6l9$3i4K6u0r3K9Y4y4U0x3X3A6K6

这个仓库新一些,也有 patch + CI 体系。

把 patch 套到 v8 9.4.146.24,结果仍然和第一轮差不多。

这时候基本就炸毛了(先躺一会)

字节码本来就不太好 hook,现成工具又不稳定。

于是查阅相关资料:

dceK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2S2P5h3&6S2K9$3g2&6j5g2)9J5k6h3y4G2L8g2)9J5c8X3q4J5N6r3W2U0L8r3g2K6i4K6u0r3j5%4c8X3i4K6u0r3j5g2)9J5k6s2q4#2K9h3y4C8i4K6u0V1k6%4g2A6k6r3g2Q4x3X3c8@1L8#2)9J5k6r3c8A6M7$3q4K6M7$3g2E0j5X3I4W2i4K6u0V1N6U0S2Q4x3X3c8T1P5i4c8W2j5$3!0V1k6g2)9J5c8R3`.`.

afcK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6J5j5$3g2Q4x3X3g2E0L8$3g2Q4x3V1j5J5x3o6t1#2i4K6u0r3x3o6q4Q4x3V1j5H3y4#2)9J5c8Y4j5^5i4K6u0V1j5Y4W2@1k6h3y4G2k6r3g2Q4x3X3c8V1k6h3y4G2L8i4m8A6L8r3g2J5i4K6u0r3

V8 bytecode 就是 V8 自己序列化的一段内部数据。

想稳定拿结果,必须回到 V8 源码层改输出逻辑。

不同 V8 版本在字节码层差异很大,尤其是 opcode、参数语义、寄存器布局。

先patch,再单独构建 d8:

构建参数:

真男人就要硬刚v8,部分diff我就不贴出来了,把问题和思路贴一下,欸嘿~

CodeSerializer::Deserialize 默认会严检 magic/version/flags/hash/checksum/source hash。

如果任何一项没通过,它会直接 reject 掉这份缓存,返回空对象。

src/snapshot/code-serializer.cc

src/snapshot/deserializer.cc

这里就是之前view8打印不出来的主要问题:

BytecodeArray::Disassemble

打常量池

命中 SharedFunctionInfo

SharedFunctionInfoPrint

再次 Disassemble

深度叠加,最终栈爆

src/diagnostics/objects-printer.cc

src/objects/objects.cc

src/d8/d8.cc

src/objects/string.cc

来来回回折腾了三天 hhhhhhh

写完跑路

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}")
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}")
@@ 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;
@@ SerializedCodeData::SanityCheck
 

[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!

收藏
免费 2
支持
分享
最新回复 (2)
雪    币: 7
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
感谢分享
10小时前
0
雪    币: 104
活跃值: (7717)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
tql
8小时前
0
游客
登录 | 注册 方可回帖
返回