通过前两章我们已经了解了jsfuck的基本原理与实现,现在不妨先设想一下,假设我们要自己实现这么一个加密代码,应该如何去做。
首先,我们拿到了一段明文代码“alert(1)”,为了把他变成jsf的模式,我们要按照第二章的描述对各个字符挨个加密,最后拼接成想要的代码。那么如果我们想把这个过程工程化,我们就需要一个map,里面有每个字符对应的jsf代码,这样我们加密一串代码只需要拼接就行了,事实上jsf的原作者也是这么做的。
打开github:https://github.com/aemkei/jsfuck,根目录下有一个叫做jsfuck.js的文件,里面部分代码如下:
const SIMPLE = {
'false': '![]',
'true': '!![]',
'undefined': '[][[]]',
'NaN': '+[![]]',
'Infinity': '+(+!+[]+(!+[]+[])[!+[]+!+[]+!+[]]+[+!+[]]+[+[]]+[+[]]+[+[]])' // +"1e1000"
};
const CONSTRUCTORS = {
'Array': '[]',
'Number': '(+[])',
'String': '([]+[])',
'Boolean': '(![])',
'Function': '[]["fill"]',
'RegExp': 'Function("return/"+false+"/")()',
'Object': '[]["entries"]()'
};
const MAPPING = {
'a': '(false+"")[1]',
'b': '([]["entries"]()+"")[2]',
'c': '([]["fill"]+"")[3]',
'd': '(undefined+"")[2]',
'e': '(true+"")[3]',
'f': '(false+"")[0]',
'g': '(false+[0]+String)[20]',
'h': '(+(101))["to"+String["name"]](21)[1]',
……
……
在这里我们可以看到三个常量,其中的MAPPING确实如我们所想的一样,是一个字符与代码的对应,不过对应的值看起来并不是jsf的代码,并且有些甚至是缺失的,比如:'P': USE_CHAR_CODE。这些字符我们不能如a、b、c那样轻易的在现有的字符串中找到,需要特殊的处理。
那么jsf的源码到底是怎么工作的呢,接下来我们就来整体分析一下jsfuck.js文件的代码。
我们可以把代码折叠一下,得到下图:
由上图可看出,代码分为三个部分,常量、方法、执行。
MAPPING补全:
我们直接来看第三部分,方法的执行,第一个方法fillMissingDigits,字面意思可以看出是填补缺失的数字,代码如下:
function fillMissingDigits(){
var output, number, i;
for (number = 0; number < 10; number++){
output = "+[]";
if (number > 0){ output = "+!" + output; }
for (i = 1; i < number; i++){ output = "+!+[]" + output; }
if (number > 1){ output = output.substr(1); }
MAPPING[number] = "[" + output + "]";
}
}
这段代码很简单,从中可以看出,这个方法的功能是填补MAPPING中数字(0 - 9)的键值对,如:0: '[+[]