#来些干货,Electron高级使用# 源码保护

982 阅读3分钟

前言

众说周知,Electron本身没有提供任何源码保护方案,只有一个ASAR的打包方式。

以下简单罗列下几个探索的方向

Asar

ASAR 表示 Atom Shell Archive Format。 一个 asar 档案就是一个简单的 tar 文件 - 比如将那些有关联的文件放至一个单独的文件格式中。 Electron 能够任意读取其中的文件并且不需要解压整个文件。

ASAR格式是为了在Windows系统读取大量的小文件时 (比如像从node_modules加载应用的JavaScript依赖关系树) 提高性能。

ASAR本意不是用来做源码加密的,只是一个副作用, 而且只能防小白,不能防了解Electron的开发者。

破解之道

npm i asar -g
asar e app.asar /some/path

代码混淆

  • 经典前端使用套路,只是增加阅读成本,不能保护源码

V8-Code-Cache

code cache 是 V8 中的一个特性,简单说就是 JavaScript 代码在执行前,取消进行解析和编译,才能正确执行,解析编译过程是耗时的,V8 暴露了一个方法,可以将编译产物序列化存储下来,下次再执行相同一段代码时,就可以用之前缓存的内容,节省了解析编译的时间

根据描述,code cache是源码编译生成的字节码,无法还原为源码,我们是否可以使用这个制定我们的加密方案呢? “理论可以”

步骤如下:

  1. 打包时候(客户端构建):生成codecahe
  2. 打包时候(客户端构建):修改原始js
  3. 运行时候加载

打包时生成CodeCache

// 加密函数
function createCachedData (filename, content) {
  var wrapper = Module.wrap(content)
  var script = new vm.Script(wrapper, {
    filename: filename,
    lineOffset: 0,
    displayErrors: true,
    produceCachedData: true,
  });

  return script.createCachedData()
}

//...

// 对所有.js遍历执行
let cachedData = createCachedData(filename, filedata)
// 保存下来
fs.writeFileSync(savefilename + '.cache', filedata)


loader.js(加载器)

  • 通过扩展原始require方法,支持加载.cache文件
const path = require('path');
const vm = require('vm');
const fs = require('fs');
const Module = require('module');
const crypto = require('crypto');


Module.prototype._compile = function(content, filename) {
  const mod = this;

  function require(id) {
    return mod.require(id);
  }

  // // https://github.com/nodejs/node/blob/v10.15.3/lib/internal/modules/cjs/helpers.js#L28
  function resolve(request, options) {
    return Module._resolveFilename(request, mod, false, options);
  }
  require.resolve = resolve;


  require.main = process.mainModule;

  // // Enable support to add extra extension types
  require.extensions = Module._extensions;
  require.cache = Module._cache;



  var cachedData
  var invalidationKey
  const dirname = path.dirname(filename);
  const hash = /\.cache$/.test(filename) && path.basename(filename, '.source')
  console.log(filename)

  if (hash) {
    invalidationKey = hash
  } else {
    invalidationKey = crypto
    .createHash('sha1')
    .update(filename, 'utf8')
    .digest('hex');
  }

  var p = path.join(process.resourcesPath, 'xx', invalidationKey) // xx替换为正确的路径
  if (fs.existsSync(p)) {
    cachedData = fs.readFileSync(p)
  }


  var wrapper = Module.wrap(content)
  var script = new vm.Script(wrapper, {
    filename: filename,
    lineOffset: 0,
    displayErrors: true,
    cachedData: cachedData,
  });

  if (!cachedData || script.cachedDataRejected) {
    fs.writeFileSync(p, script.createCachedData())
  }

  var compiledWrapper = script.runInThisContext({
    filename: filename,
    lineOffset: 0,
    columnOffset: 0,
    displayErrors: true,
  });


  // console.log(dirname)

  const args = [mod.exports, require, mod, filename, dirname, process, global, Buffer];
  return compiledWrapper.apply(mod.exports, args)
}

覆盖原始的js

require(resourcesDirPath + '/loader.js')
require(savefilename + '.cache' )

小结

  • 以上代码是我亲自写,但是有点历史,大家只能借鉴不能完全拷贝使用。(当时发现,生成的codecache不能在其他机器使用,没有往下分析)

扩展阅读

魔改Asar

  • 该方法,就是通过修改Electron原始代码,改变asar的的逻辑,无法通过npm的asar解密。(飞书之前Electron版本就是通过这方法)。但是这个方法要求有较强的C语言开发能力,修改和编译Electron,小团队无法复制。

大器Native Addon

通过原生组件加密,原生组件解密并在底层v8引擎执行达到目的。(文件都是加密否的二进制文件)

  1. 通过C语言,开发原生组件Addon
  2. 源码编译(客户端打包):加密源码并生产密文文件,同时替换原始文件(该步骤类似CodeCache生成,只不过加密是自定义的)
  3. 运行时:通过原生组件Addon,加载密文文件,并在底层v8引擎执行

生成的.js内容如下

var loadjs = require(process.resourcesPath + '/app.asar.unpacked/node_modules/@xxx_encrypt');
loadjs.execute(filename , require, __filename, __dirname, process);