如何实现自己的 JS 混淆编码?
作者: whut开源后端组梁思远
简介
你是否见过过 JSFuck、JJEncode、AAEncode这类混淆编码?它们既可以用于前端代码混淆,也可以用于 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 编码的字符即可。也就是说,我们需要设法获取 0
至 f
这些十六进制数字以及 n
、o
、s
、t
、u
这五个字母。
首先,我们想一想该如何方便地获取 0
到 9
这十个十进制数字。我们可以借助数据类型转换来获取 -1
,然后通过 ++
自增来得到十进制数字。获取 -1
可以使用 ~[]
或者 ~{}
。于是有:
// -1
学=~[],
// 0
武=++学
// 1
汉=++学
// 2
理=++学
// 3
工=++学
// 4
开=++学
// 5
源=++学
// 6
技=++学
// 7
术=++学
// 8
协=++学
// 9
会=++学
那么我们要如何获取 abcdefnostu
这些字母呢?还是类型转换。
首先,我们可以通过 ![]
得到 false
,然后通过 false + ''
得到字符串 'false'
。在 'false'
这个字符串中通过下标我们就可以得到 a
、e
、f
、s
这四个字母。
同理,我们可以通过构造字符串 'true'
来截取字母 t
、u
。
接着,我们可以通过 '' + {}
来获取字符串 '[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);
上面的设计和实现还是比较粗糙的,可以参考扩展阅读中的文章作进一步的优化。