混淆之后的代码功能不变,但是在逆向的时候不是一定得还原代码,要是补环境之后能运行也可以,要是想知道业务逻辑怎么实现的,要去还原

十六进制与Unicode字符串

1
2
3
4
5
6
7
8
9
10
11
Date.prototype.format = function(formatStr) {
var str = formatStr;//ASCIIEncrypt
var Week = ['日', '一', '二', '三', '四', '五', '六'];
str = str.replace(/yyyy|YYYY/, this.getFullYear()); //Base64Encrypt
str = str.replace(/MM/, (this.getMonth() + 1) > 9 ? (this.getMonth() + 1).toString() : '0' + (this.getMonth() + 1));
// b'TlRNd05qVXhOall3'
str = str.replace(/dd|DD/, this.getDate() > 9 ? this.getDate().toString() : '0' + this.getDate());
return str;
}
console.log( new Date().format('yyyy-MM-dd') );
//输出结果 2020-07-04

访问属性的方法,str['replace']或者str.replace,对于前者,字符串replace是可以加密的

而且,这里Date()是window的内置对象,所以new Date()也就是new window.Date() ,或者new window['Date']()

十六进制字符串可以表示正常的字符串,比如'yyyy-MM-dd',十六进制字符串表示就是'\x79\x79\x79\x79\x2d\x4d\x4d\x2d\x64\x64'

也就是把字符转成ASCII码,然后再转成十六进制,y的ASCII码是121,也就是0x79

十六进制转换

1
2
3
4
5
6
7
function hexEnc(code) {
for(var hexStr=[],i=0,s; i<code.length; i++){
s = code.charCodeAt(i).toString(16);
hexStr += "\\x" + s;
}
return hexStr;
}

unicode编码和十六进制类似

1
2
3
4
5
6
7
function unicodeEnc(str) {
var value = '';
for(var i=0; i<str.length; i++){
value += "\\u" + ("0000" + parseInt(str.charCodeAt(i)).toString(16).substr(-4));
}
return value;
}

ASCII码混淆

字符转ASCII码,charCodeAt方法

ASCII码还原字符串,String.fromCharCode(120) ,可以接受数组String.fromCharCode(120,105)

字符串转ASCII码

1
2
3
4
5
6
7
function stringToByte(str) {
var byteArr = [];
for(var i = 0; i < str.length; i++){
byteArr.push(str.charCodeAt(i));
}
return byteArr;
}

那么str = str.replace(/MM/, (this.getMonth() + 1) > 9 ? (this.getMonth() + 1).toString() : '0' + (this.getMonth() + 1));转换成ASCII码就是

1
115,116,114,32,61,32,115,116,114,46,114,101,112,108,97,99,101,40,47,77,77,47,44,32,40,116,104,105,115,46,103,101,116,77,111,110,116,104,40,41,32,43,32,49,41,32,62,32,57,32,63,32,40,116,104,105,115,46,103,101,116,77,111,110,116,104,40,41,32,43,32,49,41,46,116,111,83,116,114,105,110,103,40,41,32,58,32,39,48,39,32,43,32,40,116,104,105,115,46,103,101,116,77,111,110,116,104,40,41,32,43,32,49,41,41,59

但是这样是字符串,不能直接当成代码用,要用eval去执行

也就是

1
eval(String.fromCharCode(115,116,114,32,61,32,115,116,114,46,114,101,112,108,97,99,101,40,47,77,77,47,44,32,40,116,104,105,115,46,103,101,116,77,111,110,116,104,40,41,32,43,32,49,41,32,62,32,57,32,63,32,40,116,104,105,115,46,103,101,116,77,111,110,116,104,40,41,32,43,32,49,41,46,116,111,83,116,114,105,110,103,40,41,32,58,32,39,48,39,32,43,32,40,116,104,105,115,46,103,101,116,77,111,110,116,104,40,41,32,43,32,49,41,41,59))

字符串常量加密

不是所有的字符串常量都能加密

数值常量加密

算法的初始化常量进行加密,比如可以两个值异或得到需要的值

数组混淆和乱序

可以把需要的字符串和数值和函数都放到一个数组中,然后对数组进行加密

乱序,就是把数组的元素顺序打乱,在调用数组之前还原正常的数组元素顺序

花指令

花指令,就是混淆视听的没有意义的代码

函数可以无限套娃,越写越膨胀

JSFuck

就是用六个字符去表示js代码

有网站可以解密,如果网站解密不了,再去分步分析,分析的方法是按照括号为组去执行,看执行的结果,然后去把代码用解出来的明文结果替换

流程平坦化

一般的代码是从上往下执行,在上面的代码就是先执行

用条件语句可以让代码按索引去执行,case块的索引可以是字符也可以是数字,字符还可以放到数组中去

逗号表达式混淆

1
2
3
4
function t(){
let a,b,c,d,e,f;
return a=1000,b=a+1,c=b+1,d=c+1,f=d+1,f
}

上面的代码最终返回的是f,逗号前面的代码会被执行,但是最终只返回f

同样,var a = ( a = 1000, a += 2000); ,最终a等于3000

逗号表达式里还能加一些垃圾代码,只计算不赋值的

函数调用可以加逗号表达式,不影响函数调用,比如

1
(1 + 1 + 1 + 1 + 'thisisatest',sub)(a,b)

同理,对象访问属性也可以用逗号表达式,如obj.name,可以混淆成(bbb = (1 + 1 + 1 + 'test', obj)).name

eval加密

eval会把传入的字符串参数当成代码去运行,字符串可以加密,下面的function是解密函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
eval(function (p, a, c, k, e, r) {
e = function (c) {
return c.toString(36)
};
if ('0'.replace(0, e) == 0) {
while (c--)
r[e(c)] = k[c];
k = [function (e) {
return r[e] || e
}
];
e = function () {
return '[2-8a-f]'
};
c = 1
};
while (c--)
if (k[c])
p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c]);
return p
}('7.prototype.8=function(a){b 2=a;b Week=[\'日\',\'一\',\'二\',\'三\',\'四\',\'五\',\'六\'];2=2.4(/c|YYYY/,3.getFullYear());2=2.4(/d/,(3.5()+1)>9?(3.5()+1).e():\'0\'+(3.5()+1));2=2.4(/f|DD/,3.6()>9?3.6().e():\'0\'+3.6());return 2};console.log(new 7().8(\'c-d-f\'));', [], 16, '||str|this|replace|getMonth|getDate|Date|format||formatStr|var|yyyy|MM|toString|dd'.split('|'), 0, {}));

可以直接把这里eval改成console.log去输出就得到解密之后的代码

格式化检测

代码格式化检测是一种防止代码被格式化或美化的技术。

许多网站的加密/反爬/反调试代码会使用混淆、压缩让代码变得难以阅读,而开发者在调试时可能会使用格式化工具让代码变得易读。

网站可以通过检测代码是否被格式化来识别调试行为,并进行反调试或反分析。

JavaScript 的 Function.prototype.toString() 方法会返回函数的源码

如果代码被格式化,返回的字符串会有换行、空格增加,可以通过计算字符串长度发现代码是否被格式化。

1
2
3
4
5
6
7
8
9
10
11
12
(function () {
function isFormatted() {
return isFormatted.toString().length > 100;
}

if (isFormatted()) {
console.log("检测到代码被格式化!执行反调试...");
while (true) {} // 让代码卡死
} else {
console.log("代码正常运行...");
}
})();

isFormatted.toString() 返回 isFormatted 函数的源码,如果源码的长度超过100(说明代码有换行或空格被美化了),就判定代码被格式化,并进行反调试(死循环等)。

Function混淆

1
2
3
4
5
6
7
function test(a,b) {
return a+b;
}

//等价于
var test = Function('a','b','return a+b;');
var test = new Function('a','b','return a+b;');