🕸️
JsCrack
  • README
  • 🌱AST
    • JS代码混淆基础
    • AST原理与实现
    • Babel API
    • AST自动化混淆JS
    • AST自动化还原JS
  • 🛠️Tricks
    • 经验之谈
    • 解密定位
    • Cookie加密
    • WebPack混淆
    • 进制流解密
    • RPC调用
    • TLS握手流程
    • JAR3算法
  • 🎬Slider
    • 极验滑块JS逆向
  • 🍖Practice
    • 某查查爬取统一社会信用代码
    • 某安全社区文章爬取
Powered by GitBook
On this page
  • 0x01 String obfuscation
  • Object Attributes Access
  • Hex
  • unicode
  • ASCII Array
  • String Constant
  • Number Constant
  • 0x02 Reference obfuscation
  • Array Index
  • Array Shuffle
  • Junk Code
  • JsFuck
  • 0x03 Protection in Execution Flow
  • Control Flow Flattening
  • Comma Expression
  • 0x04 Other Protection Strategies
  • Eval Encryption
  • Memory Explosion
  • Formatted Code Detection
  • Forever Debugger Loop
  • 0x05 Sum up

Was this helpful?

  1. 🌱AST

JS代码混淆基础

Every flight begins with a fall

Basic Confusions About JavaScript

JS混淆:通过复杂冗余的代码替换,把原来可读性高的代码转化为可读性低的代码,且前后执行效果等同

Date.prototype.format = function(formatStr) {
    var str = formatStr;
    var WEEK = ['日', '一', '二', '三', '四', '五', '六'];
    str = str.replace(/yyyy|YYYY/, this.getFullYear())
    .replace(/mm|MM/, (this.getMonth() + 1) > 9 ? (this.getMonth() + 1).toString() : '0' + (this.getMonth() + 1).toString())
    .replace(/dd|DD/, this.getDate() > 9 ? this.getDate().toString() : '0' + this.getDate().toString());
    return str;
}
console.log(new Date().format('yyyy-MM-dd'));

上面是一段简单的代码,用于实现日期格式化

但若这是某网站前端的一段关键代码,如前后端的通信过程,那么在代码发布后就很可能被某些有心人利用,而JS混淆做的事情就是增加破解的成本,用以防护JS代码。

0x01 String obfuscation

Object Attributes Access

function Dog(name) {
    this.name = name;
}
Dog.prototype.bark = function(){
    console.log("Hello");
}
var d = new Dog('taco');
console.log(d.name);  // taco
d.bark();  // Hello
console.log(d['name']);  // taco
d['bark'](); // Hello 

有两种方式访问对象的属性(方法可以看作特殊的属性):

  • d.name:name为一个标识符,无法加密或拼接

  • d['name']:name为一个字符串,可以进行加密或拼接

Date是JS的内置对象,在JS中,很多内置对象都是window对象的属性。实际上JS代码中定义的全局变量/方法都是全局对象window的属性/方法,全局对象的属性和方法在调用时可省略对象名

new window.Date();
new Date(); // 等价

替换脚本:

import re

with open('test.js', 'r') as file:
    js_code = file.read()

# Use regular expressions to find and replace dot notation with bracket notation
modified_code = re.sub(r'(\s)Date(\()', r"\1window.Date\2" ,js_code)
modified_code = re.sub(r'(\w+|\s|\)|\])\.(\w+)', r"\1['\2']", modified_code)

print(modified_code)

所以上面的代码可以改写为如下:

Date['prototype'].format = function(formatStr) {
    var str = formatStr;
    str = str['replace'](/yyyy|YYYY/, this['getFullYear']())
    ['replace'](/mm|MM/, (this['getMonth']() + 1) > 9 ? (this['getMonth']() + 1)['toString']() : '0' + (this['getMonth']() + 1)['toString']())
    ['replace'](/dd|DD/, this['getDate']() > 9 ? this['getDate']()['toString']() : '0' + this['getDate']()['toString']());
    return str;
}
console['log'](new window['Date']()['format']('yyyy-MM-dd'));

将对象属性/方法的访问方式改成中括号后,就能进行下一步字符串混淆了

Hex

JS中的字符串支持十六进制表示

text = 'yyyy-MM-dd'
hexStr = ''
for _ in text:
    hexStr += hex(ord(_)).replace('0', '\\', 1)
print(hexStr)
# '\x79\x79\x79\x79\x2d\x4d\x4d\x2d\x64\x64'

十六进制的字符串放到控制台回车就能还原

unicode

JS中不止字符串可以用unicode,标识符也支持unicode形式

string = "Date"
unicode_string = ''.join(['\\u' + hex(ord(char))[2:].zfill(4) for char in string])
print(unicode_string)
# \u0044\u0061\u0074\u0065

new \u0044\u0061\u0074\u0065() = new Date()

非常之amazing啊

Date.prototype.\u0066\u006f\u0072\u006d\u0061\u0074 = function(formatStr) {
    var \u0073\u0074\u0072 = \u0066\u006f\u0072\u006d\u0061\u0074\u0053\u0074\u0072;
    str = str.\u0072\u0065\u0070\u006c\u0061\u0063\u0065(/yyyy|YYYY/, this.getFullYear())
    .replace(/mm|MM/, (this.getMonth() + 1) > 9 ? (this.getMonth() + 1).toString() : '0' + (this.getMonth() + 1).toString())
    .replace(/dd|DD/, this.getDate() > 9 ? this.getDate().toString() : '0' + this.getDate().toString());
    return str;
}
console.log(new \u0077\u0069\u006e\u0064\u006f\u0077['\u0044\u0061\u0074\u0065']()['format']('\u0079\u0079\u0079\u0079\u002d\u004d\u004d\u002d\u0064\u0064'));

但实际JS混淆中,标识符一般不会替换成unicode形式,因为要还原十分简单,直接放控制台输出就行。通常的混淆方式是替换成没有语义的,但看上去相似的名字,如_0x21dd83、_0x21dd84,或者是由大写字母O、小写字母o、数字0组成的名字,如O0o00O、O0o00o(标识符不以数字开头)

ASCII Array

既可以用来混淆字符串,也可以用来混淆代码

function stringToCharCodes(str){
    var arr = [];
    for(var i = 0; i < str.length; i++){
        arr.push(str.charCodeAt(i));
    }
    return arr;
}
console.log(stringToCharCodes('var a = 100;alert(a);'));

使用fromCharCode来还原,但这个函数接收可变长度参数,不接收数组,可以使用apply(apply方法从Function.prototype继承过来)

var codeStr = String.fromCharCode.apply(null,[118, 97, 114, 32, 97, 32, 61, 32, 49, 48, 48, 59, 97, 108, 101, 114, 116, 40, 97, 41, 59]);

通过eval来执行字符串代码

eval(codeStr)

String Constant

将字符串编码得到密文,使用前再调用相应的解码函数去解密,得到明文

btoa('secret')  // base64编码
atob('c2VjcmV0')  // base64解码

建议减少使用JS自带的函数,而是自己去实现相应的函数,因为不管如何混淆,最终执行过程中,JS自带的函数名是固定的,可以通过Hook技术定位到关键代码。

Number Constant

在使用一些加密算法或哈希算法中,会用到一些数值常量,如MD5中的0x67452301、0xefcdab89、0x98badcfe,通过这些常量可以识别出使用的算法,因此有必要对这些常量进行编码。如使用异或的特性:a ^ b = c 则 c ^ b = a,要替换掉a,c相当于密文,b相当于密钥

0x02 Reference obfuscation

Array Index

JS的数组可以存在各种类型,将代码中的字符串、布尔值、数组、函数、对象等放到一个大数组中,使用的时候再进行引用,可以有效降低代码可读性

var BigArr = [
    'cmVwbGFjZQ==', // replace
    'Z2V0RnVsbFllYXI=', // getFullYear
    'Z2V0TW9udGg=', // getMonth
    'Z2V0RGF0ZQ==', // getDate
    'dG9TdHJpbmc=', // toString
    'MA==', // 0
    ""['constructor']['fromCharCode']  // ""['constructor']等同于String
]
Date.prototype.\u0066\u006f\u0072\u006d\u0061\u0074 = function(formatStr) {
    var \u0073\u0074\u0072 = \u0066\u006f\u0072\u006d\u0061\u0074\u0053\u0074\u0072;
    str = str[atob(BigArr[0])](/yyyy|YYYY/, this[atob(BigArr[1])]())
    .replace(/mm|MM/, (this[atob(BigArr[2])]() + 1) > 9 ? (this[atob(BigArr[2])] + 1)[atob(BigArr[4])]() : atob(BigArr[5]) + (this[atob(BigArr[2])]() + 1)[atob(BigArr[4])]())
    .replace(/dd|DD/, this[atob(BigArr[3])]() > 9 ? this[atob(BigArr[3])]()[atob(BigArr[4])]() : atob(BigArr[5]) + this[atob(BigArr[3])]()[atob(BigArr[4])]());
    return str;
}
console.log(new \u0077\u0069\u006e\u0064\u006f\u0077['\u0044\u0061\u0074\u0065']()['format']('\u0079\u0079\u0079\u0079\u002d\u004d\u004d\u002d\u0064\u0064'));
eval(BigArr[6].apply(null, [97, 108, 101, 114, 116, 40, 34, 54, 54, 54, 34, 41, 59]));

Array Shuffle

上面数组混淆成员与索引是一一对应的,可以将数组打乱以增加逆向工作量,但引用前需要进行还原。

  • pop:右边弹出

  • shift:左边弹出

  • unshift:左边插入

  • push:右边插入

var BigArr = [
    'cmVwbGFjZQ==', // replace
    'Z2V0RnVsbFllYXI=', // getFullYear
    'Z2V0TW9udGg=', // getMonth
    'Z2V0RGF0ZQ==', // getDate
    'dG9TdHJpbmc=', // toString
    'MA==', // 0
    ""['constructor']['fromCharCode']  // ""['constructor']等同于String
];
(function(arr, num){
    var shuffer = function(nums){
        while(nums--){
            arr.unshift(arr.pop());
        }
    };
    shuffer(num);
})(BigArr, 0x20);
console.log(BigArr);
// (7) ['Z2V0RGF0ZQ==', 'dG9TdHJpbmc=', 'MA==', ƒ, 'cmVwbGFjZQ==', 'Z2V0RnVsbFllYXI=', 'Z2V0TW9udGg=']

fromCharCode由原本的6换到了4

var BigArr = [
    'Z2V0RGF0ZQ==', 
    'dG9TdHJpbmc=', 
    'MA==', 
    ""['constructor']['fromCharCode'], 
    'cmVwbGFjZQ==', 
    'Z2V0RnVsbFllYXI=', 
    'Z2V0TW9udGg='
];
(function(arr, num){
    var shuffer = function(nums){
        while(nums--){
            arr['push'](arr['shift']());
        }
    };
    shuffer(num);
})(BigArr, 0x20);
console.log(BigArr);
Date.prototype.\u0066\u006f\u0072\u006d\u0061\u0074 = function(formatStr) {
    var \u0073\u0074\u0072 = \u0066\u006f\u0072\u006d\u0061\u0074\u0053\u0074\u0072;
    str = str[atob(BigArr[0])](/yyyy|YYYY/, this[atob(BigArr[1])]())
    .replace(/mm|MM/, (this[atob(BigArr[2])]() + 1) > 9 ? (this[atob(BigArr[2])] + 1)[atob(BigArr[4])]() : atob(BigArr[5]) + (this[atob(BigArr[2])]() + 1)[atob(BigArr[4])]())
    .replace(/dd|DD/, this[atob(BigArr[3])]() > 9 ? this[atob(BigArr[3])]()[atob(BigArr[4])]() : atob(BigArr[5]) + this[atob(BigArr[3])]()[atob(BigArr[4])]());
    return str;
}
console.log(new \u0077\u0069\u006e\u0064\u006f\u0077['\u0044\u0061\u0074\u0065']()['format']('\u0079\u0079\u0079\u0079\u002d\u004d\u004d\u002d\u0064\u0064'));
eval(BigArr[6].apply(null, [97, 108, 101, 114, 116, 40, 34, 54, 54, 54, 34, 41, 59]));

Junk Code

一些没有意义但可以混淆视听的代码

function _0x20ab1fxe2(a, b){
    return a + b;
}
function _0x20ab1fxe1(a, b){
    return _0x20ab1fxe2(a, b);
}
_0x20ab1fxe1(new Date().getMonth(), 1);

上面将加法二项式拆成一个函数

函数调用表达式也可以处理成类似的花指令

function test(a, b) {
    return (a + b) * 10;
}
function _0x20ab1fxe2(a, b, c){
    return a(b, c);
}
var str = 'mm'
str = _0x20ab1fxe2(test, 3, 4); // 70

JsFuck

将js代码转化为只用6个字符就能表示的代码

[、(、+、!)、]
  • JS中的七种假值,其余均为真

    • false

    • undefined

    • null

    • 0

    • -0

    • NaN

    • ""

  • +作一元运算符可以强转为数值类型

    • +[] => 0

    • ![] => false

    • !+[] => true

    • !![] => true

如下在控制台输入会输出hello

(+(+!+[]+[+[]]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+[+!+[]])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]

实际开发中,jsfuck应用有限,一般只应用于一部分代码(jsfuck代码量太大)

0x03 Protection in Execution Flow

Control Flow Flattening

代码开发中会有很多流程控制相关的代码,即代码中有很多分支语句(if语句、switch语句),其中switch语句中case块是平级的,这就能应用到控制流平坦化。以下面代码为例

function test(){
    var a = 100;
    var b = a + 200;
    var c = b + 300;
    var d = c + 400;
    var e = d + 500;
    var f = e + 600;
    return f;
}
console.log(test());

首先打乱语句顺序,将其放入switch语句中,再通过循环遍历分发器来决定执行顺序

function test(){
    var distributor = '4|3|1|6|2|7|5'.split('|'), i = 0;
    while(!![]){
        switch(distributor[i++]) {
            case '1':
                var c = b + 300;
                continue;
            case '2':
                var e = d + 500;
                continue;
            case '3':
                var b = a + 200;
                continue;
            case '4':
                var a = 100;
                continue;
            case '5':
                return f;
                continue;
            case '6':
                var d = c + 400;
                continue;
            case '7':
                var f = e + 600;
                continue;
        }
        // 当switch计算结果与每个case都不匹配,退出循环
        break;
    }
}
console.log(test());
  • 假如函数有return语句,执行到最后一个case就会退出(这里为case '5')

  • 假设函数没有return语句,JS中数组越界会返回undefined,匹配不到case会执行到break

var BigArr = [
    'Z2V0RGF0ZQ==', 
    'dG9TdHJpbmc=', 
    'MA==', 
    ""['constructor']['fromCharCode'], 
    'cmVwbGFjZQ==', 
    'Z2V0RnVsbFllYXI=', 
    'Z2V0TW9udGg='
];
(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 distributor = '2|5|1|7|4'.split('|'), i = 0;
    while(!![]){
        switch(distributor[i++]){
            case '1':
                str = str[BigArr[6].apply(null, [114, 101, 112, 108, 97, 99, 101])](/mm|MM/, (this[atob(BigArr[2])]() + 1) > 9 ? (this[atob(BigArr[2])] + 1)[atob(BigArr[4])]() : atob(BigArr[5]) + (this[atob(BigArr[2])]() + 1)[atob(BigArr[4])]());
                continue;
            case '2':
                var \u0073\u0074\u0072 = \u0066\u006f\u0072\u006d\u0061\u0074\u0053\u0074\u0072;
                continue;
            case '4':
                return str;
                continue;
            case '5':
                str = str[atob(BigArr[0])](/yyyy|YYYY/, this[atob(BigArr[1])]());
                continue;
            case '7':
                str = str[BigArr[6].apply(null, [114, 101, 112, 108, 97, 99, 101])](/dd|DD/, this[atob(BigArr[3])]() > 9 ? this[atob(BigArr[3])]()[atob(BigArr[4])]() : atob(BigArr[5]) + this[atob(BigArr[3])]()[atob(BigArr[4])]());
                continue;
        }
        break;
    }
}
console.log(new \u0077\u0069\u006e\u0064\u006f\u0077['\u0044\u0061\u0074\u0065']()['format']('\u0079\u0079\u0079\u0079\u002d\u004d\u004d\u002d\u0064\u0064'));

Comma Expression

逗号运算符把多个表达式或语句连成一个复合语句

上面的test函数可以修改为如下:

(这里把变量声明都放到了函数的形参,JS允许参数实参和形参数量不一致,没有传入实参为undefined)

function test(a, b, c, d, e, f){
    return a = 100, b = a + 200, c = b + 300, d = c + 400, e = d + 500, f = e + 600, f;
}
console.log(test());

但这样只是把多条语句写在一行,没有太大的混淆力度,可以试试嵌套的逗号表达式

function test(a, b, c, d, e, f){
    return f = (e = (d = (c = (b = (a = 100, a + 200), b + 300), c + 400), d + 500), a + 50, c + 80, e + 600), f;
}
console.log(test());

逗号表达式会顺序执行每条语句,并返回最后一条语句的执行结果,因此可以在中间添加没有意义的花指令(如这里在e + 600前加了a + 50、c + 80)

逗号表达式混淆不仅可以处理赋值表达式,也可以处理函数调用和对象成员表达式。试着使用逗号表达式混淆下面的test函数

var obj = {
    num : '0x',
    add : function(a, b) {
        return a + b;
    }
}
function sub(a, b) {
    return a - b;
}
function test() {
    var a = 1000;
    var b = sub(a, 500) + 300;
    var c = b + obj.add(b, 100);
    return obj.num + c;
}
console.log(test());
function test(a, b, c) {
    return ((c = (b = (a = 1000, sub)(a, 500) + 300, obj.add)(b, 100) + b), obj.num + c); 
}
console.log(test());

0x04 Other Protection Strategies

Eval Encryption

上面已经讲过了eval会把字符串当js代码执行,但直接传字符串未免太显眼,除了上面的fromCharCode来还原字符串再传入eval,还可以把一个自执行函数作为eval的参数,自执行函数(可以看成一个解密函数)将返回一个字符串。

Memory Explosion

往代码中加入死代码,正常情况下这段代码不会执行,当检测到函数被格式化或者函数被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;
    }
}

上面这段代码不像while(true)那么明显,它不断往数组push,循环结束条件是i<c,但每次都更新c为数据长度,这段代码就永远不会结束了。

Formatted Code Detection

先将代码压缩,并在代码里面检测代码是否格式化,在Chrome开发者工具中把代码格式化后会产生一个后缀为formatted的文件。

js中函数是可以转字符串的,func.toString()或func + ''

这里利用的是格式化后多出来的换行符

console.log("start");
function t(){
    return t.toString().search('(((.+)+)+)+$').toString();
}
a=t();
console.log("end");

执行上面这段代码会发现走不到log("end"),接着程序就卡死了

但若去掉t函数的回车换行就可以了

console.log("start");
function t(){return t.toString().search('(((.+)+)+)+$').toString();}
a=t();
console.log("end");

serach 与 indexof 类似,不同的是 search 使用正则表达式匹配,而 indexOf 只是按字符串匹配的。serach将(((.+)+)+)+$视为正则表达式进行匹配。多了换行符就会无限递归下去。

下面是混淆后的版本:

var _0x5647a6=function(){var _0xf77285=!![];return function(_0x138773,_0x1b2add){var _0x5d2349=_0xf77285?function(){if(_0x1b2add){var _0x5daeb2=_0x1b2add['apply'](_0x138773,arguments);_0x1b2add=null;return _0x5daeb2;}}:function(){};_0xf77285=![];return _0x5d2349;};}();var _0x16e48a=_0x5647a6(this,function(){return _0x16e48a['toString']()['search']('(((.+)+)+)+$')['toString']()['constructor'](_0x16e48a)['search']('(((.+)+)+)+$');});_0x16e48a();var a=0x1;
var _0x5647a6 = function() {
	var _0xf77285 = !![];
	return function(_0x138773, _0x1b2add) {
		var _0x5d2349 = _0xf77285 ?
		function() {
			if (_0x1b2add) {
				var _0x5daeb2 = _0x1b2add['apply'](_0x138773, arguments);
				_0x1b2add = null;
				return _0x5daeb2;
			}
		}: function() {};
		_0xf77285 = ![];
		return _0x5d2349;
	};
} ();
var _0x16e48a = _0x5647a6(this,
function() {
	return _0x16e48a['toString']()['search']('(((.+)+)+)+$')['toString']()['constructor'](_0x16e48a)['search']('(((.+)+)+)+$');
});
_0x16e48a();
var a = 0x1;

Forever Debugger Loop

写个定时器死循环来无限debug

function debug() {
    debugger;
    setTimeout(debug, 1);
}
debug();

0x05 Sum up

本文介绍了常见的JS混淆手段

JS中的对象访问可以通过点访问也能通过中括号访问,改成中括号访问可以进一步对字符串进行混淆。对于字符串,可以通过各种编码或自定义的编码函数进行混淆,如JS支持字符串以十六进制表示,JS标识符支持unicode形式,还可以用ASCCI数组、base64等。对于数值常量,可以将其改写成两个数值异或。对字符串进行混淆后,在代码中可通过数组索引的方式来引用,再把字符串数组乱序。表达式、函数调用可以替换成花指令,即没有意义的嵌套。jsfuck将代码转化为6字符的表示,但由于其代码量太大,实际中只能少部分代码利用。接着是执行流程的混淆,控制流平坦化是利用switch case语句,在一个死循环中遍历一个分发器,分发器决定代码的执行顺序(case),最终匹配不到,跳到default执行break退出循环。还有就是Eval混淆,将一串编码后的字符串输入到解码函数后,得到待执行的代码字符串,再传入eval执行。这种混淆的特征太明显了,就是一大段字符串传入一个函数。或许可以改进一下,把字符串拆分为多段,再放入一个数组,可以再乱序一下,最后通过数组索引拼接后传入eval

还有一些反调试手段。如对代码进行压缩,并检测代码是否被格式化,若格式化则进行内存爆破(死循环增大数组、正则无限回溯);无限debugger。

PreviousREADMENextAST原理与实现

Last updated 1 year ago

Was this helpful?