NodeJS代码保护:把NodeJS“模块”编译为字节码

将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,是把代码变成“乱码”,使代码不能正常阅读理解。而此字节码方式,是把代码变成了非文本模式的二进制格式,于安全的目标而言,两者异曲同工。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值