javascript加固

0.测试通用demo

1
2
3
4
5
6
7
var arrs=new Array("java","python","js","c++","go");
function testDemo(){
for(i=0;i<arrs.length;i++){
console.log(arrs[i])
}
}
testDemo()

1.JavaScript 压缩

JavaScript 压缩即去除 JavaScript 代码中的不必要的空格、换行等内容或者把一些可能公用的代码进行处理实现共享,最后输出的结果都压缩为几行内容,代码可读性变得很差,同时也能提高网站加载速度。

工具:Webpack、在线压缩工具

1
var arrs=new Array("java","python","js","c++","go");function testDemo(){for(i=0;i<arrs.length;i++){console.log(arrs[i])}}testDemo();

还原工具:在线解缩工具、ide、chrome

可以发现通过压缩方式,几乎没有任何作用,因为这种压缩方式仅仅是降低了代码的直接可读性。

2.JavaScript代码混淆

使用变量替换、字符串阵列化、控制流平坦化、多态变异、僵尸函数、调试保护等手段,使代码变地难以阅读和分析,达到最终保护的目的。

混淆技术主要有以下几种:

  1. 变量混淆:将带有含意的变量名、方法名、常量名随机变为无意义的类乱码字符串,降低代码可读性,如转成单个字符或十六进制字符串。
  2. 字符串混淆:将字符串阵列化集中放置、并可进行 MD5 或 Base64 加密存储,使代码中不出现明文字符串,这样可以避免使用全局搜索字符串的方式定位到入口点。
  3. 属性加密:针对 JavaScript 对象的属性进行加密转化,隐藏代码之间的调用关系。
  4. 控制流平坦化:打乱函数原有代码执行流程及函数调用关系,使代码逻变得混乱无序。
  5. 僵尸代码:随机在代码中插入无用的僵尸代码、僵尸函数,进一步使代码混乱。
  6. 调试保护:基于调试器特性,对当前运行环境进行检验,加入一些强制调试 debugger 语句,使其在调试模式下难以顺利执行 JavaScript 代码。
  7. 多态变异:使 JavaScript 代码每次被调用时,将代码自身即立刻自动发生变异,变化为与之前完全不同的代码,即功能完全不变,只是代码形式变异,以此杜绝代码被动态分析调试。
  8. 锁定域名:使 JavaScript 代码只能在指定域名下执行。
  9. 反格式化:如果对 JavaScript 代码进行格式化,则无法执行,导致浏览器假死。
  10. 特殊编码:将 JavaScript 完全编码为人不可读的代码,如表情符号、特殊表示内容等等。如使用 aaencode、jjencode、jsfuck 等工具对代码进行混淆和编码。
  11. 加壳干扰:在代码用eval包裹,然后对eval参数进行加密,并埋下陷阱,在解码时插入无用代码,干扰显示,大量换行、注释、字符串等大量特殊字符,导致显示卡顿。

工具:Obfuscator

以下简单举几个有意思的例子:

2.1调试保护

这里仅对demo做了修改标识符名称与调试保护,可以看到代码以经不是很好看懂了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
var arrs = new Array('java', 'python', 'js', 'c++', 'go');
function testDemo() {
var _0x3524b8 = function () {
var _0x18f4aa = !![];
return function (_0x4e8bb3, _0x4f8605) {
var _0x21daca = _0x18f4aa ? function () {
if (_0x4f8605) {
var _0x290f33 = _0x4f8605['apply'](_0x4e8bb3, arguments);
_0x4f8605 = null;
return _0x290f33;
}
} : function () {
};
_0x18f4aa = ![];
return _0x21daca;
};
}();
(function () {
_0x3524b8(this, function () {
var _0x2f125b = new RegExp('function\x20*\x5c(\x20*\x5c)');
var _0x5e4484 = new RegExp('\x5c+\x5c+\x20*(?:[a-zA-Z_$][0-9a-zA-Z_$]*)', 'i');
var _0xb2a7b4 = nffas('init');
if (!_0x2f125b['test'](_0xb2a7b4 + 'chain') || !_0x5e4484['test'](_0xb2a7b4 + 'input')) {
_0xb2a7b4('0');
} else {
nffas();
}
})();
}());
for (i = 0x0; i < arrs['length']; i++) {
console['log'](arrs[i]);
}
}
testDemo();
function nffas(_0x56a982) {
function _0x6b9ad9(_0x5641b1) {
if (typeof _0x5641b1 === 'string') {
return function (_0x5b7ddc) {
}['constructor']('while\x20(true)\x20{}')['apply']('counter');
} else {
if (('' + _0x5641b1 / _0x5641b1)['length'] !== 0x1 || _0x5641b1 % 0x14 === 0x0) {
(function () {
return !![];
}['constructor']('debu' + 'gger')['call']('action'));
} else {
(function () {
return ![];
}['constructor']('debu' + 'gger')['apply']('stateObject'));
}
}
_0x6b9ad9(++_0x5641b1);
}
try {
if (_0x56a982) {
return _0x6b9ad9;
} else {
_0x6b9ad9(0x0);
}
} catch (_0x30f316) {
}
}
这里使用下条件断点的方式完轻松绕过。

2.2反格式化

开启之后,混淆后的 JavaScript 会以强制一行形式显示,如果我们将混淆后的代码进行格式化(美化)或者重命名,该段代码将无法执行.

2.3控制流平坦化

将代码的执行逻辑混淆,使其变得复杂难读。其基本思想是将一些逻辑处理块都统一加上一个前驱逻辑块,每个逻辑块都由前驱逻辑块进行条件判断和分发,构成一个个闭环逻辑,导致整个执行逻辑十分复杂难读。

1
2
3
4
5
6
7
8
9
10
11
12
var arrs = new Array('java', 'python', 'js', 'c++', 'go');
function testDemo() {
var a = {
'wQgkE': function (b, c) {
return b < c;
}
};
for (i = 0x0; a['wQgkE'](i, arrs['length']); i++) {
console['log'](arrs[i]);
}
}
testDemo();

由于例子逻辑简单,只是一个简单的循环,但还是可以看出通过该方式处理后的代码,在循环前加了一个逻辑判断。因此,使用控制流扁平化可以使得执行逻辑更加复杂难读,前端混淆都会加上这个选项。

2.4eval

js中的eval()方法就是一个js语言的执行器,它能把其中的参数按照JavaScript语法进行解析并执行,简单来说就是把原本的js代码变成了eval的参数,变成参数后代码就成了字符串,其中的一些字符就会被按照特定格式“编码”。

在线加解密工具:http://www.jqueryfuns.com/tools/jsencode

效果如下:

1
eval(function(p,a,c,k,e,r){e=String;if('0'.replace(0,e)==0){while(c--)r[e(c)]=k[c];k=[function(e){return r[e]||e}];e=function(){return'[23]'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('var 2=new Array("java","python","js","c++","go");function 3(){for(i=0;i<2.length;i++){console.log(2[i])}}3()',[],4,'||arrs|testDemo'.split('|'),0,{}))

3.代码加密:

可以通过某种手段将 JavaScript 代码进行加密,转成人无法阅读或者解析的代码,如将代码完全抽象化加密,如 eval 加密。另外还有更强大的加密技术,可以直接将 JavaScript 代码用 C/C++ 实现,JavaScript 调用其编译后形成的文件来执行相应的功能,如 Emscripten 还有 WebAssembly。

基本思路是将一些核心逻辑使用诸如 C/C++ 语言来编写,并通过 JavaScript 调用执行,从而起到二进制级别的防护作用。

加密方式:

3.1Emscripten 这个编译器可以将 C / C++ 代码编译成 asm.js

3.2WebAssembly可以将 C / C++ 代码编译二进制字节码

先用在线工具 生成一个wasm文件,http://mbebenita.github.io/WasmExplorer/

用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function loadWebAssembly(filename, imports) {
// Fetch the file and compile it
return fetch(filename)
.then(response => response.arrayBuffer())
.then(buffer => WebAssembly.compile(buffer))
.then(module => {
// Create the imports for the module, including the
// standard dynamic library imports
imports = imports || {};
imports.env = imports.env || {};
imports.env.memoryBase = imports.env.memoryBase || 0;
imports.env.tableBase = imports.env.tableBase || 0;
if (!imports.env.memory) {
imports.env.memory = new WebAssembly.Memory({ initial: 256 });
}
if (!imports.env.table) {
imports.env.table = new WebAssembly.Table({ initial: 0, element: 'anyfunc' });
}
// Create the instance.
return new WebAssembly.Instance(module, imports);
});
}

// Main part of this example, loads the module and uses it.
loadWebAssembly('test.wasm')
.then(instance => {
var exports = instance.exports; // the exports of that instance
console.log(exports._doubler(55)) // Output: 110
}
);