深入解析JSFuck:从字符魔术到JavaScript本质
当你第一次看到(+[![]]+[])[+[]]这样的代码时,可能会以为这是某种加密算法或是乱码。但实际上,这是一段完全合法的JavaScript代码——这就是JSFuck的神奇之处。作为JavaScript语言特性的一种极端表现形式,JSFuck向我们展示了这门语言的灵活性和类型转换机制的强大能力。
1. JSFuck的魔法基础:六个字符构建JavaScript世界
JSFuck的核心在于仅使用[ ] ( ) ! +这六个字符来表达完整的JavaScript语法。这种看似不可能的任务,实际上是通过巧妙利用JavaScript的类型转换和运算符优先级实现的。
1.1 基本构建块:从空值到数字和布尔值
让我们从最简单的构建块开始:
+[] // 结果为0 ![] // 结果为false +![] // false转换为数字0,然后取正,还是0 !![] // 结果为true +!![] // true转换为数字1通过这些基本操作,我们已经能够构造出数字0和1。接下来,我们可以通过加法来构造更大的数字:
+!![] + +!![] // 1 + 1 = 21.2 字符串的构造艺术
要构造字符串,我们需要利用JavaScript的类型转换特性。当数组与字符串相加时,JavaScript会自动调用toString()方法:
[] + [] // 结果为""(空字符串) [] + ![] // 结果为"false"通过这种方式,我们可以逐步构造出所需的字符。例如,要得到字母"a":
(![] + [])[+!![]] // "false"[1] → "a"1.3 关键函数和方法的获取
要执行任意代码,我们需要获取关键函数如constructor和eval。这可以通过字符串拼接和属性访问实现:
[][[]] // 结果为undefined ([] + [][[]]) // "undefined" ([] + [][[]])[+[]] // "u"通过这种逐步构建的方式,最终我们可以拼出完整的函数名并执行它们。
2. JSFuck编码原理深度解析
理解JSFuck的编码原理,实际上是在深入理解JavaScript的语言特性。下面我们通过一个完整的例子来解析其工作原理。
2.1 从简单表达式到复杂功能
考虑这段代码如何构造数字10:
+((+(+!+[]+[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+[!+[]+!+[]]+[+[]])+[])[+!+[]]+[+[]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+[]]+[+!+[]]])分解步骤:
+!+[]→ 0[+!+[]]→ [0](!![]+[])[!+[]+!+[]+!+[]]→ "true"[3] → "e"- 拼接后得到"0e0"
- 转换为字符串后取特定字符
- 最终组合出数字10
2.2 字符编码表与构造方法
下表展示了常见字符的构造方式:
| 字符 | 构造方法 | 原理说明 |
|---|---|---|
| "a" | (![] + [])[+!![]] | "false"[1] |
| "b" | ({} + [])[+!![] + !![]] | "[object Object]"[2] |
| "c" | ({} + [])[+!![] + !![] + !![] + !![] + !![]] | "[object Object]"[5] |
| "d" | ([][[]] + [])[+!![] + !![]] | "undefined"[2] |
| "e" | (!![] + [])[!+[] + !+[] + !+[]] | "true"[3] |
2.3 函数调用链的实现
要实现函数调用,JSFuck需要构造出函数名并通过适当的方式调用。例如执行alert(1):
[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+[![]]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(![]+[])[+!+[]]+(+(!+[]+!+[]+[+!+[]]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(![]+[])[!+[]+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[+!+[]])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]])()这段代码实际上构造了"alert"字符串并通过函数调用方式执行。
3. 手工解密JSFuck的方法与技巧
虽然在线工具可以快速解码JSFuck,但手工解密能帮助我们更深入理解JavaScript语言特性。下面介绍一种系统化的解密方法。
3.1 分步解析法
- 识别基本模式:寻找
+[]、![]等基本模式 - 逐步求值:从内到外逐步计算表达式
- 字符串拼接:注意
+运算符在字符串上下文中的作用 - 属性访问:识别类似
(...)[...]的结构 - 函数调用:识别最后的
()执行操作
3.2 实用解密工具包
虽然手工解密很有教育意义,但实际工作中我们可以使用一些工具辅助:
// 简单的JSFuck解释器框架 function debugJSFuck(code) { try { const steps = []; const proxy = new Proxy(window, { get(target, prop) { steps.push(`Getting ${String(prop)}`); return Reflect.get(...arguments); } }); with(proxy) { eval(code); } return steps; } catch (e) { return `Error: ${e.message}`; } }注意:在实际解密过程中,建议在安全隔离的环境中进行,避免执行恶意代码。
3.3 解密实战:解析一个简单JSFuck表达式
让我们解析这个简单的例子:(+[![]]+[])[+[]]
- 最内层表达式:
![]→ false [![]]→ [false]+[false]→ 将数组转换为数字 → 0+[]→ 空数组转换为数字 → 0(+[![]]+[])→ (0 + "") → "0""0"[0]→ "0"
4. JSFuck与其他混淆技术的对比
JSFuck只是JavaScript混淆技术中的一种,了解它与其它技术的区别有助于我们更好地应对各种混淆场景。
4.1 Jother:JSFuck的近亲
Jother与JSFuck非常相似,但使用了不同的字符集:
| 特性 | JSFuck | Jother |
|---|---|---|
| 使用字符 | [ ] ( ) ! + | { } [ ] ( ) ! + |
| 编码效率 | 较低 | 稍高 |
| 可读性 | 极低 | 极低 |
| 典型用途 | 代码混淆、CTF挑战 | 代码混淆、恶意代码 |
4.2 常见混淆技术对比
下表对比了几种常见的JavaScript混淆技术:
| 技术类型 | 原理 | 可逆性 | 识别特征 | 典型用途 |
|---|---|---|---|---|
| JSFuck/Jother | 极简字符集 | 高 | 大量[]() | CTF、学术研究 |
| Packer | 代码压缩与eval | 中 | eval(function(p,a,c,k,e,r) | 代码保护 |
| Obfuscator | 标识符重命名 | 低 | 短变量名、十六进制 | 商业代码保护 |
| 编码混淆 | Base64/十六进制编码 | 高 | 长字符串+eval | 简单代码隐藏 |
4.3 为什么选择手工解密?
虽然自动化工具方便,但手工解密有以下优势:
- 深入理解JavaScript语言特性
- 能够处理变种或自定义混淆方案
- 在无法使用在线工具的环境中工作
- 提高调试和逆向工程能力
在实际CTF比赛或安全研究中,这种底层理解往往能帮助你发现自动化工具无法识别的问题模式。