CTF实战:JSFuck编码原理与解密工具全解析
1. 项目概述从一道CTF真题看JSFuck编码的实战解密最近复盘了LitCTF 2023的一道Web题目核心考点是JSFuck编码的识别与解密。这道题本身难度不算太高但非常典型它完美地展示了CTF比赛中“编码混淆”类题目的常见套路和解题思路。很多刚接触CTF的朋友尤其是Web方向的新手一看到满屏的括号、加号和叹号可能直接就懵了不知道从何下手。其实只要理解了JSFuck的基本原理并掌握几个高效的在线工具和手动分析方法这类题目就能迎刃而解甚至成为你的“送分题”。JSFuck是一种极端的JavaScript混淆技术它仅使用六个字符[、]、(、)、、!来编写任何有效的JavaScript代码。在CTF的Web题目中出题人经常用它来隐藏关键的验证逻辑、flag生成算法或者简单的提示信息。解题者的任务就是将这些看似天书的字符组合还原成可读、可理解的JavaScript代码从而找到突破口。本文将基于LitCTF 2023的真题手把手带你走完从“看到一团乱码”到“成功获取flag”的完整流程重点分享我常用的在线工具、手动解密的小技巧以及在实战中容易踩的坑。2. 核心原理JSFuck是如何“无中生有”的在动手解密之前我们有必要花点时间理解JSFuck的“魔法”是如何实现的。这不仅能帮你更好地使用工具当工具失效时你还能依靠原理进行手动分析。它的核心思想是利用JavaScript语言中类型转换和运算符的特性从“无”中构造出“有”。2.1 构造逻辑值与数字的基石false,true,0,1一切始于两个特殊的表达式![]的结果是false。因为空数组[]在布尔上下文中为true取反后得到false。!![]的结果是true。双重否定恢复了原始的布尔真值。有了false和true我们就可以构造数字了。在JavaScript中布尔值参与算术运算时会被转换为数字false转为0true转为1。!![]的结果是数字1。!![]是true前面的是一元加运算符将其强制转换为数字1。![]的结果是数字0。2.2 构造任意数字字符串连接与类型转换如何得到数字2、3甚至更大的数字呢这里用到了数组的运算符进行字符串连接以及parseInt函数的原理。 例如构造数字2我们有![]是false其字符串形式是false。![][]的结果是字符串false。这里的[]是将空数组转换为空字符串然后与false连接结果仍是false。字符串false可以通过下标访问字符(![][])[!![]]。我们来拆解(![][])是false。[!![]]是[1]表示取下标为1的字符。false[1]是字母a。同理我们可以构造出字符l,s,e最终拼出完整的字符串false和true。更重要的是我们可以拼出undefined、[object Object]等包含更多字符的字符串。有了这些字符就可以拼出像constructor这样的关键字符串。然后通过访问数字的toString方法即(数字)[toString]来得到不同进制的数字字符串表示。例如数字1的toString方法可以传入进制参数(1)[toString](2)得到二进制字符串1(1)[toString](36)可以得到36进制下的1而36进制包含了数字0-9和字母a-z。通过复杂的组合JSFuck最终可以用这六个字符表示出任意字符的Unicode码点进而通过String.fromCharCode函数构造出任意字符串最终拼接成完整的JavaScript代码。注意对于解题而言我们不需要自己从头构造这些。但理解这个原理至关重要。当你看到一段JSFuck代码时你能意识到它本质上是一段被“翻译”成特殊字符集的JavaScript而不是不可读的加密数据。这种思维转换是解题的第一步。2.3 JSFuck在CTF中的常见形态在题目中你通常会在以下几个地方遇到JSFuck网页源代码script标签内这是最常见的形式可能直接是一大段JSFuck代码或者作为某个变量的值。响应头或Cookie值有时出题人会将JSFuck代码放在HTTP响应头如X-Data或Cookie里需要你查看网络请求才能发现。经过其他编码如Base64二次处理为了增加一点难度出题人可能会先将JSFuck代码进行Base64编码你需要先解码Base64才能看到原始的JSFuck字符。动态生成通过AJAX请求获取或由一段正常的JavaScript代码动态生成并执行。LitCTF 2023的这道题就属于第一种情况非常直接。3. 实战解密流程与工具链理论清楚了我们进入实战环节。拿到一段JSFuck代码我一般的解密流程是在线工具优先尝试 - 浏览器控制台辅助验证 - 必要时手动分析关键片段。3.1 第一步识别与提取首先打开题目给出的网页按F12查看源代码。在script标签里你可能会看到类似下面这样的内容此为示例非原题script [][(![][])[[]]([![]][][[]])[![][[]]](![][])[![]![]](!![][])[[]](!![][])[![]![]![]](!![][])[![]]][([][(![][])[[]]([![]][][[]])[![][[]]](![][])[![]![]](!![][])[[]](!![][])[![]![]![]](!![][])[![]]][])[![]![]![]](!![][][(![][])[[]]([![]][][[]])[![][[]]](![][])[![]![]](!![][])[[]](!![][])[![]![]![]](!![][])[![]]])[![][[]]]([][[]][])[![]](![][])[![]![]![]](!![][])[[]](!![][])[![]]([][[]][])[[]]([][(![][])[[]]([![]][][[]])[![][[]]](![][])[![]![]](!![][])[[]](!![][])[![]![]![]](!![][])[![]]][])[![]![]![]](!![][])[[]](!![][][(![][])[[]]([![]][][[]])[![][[]]](![][])[![]![]](!![][])[[]](!![][])[![]![]![]](!![][])[![]]])[![][[]]](!![][])[![]]]((!![][])[![]](!![][])[![]![]![]](!![][])[[]]([][[]][])[[]](!![][])[![]]([][[]][])[![]]([![]][])[![][![][[]]]](!![][])[![]![]![]]((![]![]![][![]]))[(!![][])[[]](!![][][(![][])[[]]([![]][][[]])[![][[]]](![][])[![]![]](!![][])[[]](!![][])[![]![]![]](!![][])[![]]])[![][[]]]([([][(![][])[[]]([![]][][[]])[![][[]]](![][])[![]![]](!![][])[[]](!![][])[![]![]![]](!![][])[![]]][])[![]![]![]](!![][][(![][])[[]]([![]][][[]])[![][[]]](![][])[![]![]](!![][])[[]](!![][])[![]![]![]](!![][])[![]]])[![][[]]]([][[]][])[![]](![][])[![]![]![]](!![][])[[]](!![][])[![]]([][[]][])[[]]([][(![][])[[]]([![]][][[]])[![][[]]](![][])[![]![]](!![][])[[]](!![][])[![]![]![]](!![][])[![]]][])[![]![]![]](!![][])[[]](!![][][(![][])[[]]([![]][][[]])[![][[]]](![][])[![]![]](!![][])[[]](!![][])[![]![]![]](!![][])[![]]])[![][[]]](!![][])[![]]])(![][[]][![]])(![]![]![][![]![]])(![][])[![]](![][])[![]![]])()/script我们的任务就是把这一长串字符复制出来。一个关键的细节复制时务必检查开头和结尾确保没有遗漏字符。有时JSFuck代码会被分在多行或者夹杂在HTML注释中需要仔细查看。3.2 第二步使用在线解密工具对于99%的CTF题目在线工具都能直接搞定。这里推荐几个我常用的它们各有特点JSFuck Decoder (https://enkhee-osiris.github.io/Decoder-JSFuck/)优点界面简洁解密速度快支持大段代码。解密后可以直接在页面的模拟控制台里执行看到结果。用法将JSFuck代码粘贴到左侧输入框点击“Decode”按钮右侧就会显示解密后的JavaScript代码。实战技巧解密后不要只看代码一定要关注工具下方可能出现的“执行结果”。有时解密出的代码会直接输出flag或关键信息。CTF在线工具集如题目中提到的那个这类网站通常集成了JSFuck、JJEncode、AAEncode等多种编码的解密功能。优点一站式解决多种编码问题适合在比赛中快速切换。注意有些工具站广告较多需要仔细找到正确的输入框和按钮。浏览器扩展如 HackTools对于经常打CTF的同学可以在浏览器安装集成了编码解码工具的扩展。这样无需离开题目页面就能快速解密效率极高。以LitCTF 2023的题目为例将源代码中的JSFuck代码完整复制粘贴到上述任一工具的输入框点击解密。你通常会得到一段清晰得多的JavaScript代码。3.3 第三步分析与执行解密后的代码工具解密出的代码可能仍然不是最终答案。常见的情况有以下几种情况一代码直接包含flag。解密后的代码可能像这样console.log(flag{th1s_1s_an_3x4mpl3_fl4g});或者通过alert、document.write输出。这种情况下flag已经直接呈现在你面前。情况二代码是逻辑判断或计算。解密后的代码可能是一个函数用于验证用户输入或者计算出一个值。例如function check(input) { if (input some_secret_key) { return flag{...}; } else { return Wrong!; } }这时你需要分析这个逻辑找到正确的输入some_secret_key或者直接运行这个函数看看它返回什么。一个非常重要的技巧将解密后的代码复制到浏览器控制台F12 - Console中执行。如果它是一个自执行函数可能会直接输出结果如果是一个定义好的函数你可以手动调用它。情况三代码再次进行其他操作如网络请求。有时JSFuck代码解密后会执行一个fetch或XMLHttpRequest去另一个地址获取真正的flag。这时你需要按照解密代码的指示去访问那个URL或者查看网络请求记录。在LitCTF 2023的这道题中解密后的代码很可能是一个简单的字符串操作或变量赋值直接指向了flag。3.4 第四步当在线工具失效时的备选方案虽然很少见但偶尔你会遇到一些“非标准”或经过修改的JSFuck变种导致在线工具无法正确解密。这时不要慌可以尝试以下方法使用Node.js环境手动执行将JSFuck代码保存为一个.js文件比如challenge.js。在命令行中运行node challenge.js。原理JSFuck本身就是有效的JavaScriptNode.js引擎可以直接解析并执行它。如果代码设计为输出内容你会在终端看到结果。风险提示绝对不要随意执行来源不明的JS代码尤其是在本地有重要数据的机器上。CTF题目通常是在隔离的虚拟环境或赛方提供的平台上进行风险可控。在不确定的情况下最好使用在线的代码沙盒环境。浏览器控制台直接执行这是最直接的方法。在题目页面的控制台里直接粘贴整段JSFuck代码并回车。如果代码是立即执行的表达式你会立刻看到结果。如果代码只是定义了函数或变量你需要根据上下文去调用或查看它们。优点环境与题目完全一致避免了因环境差异导致的问题。分段分析与解密如果代码非常长可以尝试将其分成几段分别用在线工具解密然后拼凑起来理解逻辑。或者寻找代码中可能输出信息的部分比如(alert)、(console.log)等构造重点解密这些部分。4. 进阶技巧与深度排查指南掌握了基本流程你已经能解决大部分JSFuck题目。但要成为高手还需要一些进阶技巧来应对更复杂的情况。4.1 识别混合编码与多层嵌套出题人不会总是直接给你一段纯净的JSFuck。常见的“套娃”手法有JSFuck Base64你看到的可能是一段Base64字符串。解码后得到的才是JSFuck代码。在CTF中遇到一段看似随机但字符集符合Base64的字符串先尝试Base64解码是良好习惯。JSFuck 其他JS混淆如JJEncode, AAEncodeJJEncode使用$、_等字符AAEncode使用颜文字。有时会先进行其他混淆再转成JSFuck。这就需要你根据字符特征比如大量()、[]是JSFuck大量$是JJEncode来判断第一层是什么逐层剥离。JSFuck作为字符串被eval解密后得到的代码可能是eval(另一段JSFuck代码字符串)。这时你需要对里面的字符串再次进行JSFuck解密。排查流程观察字符集。如果全是[ ] ( ) !直奔JSFuck解密。如果结尾有或字符符合A-Za-z0-9/尝试Base64解码。如果解密后得到的结果仍然是不可读的代码观察其结构判断是否属于其他类型的混淆。4.2 利用浏览器开发者工具进行动态调试对于复杂的、交互式的题目静态解密可能不够。你需要动态跟踪代码执行。Sources面板设置断点在解密后的JS代码关键行如if判断、return语句左侧点击设置断点。刷新页面或触发相应操作代码执行会在断点处暂停。此时你可以查看当前所有变量的值单步执行F10步入函数F11这对理解代码逻辑有巨大帮助。Console面板交互在代码暂停时你可以在Console里直接输入变量名查看其值甚至修改变量值来改变执行流程这在某些“绕过”类题目中很有用。Network面板监控请求如果JS代码发送了网络请求获取flagNetwork面板会记录下所有的请求和响应。这里往往是flag的最终藏身之处。4.3 编写简易解密脚本应对特殊变种如果题目对JSFuck做了微小改动比如替换了某个字符导致通用工具失效你可以根据其原理自己写一个简单的替换脚本。例如如果题目把!换成了~你可以先用文本编辑器的替换功能把~全部换回!然后再用工具解密。更一般的情况你可以写一个Node.js脚本利用eval直接执行JSFuck代码并捕获输出// decrypt.js const fs require(fs); let jsfuckCode fs.readFileSync(challenge.txt, utf-8); try { // 注意在生产环境或不确定代码安全性时严禁这样做 let result eval(jsfuckCode); console.log(执行结果:, result); } catch (error) { console.error(执行出错:, error.message); // 有时代码不直接输出而是定义了全局变量 // 可以尝试检查全局对象 console.log(尝试查看全局变量...); }重要警告eval会执行任意代码极度危险仅限在完全可控、隔离的CTF比赛环境中使用切勿用于处理未知来源的代码。5. 常见问题与避坑实录在这一部分我总结了一些新手在解JSFuck题目时最容易遇到的问题以及我的解决方法。5.1 工具解密后是乱码或空白可能原因1复制不完整。JSFuck代码通常很长手动复制时容易漏掉开头或结尾的几个字符。务必使用鼠标全选或者查看网页源代码CtrlU直接复制整个script标签内的内容。可能原因2存在不可见字符。网页中可能夹杂着换行符、制表符或零宽空格影响解密。尝试将代码粘贴到纯文本编辑器如VS Code、Notepad中显示所有字符清理掉非[ ] ( ) !之外的任何字符。可能原因3代码被HTML实体编码。例如lt;代表。在网页源代码里JSFuck代码可能被这样编码以正常显示。你需要先对这段代码进行HTML解码得到原始的、等符号虽然JSFuck本身不用这些符号但包裹它的脚本标签可能需要。在线HTML解码工具很容易找到。解决方案先确保代码纯净再尝试不同的在线工具。如果都不行尝试在浏览器控制台直接执行看是否有错误信息。5.2 解密出的代码看不懂或无法执行可能原因解密出的代码可能被“压缩”或“混淆”过例如变量名都是a,b,c或者它只是一个复杂的表达式需要进一步计算。解决方案格式化代码使用在线的JavaScript代码美化工具如 Prettier.io让代码结构清晰。重命名变量如果变量名无意义尝试根据其用途赋予有意义的名称便于理解。分段执行在浏览器控制台里将长表达式拆分成几部分分别执行并查看中间结果。例如如果解密结果是(ab)(c)你可以先执行console.log(a)console.log(b)看看a和b到底是什么。搜索关键函数在解密后的代码中搜索alert、prompt、console.log、document.write、fetch、location等关键字它们通常是输出或跳转的关键。5.3 执行后没有反应或报错可能原因1代码依赖浏览器特定环境。有些JSFuck代码可能调用了document、window等对象在Node.js环境下执行就会报错。务必在浏览器控制台执行。可能原因2代码需要特定触发条件。解密出的代码可能是一个函数需要特定的事件如点击按钮、输入内容才会被调用。你需要查看网页的HTML结构找到绑定事件的元素或者手动在控制台调用这个函数。可能原因3代码存在语法错误。极少数情况下出题人可能故意设置了一个小陷阱。仔细检查解密后的代码或者用浏览器的“Sources”面板看看是否有语法错误提示。解决方案始终优先在题目本身的浏览器环境中执行代码。如果是一个函数尝试找出它的名字并直接调用例如window.onload()或checkFlag(“test”)。5.4 如何提高解题速度建立工具书签将几个可靠的JSFuck解密、Base64解码、代码美化网站加入浏览器书签栏一键直达。训练识别能力看到[][(![][])...这种模式立刻反应是JSFuck而不是去思考它的逻辑。养成检查习惯拿到Web题F12看源代码是标准动作。不仅看script还要看body内的内联脚本、注释以及Network中的JS文件。利用浏览器控制台作为主战场解密、执行、调试、查看变量全部在控制台完成效率最高。回过头看LitCTF 2023这道题它更像是一个标准的“热身题”目的是让选手熟悉JSFuck这种形式。通过这道题我们巩固了从识别、提取到使用工具解密、分析执行结果的全过程。在更复杂的比赛中JSFuck可能只是整个链条中的一环后面或许接着逆向分析、密码学解密或SQL注入。但无论如何扎实地掌握这种基础编码的解密方法是你在CTF赛场上稳步前进的可靠基石。下次再看到满屏的括号和叹号你大可以自信地打开收藏夹里的工具开始你的“解码”表演了。