題避坑指南:base64解碼與‘認(rèn)真你就輸了’的復(fù)盤)
有些CTF雜項(xiàng)題出題人把腦筋急轉(zhuǎn)彎包裝成一個(gè)壓縮包、一個(gè)文本文件就敢跟你打賭。我碰到[BJDCTF2020]認(rèn)真你就輸了這道題時(shí)第一反應(yīng)是這名字是不是在嘲諷我下載附件解壓里面只有一個(gè)txt打開后是一串字符ZmxhZ3tBcmVfeW91X3NlcmlvdXN9。沒有圖片沒有音頻沒有多余文件。我當(dāng)時(shí)心里一沉這種看起來越簡單的題往往越容易藏東西。于是我開始了整整一個(gè)晚上“認(rèn)真”的排查binwalk、foremost、stegsolve、零寬字符檢測(cè)能想到的全上了。等我把能想到的隱寫套路都走了一遍猛然意識(shí)到一件事——把它丟給base64解一下就結(jié)束了。沒錯(cuò)flag就這么明晃晃地躺在第一個(gè)解碼步驟里。這篇復(fù)盤我就好好說說自己是怎么把一道送分題做成難題的以及這類“玩笑題”背后的出題邏輯希望對(duì)剛?cè)肟与s項(xiàng)的你有幫助。1. 拿到附件后的第一反應(yīng)先按“常規(guī)隱寫”處理1.1 解壓與文件識(shí)別正常來說雜項(xiàng)題的附件要么是圖片要么是壓縮包要么是音頻。這道題的附件是一個(gè)zip壓縮包大小只有3KB左右。解壓之后出現(xiàn)一個(gè)名叫nzky.txt的文件沒有比這更普通的了。我用file命令看了一眼$ file nzky.txt nzky.txt: ASCII text再用wc -c看大小$ wc -c nzky.txt 28 nzky.txt28個(gè)字節(jié)。一個(gè)28字節(jié)的純文本文件能藏什么當(dāng)時(shí)我滿腦子都是“小文件里往往藏著大玄機(jī)”根本沒意識(shí)到自己的思考方向已經(jīng)跑偏了。用cat直接把內(nèi)容打印出來所見即所得就是這串看起來毫無感情的字符ZmxhZ3tBcmVfeW91X3NlcmlvdXN9我盯著這串字符看了幾秒鐘感覺它像是base64但轉(zhuǎn)念一想base64結(jié)尾不是應(yīng)該帶等號(hào)嗎這串沒有等號(hào)所以肯定不是。就是這個(gè)錯(cuò)誤認(rèn)知把我坑了很久。后面我會(huì)細(xì)說。當(dāng)時(shí)我決定按“隱藏信息”來處理于是開始上工具。1.2 工具輪番上陣的“認(rèn)真現(xiàn)場(chǎng)”先把binwalk丟上去掃描結(jié)果只有一行ASCII text沒有發(fā)現(xiàn)任何嵌入文件。再用foremost對(duì)附件和txt分別做分離也沒有任何輸出。因?yàn)閠xt不是圖片我不能直接用stegsolve打開但我靈機(jī)一動(dòng)把nzky.txt改成nzky.png再去打開結(jié)果stegsolve直接報(bào)錯(cuò)根本不是一個(gè)合法圖像文件。這說明文件確實(shí)只是個(gè)純文本不是被改后綴的圖片??墒俏也凰佬挠钟胹trings掃了一遍輸出仍然只有那串字符用grep -R搜索常見flag特征也沒找到額外字符串。我甚至做了完整的十六進(jìn)制dump$ xxd nzky.txt 00000000: 5a6d 7868 5a33 7442 636d 5666 6559 3131 ZmxhZ3tBcmVfeW91 00000010: 584e 6c63 6d6c 754e 584e 3900 XNlcmlvdXN9.字節(jié)都在預(yù)期范圍內(nèi)沒有多余內(nèi)容沒有文件結(jié)尾追加的數(shù)據(jù)也沒有奇怪的BOM頭。在一個(gè)純文本文件里常規(guī)的“文件分離”手段基本全部失效。走到這一步我把目光轉(zhuǎn)向了文本隱寫。2. 陷入“認(rèn)真”陷阱那些我懷疑過的隱寫術(shù)2.1 為什么我會(huì)優(yōu)先懷疑“雜亂字符串”雜項(xiàng)題的經(jīng)典思路是顯眼的東西往往是誘餌真正的flag藏在看不見的地方。這串字符太顯眼于是我斷定它是誘餌?,F(xiàn)在回想起來這個(gè)判斷不能說完全錯(cuò)但確實(shí)把我?guī)нM(jìn)了坑里。因?yàn)閎ase64本身也是一個(gè)非常常見的隱藏層它不叫“顯眼”而叫“編碼”。我在潛意識(shí)里給“隱寫”和“編碼”畫了條界線總覺得編碼應(yīng)該在最后一步才考慮而隱寫必須在前面處理。這其實(shí)是本末倒置。對(duì)于MISC新人來說最容易踩坑的一點(diǎn)就是把所有“看不懂的字符串”當(dāng)作需要破譯的密文而不考慮它可能只是最基礎(chǔ)的編碼。2.2 文本隱寫的常見形式與我的排除過程針對(duì)txt文件文本隱寫大致有這幾種零寬字符、行尾空格、制作成Word/PDF后隱藏對(duì)象、字體顏色、字間距、文本內(nèi)容替換等。我逐一做了排查。先排查零寬字符。用Python遍歷文件內(nèi)容檢查是否有U200B、U200C、U200D、UFEFF這些不可見字符。代碼很簡單data open(nzky.txt, rb).read().decode(utf-8) unusual [hex(ord(c)) for c in data if ord(c) 127] print(unusual)輸出是空列表。全文都是可打印ASCII沒有任何零寬字符。再排查行尾空格和制表符。全文只有一行沒有多字節(jié)空白也沒有大段連續(xù)空格。至于字體顏色和字間距那只對(duì)富文本有效而這里確認(rèn)是純文本直接排除。我還想過這串字符會(huì)不會(huì)是摩斯電碼的某種變形或者用字符的ASCII碼值換算成坐標(biāo)。我寫了一個(gè)腳本把每個(gè)字符的ASCII碼轉(zhuǎn)成二進(jìn)制、八進(jìn)制、十六進(jìn)制貼在記事本里看了半天沒有任何可讀規(guī)律。老實(shí)說28個(gè)字符的樣本量太小統(tǒng)計(jì)分析和頻率分析都形同虛設(shè)越分析越像玄學(xué)。另一個(gè)經(jīng)典操作是看壓縮包注釋。很多雜項(xiàng)題會(huì)把提示放進(jìn)zip注釋里我用zipinfo -v翻了一遍注釋欄是空的。文件名nzky.txt看起來像拼音縮寫但怎么組都組不出有意義的詞也只能丟到一邊。到這里我?guī)缀醢盐谋倦[寫能踩的點(diǎn)都踩了一遍依然一無所獲。現(xiàn)在回頭看完全沒有隱藏信息當(dāng)然什么都找不出來。3. 真正的解法平淡無奇Base64一步到位3.1 為什么會(huì)突然想到base64折騰到快沒耐心的時(shí)候我重新把字符串抄到了終端里正準(zhǔn)備寫一個(gè)“暴力枚舉常見編碼”的腳本。就在輸入字符串的過程中我忽然注意到它的字符集合實(shí)在太干凈了有大小寫字母、有數(shù)字全部落在base64字符集里。更關(guān)鍵的是字符串長度是28而28是4的倍數(shù)符合base64編碼產(chǎn)物常見的長度特征。最重要的還是開頭部分ZmxhZ3tBcmVfeW91X3NlcmlvdXN9前6個(gè)字符是ZmxhZ3這個(gè)序列正好對(duì)應(yīng)flag三個(gè)字母的base64編碼產(chǎn)物。我之前在終端里做過太多次編碼轉(zhuǎn)換對(duì)ZmxhZ3這個(gè)前綴非常熟悉??吹介_頭的ZmxhZ3心里基本就有底了后面跟的肯定就是flag內(nèi)容。我還順便想明白了一個(gè)問題為什么這串base64沒有等號(hào)因?yàn)樵紨?shù)據(jù)是flag{Are_you_serious}一共21個(gè)字節(jié)而21是3的倍數(shù)base64編碼時(shí)不需要補(bǔ)充padding所以沒有等號(hào)。很多新手看到base64就等號(hào)看到?jīng)]有等號(hào)就不敢認(rèn)這恰恰是經(jīng)驗(yàn)不足的表現(xiàn)。3.2 解碼過程與flag提取接下來就沒什么好說的了一行命令echo ZmxhZ3tBcmVfeW91X3NlcmlvdXN9 | base64 -d輸出flag{Are_you_serious}如果不習(xí)慣命令行工具用Python同樣能解決import base64 s ZmxhZ3tBcmVfeW91X3NlcmlvdXN9 print(base64.b64decode(s).decode())提交flag{Are_you_serious}題目通過。拿到flag的那一刻我盯著屏幕笑了半天。Are_you_serious你認(rèn)真了嗎答案恰恰是如果你認(rèn)真地去追求復(fù)雜你就輸了。這個(gè)flag內(nèi)容本身就是對(duì)題目名最直接的呼應(yīng)。3.3 這個(gè)解法讓我意識(shí)到的問題第一base64不是加密只是編碼。它的目的不是藏信息而是把二進(jìn)制數(shù)據(jù)變成可打印文本。一個(gè)可打印字符串只要字符集和長度符合base64特征就應(yīng)該率先嘗試解碼而不是想太多。第二CTF里出現(xiàn)的亂碼字符串有相當(dāng)一部分不是“密碼”而只是“編碼”。編碼和加密有著本質(zhì)區(qū)別編碼是可逆的、無密鑰的轉(zhuǎn)換解碼過程完全透明加密則依賴密鑰或算法強(qiáng)度。在雜項(xiàng)題里先做編碼解碼驗(yàn)證的成本極低收益卻可能極大。第三做題時(shí)的“分類思維”不能太固化。我不應(yīng)該把“雜項(xiàng)”等同于“隱寫”。雜項(xiàng)題里同樣包含“找編碼”這類基礎(chǔ)關(guān)卡二者并不矛盾。先花30秒做解碼嘗試比上來就binwalk靠譜得多。4. 事后復(fù)盤這種“玩笑題”到底在考什么4.1 考的是對(duì)編碼的敏感度不是隱寫工具一場(chǎng)比賽里如果出一道名稱本身就在提示你的題它的重點(diǎn)往往不在技術(shù)深度而在基礎(chǔ)敏感度。雜項(xiàng)題里常見的編碼類型就那么幾種base64、hex、URL編碼、Unicode、ROT、base32、base85等。拿到這種字符串應(yīng)該先做“識(shí)別特征”而不是急著上隱寫工具。我把幾個(gè)常見編碼的特征列在下面方便新手對(duì)號(hào)入座。編碼類型典型特征簡單解碼示例Base64大小寫字母數(shù)字/長度是4的倍數(shù)末尾可有1-2個(gè)echo STR | base64 -dHex僅0-9 a-f長度通常為偶數(shù)xxd -r -pURL編碼%后跟兩位十六進(jìn)制Pythonurllib.parse.unquoteROT13僅大小寫字母移位13位tr A-Za-z N-ZA-Mn-za-mASCII碼文本數(shù)字序列用空格/逗號(hào)分隔如72 101 108 108 111Pythonchr()拿這道題來說字符集合干凈、長度28、開頭ZmxhZ3都是再明顯不過的base64提示??上耶?dāng)時(shí)抱著“沒有等號(hào)就不是base64”的刻板印象硬生生繞開了最簡單的一條路。4.2 題目名稱就是最大的提示把題目名和flag內(nèi)容放一起看會(huì)發(fā)現(xiàn)出題人已經(jīng)把答案寫在臉上了認(rèn)真你就輸了。flag是Are_you_serious整道題的考點(diǎn)就是“不要過度認(rèn)真”。CTF出題人會(huì)習(xí)慣性地把線索藏在題目名稱、題目描述、文件注釋甚至文件名里。越是一眼看穿的題越想通過外在形式把你往復(fù)雜方向帶。如果你忽略這些元信息單憑著“這題肯定不簡單”的信念去做就正好掉進(jìn)陷阱?,F(xiàn)在遇到類似的名字我都會(huì)先反問一句出題人到底想讓我在哪里犯傻這道題里愚蠢的位置太明顯了——明明一個(gè)解base64就能解決的題非得讓自己從MISC工具全家桶里走一遍才肯承認(rèn)答案就在眼前。4.3 從“認(rèn)真”到“會(huì)偷懶”的思維轉(zhuǎn)變做題時(shí)認(rèn)真是好事但認(rèn)真不等于蠻干。所謂“會(huì)偷懶”是指優(yōu)先用最便宜的手段驗(yàn)證最可能的情況??吹揭粋€(gè)可打印字符串先復(fù)制到終端或在線解碼器里試一遍base64、hex、url不過幾秒鐘看到一個(gè)zip文件先看注釋、試偽加密看到一張圖片先看文件尾部和EXIF信息。這些低成本操作全部做一遍才談得上深入隱寫分析。我在這題上走了一個(gè)大彎路恰恰是因?yàn)槲野选罢J(rèn)真”理解成了“把所有工具都用一遍”卻忘了最基礎(chǔ)的觀察和判斷。真正高效的做題節(jié)奏是先花兩分鐘觀察再花五分鐘驗(yàn)證而不是一上來就敲binwalk。這個(gè)教訓(xùn)比flag本身值錢得多。5. CTF雜項(xiàng)新手的破題順序與避坑清單5.1 一個(gè)我個(gè)人常用的檢查順序經(jīng)過這道題之后我給自己列了一個(gè)雜項(xiàng)破題順序表遇到簡單題基本不會(huì)漏。先看文件類型再看字符串然后識(shí)別編碼再考慮分離和隱寫最后才是密碼爆破。這個(gè)順序的核心邏輯是從最不可能出錯(cuò)的操作開始。你不需要每道題都從頭到尾走一遍但做題時(shí)要有一個(gè)默認(rèn)的路標(biāo)防止像這次一樣一上來就鉆進(jìn)隱寫洞穴。優(yōu)先級(jí)操作工具/命令預(yù)期結(jié)果1查看文件真實(shí)類型file xxx明確是txt/png/zip等2查看十六進(jìn)制頭尾xxd xxx | head / tail驗(yàn)證文件頭、尾部追加數(shù)據(jù)3提取可打印字符串strings xxx找到可能的提示或編碼串4嘗試常見編碼解碼base64 -d、xxd -r -p直接獲得明文flag5查看壓縮包注釋、偽加密zipinfo -v、修改加密位找到隱藏信息/解開壓縮包6分離嵌入式文件binwalk、foremost發(fā)現(xiàn)隱藏文件7深度隱寫分析stegsolve、zsteg、音頻頻譜從圖片/音頻中提取數(shù)據(jù)8暴力破解弱口令fcrackzip、hashcat打開被加密的壓縮包這個(gè)表不是固定的但第1到第4步對(duì)大多數(shù)“看字符串題”已經(jīng)足夠。很多送分題到第4步就能結(jié)束根本走不到第6步以后。5.2 那些容易被忽略的細(xì)節(jié)有幾個(gè)細(xì)節(jié)值得單獨(dú)拎出來說。文件大小是個(gè)被嚴(yán)重低估的信號(hào)。一個(gè)只有28字節(jié)的txt文件幾乎不可能承載LSB隱寫、附加壓縮包等復(fù)雜隱藏信息它的全部內(nèi)容就是那28個(gè)字符。如果文件只有幾十字節(jié)心里應(yīng)該馬上亮起“簡單編碼”的指示燈而不是“深度隱寫”。壓縮包注釋和文件名同樣可能是線索。如果這道題的文件名是flag_base64.txt你可能一眼就秒了。而出題人用了nzky.txt這種無意義名增加迷惑性的同時(shí)也在暗示別想太多。純文本文件不要總想著“應(yīng)該藏了什么”當(dāng)你檢查了零寬字符、行尾空白、文件尾部追加數(shù)據(jù)之后如果都沒有發(fā)現(xiàn)異常那往往不是你的工具不夠強(qiáng)而是真的沒有隱藏層。5.3 一道同類題的擴(kuò)展思路類似的“玩笑題”其實(shí)很多。我印象里還有一種是附件里只有一個(gè)flag.txt打開是一段中文提示“密碼認(rèn)真你就輸了”真正要提交的flag是flag{md5(認(rèn)真你就輸了)}。這類題目的共同點(diǎn)就是把中文提示當(dāng)成后續(xù)步驟的密碼或編碼起點(diǎn)。所以后來遇到中文提示我也會(huì)有意識(shí)地想到常見處理方式UTF-8、GBK、MD5、Unicode編碼等。不要因?yàn)閮?nèi)容是英文之外的文本就不知所措。核心還是那句話先讀題、先看名字、先做最基礎(chǔ)的驗(yàn)證再考慮復(fù)雜手段。很多“認(rèn)真你就輸了”式的題目考的就是這點(diǎn)常識(shí)。寫這篇復(fù)盤的時(shí)候我又重新想了想“認(rèn)真你就輸了”這五個(gè)字。在CTF練習(xí)里“認(rèn)真”本就是優(yōu)點(diǎn)但如果認(rèn)真用錯(cuò)了方向就會(huì)把簡單問題復(fù)雜化。我現(xiàn)在養(yǎng)成了一個(gè)習(xí)慣拿到任何附件先把文件名、擴(kuò)展名、文件大小、題目描述這些“肉眼信息”念一遍再?zèng)Q定動(dòng)手。因?yàn)楹芏嗫颖緛砭涂梢钥俊安徽J(rèn)真”避開。希望這篇復(fù)盤能讓你少走點(diǎn)彎路至少下次看到一個(gè)大寫字母加數(shù)字混排、長度是4倍數(shù)的字符串先無腦base64試一下。有時(shí)候答案就藏在最簡單的那個(gè)動(dòng)作里。