基本概念
一、代码安全
Ref: 前端核心代码保护技术面面观
对于iOS或是Android来说,我们可以将相关的算法通过C/C++进行编写,然后编译为dylib或是so并进行混淆以此来增加破解的复杂度,但是对于前端来说,并没有类似的技术可以使用。当然,自从asm.js及WebAssembly的全面推进后,我们可以使用其进一步增强我们核心代码的安全性,但由于asm.js以及WebAssembly标准的开放,其安全强度也并非想象中的那么美好。
使用Javascript的混淆器
对于Javascript的混淆器我们是不陌生的,我们常常使用其进行代码的压缩以及混淆以此来减少代码体积并增加人为阅读代码的复杂度。
使用Flash的C/C++扩展方式
在Flash还大行其道的时期,为了更好的方便引擎开发者使用C/C++来提升Flash游戏相关引擎的性能,Adobe开源了 CrossBridge 这个技术。在这种过程中,原有的C/C++代码经过LLVM IR变为Flash运行时所需要的目标代码,不管是从效率提升上还是从安全性上都有了非常大的提升。对于目前的开源的反编译器来说,很难反编译由CorssBridge编译的C/C++代码,并且由于Flash运行时生产环境中禁用调试,因此也很难进行对应的单步调试。
使用asm.js或WebAssembly
目前看起来WebAssembly是目前最理想的前端核心代码保护的方案了,我们可以使用C/C++编写相关的代码,使用Emscripten相关工具链编译为asm.js和wasm,根据不同的浏览器的支持情况选择使用asm.js还是wasm。并且对于PC端IE10以下的浏览器,我们还可以通过CrossBridge复用其C/C++代码,产出对应的Flash目标代码,从而达到非常好的浏览器兼容性。
然而使用asm.js/wasm后对于前端核心代码的保护就可以高枕无忧了么?
由于asm.js以及wasm的标准规范都是完全公开的,因此对于asm.js/wasm标准实现良好反编译器来说,完全可以尽可能的产出阅读性较强的代码从而分析出其中的核心算法代码。但幸运的是,目前作者还暂时没有找到实现良好的asm.js/wasm反编译器,因此我暂时认为使用此种方法在保护前端核心代码的安全性上已经可堪重用了。
#include <string>#include <emscripten.h>#include <emscripten/bind.h>#include "md5.h"#define SALTKEY "md5 salt key"std::string sign(std::string str){ return md5(str + string(SALTKEY)); }// 此处导出sign方法供Javascript外部环境使用EMSCRIPTEN_BIND(my_module){ emscripten::function("sign", &sign); }
Compile and export js.
em++ -std=c++11 -Oz --bind \ -I ./md5 ./md5/md5.cpp ./sign.cpp \ -o ./sign.js
<body> <script src="./sign.js"></script> <script> // output: 0b57e921e8f28593d1c8290abed09ab2 Module.sign("This is a test string"); </script> </body>
SecurityWorker - 更好的思路及其实现
/* 作者自己的策略,详见原链接 */
Ref: 发现比起混淆, emscripten 或 webassenbly 才是保护代码的好办法
开发者在遇到较大代码量时,可以考虑上 emscripten 或者 webassenbly 来保护自己的代码。
虽说前端没有破不了的防护,但这两样东西真的是靠很简单的方法就能达到相当好的保护目的能。而且尤其是当你代码混淆+这些之后,对攻击者来说,那酸爽,简直了。
二、概念理解
Ref: Javascript支持3D游戏,asm.js 和 Emscripten工具,以及和WebAssembly的区别
2012年,Mozilla 的工程师 Alon Zakai 在研究 LLVM 编译器时突发奇想:许多 3D 游戏都是用 C / C++ 语言写的,如果能将 C / C++ 语言编译成 JavaScript 代码,它们不就能在浏览器里运行了吗?众所周知,JavaScript 的基本语法与 C 语言高度相似。为此专门做了一个编译器项目 Emscripten。这个编译器可以将 C / C++ 代码编译成 JS 代码,但不是普通的 JS,而是一种叫做 asm.js 的 JavaScript 变体。
asm.js 与 WebAssembly 的异同
还有一种叫做 WebAssembly 的技术。两者的功能基本一致,就是转出来的代码不一样:asm.js 是文本,WebAssembly 是二进制字节码,因此运行速度更快、体积更小。
但是,这并不意味着 asm.js 肯定会被淘汰,因为它有两个优点:首先,它是文本,人类可读,比较直观;其次,所有浏览器都支持 asm.js,不会有兼容性问题。
编译执行流程
Emscripten 的底层是 LLVM 编译器,理论上任何可以生成 LLVM IR(Intermediate Representation)的语言,都可以编译生成 asm.js。 但是实际上,Emscripten 几乎只用于将 C / C++ 代码编译生成 asm.js。
C/C++ ⇒ LLVM ==> LLVM IR ⇒ Emscripten ⇒ asm.js
三、新书资源
我的新书《深入浅出WebAssembly》出版啦(。・ω・。)ノ [有点意思]
未来技术变革
https://myslide.cn/slides/21201#
mxnet也是一个不错的选择。
安装配置
一、Developer’s Guide
https://webassembly.org/getting-started/developers-guide/
https://www.wasm.com.cn/getting-started/developers-guide/
二、安装
想要编译成WebAssembly,你首先需要先编译 LLVM。
安装LLVM
Ref: LLVM概述——介绍与安装
Goto: https://releases.llvm.org/
选择本地编译安装。
安装emcc
How to install the latest emscripten on Ubuntu using command line?
From the emscripten/. Make the SDK //
这里报错如下,但先不管,上述步骤后,已经可以使用emcc命令。
$ ./emsdk install sdk-incoming-64bit binaryen-master-64bit Error: No tool or SDK found by name 'sdk-incoming-64bit'.
三、测试环境
C语言
下面这些命令可能让你创建一个简单的“hello word”程序,并且编译它。
$ mkdir hello $ cd hello $ echo '#include <stdio.h>' > hello.c $ echo 'int main(int argc, char ** argv) {' >> hello.c $ echo 'printf("Hello, world!\n");' >> hello.c $ echo '}' >> hello.c $ emcc hello.c -s WASM=1 -o hello.html
我们可以使用 emrun
命令来创建一个 http 协议的 web server 来展示我们编译后的文件。
$ emrun --no_browser --port 8080 .
HTTP 服务开启后,您可以在浏览器中打开。如果你看到了“Hello,word!”输出到了 Emscripten 的 控制面板,恭喜你!你的 WebAssembly 程序编译成功了!
C++语言
要先执行自动环境变量配置,或者直接手动在 ~/.bashrc 中设置也可。
$ emsdk_env.sh --build=Release Adding directories to PATH: PATH += /usr/local/emsdk/upstream/emscripten PATH += /usr/local/emsdk/node/12.9.1_64bit/bin Setting environment variables: EMSDK = /usr/local/emsdk EM_CONFIG = /home/jeffrey/.emscripten EM_CACHE = /usr/local/emsdk/upstream/emscripten/cache EMSDK_NODE = /usr/local/emsdk/node/12.9.1_64bit/bin/node
步骤与上一条的C语言操作类似。
em++ hello.cpp -s WASM=1 -o hello.html $ ls hello.cpp hello.html hello.js hello.wasm emrun --no_browser --port 8080 .
原理剖析
一、基本JS解析步骤
Ref: 几张图让你看懂WebAssembly
Parsing | 讲源码转换成解释器可以运行的东西所用的事情。 |
Compiling + optimizing | 花费在基础编译和优化编译上的时间。有一些优化编译的工作不在主线程,所以这里并不包括这些时间。 |
Re-optimizing | 当预先编译优化的代码不能被优化的情况下,JIT 将这些代码重新优化,如果不能重新优化那么久丢给基础编译去做。这个过程叫做重新优化。 |
Execution | 执行代码的过程 |
Garbage collection | 清理内存的时间 |
一个重要的事情要注意:这些任务不会发生在离散块或特定的序列中。相反,它们将被交叉执行。
比如正在做一些代码解析时,还执行者一些其他的逻辑,有些代码编译完成后,引擎又做了一些解析,然后又执行了一些逻辑,等等。
WebAssembly 和别的汇编语言是有一些不同的。所以他是一个概念机上的机器语言,不是在一个真正存在的物理机上运行的机器语言。
正因如此,WebAssembly 指令有时候被称为虚拟指令。它比 JavaScript 代码更快更直接的转换成机器代码,但它们不直接和特定硬件的特定机器代码对应。
在浏览器下载 WebAssembly后,使 WebAssembly 的迅速转换成目标机器的汇编代码。