:JSFuck編碼原理與解密工具全解析)
1. 項目概述從一道CTF真題看JSFuck編碼的實戰(zhàn)解密最近復(fù)盤了LitCTF 2023的一道Web題目核心考點是JSFuck編碼的識別與解密。這道題本身難度不算太高但非常典型它完美地展示了CTF比賽中“編碼混淆”類題目的常見套路和解題思路。很多剛接觸CTF的朋友尤其是Web方向的新手一看到滿屏的括號、加號和嘆號可能直接就懵了不知道從何下手。其實只要理解了JSFuck的基本原理并掌握幾個高效的在線工具和手動分析方法這類題目就能迎刃而解甚至成為你的“送分題”。JSFuck是一種極端的JavaScript混淆技術(shù)它僅使用六個字符[、]、(、)、、!來編寫任何有效的JavaScript代碼。在CTF的Web題目中出題人經(jīng)常用它來隱藏關(guān)鍵的驗證邏輯、flag生成算法或者簡單的提示信息。解題者的任務(wù)就是將這些看似天書的字符組合還原成可讀、可理解的JavaScript代碼從而找到突破口。本文將基于LitCTF 2023的真題手把手帶你走完從“看到一團亂碼”到“成功獲取flag”的完整流程重點分享我常用的在線工具、手動解密的小技巧以及在實戰(zhàn)中容易踩的坑。2. 核心原理JSFuck是如何“無中生有”的在動手解密之前我們有必要花點時間理解JSFuck的“魔法”是如何實現(xiàn)的。這不僅能幫你更好地使用工具當工具失效時你還能依靠原理進行手動分析。它的核心思想是利用JavaScript語言中類型轉(zhuǎn)換和運算符的特性從“無”中構(gòu)造出“有”。2.1 構(gòu)造邏輯值與數(shù)字的基石false,true,0,1一切始于兩個特殊的表達式![]的結(jié)果是false。因為空數(shù)組[]在布爾上下文中為true取反后得到false。!![]的結(jié)果是true。雙重否定恢復(fù)了原始的布爾真值。有了false和true我們就可以構(gòu)造數(shù)字了。在JavaScript中布爾值參與算術(shù)運算時會被轉(zhuǎn)換為數(shù)字false轉(zhuǎn)為0true轉(zhuǎn)為1。!![]的結(jié)果是數(shù)字1。!![]是true前面的是一元加運算符將其強制轉(zhuǎn)換為數(shù)字1。![]的結(jié)果是數(shù)字0。2.2 構(gòu)造任意數(shù)字字符串連接與類型轉(zhuǎn)換如何得到數(shù)字2、3甚至更大的數(shù)字呢這里用到了數(shù)組的運算符進行字符串連接以及parseInt函數(shù)的原理。 例如構(gòu)造數(shù)字2我們有![]是false其字符串形式是false。![][]的結(jié)果是字符串false。這里的[]是將空數(shù)組轉(zhuǎn)換為空字符串然后與false連接結(jié)果仍是false。字符串false可以通過下標訪問字符(![][])[!![]]。我們來拆解(![][])是false。[!![]]是[1]表示取下標為1的字符。false[1]是字母a。同理我們可以構(gòu)造出字符l,s,e最終拼出完整的字符串false和true。更重要的是我們可以拼出undefined、[object Object]等包含更多字符的字符串。有了這些字符就可以拼出像constructor這樣的關(guān)鍵字符串。然后通過訪問數(shù)字的toString方法即(數(shù)字)[toString]來得到不同進制的數(shù)字字符串表示。例如數(shù)字1的toString方法可以傳入進制參數(shù)(1)[toString](2)得到二進制字符串1(1)[toString](36)可以得到36進制下的1而36進制包含了數(shù)字0-9和字母a-z。通過復(fù)雜的組合JSFuck最終可以用這六個字符表示出任意字符的Unicode碼點進而通過String.fromCharCode函數(shù)構(gòu)造出任意字符串最終拼接成完整的JavaScript代碼。注意對于解題而言我們不需要自己從頭構(gòu)造這些。但理解這個原理至關(guān)重要。當你看到一段JSFuck代碼時你能意識到它本質(zhì)上是一段被“翻譯”成特殊字符集的JavaScript而不是不可讀的加密數(shù)據(jù)。這種思維轉(zhuǎn)換是解題的第一步。2.3 JSFuck在CTF中的常見形態(tài)在題目中你通常會在以下幾個地方遇到JSFuck網(wǎng)頁源代碼script標簽內(nèi)這是最常見的形式可能直接是一大段JSFuck代碼或者作為某個變量的值。響應(yīng)頭或Cookie值有時出題人會將JSFuck代碼放在HTTP響應(yīng)頭如X-Data或Cookie里需要你查看網(wǎng)絡(luò)請求才能發(fā)現(xiàn)。經(jīng)過其他編碼如Base64二次處理為了增加一點難度出題人可能會先將JSFuck代碼進行Base64編碼你需要先解碼Base64才能看到原始的JSFuck字符。動態(tài)生成通過AJAX請求獲取或由一段正常的JavaScript代碼動態(tài)生成并執(zhí)行。LitCTF 2023的這道題就屬于第一種情況非常直接。3. 實戰(zhàn)解密流程與工具鏈理論清楚了我們進入實戰(zhàn)環(huán)節(jié)。拿到一段JSFuck代碼我一般的解密流程是在線工具優(yōu)先嘗試 - 瀏覽器控制臺輔助驗證 - 必要時手動分析關(guān)鍵片段。3.1 第一步識別與提取首先打開題目給出的網(wǎng)頁按F12查看源代碼。在script標簽里你可能會看到類似下面這樣的內(nèi)容此為示例非原題script [][(![][])[[]]([![]][][[]])[![][[]]](![][])[![]![]](!![][])[[]](!![][])[![]![]![]](!![][])[![]]][([][(![][])[[]]([![]][][[]])[![][[]]](![][])[![]![]](!![][])[[]](!![][])[![]![]![]](!![][])[![]]][])[![]![]![]](!![][][(![][])[[]]([![]][][[]])[![][[]]](![][])[![]![]](!![][])[[]](!![][])[![]![]![]](!![][])[![]]])[![][[]]]([][[]][])[![]](![][])[![]![]![]](!![][])[[]](!![][])[![]]([][[]][])[[]]([][(![][])[[]]([![]][][[]])[![][[]]](![][])[![]![]](!![][])[[]](!![][])[![]![]![]](!![][])[![]]][])[![]![]![]](!![][])[[]](!![][][(![][])[[]]([![]][][[]])[![][[]]](![][])[![]![]](!![][])[[]](!![][])[![]![]![]](!![][])[![]]])[![][[]]](!![][])[![]]]((!![][])[![]](!![][])[![]![]![]](!![][])[[]]([][[]][])[[]](!![][])[![]]([][[]][])[![]]([![]][])[![][![][[]]]](!![][])[![]![]![]]((![]![]![][![]]))[(!![][])[[]](!![][][(![][])[[]]([![]][][[]])[![][[]]](![][])[![]![]](!![][])[[]](!![][])[![]![]![]](!![][])[![]]])[![][[]]]([([][(![][])[[]]([![]][][[]])[![][[]]](![][])[![]![]](!![][])[[]](!![][])[![]![]![]](!![][])[![]]][])[![]![]![]](!![][][(![][])[[]]([![]][][[]])[![][[]]](![][])[![]![]](!![][])[[]](!![][])[![]![]![]](!![][])[![]]])[![][[]]]([][[]][])[![]](![][])[![]![]![]](!![][])[[]](!![][])[![]]([][[]][])[[]]([][(![][])[[]]([![]][][[]])[![][[]]](![][])[![]![]](!![][])[[]](!![][])[![]![]![]](!![][])[![]]][])[![]![]![]](!![][])[[]](!![][][(![][])[[]]([![]][][[]])[![][[]]](![][])[![]![]](!![][])[[]](!![][])[![]![]![]](!![][])[![]]])[![][[]]](!![][])[![]]])(![][[]][![]])(![]![]![][![]![]])(![][])[![]](![][])[![]![]])()/script我們的任務(wù)就是把這一長串字符復(fù)制出來。一個關(guān)鍵的細節(jié)復(fù)制時務(wù)必檢查開頭和結(jié)尾確保沒有遺漏字符。有時JSFuck代碼會被分在多行或者夾雜在HTML注釋中需要仔細查看。3.2 第二步使用在線解密工具對于99%的CTF題目在線工具都能直接搞定。這里推薦幾個我常用的它們各有特點JSFuck Decoder (https://enkhee-osiris.github.io/Decoder-JSFuck/)優(yōu)點界面簡潔解密速度快支持大段代碼。解密后可以直接在頁面的模擬控制臺里執(zhí)行看到結(jié)果。用法將JSFuck代碼粘貼到左側(cè)輸入框點擊“Decode”按鈕右側(cè)就會顯示解密后的JavaScript代碼。實戰(zhàn)技巧解密后不要只看代碼一定要關(guān)注工具下方可能出現(xiàn)的“執(zhí)行結(jié)果”。有時解密出的代碼會直接輸出flag或關(guān)鍵信息。CTF在線工具集如題目中提到的那個這類網(wǎng)站通常集成了JSFuck、JJEncode、AAEncode等多種編碼的解密功能。優(yōu)點一站式解決多種編碼問題適合在比賽中快速切換。注意有些工具站廣告較多需要仔細找到正確的輸入框和按鈕。瀏覽器擴展如 HackTools對于經(jīng)常打CTF的同學可以在瀏覽器安裝集成了編碼解碼工具的擴展。這樣無需離開題目頁面就能快速解密效率極高。以LitCTF 2023的題目為例將源代碼中的JSFuck代碼完整復(fù)制粘貼到上述任一工具的輸入框點擊解密。你通常會得到一段清晰得多的JavaScript代碼。3.3 第三步分析與執(zhí)行解密后的代碼工具解密出的代碼可能仍然不是最終答案。常見的情況有以下幾種情況一代碼直接包含flag。解密后的代碼可能像這樣console.log(flag{th1s_1s_an_3x4mpl3_fl4g});或者通過alert、document.write輸出。這種情況下flag已經(jīng)直接呈現(xiàn)在你面前。情況二代碼是邏輯判斷或計算。解密后的代碼可能是一個函數(shù)用于驗證用戶輸入或者計算出一個值。例如function check(input) { if (input some_secret_key) { return flag{...}; } else { return Wrong!; } }這時你需要分析這個邏輯找到正確的輸入some_secret_key或者直接運行這個函數(shù)看看它返回什么。一個非常重要的技巧將解密后的代碼復(fù)制到瀏覽器控制臺F12 - Console中執(zhí)行。如果它是一個自執(zhí)行函數(shù)可能會直接輸出結(jié)果如果是一個定義好的函數(shù)你可以手動調(diào)用它。情況三代碼再次進行其他操作如網(wǎng)絡(luò)請求。有時JSFuck代碼解密后會執(zhí)行一個fetch或XMLHttpRequest去另一個地址獲取真正的flag。這時你需要按照解密代碼的指示去訪問那個URL或者查看網(wǎng)絡(luò)請求記錄。在LitCTF 2023的這道題中解密后的代碼很可能是一個簡單的字符串操作或變量賦值直接指向了flag。3.4 第四步當在線工具失效時的備選方案雖然很少見但偶爾你會遇到一些“非標準”或經(jīng)過修改的JSFuck變種導(dǎo)致在線工具無法正確解密。這時不要慌可以嘗試以下方法使用Node.js環(huán)境手動執(zhí)行將JSFuck代碼保存為一個.js文件比如challenge.js。在命令行中運行node challenge.js。原理JSFuck本身就是有效的JavaScriptNode.js引擎可以直接解析并執(zhí)行它。如果代碼設(shè)計為輸出內(nèi)容你會在終端看到結(jié)果。風險提示絕對不要隨意執(zhí)行來源不明的JS代碼尤其是在本地有重要數(shù)據(jù)的機器上。CTF題目通常是在隔離的虛擬環(huán)境或賽方提供的平臺上進行風險可控。在不確定的情況下最好使用在線的代碼沙盒環(huán)境。瀏覽器控制臺直接執(zhí)行這是最直接的方法。在題目頁面的控制臺里直接粘貼整段JSFuck代碼并回車。如果代碼是立即執(zhí)行的表達式你會立刻看到結(jié)果。如果代碼只是定義了函數(shù)或變量你需要根據(jù)上下文去調(diào)用或查看它們。優(yōu)點環(huán)境與題目完全一致避免了因環(huán)境差異導(dǎo)致的問題。分段分析與解密如果代碼非常長可以嘗試將其分成幾段分別用在線工具解密然后拼湊起來理解邏輯。或者尋找代碼中可能輸出信息的部分比如(alert)、(console.log)等構(gòu)造重點解密這些部分。4. 進階技巧與深度排查指南掌握了基本流程你已經(jīng)能解決大部分JSFuck題目。但要成為高手還需要一些進階技巧來應(yīng)對更復(fù)雜的情況。4.1 識別混合編碼與多層嵌套出題人不會總是直接給你一段純凈的JSFuck。常見的“套娃”手法有JSFuck Base64你看到的可能是一段Base64字符串。解碼后得到的才是JSFuck代碼。在CTF中遇到一段看似隨機但字符集符合Base64的字符串先嘗試Base64解碼是良好習慣。JSFuck 其他JS混淆如JJEncode, AAEncodeJJEncode使用$、_等字符AAEncode使用顏文字。有時會先進行其他混淆再轉(zhuǎn)成JSFuck。這就需要你根據(jù)字符特征比如大量()、[]是JSFuck大量$是JJEncode來判斷第一層是什么逐層剝離。JSFuck作為字符串被eval解密后得到的代碼可能是eval(另一段JSFuck代碼字符串)。這時你需要對里面的字符串再次進行JSFuck解密。排查流程觀察字符集。如果全是[ ] ( ) !直奔JSFuck解密。如果結(jié)尾有或字符符合A-Za-z0-9/嘗試Base64解碼。如果解密后得到的結(jié)果仍然是不可讀的代碼觀察其結(jié)構(gòu)判斷是否屬于其他類型的混淆。4.2 利用瀏覽器開發(fā)者工具進行動態(tài)調(diào)試對于復(fù)雜的、交互式的題目靜態(tài)解密可能不夠。你需要動態(tài)跟蹤代碼執(zhí)行。Sources面板設(shè)置斷點在解密后的JS代碼關(guān)鍵行如if判斷、return語句左側(cè)點擊設(shè)置斷點。刷新頁面或觸發(fā)相應(yīng)操作代碼執(zhí)行會在斷點處暫停。此時你可以查看當前所有變量的值單步執(zhí)行F10步入函數(shù)F11這對理解代碼邏輯有巨大幫助。Console面板交互在代碼暫停時你可以在Console里直接輸入變量名查看其值甚至修改變量值來改變執(zhí)行流程這在某些“繞過”類題目中很有用。Network面板監(jiān)控請求如果JS代碼發(fā)送了網(wǎng)絡(luò)請求獲取flagNetwork面板會記錄下所有的請求和響應(yīng)。這里往往是flag的最終藏身之處。4.3 編寫簡易解密腳本應(yīng)對特殊變種如果題目對JSFuck做了微小改動比如替換了某個字符導(dǎo)致通用工具失效你可以根據(jù)其原理自己寫一個簡單的替換腳本。例如如果題目把!換成了~你可以先用文本編輯器的替換功能把~全部換回!然后再用工具解密。更一般的情況你可以寫一個Node.js腳本利用eval直接執(zhí)行JSFuck代碼并捕獲輸出// decrypt.js const fs require(fs); let jsfuckCode fs.readFileSync(challenge.txt, utf-8); try { // 注意在生產(chǎn)環(huán)境或不確定代碼安全性時嚴禁這樣做 let result eval(jsfuckCode); console.log(執(zhí)行結(jié)果:, result); } catch (error) { console.error(執(zhí)行出錯:, error.message); // 有時代碼不直接輸出而是定義了全局變量 // 可以嘗試檢查全局對象 console.log(嘗試查看全局變量...); }重要警告eval會執(zhí)行任意代碼極度危險僅限在完全可控、隔離的CTF比賽環(huán)境中使用切勿用于處理未知來源的代碼。5. 常見問題與避坑實錄在這一部分我總結(jié)了一些新手在解JSFuck題目時最容易遇到的問題以及我的解決方法。5.1 工具解密后是亂碼或空白可能原因1復(fù)制不完整。JSFuck代碼通常很長手動復(fù)制時容易漏掉開頭或結(jié)尾的幾個字符。務(wù)必使用鼠標全選或者查看網(wǎng)頁源代碼CtrlU直接復(fù)制整個script標簽內(nèi)的內(nèi)容。可能原因2存在不可見字符。網(wǎng)頁中可能夾雜著換行符、制表符或零寬空格影響解密。嘗試將代碼粘貼到純文本編輯器如VS Code、Notepad中顯示所有字符清理掉非[ ] ( ) !之外的任何字符。可能原因3代碼被HTML實體編碼。例如lt;代表。在網(wǎng)頁源代碼里JSFuck代碼可能被這樣編碼以正常顯示。你需要先對這段代碼進行HTML解碼得到原始的、等符號雖然JSFuck本身不用這些符號但包裹它的腳本標簽可能需要。在線HTML解碼工具很容易找到。解決方案先確保代碼純凈再嘗試不同的在線工具。如果都不行嘗試在瀏覽器控制臺直接執(zhí)行看是否有錯誤信息。5.2 解密出的代碼看不懂或無法執(zhí)行可能原因解密出的代碼可能被“壓縮”或“混淆”過例如變量名都是a,b,c或者它只是一個復(fù)雜的表達式需要進一步計算。解決方案格式化代碼使用在線的JavaScript代碼美化工具如 Prettier.io讓代碼結(jié)構(gòu)清晰。重命名變量如果變量名無意義嘗試根據(jù)其用途賦予有意義的名稱便于理解。分段執(zhí)行在瀏覽器控制臺里將長表達式拆分成幾部分分別執(zhí)行并查看中間結(jié)果。例如如果解密結(jié)果是(ab)(c)你可以先執(zhí)行console.log(a)console.log(b)看看a和b到底是什么。搜索關(guān)鍵函數(shù)在解密后的代碼中搜索alert、prompt、console.log、document.write、fetch、location等關(guān)鍵字它們通常是輸出或跳轉(zhuǎn)的關(guān)鍵。5.3 執(zhí)行后沒有反應(yīng)或報錯可能原因1代碼依賴瀏覽器特定環(huán)境。有些JSFuck代碼可能調(diào)用了document、window等對象在Node.js環(huán)境下執(zhí)行就會報錯。務(wù)必在瀏覽器控制臺執(zhí)行。可能原因2代碼需要特定觸發(fā)條件。解密出的代碼可能是一個函數(shù)需要特定的事件如點擊按鈕、輸入內(nèi)容才會被調(diào)用。你需要查看網(wǎng)頁的HTML結(jié)構(gòu)找到綁定事件的元素或者手動在控制臺調(diào)用這個函數(shù)。可能原因3代碼存在語法錯誤。極少數(shù)情況下出題人可能故意設(shè)置了一個小陷阱。仔細檢查解密后的代碼或者用瀏覽器的“Sources”面板看看是否有語法錯誤提示。解決方案始終優(yōu)先在題目本身的瀏覽器環(huán)境中執(zhí)行代碼。如果是一個函數(shù)嘗試找出它的名字并直接調(diào)用例如window.onload()或checkFlag(“test”)。5.4 如何提高解題速度建立工具書簽將幾個可靠的JSFuck解密、Base64解碼、代碼美化網(wǎng)站加入瀏覽器書簽欄一鍵直達。訓練識別能力看到[][(![][])...這種模式立刻反應(yīng)是JSFuck而不是去思考它的邏輯。養(yǎng)成檢查習慣拿到Web題F12看源代碼是標準動作。不僅看script還要看body內(nèi)的內(nèi)聯(lián)腳本、注釋以及Network中的JS文件。利用瀏覽器控制臺作為主戰(zhàn)場解密、執(zhí)行、調(diào)試、查看變量全部在控制臺完成效率最高。回過頭看LitCTF 2023這道題它更像是一個標準的“熱身題”目的是讓選手熟悉JSFuck這種形式。通過這道題我們鞏固了從識別、提取到使用工具解密、分析執(zhí)行結(jié)果的全過程。在更復(fù)雜的比賽中JSFuck可能只是整個鏈條中的一環(huán)后面或許接著逆向分析、密碼學解密或SQL注入。但無論如何扎實地掌握這種基礎(chǔ)編碼的解密方法是你在CTF賽場上穩(wěn)步前進的可靠基石。下次再看到滿屏的括號和嘆號你大可以自信地打開收藏夾里的工具開始你的“解碼”表演了。