龙空技术网

把NodeJS“模块”编译为字节码

JShaman 137

前言:

而今大家对“js双字节”可能比较讲究,兄弟们都需要了解一些“js双字节”的相关文章。那么小编在网摘上收集了一些关于“js双字节””的相关内容,希望大家能喜欢,兄弟们一起来学习一下吧!

将NodeJS模块编译为字节码文件,目的,与JS混淆加密相似,也是为了JS代码的安全性,使代码不可阅读。

一、编译模块为字节码

在NodeJS中,与编译一个直接执行的JS文件为字节码不同,如果JS代码是一个模块,有导出函数,是要被其它文件require,那么,它不能直接的调用VM.script编译成bytecode。

例如代码:

exports.hello = function () {    console.log('Hello');}

有导出函数是hello,用以下代码编译:

  //读取文件  var code = fs.readFileSync(filePath, 'utf-8');  //调用v8虚拟机,编译代码 var script = new vm.Script(require('module').wrap(code));  //得到字节码,即bytecode  var bytecode = script.createCachedData();    //写文件,后缀为.bytecode  fs.writeFileSync(filePath.replace(/\.js$/i, '.bytecode'), bytecode);

特殊之处是module模块的warp方法,它会对代码进行包裹,前面的代码,经warp之后,会成为:

(function (exports, require, module, __filename, __dirname) { exports.hello = function () {    console.log('Hello');}

这是必须遵守的约定,然后才能进行编译。

二、加载并调用字节码模块

编译出字节码模块后,自然是require并调用它,方法如下:

const _module = require('module');const path = require('path');_module._extensions['.bytecode'] = function (module, filename) {    //读取bytecode式的模块  var bytecode = fs.readFileSync(filename);  //设置正确的文件头信息  setHeader(bytecode, 'flag_hash', getFlagBuf());  var sourceHash = buf2num(getHeader(bytecode, 'source_hash'));  //申请空间并放入bytecode  const script = new vm.Script('0'.repeat(sourceHash), {    cachedData: bytecode,      });  //bind为输出函数  const wrapperFn = script.runInThisContext();  // 这里的参数列表和之前的 wrapper 函数是一一对应的  wrapperFn.bind(module.exports)(module.exports, require, module, filename, path.dirname(filename));}//require字节码模块const hello = require('./hello.bytecode');hello.hello();

代码中调用到的几个函数如下:

let _flag_buf;function getFlagBuf() {  if (!_flag_buf) {    const script = new vm.Script("");    _flag_buf = getHeader(script.createCachedData(), 'flag_hash');  }  return _flag_buf;}function getHeader(buffer, type) {  const offset = HeaderOffsetMap[type];  return buffer.slice(offset, offset + 4);}function setHeader(buffer, type, vBuffer) {  vBuffer.copy(buffer, HeaderOffsetMap[type]);}function buf2num(buf) {  // 注意字节序问题  let ret = 0;  ret |= buf[3] << 24;  ret |= buf[2] << 16;  ret |= buf[1] << 8;  ret |= buf[0];  return ret;}

重点是bind方法,将函数绑定到某个对象,bind()会创建一个函数,函数体内的this对象的值会被绑定到传入bind()中的第一个参数的值,例如:f.bind(obj),实际上可以理解为obj.f(),

bind(module.exports)则给输出函数进行了绑定。

const hello = require('./hello.bytecode');hello.hello();这段代码的执行效果:

与直接require原始的js文件效果是一致的。

传统的混淆加密,比如JShaman,是把代码变成“乱码”,使代码不能正常阅读理解。而此字节码方式,是把代码变成了非文本模式的二进制格式,于安全的目标而言,两者异曲同工。

标签: #js双字节