JS代码安全防护常见的方式
文章目录
- 1. 常量的混淆
- 1.1 十六进制字符串
- 1.2 unicode字符串
- 1.3 字符串的ASCII码混淆
- 1.4 字符串常量加密
- 1.5 数值常量加密
- 2. 增加逆向分析难度
- 2.1 数组混淆
- 2.2 数组乱序
- 2.3 花指令
- 2.4 jsfuck
- 3. 代码执行流程的防护
- 3.1 流程平坦化
- 3.2 逗号表达式
- 4. 其他代码防护方案
- 4.1 eval加密
- 4.2 内存爆破
- 4.3 检测代码是否格式化
- 5. 小结
正式学AST之前,还是有必要了解一下常见的JS代码安全防护方式,
最近看了一本名叫《反爬虫AST原理与还原混淆实战》的书,对于常见的JS代码安全防护方式,做一下学习笔记记录总结。
1. 常量的混淆
1.1 十六进制字符串
'yyyy-MM-dd'====>'\x79\x79\x79\x79\x2d\x4d\x4d\x2d\x64\x64'
1.2 unicode字符串
var Week = ['日', '一', '二', '三', '四', '五', '六']======>var Week = ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d']
1.3 字符串的ASCII码混淆
console.log('x'.charCodeAt())
console.log('b'.charCodeAt())
console.log(String.fromCharCode(120,98))结果:
120
98
xb
// 字符串转字节数组
function stringToByte(str) {var byteArr = [];for (var i = 0; i < str.length; i++) {byteArr.push(str.charCodeAt(i));}return byteArr;
}console.log(stringToByte('AstIsGood'));// 结果
[65, 115, 116, 73,115, 71, 111, 111,100
]
1.4 字符串常量加密
字符串常量加密的核心思想是,先把字符串加密得到密文,然后在使用前,调用对应的解密函数去解密,得到明文。代码中仅出现解密函数和密文。当然,也可以使用不同的加密方法去加密字符串,再调用不同的解密函数去解密。
比如,字符串加密方式采用最简单的 Base64编码:
replace Base64编码后为 cmVwbGFjZQ==
getMonth Base64编码后为 Z2V0TW9udGg=
getDate Base64编码后为 Z2V0RGF0ZQ==
0 Base64编码后为 MA==
toString Base64编码后为 dG9TdHJpbmc=
浏览器中有自带的Base64编码和解码的函数,其中btoa用来编码, atob用来解码。但在实际的混淆应用中,最好还是自己去实现它们,然后加以混淆。注意,字符串加密后,需要把对应的解密函数也放入代码中,才能正常运行。
下面有一段js代码:
Date.prototype.format = function (formatStr) {var str = formatStr;var Week = ['日', '一', '二', '三', '四', '五', '六'];str = str.replace(/yyyy|YYYY/, this.getFullYear());str = str.replace(/MM/, (this.getMonth() + 1) > 9 ? (this.getMonth() + 1).toString() : '0' + (this.getMonth() + 1));str = str.replace(/dd|DD/, this.getDate() > 9 ? this.getDate().toString() : '0' + this.getDate());return str;
}
console.log(new Date().format('yyyy-MM-dd'));
通过以上几种方式混淆之后:
Date.prototype.\u0066\u006f\u0072\u006d\u0061\u0074 = function(formatStr) {var \u0073\u0074\u0072 = \u0066\u006f\u0072\u006d\u0061\u0074\u0053\u0074\u0072;var Week = ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d'];eval(String.fromCharCode(115, 116, 114, 32, 61, 32, 115, 116, 114, 91, 39, 114, 101, 112, 108, 97, 99, 101, 39, 93, 40, 47, 121, 121, 121, 121, 124, 89, 89, 89, 89, 47, 44, 32, 116, 104, 105, 115, 91, 39, 103, 101, 116, 70, 117, 108, 108, 89, 101, 97, 114, 39, 93, 40, 41, 41, 59));str = str[atob('cmVwbGFjZQ==')](/MM/, (this[atob('Z2V0TW9udGg=')]() + 1) > 9 ? (this[atob('Z2V0TW9udGg=')]() + 1)[atob('dG9TdHJpbmc=')]() : atob('MA==') + (this[atob('Z2V0TW9udGg=')]() + 1));str = str[atob('cmVwbGFjZQ==')](/dd|DD/, this[atob('Z2V0RGF0ZQ==')]() > 9 ? this[atob('Z2V0RGF0ZQ==')]()[atob('dG9TdHJpbmc=')]() : atob('MA==') + this[atob('Z2V0RGF0ZQ==')]());return str;
}
console.log( new \u0077\u0069\u006e\u0064\u006f\u0077['\u0044\u0061\u0074\u0065']()[String.fromCharCode(102, 111, 114, 109, 97, 116)]('\x79\x79\x79\x79\x2d\x4d\x4d\x2d\x64\x64') );
1.5 数值常量加密
算法加密过程中,会使用一些固定的数值常量,如 MD5中的常量0x67452301、0xefcdab89、0x98badcfe 和 0x10325476,
参考:JS 实现MD5加密
以及sha1中的常量0x67452301、0xefcdab89、0x98badcfe、0x10325476和0xc3d2elf0。
参考:JS实现 Sha1 加密
因此,在标准算法逆向中,会通过搜索这些数值常量,来定位代码关键位置,或者确定使用的是哪个算法。当然,在代码中不一定会写十六进制形式,如0x67452301,在代码中可能会写成十进制的1732584193。
sha1 : 0xefcdab89 4023233417
这里只是提供一种思路,实际上现在很多比较新的网站用的是自己写的MD5或者魔改之后的MD5,通过搜索以上变量不一定能搜到算法位置。
2. 增加逆向分析难度
2.1 数组混淆
该类混淆没有统―称呼,将所有的字符串都提取到一个数组中,然后在需要引用字符串的地方,全部都以数组下标的方式访问数组成员。
例如:
var bigArr = ['Date', 'getTime', 'log'];
console[bigArr[2]](new window[bigArr[0]]()[bigArr[1]]());
这里展示的代码,阅读难度已经大大增加。当代码为上千行,数组提取的字符串也有上千个。在代码中要引用字符串时,全都以bigArr[1001]和 bigArr[1002]访问,就会大大增加理解难度,不容易建立对应关系。
在JavaS语言中,语法灵活,同一个数组中,可以同时存放各种类型,如布尔值、字符串﹑数值、数组、对象和函数等。例如:
var bigArr = [true,'astisGood',1000,[100, 200, 300], {name: 'astisGood',money: 0},function () {console.log('Hello')}
];
console.log(bigArr[0]); //true
console.log(bigArr[1]); //astisGood
console.log(bigArr[2]); //1000
console.log(bigArr[3][0]); //100
console.log(bigArr[4].money); //0
console.log(bigArr[5]()); //Hello
因此,可以把代码中的一部分函数提取到大数组中。并且为了安全,通常会对提取到数组中的字符串进行加密处理,把代码处理成字符串就可以进行加密了。对于1.4那段js代码,可以改写为以下形式:
var bigArr = ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', 'cmVwbGFjZQ==', 'Z2V0TW9udGg=', 'dG9TdHJpbmc=', 'Z2V0RGF0ZQ==', 'MA==', ""['constructor']['fromCharCode']
];
Date.prototype.\u0066\u006f\u0072\u006d\u0061\u0074 = function(formatStr) {var \u0073\u0074\u0072 = \u0066\u006f\u0072\u006d\u0061\u0074\u0053\u0074\u0072;var Week = [bigArr[0], bigArr[1], bigArr[2], bigArr[3], bigArr[4], bigArr[5], bigArr[6]];eval(String.fromCharCode(115, 116, 114, 32, 61, 32, 115, 116, 114, 91, 39, 114, 101, 112, 108, 97, 99, 101, 39, 93, 40, 47, 121, 121, 121, 121, 124, 89, 89, 89, 89, 47, 44, 32, 116, 104, 105, 115, 91, 39, 103, 101, 116, 70, 117, 108, 108, 89, 101, 97, 114, 39, 93, 40, 41, 41, 59));str = str[atob(bigArr[7])](/MM/, (this[atob(bigArr[8])]() + 1) > 9 ? (this[atob(bigArr[8])]() + 1)[atob(bigArr[9])]() : atob(bigArr[11]) + (this[atob(bigArr[8])]() + 1));str = str[atob(bigArr[7])](/dd|DD/, this[atob(bigArr[10])]() > 9 ? this[atob(bigArr[10])]()[atob(bigArr[9])]() : atob(bigArr[11]) + this[atob(bigArr[10])]());return str;
}
console.log( new \u0077\u0069\u006e\u0064\u006f\u0077['\u0044\u0061\u0074\u0065']()[bigArr[12](102, 111, 114, 109, 97, 116)]('\x79\x79\x79\x79\x2d\x4d\x4d\x2d\x64\x64') );
2.2 数组乱序
观察以上处理后的代码,数组成员与被引用的地方是一一对应的。如引用bigArr[12]的地方,需要的是String. fromCharCode函数,而该数组中下标为12的成员,也是这个函数。
将数组顺序打乱可以解决这个问题,不过在数组顺序混乱后,本身的代码也引用不到正确的数组成员。此处的解决方案是,在代码中内置一段还原顺序的代码。
可以使用以下代码打乱数组顺序:
var bigArr = ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', 'cmVwbGFjZQ==', 'Z2V0TW9udGg=', 'dG9TdHJpbmc=', 'Z2V0RGF0ZQ==', 'MA==', ""['constructor']['fromCharCode']
];
(function(arr, num){var shuffer = function(nums){while(--nums){arr.unshift(arr.pop());}};shuffer(++num);
}(bigArr, 0x20));
console.log( bigArr );//["cmVwbGFjZQ==", "Z2V0TW9udGg=", "dG9TdHJpbmc=", "Z2V0RGF0ZQ==", "MA==", f, "日", "一", "二", "三", "四", "五", "六"]
在这段代码中,有一个自执行的匿名函数。实参部分传入的是数组和一个任意数值。在这个函数内部,通过对数组进行弹出和压入操作来打乱顺序。除此之外,只要控制台输出,Unicode处理后的字符串就变成原来的中文。这就是之前说的十六进制字符串和Unicode都很容易被还原。
String.fromCharCode函数被移动到了下标为5的地方,但代码处引用的仍是bigArr[12],所以需要把还原数组顺序的函数放入代码中,还原数组顺序的代码逆向编写即可,如下所示:
var bigArr = ['cmVwbGFjZQ==', 'Z2V0TW9udGg=', 'dG9TdHJpbmc=', 'Z2V0RGF0ZQ==', 'MA==', ""['constructor']['fromCharCode'], '\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d'
];
(function(arr, num){var shuffer = function(nums){while(--nums){arr['push'](arr['shift']());}};shuffer(++num);
}(bigArr, 0x20));Date.prototype.\u0066\u006f\u0072\u006d\u0061\u0074 = function(formatStr) {var \u0073\u0074\u0072 = \u0066\u006f\u0072\u006d\u0061\u0074\u0053\u0074\u0072;var Week = [bigArr[0], bigArr[1], bigArr[2], bigArr[3], bigArr[4], bigArr[5], bigArr[6]];eval(String.fromCharCode(115, 116, 114, 32, 61, 32, 115, 116, 114, 91, 39, 114, 101, 112, 108, 97, 99, 101, 39, 93, 40, 47, 121, 121, 121, 121, 124, 89, 89, 89, 89, 47, 44, 32, 116, 104, 105, 115, 91, 39, 103, 101, 116, 70, 117, 108, 108, 89, 101, 97, 114, 39, 93, 40, 41, 41, 59));str = str[atob(bigArr[7])](/MM/, (this[atob(bigArr[8])]() + 1) > 9 ? (this[atob(bigArr[8])]() + 1)[atob(bigArr[9])]() : atob(bigArr[11]) + (this[atob(bigArr[8])]() + 1));str = str[atob(bigArr[7])](/dd|DD/, this[atob(bigArr[10])]() > 9 ? this[atob(bigArr[10])]()[atob(bigArr[9])]() : atob(bigArr[11]) + this[atob(bigArr[10])]());return str;
}
console.log( new \u0077\u0069\u006e\u0064\u006f\u0077['\u0044\u0061\u0074\u0065']()[bigArr[12](102, 111, 114, 109, 97, 116)]('\x79\x79\x79\x79\x2d\x4d\x4d\x2d\x64\x64') );
2.3 花指令
添加一些没有意义却可以混淆视听的代码,是花指令的核心。
这里介绍一种比较简单的花指令实现方式,以 6.1节的代码为例:
str = str.replace(/MM/, (this.getMonth() + 1) > 9 ? (this.getMonth() + 1).toString() : '0' + (this.getMonth() + 1));
把this.getMonth()+1这个二项式改为如下形式:
function _0x20ab1fxe1(a, b){return a + b;
}
//_0x20ab1fxe1(this.getMonth(), 1);
_0x20ab1fxe1(new Date().getMonth(), 1);
//为了能够在控制台正常运行,把this改成new Date()
本质是把二项式拆开成三部分:二项式的左边、二项式的右边和运算符。二项式的左边和右边作为另外一个函数的两个参数,二项式的运算符作为该函数的运行逻辑。这个函数本身是没有意义的,但它能瞬间增加代码量,从而增加JS逆向者的工作量。
这个案例较为简单,但是在实际混淆中,代码可能有几千行,函数定义部分与调用部分往往相差甚远。
另外,具有相同运算符的二项式,并不是一定要调用相同的函数。如下所示代码:
function _0x20ab1fxe2(a, b){return a + b;
}
function _0x20ab1fxe1(a, b){return _0x20ab1fxe2(a, b);
}
_0x20ab1fxe1(new Date().getMonth(), 1);
上面介绍的是二项式转变为函数的花指令,其实函数调用表达式也可以处理成类似的花指令。代码如下:
function _0x20ab1fxe2(a, b){return a + b;
}
function _0x20ab1fxe1(a, b){return _0x20ab1fxe2(a, b);
}
function _0x20ab1fxe3(a, b){return a + b;
}
function _0x20ab1fxe4(a, b){return _0x20ab1fxe3(a, b);
}
_0x20ab1fxe4('0', _0x20ab1fxe1(new Date().getMonth(), 1));
2.4 jsfuck
jsfuck也可以算是一种编码。它能把JS代码转化成只用6个字符就可以表示的代码,并可以正常执行。
这6个字符分别是“(”、“+”、“!”、“[”、”]”和“)”。
转换后的JS代码难以阅读,可作为简单的保密措施,如数值常量8转成jsfuck后为:
!+[]+!![]+!![]+!![]+!![]+!![]+!![]+!![]
接下来介绍jsfuck 的基本原理:
+是JS中的一个运算符,当它作为一元运算符使用时,代表强转为数值类型。
[]在JS中表示空数组,因此+[]等于0,!+[]等同于! 0。
JS是一种弱类型的语言,弱类型并不是代表没有类型,是指JS引擎会在适当的时候,自动完成类型的隐式转换。
!是JS中的取反,这时需要一个布尔值。在JS中,七种值为假值,其余均为真值。这七种值分别是 false、undefined , null、 0 、-0、NaN 和""。因此,0转换为布尔值为false,再取反就是true,也就是!+[]== true。又如!![],数组转换成布尔值为true,然后两次取反,依旧等于true。
JS中的+作为二元运算符时,假如有一边是字符串,就代表着拼接;两边都没有字符串,就代表着数值相加。true转换为数值等于1。剩余的部分原理相同,不再赘述。
在实际开发中,jsfuck 的应用有限,只会应用于JS文件中的一部分代码。主要原因是它的代码量非常庞大且还原它较为容易。例如,把上述代码直接输入控制台运行,就会输出8。
遇到jsfuck混淆,可以拿到控制台输出分析,也可以使用在线解析网站解析如:jsfunck在解析线网站
一些网站之所以用它进行加密,是因为个别情况下,把整段jsfuck代码输人控制台运行会报错,尤其是当它跟别的代码混杂时。
可以看一下网洛者第四题:jsfuck混淆
3. 代码执行流程的防护
3.1 流程平坦化
在一般的代码开发中,会有很多的流程控制相关代码,即代码中有很多分支,这些分支会具有一定的层级关系。
在流程平坦化混淆中,会用到switch语句,因为switch语句中的case块是平级的,而且调换case块的前后顺序并不影响代码原先的执行逻辑。
为了方便理解,这里举一个简单的例子,代码如下:
function test1(){var a = 1000;var b = a + 2000;var c = b + 3000;var d = c + 4000;var e = d + 5000;var f = e + 6000;return f;
}
console.log( test1() );
//输出 21000
混淆test1函数中的代码执行流程为:首先把代码分块,且打乱代码块的顺序,分别添加到不同的case块中。
当代码块打乱后,如果想要跟原先的执行顺序一样,那么case块的跳转顺序应该是7、5、1、3、2、4、6。
只有case块按照这个流程执行,才能跟原始代码的执行顺序保持一致。
其次,需要一个循环。因为 switch语句只计算一次switch表达式,它的执行流程如下:
(1)计算一次 switch表达式。
(2)把表达式的值与每个case的值进行对比。
(3)如果存在匹配,则执行对应case块。
可处理代码如下:
function test2(){var arrStr = '7|5|1|3|2|4|6'.split('|'), i = 0;while (!![]) {switch(arrStr[i++]){case '1':var c = b + 3000;continue;case '2':var e = d + 5000;continue;case '3':var d = c + 4000;continue;case '4':var f = e + 6000;continue;case '5':var b = a + 2000;continue;case '6':return f;continue;case '7':var a = 1000;continue;}break;}
}
console.log( test2() );
//输出 21000
3.2 逗号表达式
逗号运算符的主要作用是把多个表达式或语句连接成一个复合语句。
如下js代码:
function test1(){var a = 1000;var b = a + 2000;var c = b + 3000;var d = c + 4000;var e = d + 5000;var f = e + 6000;return f;
}
console.log( test1() );
//输出 21000
等价于:
function test1(){var a, b, c, d, e, f;return a = 1000,b = a + 2000,c = b + 3000,d = c + 4000,e = d + 5000,f = e + 6000,f
}
console.log( test1() );
//输出 21000
return语句后通常只能跟一个表达式,它会返回这个表达式计算后的结果。但是逗号运算符可以把多个表达式连接成一个复合语句。因此上述代码中, return语句的使用也是没有问题的,它会返回最后一个表达式计算后的结果,但是前面的表达式依然会执行。
上述案例只是单纯的连接语句,没有混淆力度。可以再做进一步处理,代码如下:
function test2(){var a, b, c, d, e, f;return f = (e = (d = (c = (b = (a = 1000, a + 2000), b + 3000), c + 4000), d + 5000), e + 6000);
}
console.log( test2() );
//输出 21000
这段代码有一个声明一系列变量的语句。这个语句很多余,可以放到参数列表上,这样就不需要var声明了。
另外,既然逗号运算符连接多个表达式,只会返回最后一个表达式计算后的结果,那么可以在最后一个表达式之前插入不影响结果的花指令。
最终处理后的代码如下:
function test2(a, b, c, d, e, f){return f = (e = (d = (c = (b = (a = 1000, a + 50, b + 60, c + 70, a + 2000), d + 80, b + 3000), e + 90, c + 4000), f + 100 ,d + 5000), e + 6000);
}
console.log( test2() );
// 输出 21000
上述代码中a+50,b+60,c+70、d+80,e+90,f+100这些花指令并无实际意义,不影响原先的代码逻辑。
test2虽有6个参数,但是不传参也可以调用,只不过各参数的初始值为undefined。
逗号表达式混淆不仅能处理赋值表达式,还能处理调用表达式、成员表达式等。
如:
var obj = {name: 'astisgood',add: function(a, b){return a + b;}
}
function sub(a, b){return a - b;
}
function test(){var a = 1000;var b = sub(a,3000) + 1;var c = b + obj.add(b, 2000);return c + obj.name
}
========>
var obj = {name: 'astisgood',add: function(a, b){return a + b;}
}
function sub(a, b){return a - b;
}
function test() {return c = (b = (a = 1000, sub)(a, 3000) + 1, b + (0, obj).add(b, 2000)),c + (0, obj).name;
}
test函数中有函数调用表达式sub,还有成员表达式obj.add等,可以使用以下两种方法对其进行处理。
(1 提升变量声明到参数中。
(2) b=(a=1000,sub)(a,3000)+1中的(a=1000,sub)可以整体返回sub函数,然后直接调用,计算的结果加1后赋值给b(等号的运算符优先级很低)。同理,如果sub函数改为obj.add 的话,可以处理成( a =1000, obj.add)( a,3000)或者(a= 1000,obj).add(a,3000)。
第⒉种方法是调用表达式在等号右边的情况。例如 test函数中的第3条语句里面的b+obj. add(b,2000),可以对obj. add进行包装,处理成b+(0,obj.add)(b,2000)或者b+(0,obj).add(b,2000),括号中的0可以是其他花指令。
了解以上知识之后,我们可以接着对之前的代码进行混淆,
混淆前:
var bigArr = ['cmVwbGFjZQ==', 'Z2V0TW9udGg=', 'dG9TdHJpbmc=', 'Z2V0RGF0ZQ==', 'MA==', ""['constructor']['fromCharCode'], '\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d'
];
(function(arr, num){var shuffer = function(nums){while(--nums){arr['push'](arr['shift']());}};shuffer(++num);
}(bigArr, 0x20));Date.prototype.\u0066\u006f\u0072\u006d\u0061\u0074 = function(formatStr) {var \u0073\u0074\u0072 = \u0066\u006f\u0072\u006d\u0061\u0074\u0053\u0074\u0072;var Week = [bigArr[0], bigArr[1], bigArr[2], bigArr[3], bigArr[4], bigArr[5], bigArr[6]];eval(String.fromCharCode(115, 116, 114, 32, 61, 32, 115, 116, 114, 91, 39, 114, 101, 112, 108, 97, 99, 101, 39, 93, 40, 47, 121, 121, 121, 121, 124, 89, 89, 89, 89, 47, 44, 32, 116, 104, 105, 115, 91, 39, 103, 101, 116, 70, 117, 108, 108, 89, 101, 97, 114, 39, 93, 40, 41, 41, 59));str = str[atob(bigArr[7])](/MM/, (this[atob(bigArr[8])]() + 1) > 9 ? (this[atob(bigArr[8])]() + 1)[atob(bigArr[9])]() : atob(bigArr[11]) + (this[atob(bigArr[8])]() + 1));str = str[atob(bigArr[7])](/dd|DD/, this[atob(bigArr[10])]() > 9 ? this[atob(bigArr[10])]()[atob(bigArr[9])]() : atob(bigArr[11]) + this[atob(bigArr[10])]());return str;
}
console.log( new \u0077\u0069\u006e\u0064\u006f\u0077['\u0044\u0061\u0074\u0065']()[bigArr[12](102, 111, 114, 109, 97, 116)]('\x79\x79\x79\x79\x2d\x4d\x4d\x2d\x64\x64') );
混淆后:
//最开始的大数组
var bigArr = ['cmVwbGFjZQ==', 'Z2V0TW9udGg=', 'dG9TdHJpbmc=', 'Z2V0RGF0ZQ==', 'MA==', ""['constructor']['fromCharCode'], '\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d'
];
//还原数组顺序的自执行函数
(function(arr, num){var shuffer = function(nums){while(--nums){arr['push'](arr['shift']());}};shuffer(++num);
}(bigArr,0x20));
//把原先的变量定义提取到参数列表中
Date.prototype.\u0066\u006f\u0072\u006d\u0061\u0074 = function(formatStr, str, Week) {
//因为基本上都会处理成一行代码,所以return语句可以提到最上面
return str = (str = (Week = (\u0073\u0074\u0072 = \u0066\u006f\u0072\u006d\u0061\u0074\u0053\u0074\u0072, [bigArr[0], bigArr[1], bigArr[2], bigArr[3], bigArr[4], bigArr[5], bigArr[6]]//上面这个表达式的结果,会赋值给Week), eval(String.fromCharCode(115, 116, 114, 32, 61, 32, 115, 116, 114, 91, 39, 114, 101, 112, 108, 97, 99, 101, 39, 93, 40, 47, 121, 121, 121, 121, 124, 89, 89, 89, 89, 47, 44, 32, 116, 104, 105, 115, 91, 39, 103, 101, 116, 70, 117, 108, 108, 89, 101, 97, 114, 39, 93, 40, 41, 41, 59)), str[atob(bigArr[7])](/MM/, (this[atob(bigArr[8])]() + 1) > 9 ? (this[atob(bigArr[8])]() + 1)[atob(bigArr[9])]() : atob(bigArr[11]) + (this[atob(bigArr[8])]() + 1))//上面这个表达式的结果,会赋值给第二个str),str[atob(bigArr[7])](/dd|DD/, this[atob(bigArr[10])]() > 9 ? this[atob(bigArr[10])]()[atob(bigArr[9])]() : atob(bigArr[11]) + this[atob(bigArr[10])]())//上面这个表达式的结果,会赋值给第一个str);
}
console.log( new \u0077\u0069\u006e\u0064\u006f\u0077['\u0044\u0061\u0074\u0065']()[bigArr[12](102, 111, 114, 109, 97, 116)]('\x79\x79\x79\x79\x2d\x4d\x4d\x2d\x64\x64') );
再看看它最初始的样子:
Date.prototype.format = function (formatStr) {var str = formatStr;var Week = ['日', '一', '二', '三', '四', '五', '六'];str = str.replace(/yyyy|YYYY/, this.getFullYear());str = str.replace(/MM/, (this.getMonth() + 1) > 9 ? (this.getMonth() + 1).toString() : '0' + (this.getMonth() + 1));str = str.replace(/dd|DD/, this.getDate() > 9 ? this.getDate().toString() : '0' + this.getDate());return str;
}
console.log(new Date().format('yyyy-MM-dd'));
大眼一瞅,对比一下,已经面目全非了。
4. 其他代码防护方案
4.1 eval加密
看以下代码:
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()函数,它用来把一段字符串当作JS代码来执行。也就是说,传给eval()的参数是一段字符串。但在上述代码中,传给eval()函数的参数是一个自执行的匿名函数。这说明,这个匿名函数执行后会返回一段字符串,并且用eval()执行这段字符串,执行效果与eval加密前的代码效果等同。那就可以把这个匿名函数理解成是一个解密函数了。由此可见, eval加密其实和eval()关系不大, eval()只是用来执行解密出来的代码。
再来观察传给这个匿名函数的实参部分。观察第1个实参p和第4个实参k。可以看出处理方式很简单,提取原始代码中的一部分标识符,然后用它自己的符号占位,最后再对应替换回去就解密了。
最后介绍eval解密。这个比较容易,既然这个自执行的匿名函数就是解密函数,把上述代码中的eval删去,剩余代码在控制台中执行,就得到原始代码。
4.2 内存爆破
内存爆破是在代码中加入死代码,正常情况下这段代码不执行,当检测到函数被格式化或者函数被Hook,就跳转到这段代码并执行,直到内存溢出,浏览器会提示Out of Memory程序崩溃。内存爆破的代码如下所示:
var d =[0x1,0x0,0x0];
function b(){for(var i=0x0,c=d.length; i<c; i++){d.push(Math.round(Math.random()));c=d.length;}
}
for循环的结束条件是i<c,其中c的初始化值是数组的大小。看着像是一个遍历数组的操作,但是在循环中,又往数组中 push了成员,接着又重新给c赋值为数组的大小。这时这段代码就永远也不会结束了,直到内存溢出。
这段代码中的for循环是一个死循环,它的形式不像while(true)这样明显。尤其是考虑将代码混淆以后,更具迷惑性,增加了逆向分析难度。
4.3 检测代码是否格式化
检测的思路很简单,在JS中,函数是可以转为字符串的。因此可以选择一个函数转为字符串,然后跟内置的字符串对比或者用正则匹配。
函数转为字符串很简单,直接.toString()就🆗了。
在Chrome开发者工具中,把代码格式化后,会产生一个后缀为:formatted的文件。之后这个文件中设置断点,触发断点后,会停在这个文件中。但是,这时把某个函数转为字符串,取到的依然是格式化之前的代码。
在算法逆向中,分析完算法,为了得到想要的结果,就需要实现这个算法。简单的算法一般可以直接调用现成的加密库。复杂的算法就会选择直接修改原文件,然后运行得到结果。把格式化后的代码保存成一个本地文件,这时某个函数转为字符串,取到的就是格式化后的结果了。
是否触发格式化检测,关键是看原文件中是否有格式化。接着如果把内存爆破代码加入其中,检测到格式化就跳转到内存爆破代码中执行,程序就会崩溃。
5. 小结
混淆的目的是增加逆向开发者的工作量。例如,原本一小时就可以解决的算法,混淆后可能需要几天才能解决。当算法每天更新,逆向开发者自然就放弃了。目前市面上已有此类方案,只不过变化的算法仅限于微调,如算法中的常量、算法加密前的参数顺序等。如果要实现此类方案,需要一种自动化处理代码的方案,AST为此而生。
文章到此结束,感谢您的阅读,下篇文章见!
AST学习课程推荐,市面上关于AST的课程不多,这里就推荐我学过的两门吧
xjb课程:反爬虫AST混淆JavaScript与还原实战
蔡老板和风佬课程:AST入门实战+零基础JavaScript补环境课程
也可以看蔡老板的知识星球学习:AST入门与实战
相关文章:
JS代码安全防护常见的方式
文章目录1. 常量的混淆1.1 十六进制字符串1.2 unicode字符串1.3 字符串的ASCII码混淆1.4 字符串常量加密1.5 数值常量加密2. 增加逆向分析难度2.1 数组混淆2.2 数组乱序2.3 花指令2.4 jsfuck3. 代码执行流程的防护3.1 流程平坦化3.2 逗号表达式4. 其他代码防护方案4.1 eval加密…...
PHP(13)HTTP协议
PHP(13)HTTP协议一、HTTP请求1. 请求行2. 请求头3. 请求体二、HTTP响应1. 响应行2. 响应头三、设置HTTP响应四、模拟HTTP请求一、HTTP请求 1. 请求行 请求行独占一行。形式:请求方式 资源路径 协议版本号 GET /index.php HTTP/1.1 2. 请求…...
基于支持向量机 (SVM) 用php实现预测气温
Windows 10自带的天气应用有一个基于历史数据预测气温的功能,有一定的参考价值。那么如何去实现这一功能呢?本文采用php进行实现。 使用机器学习方法实现预测当日气温的算法需要涵盖许多的步骤,以下是一种基于支持向量机 (SVM) 的算法的简化…...
MySQL(五)
通过索引进行优化 索引基本知识 索引的优点 1、大大减少了服务器需要扫描的数据量2、帮助服务器避免排序和临时表3、将随机io变成顺序io 索引的用处 1、快速查找匹配WHERE子句的行2、从consideration中消除行,如果可以在多个索引之间进行选择,mysql通常会使用找到…...
Linux常用命令2
目录1.查找find(1)普通用法(2)组合用法2.xargs命令3.管道符4.查看文件内容(1)查看两个文件的差别:diff file1 fille2(2)正序查看文件内容cat(3)倒序查看文件内容tac(4)分页查看文件内容more(5)分页查看文件内容less(6)…...
『C/C++养成计划』Visual Studio Code编辑器配置(外观通用型扩展Minmal)
Visual Studio Code编辑器配置(外观&通用型扩展&Minmal)! 文章目录 一. vscode配置外观|通用型扩展1.1. 色彩主题配置扩展(GitHub Theme)1.2. 图标主题扩展(Material Icon Theme)1.3. 代码高亮扩展(better-comments)1.4. 错误警告扩展(error lens)1.5. 执行代码扩展(c…...
设计模式(适配器模式)
设计模式(适配器模式) 第二章 设计模式之适配器模式(Adapter) 一、Adapter模式介绍 适配器模式位于实际情况和需求之间,填补两者之间的差距。 二、示例程序1(使用继承的适配器) 1.示例程序示…...
在基于全志D1s的芒果派麻雀上运行国产开源rt-smart系统
想必RT-Thread系统大家不陌生了,RT-Thread Smart(简称 rt-smart)是基于 RT-Thread 操作系统衍生的新分支,面向带 MMU,中高端应用的芯片,例如 ARM Cortex-A 系列芯片,MIPS 芯片,带 MM…...
【代码随想录训练营】【Day15】第六章|二叉树|层序遍历|226.翻转二叉树|101.对称二叉树
层序遍历 题目详细:LeetCode.102 层序遍历与上一节讲的三种遍历方式有所不同,层序遍历是指按从上到下,从左到右的顺序,逐层地遍历二叉树的节点。 从其节点的遍历顺序上观察,我们可以发现其跟广度优先遍历࿰…...
基于圆展开自适应三边测量算法的室内定位
基于圆展开自适应三边测量算法的室内定位 具有无线通信功能的移动设备的日益普及刺激了室内定位服务的增长。室内定位用于实时定位设备位置,方便访问。然而,由于大量障碍物,与室外定位相比,室内定位具有挑战性。全球定位系统非常适…...
使用中断子系统实现对LED灯的控制
中断顶半部:不允许耗时操作 代码流程: 1、基于字符设备驱动的注册(手动/自动) 2、基于设备树文件的自定义完成(myled, myirq) 2、基于GPIO子系统实现led的点亮(流水/测试文件控制) 3、中断子系统操作流程 …...
《爆肝整理》保姆级系列教程python接口自动化(十五)--参数关联接口(详解)
简介 我们用自动化新建任务之后,要想接着对这个新建任务操作,那就需要用参数关联了,新建任务之后会有一个任务的Jenkins-Crumb,获取到这个Jenkins-Crumb,就可以通过传这个任务Jenkins-Crumb继续操作这个新建的任务。 …...
【JDK8】MyBatis源码导入Idea
1.背景 为了更好的将MyBatis的开发设计思想带到日常开发工作,将MyBatis源码导入到本地开发工具中(idea)。我自己在导入的时候碰到几个问题,耽误了自己一点时间,这里我把它们记下来,后边的小伙伴可不要踩我的坑。 Java版本&#x…...
三层交换机DHCP中继
关于中继,我们需要有一个对比。正常情况下我们是不是需要配置单臂路由然后开启DHCP地址池,然就设置网段网关以及DNS。这样的话考验 的其实是命令功底。但是为了方便,我们 可以添加服务器,将这个服务给到服务器去配置,这…...
C++之RALL机制
RALL是Resource acquisition is initialization的缩写,意思是“资源获取即初始化”,其核心思想是利用C对象生命周期的概念来控制程序的资源。它的技术原理很简单,如果希望对某个重要资源进行跟踪,那么创建一个对象,并将…...
回溯算法章末总结
组合问题的特点 (1)abba 选中a之后,就不再选了 (2)找出所有的组合 (长度可以不相等) 组合问题模板 做回溯题步骤 (0)判断问题类型 (1)树状图 …...
【SpringBoot】为异步任务规划线程池
一、线程池的作用 防止资源占用无限的扩张调用过程省去资源的创建和销毁所占用的时间 在上一节中,我们的一个异步任务打开了一个线程,完成后销毁。在高并发环境下,不断的分配新资源,可能导致系统资源耗尽。所以为了避免这个问题…...
SAP ABAP 输出结果带有空格
方法一: 字段内容前增加空格,需使用全角空格,使用半角空格时,ALV显示无效,空格无法显示, 全角与半角的切换方法:shift空格切换, 如下的标记部分,要想通过ALV显示空格&…...
Opengl ES之踩坑记
前因 最近在尝试使用Opengl ES实现一些LUT滤镜效果,在实现这些滤镜效果的时候遇到一些兼容性的坑,踩过这些坑后我希望把这几个坑分享给读者朋友们, 希望同在学习Opengl ES的朋友们能少走弯路。 关于LUT滤镜相关的介绍,也是这个O…...
设计模式第六讲:责任链模式和迭代器模式详解
一. 责任链模式1. 背景在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批…...
K8s 架构简介(一)
一、前言 在开始学习K8s之前,让我们对容器有一个基本的了解 1.1 什么是容器 一个容器镜像是一个可运行的软件包,其中包含了一个完整的可执行程序,包括代码和运行时需要应用、系统库和全部重要设置的默认值。 通过将应用程序本身ÿ…...
xshell6运行报错:由于找不到mfc110u.dll、MSVCR110.dll无法继续执行代码
今天给大家分享一下我刚装完系统遇到得问题,由于新盟的罗建雨【胡巴】老师帮我给电脑加了固态,又重装了系统,因此电脑里面得所有软件需要重装,在我重装的过程中遇到了一个小问题给大家分享一下,如果大家以后遇到也方便解决。 问题: 安装Xshell时电脑系统报错:“由于找…...
Baklib知识库管理平台,协助组织提升知识管理水平
随着信息时代和知识经济时代的到来,企业内部信息资料繁多冗杂,知识管理逐渐成为各大企业的重要工作之一,企业管理者无不感受到巨大的压力,怎么样将知识进行有效的管理,成为一个难点,并且随着信息不断的更迭…...
一文搞懂core-scheduling核心机制
cookie的原理借助于unsigned long型,和refcount_t引用计数器。 32位64位char *4字节8字节unsigned long4字节8字节 数据结构修改 首先看看实现core scheduling功能对数据结构有哪些修改 task_struct struct task_struct{struct rb_node core_node;unsigned long…...
IP地址在金融行业有哪些应用?
中国加入WTO以来经济得到迅速发展,金融行业随着经济发展体系越来越完善。随着西方金融公司和理念的加入中国金融行业开始多样化发展。金融行业在快速发展的同时也引发了许多弊端。如何维护挖掘客户更大需求?如何获取更多优质客户?如何提升网络…...
GT-suite v2016解决许可证过期问题(附新版liscense下载地址)
安装GT-suite v2016时遇到了如图报错的问题。当时的报错找不到了,下图是贴吧相同问题的报错图。 为了解决问题,先根据某网友的如下答复操作: 添加环境变量后仍然有相同报错。 看来需要寻找其他方法。 再尝试着卸载GT-suite v2016,…...
小红书商业笔记与普通笔记区别是什么?小红书笔记有哪几种
主攻单一平台,如何迅速打造爆文。针对软文发布类别的选择,小红书商业笔记与普通笔记区别究竟是什么,今天为大家带来的详细分析,告诉你该如何用最少的成本,做出“爆文”。1、小红书的笔记类型我们都知道,小红…...
DataWhale-统计学习方法打卡Task01
学习教材《统计学习方法(第二版)》李航 统计学习方法(第2版) by...李航 (z-lib.org).pdf https://www.aliyundrive.com/s/maJZ6M9hrTe 点击链接保存,或者复制本段内容,打开「阿里云盘」APP ,无…...
Java面试——Spring 事务
目录 1.什么是Spring 事务 2.Spring 事务的开启方式 3.Spring事务的实现方式/原理 4.事务传播机制 5.事务隔离级别 6.事务失效的原因 1.什么是Spring 事务 事务在逻辑上是一组操作,要么执行,要不都不执行。 如下: Begin; insert into…...
Python语言零基础入门教程(十九)
Python 异常处理 python提供了两个非常重要的功能来处理python程序在运行中出现的异常和错误。你可以使用该功能来调试python程序。 1、异常处理 2、断言(Assertions) python标准异常 什么是异常? 异常即是一个事件,该事件会在程序执行过程中发生&…...
邯郸市做网站的公司/搜索推广渠道
前言Object中的wait、notify、notifyAll,可以用于线程间的通信,核心原理为借助于监视器的入口集与等待集逻辑。通过这三个方法完成线程在指定锁(监视器)上的等待与唤醒,这三个方法是以锁(监视器)为中心的通信方法。除了他们之外,还…...
嘉兴专业网站建设/百度快照什么意思
up主0x000006b怎么办,开机就蓝屏,安全模式也是,好像是因为下载一个东西时我强制关机了,重新开机就蓝屏了。电脑小白[大哭][大哭][大哭][大哭]可能是你下载的“软件”和电脑中的“内存”有冲突了我给你12种方法调试,快试试吧&#…...
iapp怎么把网站做软件/最新国际新闻热点事件
众所周知,Go 在做依赖管理时会创建两个文件,go.mod 和 go.sum。相比于 go.mod,关于 go.sum 的资料明显少得多。自然,go.mod 的重要性不言而喻,这个文件几乎提供了依赖版本的全部信息。而 go.sum 看上去就是 go module …...
wordpress百家号插件/长沙建站seo公司
7、此时,设备类型我们选择“TCP/IP 设备”,主机名或 IP 地址我们输入 Windows XP 系统的电脑的 IP 地址,然后选择“下一步”。最后,系统就会自动查找局域网中 IP 地址为你填入的 IP 的电脑是否连接有共享的打印机,如果…...
驾校做网站/优化营商环境条例解读
2019独角兽企业重金招聘Python工程师标准>>> 前言 每次看到一些库npm -g install xx然后,执行xx就可以跑起来,这不就是一个shell工具了吗,那么我不就可以不用学习shell语法,直接用js写命令行脚本了吗! <!--more--> 什么是REPL应用 所谓的repl应用就是一个终端…...
专门做调查问卷的网站/seo销售是做什么的
mysql workbench 导出建表结构 导出数据,直接上图,gif简示图;...