如何实现自己的 JS 混淆编码?

作者: whut开源后端组梁思远

简介

你是否见过过 JSFuckJJEncodeAAEncode这类混淆编码?它们既可以用于前端代码混淆,也可以用于 XSS 攻击,最重要的是它们很好玩

当你第一次见到下面这段“代码”时,也许会有点懵:

゚ω゚ノ= /`m´)ノ ~┻━┻   //*´∇`*/ ['_']; o=(゚ー゚)  =_=3; c=(゚Θ゚) =(゚ー゚)-(゚ー゚); (゚Д゚) =(゚Θ゚)= (o^_^o)/ (o^_^o);(゚Д゚)={゚Θ゚: '_' ,゚ω゚ノ : ((゚ω゚ノ==3) +'_') [゚Θ゚] ,゚ー゚ノ :(゚ω゚ノ+ '_')[o^_^o -(゚Θ゚)] ,゚Д゚ノ:((゚ー゚==3) +'_')[゚ー゚] }; (゚Д゚) [゚Θ゚] =((゚ω゚ノ==3) +'_') [c^_^o];(゚Д゚) ['c'] = ((゚Д゚)+'_') [ (゚ー゚)+(゚ー゚)-(゚Θ゚) ];(゚Д゚) ['o'] = ((゚Д゚)+'_') [゚Θ゚];(゚o゚)=(゚Д゚) ['c']+(゚Д゚) ['o']+(゚ω゚ノ +'_')[゚Θ゚]+ ((゚ω゚ノ==3) +'_') [゚ー゚] + ((゚Д゚) +'_') [(゚ー゚)+(゚ー゚)]+ ((゚ー゚==3) +'_') [゚Θ゚]+((゚ー゚==3) +'_') [(゚ー゚) - (゚Θ゚)]+(゚Д゚) ['c']+((゚Д゚)+'_') [(゚ー゚)+(゚ー゚)]+ (゚Д゚) ['o']+((゚ー゚==3) +'_') [゚Θ゚];(゚Д゚) ['_'] =(o^_^o) [゚o゚] [゚o゚];(゚ε゚)=((゚ー゚==3) +'_') [゚Θ゚]+ (゚Д゚) .゚Д゚ノ+((゚Д゚)+'_') [(゚ー゚) + (゚ー゚)]+((゚ー゚==3) +'_') [o^_^o -゚Θ゚]+((゚ー゚==3) +'_') [゚Θ゚]+ (゚ω゚ノ +'_') [゚Θ゚]; (゚ー゚)+=(゚Θ゚); (゚Д゚)[゚ε゚]='\\'; (゚Д゚).゚Θ゚ノ=(゚Д゚+ ゚ー゚)[o^_^o -(゚Θ゚)];(o゚ー゚o)=(゚ω゚ノ +'_')[c^_^o];(゚Д゚) [゚o゚]='\"';(゚Д゚) ['_'] ( (゚Д゚) ['_'] (゚ε゚+(゚Д゚)[゚o゚]+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ (o^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ ((゚ー゚) + (o^_^o))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ ((o^_^o) +(o^_^o))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((o^_^o) +(o^_^o))+ (o^_^o)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ ((゚ー゚) + (o^_^o))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ ((o^_^o) +(o^_^o))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ (゚ー゚)+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ ((゚ー゚) + (゚Θ゚))+ ((゚ー゚) + (o^_^o))+ (゚Д゚)[゚ε゚]+(゚Θ゚)+ (゚ー゚)+ ((゚ー゚) + (o^_^o))+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (c^_^o)+ (゚Д゚)[゚ε゚]+(゚ー゚)+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+(o゚ー゚o)+ ((o^_^o) +(o^_^o))+ ((o^_^o) - (゚Θ゚))+ (゚Θ゚)+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(o゚ー゚o)+ ((゚ー゚) + (o^_^o))+ ((o^_^o) - (゚Θ゚))+ (o^_^o)+ (゚Θ゚)+ (゚Д゚)[゚ε゚]+(o゚ー゚o)+ ((゚ー゚) + (゚Θ゚))+ (o^_^o)+ (゚Θ゚)+ ((゚ー゚) + (o^_^o))+ (゚Д゚)[゚ε゚]+(o゚ー゚o)+ (゚ー゚)+ (゚Д゚) .゚Д゚ノ+ (゚Д゚) .゚ω゚ノ+ (゚Д゚) ['c']+ (゚Д゚)[゚ε゚]+(o゚ー゚o)+ ((゚ー゚) + (゚Θ゚))+ ((゚ー゚) + (゚ー゚) + (゚Θ゚))+ ((o^_^o) - (゚Θ゚))+ ((゚ー゚) + (゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+(o゚ー゚o)+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚) .゚Θ゚ノ+ ((゚ー゚) + (゚ー゚))+ ((゚ー゚) + (゚ー゚) + (゚Θ゚))+ (゚Д゚)[゚ε゚]+(o゚ー゚o)+ ((゚ー゚) + (゚ー゚) + (゚Θ゚))+ ((゚ー゚) + (゚Θ゚))+ (゚Д゚) .゚Д゚ノ+ ((゚ー゚) + (゚ー゚))+ (゚Д゚)[゚ε゚]+(゚ー゚)+ ((o^_^o) - (゚Θ゚))+ (゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+ (゚Θ゚)+ (゚Д゚)[゚o゚]) (゚Θ゚)) ('_');

如果你把这串颜文字拿到浏览器控制台去跑,会在控制台打印“我爱北京天安门”。

是不是很神奇?

其实它们的实现非常简单,我们也可以基于同样的原理设计一套独属于自己的编码,就像这样:

=~[],=++,=++,=++,=++,=++,=++,=++,=++,=++,=++,大会=![]+"",开会=!![]+"",大汉={}+"",理会=[][]+"",大学=大会[],开学=大汉[],武汉=大汉[],开大=理会[],理工=开会[],武大=大会[],开源=理会[],技术=大汉[],协会=开会[],武理=大会[],武理工=开会[],理工大=理会[],开源技术=武汉+技术+开源+武理+武理工+协会+理工大+武汉+武理工+技术+协会,开源协会=协会+理工+武理工+理工大+协会+开源+" ",[][开源技术][开源技术]([][开源技术][开源技术](开源协会+"\""+"\\"++++"\\"++++"\\"++++"\\"++++"\\"++++"\\"++++"\\"++++"\\"+++"\\"++++"\\"++++"\\"++++"\\"+++"\\"+++"\\"+理工大++开学+++"\\"+理工大++武汉+++"\\"+理工大+++++"\\"+理工大++开大+理工++"\\"+理工大+++++"\\"+理工大++开学+++"\\"+理工大++武大+++"\\"+理工大++理工+++"\\"+理工大+++++"\\"+理工大++++武大+"\\"+理工大++++武大+"\\"+理工大++武大++大学+"\\"+++"\\"+++"\\"+++"\\"++++"\\"++++"\\"++++"\\"++++"\\"++++"\\"+++"\\"+++"\\"+理工大++++开大+"\\"+理工大++大学+理工+武大+"\\"+理工大++开学+武汉+武大+"\\"+理工大+++理工++"\\"+理工大++理工+++"\\"+理工大+++理工+理工+"\\"+++"\\"+++"\"")())();

或者这样:

=~[],_帅=++,帅_=++,$帅=++,帅$=++,__帅=++,_帅_=++,帅__=++,_$帅=++,_帅$=++,帅_$=++,$_帅=![]+"",$帅_=!![]+"",帅$_={}+"",$$帅=[][]+"",$帅$=$_帅[帅_],帅$$=帅$_[$帅],___帅=帅$_[_帅_],__帅_=$$帅[$帅],_帅__=$帅_[帅$],帅___=$_帅[_帅],__$帅=$$帅[帅_],__帅$=帅$_[帅_],_帅_$=$帅_[帅_],帅__$=$_帅[帅$],_$_帅=$帅_[_帅],_$帅_=$$帅[_帅],_帅$_=___帅+__帅$+__$帅+帅__$+_$_帅+_帅_$+_$帅_+___帅+_$_帅+__帅$+_帅_$,帅_$_=_帅_$+_帅__+_$_帅+_$帅_+_帅_$+__$帅+" ",[][_帅$_][_帅$_]([][_帅$_][_帅$_](帅_$_+"\""+"\\"+帅_+__帅+帅$+"\\"+帅_+_帅_+_$帅+"\\"+帅_+_帅_+帅__+"\\"+帅_+帅__+帅$+"\\"+帅_+_帅_+_$帅+"\\"+帅_+_帅_+__帅+"\\"+帅_+__帅+_帅_+"\\"+_帅_+帅__+"\\"+帅_+_帅_+__帅+"\\"+帅_+_帅_+_$帅+"\\"+帅_+__帅+_$帅+"\\"+_帅_+_帅+"\\"+__帅+_$帅+"\\"+_$帅_+__帅+帅___+帅__+_帅+"\\"+_$帅_+__帅+_帅__+_帅_+帅___+"\\"+_$帅_+_帅$+帅_$+___帅+帅_$+"\\"+_$帅_+_帅_+帅___+帅_$+_$帅+"\\"+_$帅_+帅__+$帅+帅_+帅_+"\\"+_$帅_+_帅_+帅___+_帅$+_帅$+"\\"+_$帅_+_帅_+_帅__+_帅+_帅_+"\\"+_$帅_+_帅_+__帅+$帅+_$帅+"\\"+_$帅_+帅___+帅___+帅_+帅___+"\\"+__帅+_$帅+"\\"+_帅_+帅_+"\\"+_$帅+帅$+"\\"+帅_+帅__+$帅+"\\"+帅_+__帅+_帅_+"\\"+帅_+帅__+__帅+"\\"+帅_+帅__+_帅_+"\\"+帅_+帅__+$帅+"\\"+帅_+_帅_+帅__+"\\"+__帅+_帅+"\\"+__帅+_$帅+"\\"+_$帅_+帅__+帅__+$帅+帅___+"\\"+_$帅_+_$帅+帅__+_帅$+__帅+"\\"+_$帅_+帅__+$帅+帅_+帅_+"\\"+_$帅_+_$帅+_帅$+帅__+_帅__+"\\"+_$帅_+_帅_+帅$$+帅_$+_帅__+"\\"+_$帅_+_帅_+帅___+_帅$+_帅$+"\\"+_$帅_+_帅_+_帅__+_帅+_帅_+"\\"+_$帅_+帅___+帅___+_帅+帅_+"\\"+__帅+_$帅+"\\"+_$帅+帅$+"\"")())();

原理解析

这类编码之所以可运行,其关键就在于借助 new Function()Function.constructor 之类的形式构造出可执行的函数。

例如:

a = Function.constructor('i', 'console.log(i+1)');
a(5);
new Function('console.log("武汉理工大学开源技术团队");')();

运行上面的代码,可以在控制台输出 6武汉理工大学开源技术团队

new Function('console.log("武汉理工大学开源技术团队");')(); 中,首先通过 new Function('console.log("武汉理工大学开源技术团队");') 创建了一个匿名函数,紧随着的一对圆括号则执行了这个函数。

因此,我们要让自己设计的混淆编码得以运行,基本思路就是设法取得 Function 的构造函数,然后通过传入的字符串来创建一个自定义函数。

那么要如何拿到 Function 的构造函数呢?很简单,通过语句 []['constructor']['constructor'] 我们就可以得到 Function()[] 是一个空数组,而数组的构造函数则是 Array(),既然 Array() 是一个函数,那么它的构造函数就是 Function() 了。同理,我们也可以通过 ""['constructor']['constructor'] 这样的方式来获取 Function()

有了 Function(),接下来就是设法构造字符串来表示代码了。

我们要做到支持所有 ASCII 字符和汉字,一个简单有效的方案就是使用八进制编码和 Unicode 编码。例如我们可以用 '\127\110\125\124' 来表示 'WHUT',以及用 '\u6280\u672f' 来表示 '技术'

因此,我们只需要获得可以拼接出 'constructor' 和所有 Unicode 编码的字符即可。也就是说,我们需要设法获取 0f 这些十六进制数字以及 nostu 这五个字母。

首先,我们想一想该如何方便地获取 09 这十个十进制数字。我们可以借助数据类型转换来获取 -1,然后通过 ++ 自增来得到十进制数字。获取 -1 可以使用 ~[] 或者 ~{}。于是有:

// -1=~[],
// 0=++// 1=++// 2=++// 3=++// 4=++// 5=++// 6=++// 7=++// 8=++// 9=++

那么我们要如何获取 abcdefnostu 这些字母呢?还是类型转换。

首先,我们可以通过 ![] 得到 false,然后通过 false + '' 得到字符串 'false'。在 'false' 这个字符串中通过下标我们就可以得到 aefs 这四个字母。

同理,我们可以通过构造字符串 'true' 来截取字母 tu

接着,我们可以通过 '' + {} 来获取字符串 '[object Object]',然后通过下标截取 o

最后,我们可以通过 [][1] 来获取 undefined,然后通过 '' + undefined 来得到字符串 'undefined',最终截取得到 n

有了这些字符,我们就可以通过拼接来得到任意代码字符串了。

上述基本字符串的获取:

// -1=~[],
// 0=++// 1=++// 2=++// 3=++// 4=++// 5=++// 6=++// 7=++// 8=++// 9=++,
// false
大会=![]+"",
// true
开会=!![]+"",
// [object Object]
大汉={}+"",
// undefined
理会=[][]+"",
// a
大学=大会[],
// b
开学=大汉[],
// c
武汉=大汉[],
// d
开大=理会[],
// e
理工=开会[],
// f
武大=大会[],
// n
开源=理会[],
// o
技术=大汉[],
// r
协会=开会[],
// s
武理=大会[],
// t
武理工=开会[],
// u
理工大=理会[],
// constructor
开源技术=武汉+技术+开源+武理+武理工+协会+理工大+武汉+武理工+技术+协会,
// return (后接空格)
开源协会=协会+理工+武理工+理工大+协会+开源+" ";

由于涉及到编码,所以我们需要多套一层 []['constructor']['constructor'] 来解码。例如,我们想用 '\162\145\164\165\162\156\40\42\u6280\u672f\42' 来表示 'return "技术"',编码后就是 开源协会+"\""+"\\"+汉+技+理+"\\"+汉+开+源+"\\"+汉+技+开+"\\"+汉+技+源+"\\"+汉+技+理+"\\"+汉+源+技+"\\"+开+武+"\\"+开+理+"\\"+理工大+技+理+协+武+"\\"+理工大+技+术+理+武大+"\\"+开+理+"\"",这里面反斜杠是存在转义的。

通过 Function(开源协会+"\""+"\\"+汉+技+理+"\\"+汉+开+源+"\\"+汉+技+开+"\\"+汉+技+源+"\\"+汉+技+理+"\\"+汉+源+技+"\\"+开+武+"\\"+开+理+"\\"+理工大+技+理+协+武+"\\"+理工大+技+术+理+武大+"\\"+开+理+"\"")() 执行一次,我们就可以把这串编码先转换成 'return "技术"',最后再通过外层的 Function('return "技术"')() 来执行代码。

解码这一步可能不太好理解,实际动手试一试就明白了。

动手实现

直接上代码:

// 设计字符映射关系
map = {
    "0": "武",
    "1": "汉",
    "2": "理",
    "3": "工",
    "4": "开",
    "5": "源",
    "6": "技",
    "7": "术",
    "8": "协",
    "9": "会",
    "a": "大学",
    "b": "开学",
    "c": "武汉",
    "d": "开大",
    "e": "理工",
    "f": "武大"
};

// 把ASCII字符转换成八进制编码,把其它字符转换成十六进制Unicode编码,并用map中的编码表示
function encode(text) {
    let result = "";
    for (let i = 0; i < text.length; i++) {
        n = text.charCodeAt(i);
        if (n < 128) {
            result += '+\"\\\\\"' + numToString(n.toString(8));
        } else {
            result += '+\"\\\\\"+理工大' + numToString(n.toString(16));
        }
    }
    return result;
}

// 把十六进制数字和八进制数字转换成map中的编码;numToString('ab') -> '+大学+开学'
function numToString(num) {
    let result = "";
    for (let i = 0; i < num.length; i++) {
        n = num.charAt(i);
        result += "+" + map[n];
    }
    return result;
}

// 拼接出结果
function printEncrypt(text) {
    console.log(
        // 这里实际上就是生成各种字符串变量
        "学=~[],武=++学,汉=++学,理=++学,工=++学,开=++学,源=++学,技=++学,术=++学,协=++学,会=++学,大会=![]+\"\",开会=!![]+\"\",大汉={}+\"\",理会=[][学]+\"\",大学=大会[汉],开学=大汉[理],武汉=大汉[源],开大=理会[理],理工=开会[工],武大=大会[武],开源=理会[汉],技术=大汉[汉],协会=开会[汉],武理=大会[工],武理工=开会[武],理工大=理会[武],开源技术=武汉+技术+开源+武理+武理工+协会+理工大+武汉+武理工+技术+协会,开源协会=协会+理工+武理工+理工大+协会+开源+\" \","
        // 这里是使用Function.constructor(CODE)()执行代码段CODE
        + "[][开源技术][开源技术]([][开源技术][开源技术](开源协会"
        + "+\"\\\"\""
        + encode(text)
        + "+\"\\\"\""
        + ")())()"
    );
}

yourJavaScriptCode = "console.log('武汉理工大学开源技术协会');return 'hello world';";
printEncrypt(yourJavaScriptCode);

上面的设计和实现还是比较粗糙的,可以参考扩展阅读中的文章作进一步的优化。

扩展阅读

微信公众号: 前端三元同学

获取更多资料/联系加交流群