furryCTF2025赛前热身题(misc方向) 比赛网站:https://furryctf.com/games/5
1.签到题 题目描述:
1 2 3 话说,你们有发现比赛平台上藏有一个flag吗? 注意flag格式哦~
在赛题主页就可以找到:
flag为:
1 furryCTF{Hack_for_fun_not_for_profit}
2.新的一年,新的开始 题目描述:
1 2 3 4 5 6 7 8 9 10 11 Catch The Future Time to own 2025 Forever young in hacking furryCTF{h4ppY_n3w_y34r_2o25_w1th_1Ov3} 祝各位师傅: 栈上生花,堆里藏月,逆向不秃,web不坐牢,pwn穿一切,ak全场! 🚩🎉
这种就是问卷题
flag为:
1 furryCTF{h4ppY_n3w_y34r_2o25_w1th_1Ov3}
3.PassDump 1 2 3 4 5 6 7 8 9 作为CTFer,很多时候都会有电脑放一夜跑程序的经历。 但猫猫看着跑一夜碰撞之后蓝屏的电脑,陷入了沉思…… flag格式为furryCTF{出现问题的文件_蓝屏错误代码_该文件的最后一次编译时间_失败事件的缩写_当时正在使用的应用程序的名称} 例如,这是一个合法的flag: furryCTF{system.exe_0x0000001A_2024.12.31-14:00:00_DPC_Notepad}
这题需要用到windbg,这里链接就不贴了,网上一搜就有
打开windbg后
打开command,也就是命令行
一般会有这个蓝标字体,如果没有就自己输入
这里我把输出结果放一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 12: kd> !analyze -v Loading Kernel Symbols .. Press ctrl-c (cdb, kd, ntsd) or ctrl-break (windbg) to abort symbol loads that take too long. Run !sym noisy before .reload to track down problems loading symbols. ............................................................. ................................................................ ................................................................ ................................................................ .............................................. Loading User Symbols Loading unloaded module list ......................................... ******************************************************************************* * * * Bugcheck Analysis * * * ******************************************************************************* VIDEO_TDR_FAILURE (116) Attempt to reset the display driver and recover from timeout failed. Arguments: Arg1: ffffe30a4b8cd010, Optional pointer to internal TDR recovery context (TDR_RECOVERY_CONTEXT). Arg2: fffff8035ce14790, The pointer into responsible device driver module (e.g. owner tag). Arg3: ffffffffc000009a, Optional error code (NTSTATUS) of the last failed operation. Arg4: 0000000000000004, Optional internal context dependent data. Debugging Details: ------------------ Unable to load image nvlddmkm.sys, Win32 error 0n2 *** WARNING: Unable to verify timestamp for nvlddmkm.sys KEY_VALUES_STRING: 1 Key : Analysis.CPU.mSec Value: 2140 Key : Analysis.Elapsed.mSec Value: 13194 Key : Analysis.IO.Other.Mb Value: 0 Key : Analysis.IO.Read.Mb Value: 1 Key : Analysis.IO.Write.Mb Value: 0 Key : Analysis.Init.CPU.mSec Value: 484 Key : Analysis.Init.Elapsed.mSec Value: 28118 Key : Analysis.Memory.CommitPeak.Mb Value: 102 Key : Analysis.Version.DbgEng Value: 10.0.29482.1003 Key : Analysis.Version.Description Value: 10.2509.29.03 amd64fre Key : Analysis.Version.Ext Value: 1.2509.29.3 Key : Bugcheck.Code.LegacyAPI Value: 0x116 Key : Bugcheck.Code.TargetModel Value: 0x116 Key : Dump.Attributes.AsUlong Value: 0x21808 Key : Dump.Attributes.DiagDataWrittenToHeader Value: 1 Key : Dump.Attributes.ErrorCode Value: 0x0 Key : Dump.Attributes.KernelGeneratedTriageDump Value: 1 Key : Dump.Attributes.LastLine Value: Dump completed successfully. Key : Dump.Attributes.ProgressPercentage Value: 0 Key : Failure.Bucket Value: 0x116_IMAGE_nvlddmkm.sys Key : Failure.Exception.IP.Address Value: 0xfffff8035ce14790 Key : Failure.Exception.IP.Module Value: nvlddmkm Key : Failure.Exception.IP.Offset Value: 0x1854790 Key : Failure.Hash Value: {c89bfe8c-ed39-f658-ef27-f2898997fdbd} Key : Faulting.IP.Type Value: Paged Key : Hypervisor.Enlightenments.ValueHex Value: 0x7417df84 Key : Hypervisor.Flags.AnyHypervisorPresent Value: 1 Key : Hypervisor.Flags.ApicEnlightened Value: 0 Key : Hypervisor.Flags.ApicVirtualizationAvailable Value: 1 Key : Hypervisor.Flags.AsyncMemoryHint Value: 0 Key : Hypervisor.Flags.CoreSchedulerRequested Value: 0 Key : Hypervisor.Flags.CpuManager Value: 1 Key : Hypervisor.Flags.DeprecateAutoEoi Value: 1 Key : Hypervisor.Flags.DynamicCpuDisabled Value: 1 Key : Hypervisor.Flags.Epf Value: 0 Key : Hypervisor.Flags.ExtendedProcessorMasks Value: 1 Key : Hypervisor.Flags.HardwareMbecAvailable Value: 1 Key : Hypervisor.Flags.MaxBankNumber Value: 0 Key : Hypervisor.Flags.MemoryZeroingControl Value: 0 Key : Hypervisor.Flags.NoExtendedRangeFlush Value: 0 Key : Hypervisor.Flags.NoNonArchCoreSharing Value: 1 Key : Hypervisor.Flags.Phase0InitDone Value: 1 Key : Hypervisor.Flags.PowerSchedulerQos Value: 0 Key : Hypervisor.Flags.RootScheduler Value: 0 Key : Hypervisor.Flags.SynicAvailable Value: 1 Key : Hypervisor.Flags.UseQpcBias Value: 0 Key : Hypervisor.Flags.Value Value: 55185662 Key : Hypervisor.Flags.ValueHex Value: 0x34a10fe Key : Hypervisor.Flags.VpAssistPage Value: 1 Key : Hypervisor.Flags.VsmAvailable Value: 1 Key : Hypervisor.RootFlags.AccessStats Value: 1 Key : Hypervisor.RootFlags.CrashdumpEnlightened Value: 1 Key : Hypervisor.RootFlags.CreateVirtualProcessor Value: 1 Key : Hypervisor.RootFlags.DisableHyperthreading Value: 0 Key : Hypervisor.RootFlags.HostTimelineSync Value: 1 Key : Hypervisor.RootFlags.HypervisorDebuggingEnabled Value: 0 Key : Hypervisor.RootFlags.IsHyperV Value: 1 Key : Hypervisor.RootFlags.LivedumpEnlightened Value: 1 Key : Hypervisor.RootFlags.MapDeviceInterrupt Value: 1 Key : Hypervisor.RootFlags.MceEnlightened Value: 1 Key : Hypervisor.RootFlags.Nested Value: 0 Key : Hypervisor.RootFlags.StartLogicalProcessor Value: 1 Key : Hypervisor.RootFlags.Value Value: 1015 Key : Hypervisor.RootFlags.ValueHex Value: 0x3f7 Key : WER.System.BIOSRevision Value: 1.23.0.0 BUGCHECK_CODE: 116 BUGCHECK_P1: ffffe30a4b8cd010 BUGCHECK_P2: fffff8035ce14790 BUGCHECK_P3: ffffffffc000009a BUGCHECK_P4: 4 FILE_IN_CAB: furryCTF.dmp DUMP_FILE_ATTRIBUTES: 0x21808 Kernel Generated Triage Dump FAULTING_THREAD: ffffe30a86af3040 VIDEO_TDR_CONTEXT: dt dxgkrnl!_TDR_RECOVERY_CONTEXT ffffe30a4b8cd010 Symbol dxgkrnl!_TDR_RECOVERY_CONTEXT not found. PROCESS_OBJECT: 0000000000000004 BLACKBOXACPI: 1 (!blackboxacpi) BLACKBOXBSD: 1 (!blackboxbsd) BLACKBOXNTFS: 1 (!blackboxntfs) BLACKBOXPNP: 1 (!blackboxpnp) BLACKBOXWINLOGON: 1 (!blackboxwinlogon) PROCESS_NAME: System IP_IN_PAGED_CODE: nvlddmkm+1854790 fffff803`5ce14790 488b05b9f28dff mov rax,qword ptr [nvlddmkm+0x1133a50 (fffff803`5c6f3a50)] STACK_TEXT: ffffc982`1c9677d8 fffff803`43a2375d : 00000000`00000116 ffffe30a`4b8cd010 fffff803`5ce14790 ffffffff`c000009a : nt!KeBugCheckEx ffffc982`1c9677e0 fffff803`43c97be6 : fffff803`5ce14790 ffffe30a`5b80b5a0 00000000`00000004 ffffe30a`4b8cd010 : dxgkrnl!TdrBugcheckOnTimeout+0x101 ffffc982`1c967820 fffff803`43a324be : 00000000`00000000 00000000`00002000 00000000`00000004 00000000`00000004 : dxgkrnl!ADAPTER_RENDER::Reset+0x232 ffffc982`1c967850 fffff803`43a69375 : ffffe30a`00000100 00000000`00000000 ffffc982`00000000 00000000`00000000 : dxgkrnl!DXGADAPTER::Reset+0x59a ffffc982`1c9678e0 fffff803`43a694d2 : fffff803`b290ce60 00000000`00000000 ffffb881`c42d1100 fffff803`b29cfbc0 : dxgkrnl!TdrResetFromTimeout+0x15 ffffc982`1c967910 fffff803`b1c3072c : ffffe30a`86af3040 ffffe30a`478ddae0 ffffe30a`478dda00 fffff803`44b52750 : dxgkrnl!TdrResetFromTimeoutWorkItem+0x22 ffffc982`1c967950 fffff803`b1ea007a : ffffe30a`86af3040 ffffe30a`86af3040 fffff803`b1c30140 ffffe30a`478ddae0 : nt!ExpWorkerThread+0x5ec ffffc982`1c967b30 fffff803`b20a5db4 : ffffb881`c42d1180 ffffe30a`86af3040 fffff803`b1ea0020 00000000`0e5f57dc : nt!PspSystemThreadStartup+0x5a ffffc982`1c967b80 00000000`00000000 : ffffc982`1c968000 ffffc982`1c961000 00000000`00000000 00000000`00000000 : nt!KiStartSystemThread+0x34 SYMBOL_NAME: nvlddmkm+1854790 MODULE_NAME: nvlddmkm IMAGE_NAME: nvlddmkm.sys STACK_COMMAND: .process /r /p 0xffffe30a476cb040; .thread 0xffffe30a86af3040 ; kb FAILURE_BUCKET_ID: 0x116_IMAGE_nvlddmkm.sys OSPLATFORM_TYPE: x64 OSNAME: Windows 10 FAILURE_ID_HASH: {c89bfe8c-ed39-f658-ef27-f2898997fdbd} Followup: MachineOwner ---------
对比furryCTF{system.exe_0x0000001A_2024.12.31-14:00:00_DPC_Notepad}这个格式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 出现问题的文件:IMAGE_NAME: nvlddmkm.sys 蓝屏错误代码: VIDEO_TDR_FAILURE (116) <--- 括号里的 116 就是十进制代码 ... BUGCHECK_CODE: 116 <--- 这里确认 提取结果: 116 (十进制) = 0x00000116 (十六进制) 该文件的最后一次编译时间:lm v m nvlddmkm指令输入后运行 Browse all global symbols functions data Symbol Reload Timestamp: Tue Feb 11 13:40:16 2025 (67AAE2C0) <--- 就在这一行 CheckSum: 05B57FA7 失败事件的缩写: ******************************************************************************* * Bugcheck Analysis * ******************************************************************************* VIDEO_TDR_FAILURE (116) <--- 这里是全名 这里用TDR 当时正在使用的应用程序的名称:这里有点坑,需要一点阅读理解,“当前正在使用”,根据题目场景,当前正在进行碰撞,碰撞会想到啥,hash碰撞吧,想想常用的工具,hashcat就是答案
最后整合一下,flag为:
1 furryCTF{nvlddmkm.sys_0x00000116_2025.02.11-13:40:16_TDR_hashcat}
4.IIS服务器 题目描述:
1 2 3 猫猫前段时间闲着没事搭建了一个IIS服务器。 不过,最近猫猫发现,服务器上好像多了个文件……?
用wireshark打开pcap文件
依旧先ctrl+f搜索flag,找到了交了但是是错的因为那只是一个人登录用的password
确实被骗到了哈哈哈,诈骗的小曲
你观察就可以发现,流量大多数是TCP和http流,所以右键追踪流
最后找到一个传了fl2g.txt的流量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 GET /execute/f12g.txt HTTP/1.1 Host: 26.114.202.3 Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 0.000474s HTTP/1.1 200 OK Content-Type: text/plain Content-Encoding: gzip Last-Modified: Wed, 10 Jul 2024 03:14:49 GMT Accept-Ranges: bytes ETag: "c0c41d5377d2da1:0" Vary: Accept-Encoding Server: Microsoft-IIS/7.5 X-Powered-By: ASP.NET Date: Wed, 10 Jul 2024 04:37:33 GMT Content-Length: 191 0.000000s ZnVycnlDVEZ7RGlkX1lvdV9Ob3RlX1RoZV9EaWZmX0luX0Vycm9yX1BhZ2U/fQ== 0.175592s GET /favicon.ico HTTP/1.1 Host: 26.114.202.3 Connection: keep-alive User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36 Accept: image/avif,image/webp,image/apng,image/*,*/*;q=0.8 Referer: http://26.114.202.3/execute/f12g.txt Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 0.000349s HTTP/1.1 404 Not Found Content-Type: text/html Server: Microsoft-IIS/7.5 X-Powered-By: ASP.NET Date: Wed, 10 Jul 2024 04:37:34 GMT Content-Length: 1163 0.000000s <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=gb2312"/> <title>404 - ..................</title> <style type="text/css"> <!-- body{margin:0;font-size:.7em;font-family:Verdana, Arial, Helvetica, sans-serif;background:#EEEEEE;} fieldset{padding:0 15px 10px 15px;} h1{font-size:2.4em;margin:0;color:#FFF;} h2{font-size:1.7em;margin:0;color:#CC0000;} h3{font-size:1.2em;margin:10px 0 0 0;color:#000000;} #header{width:96%;margin:0 0 0 0;padding:6px 2% 6px 2%;font-family:"trebuchet MS", Verdana, sans-serif;color:#FFF; background-color:#555555;} #content{margin:0 0 0 2%;position:relative;} .content-container{background:#FFF;width:96%;margin-top:8px;padding:10px;position:relative;} --> </style> </head> <body> <div id="header"><h1>..........</h1></div> <div id="content"> <div class="content-container"><fieldset> <h2>404 - ..................</h2> <h3>......................................................</h3> </fieldset></div> </div> </body> </html>
这个是不是很像base64啊,虽然不是Zmxh这种标准开头
1 2 0.000000s ZnVycnlDVEZ7RGlkX1lvdV9Ob3RlX1RoZV9EaWZmX0luX0Vycm9yX1BhZ2U/fQ==
解密之后得到flag(这里用随波逐流)
1 furryCTF{Did_You_Note_The_Diff_In_Error_Page?}
5.盲盒 题目描述:
1 2 3 4 5 来开盲盒吧~nwn 注:本题原本的flag格式为flag{},因为懒得改附件了,所以找到flag后请将里面的“flag”修改为“furryCTF” 比如flag{Hi}修改为furryCTF{Hi}即为正确答案。
像这种word,excel,ppt这种题目,这种隐写的一般的处理方式就是把文件当压缩包去看
直接用随波逐流的binwalk工具去分离
一个一个文件找啊找,看有没有和flag相关的
在sharedStrings里
1 2 3 <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="762826" uniqueCount="2"><si><t>来开盲盒吧~</t><phoneticPr fontId="1" type="noConversion"/></si><si><t>flag 不在这</t><phoneticPr fontId="1" type="noConversion"/></si></sst> <!-- 我也没说flag在这呀nwn,你不会想在这里找到flag叭~ -->
是不是很明显的零宽隐写
到网站里,随波逐流的就可以
然后你就看到flag了
flag为:
1 flag{Z19_The_Str1ng_In_Exc9l}
6.丢失的文档 题目描述
附件是一个asd文件其实我的第一想法就是改后缀,因为asd是一个不好处理陌生的格式,而这本来就应该是word文件,所以把后缀改为.docx打开,然后发现flag出了
1 2 3 对应的flag为: furryCTF{How_To_F1x_This_Wor6_D0cument} 123
7.大伙儿好像太无聊了那就整点无聊的东西(? 题目描述:
1 2 3 大概是一些无聊的产物(? 猫猫的一点PS:有谁想读一遍这个玩意喵owo
附件内容是:
1 REOREREREREREOREREREREREREOOOREOREREREREOREREREOREREREOOREOREREREREOREREREREREOREOREREREREOOREOREOREOOREREOOREOOREOREOREREREREOOREOREREREREREOREOOOREOREREREOREOOOOREREOREOREOOOREOREREREREREOREOOREOOREREOOREREREREREOREOREOOOOREREOOREREREOREREOOREREREOREREOREOOOOREREOREREREOREOREOREREOREREREREOOREREOOOREOOOREOREOREOREREOOOOREOREOOREREREREOOREOOREREREOOREOOREREREOREOREOREREREOOREREOOREREO
第一部分:把“贪心切分”聊得明明白白
咱们把“贪心切分”这个词拆开,用大白话和生活中的例子来理解。
什么是“切分” (Tokenization)?
通俗解释: “切分”就是把一长串看起来乱糟糟的东西,按照某种规则,切成一个个有意义的“小块”。这个“小块”就叫 Token (记号)。
生活中的例子: 想象一下你读英文句子:thisisasentence。 你的大脑不会把它看成一堆字母,而是自动“切分”成单词:this, is, a, sentence。这里的每个单词,就是一个 Token。
在这道题里: 原始密文 REORERER... 就是那串长长的、没空格的句子。 通过观察,我们发现它好像不是由单个字母 R, E, O 构成的,而是由 O 和 RE 这两种“单词”拼起来的。 所以,“切分”就是要把 REORER... 切成 ['RE', 'O', 'RE', ...] 这样一个“单词列表”。
什么是“贪心” (Greedy)?
通俗解释: “贪心”是一种非常直白、简单的做事策略,就是“只顾眼前,不看长远 ”。每一步都做出当下看起来最好的选择。
生活中的例子: 假设你要找零钱 87 分,你手头有面值为 25, 10, 5, 1 的硬币。 “贪心”的做法是:
先拿出能用的最大面额:25分,还差 62 分。
再拿一个 25分,还差 37 分。
再拿一个 25分,还差 12 分。
25的用不了了,用下一个最大的:10分,还差 2 分。
10和5的都用不了,用 1分,还差 1 分。
再用 1分,找零完成。 你每一步都“贪心地”选了当前能用的最大面额,这就是贪心算法。
在这道题里: 我们从字符串的开头 s = "REORER..." 开始切分:
指针在第 0 位 (R) :
往前看 1 位是 R,它不是一个完整的“单词”(Token)。
往前看 2 位是 RE,它是一个完整的“单词”!
贪心选择 :我们选择匹配最长的那个,也就是 RE。
于是,第一个 Token 就是 RE。指针向后移动 2 位。
指针现在在第 2 位 (O) :
往前看 1 位是 O,它是一个完整的“单词”。
往前看 2 位是 OR,它不是一个合法的“单词”。
贪心选择 :我们选择匹配 O。
第二个 Token 就是 O。指针向后移动 1 位。
如此循环… 直到把整个字符串切完。
什么情况下我能想到用“贪心切分”?
这是解决问题的关键。你可以通过以下几点来判断:
特征一:由有限的几种“构件”组成。 当你看到一长串文本,但翻来覆去就那么几种固定的“模式”或“片段”在重复出现时。比如这道题,看几眼就发现,除了 O,就是 RE 粘在一起。这强烈暗示了基本构件(Token)就是 O 和 RE。
特征二:构件之间没有明显的“分隔符”。 像摩斯电码,点划之间有短停顿,字母之间有长停顿。但这道题的 RE 和 O 是紧挨着的,REO、ORE 这样,没有空格或特殊符号隔开。这就逼着我们必须自己想办法把它们切开。
特征三:构件之间没有“歧义”。 “贪心”策略能成功,是因为它不会“切错”。比如,如果我们的“单词”是 A 和 AB,那么遇到 AB 时,如果贪心切了 A,剩下的 B 就无法处理了。 但在本题中,O 和 RE 这两个 Token 非常完美:
一个以 O 开头。
一个以 R 开头。 它们的首字母完全不同,所以从任何位置开始,匹配哪个是唯一的,不存在二选一的困惑 。这种没有歧义的情况,就是使用贪心切分最理想的场景。
一句话总结:当你发现一个长字符串可以被一小组“没有歧义的、固定的小模式”完全拼成时,就应该立刻想到用“贪心切分”把它转换成一个“小模式”的序列,为后续解码铺路。
第二部分:CyberChef 复现超详细步骤(带中间结果)
下面我们一步步来,每一步都告诉你为什么这么做,以及做完后输出应该是什么样子。
准备工作 :打开 CyberChef 网站,把你的密文原文粘贴到右上角的 Input 框里。
网站地址:https://cyberchef.org/
步骤 1:Find / Replace (把 RE 换成 1)
操作 :在左侧 Operations 搜索框里输入 Find,找到 Find / Replace,把它拖到中间的 Recipe 区域。
配置 :
在 Find 框里,填入 RE。
在 Replace 框里,填入 1。
选项中,Global match,Case insensitive,Multiline matching全部打开就行
目的 :这一步是执行我们的“映射”规则,把我们认定的第一个 Token RE 转换成二进制里的 1。
中间结果 :此时,右下角 Output 框的内容会变成:
1 1O11111O111111OOO1O1111O111O111OO1O1111O11111O1O1111OO1O1O1OO11OO1OO1O1O1111OO1O11111O1OOO1O111O1OOOO11O1O1OOO1O11111O1OO1OO11OO11111O1O1OOOO11OO111O11OO111O11O1OOOO11O111O1O1O11O1111OO11OOO1OOO1O1O1O11OOOO1O1OO1111OO1OO111OO1OO111O1O1O111OO11OO11O
步骤 2:Find / Replace (把 字母 O 换成 0)
操作 :再拖动一个 Find / Replace 到 Recipe 区域,放在刚才那一步的下面。
配置 :
在 Find 框里,填入 O。
在 Replace 框里,填入 0。
目的 :完成映射的另一半,把 Token O 转换成二进制里的 0。
中间结果 :现在,Output 框里的内容就是一串纯粹的 0 和 1 了,也就是我们需要的比特流:
1 10111110111111000101111011101110010111101111101011110010101001100100101011110010111110100010111010000110101000101111101001001100111110101000011001110110011101101000011011101010110111100110001000101010110000101001111001001110010011101010111001100110
步骤 3:Reverse (反转)
操作 :在左侧搜索 Reverse,拖到 Recipe 中,放在第二步之后。
配置 :保持默认选项 Mode: Standard (character) 即可。
目的 :这是解题的关键一步。直接把二进制转文本会是乱码,我们猜测可能存在“整体倒序”的混淆。这一步就是把整条比特流从头到尾反转过来。
中间结果 :Output 框里的比特流现在是倒序的了:1 01100110011101010111001001110010011110010100001101010100010001100111101101010111011000010110111001101110011000010101111100110010010111110100010101100001011101000101111101001111010100100110010101001111010111110111101001110111011110100011111101111101
步骤 4:From Binary (从二进制转换)
操作 :在左侧搜索 From Binary,拖到 Recipe 的最后。
配置 :
在下面的选项中,确保 Data format 是 Binary,并且 Length 是 8。
其他选项默认就行。
目的 :将反转后的比特流,按照每 8 位一个字节的标准,翻译成 ASCII 字符。
最终结果 :Output 框里会清晰地显示出 flag:
1 furryCTF{Wanna_2_Eat_OReO_zwz?}
最后总结一下像这种有很多重复字符的字符串,你其实应该想到把重复字符看做0和1,只不过字符的整体需要一点经验,比如本题的RE和O,你只要想到O很像0其实就能联想到RE可能是1了。
当然可能还有另一种情况就是摩斯电码,这题很好有这个小彩蛋,但算个迷惑项。
RE = . (点)
O = - (划)
OO = 字母之间的分隔符 (我用单个空格 表示)
OOO = 单词之间的分隔符 (我用斜杠/表示)
根据这个规则,得到下面的标准摩尔斯电码:
1 -. . ...- . .-. / --. --- -. -. . / --. .. ...- . / -.-- --- ..- / ..- .--. / -. . ...- . .-. / --. --- -. -. . / .-.. . - / -.-- --- ..- / -.. --- .-- -.
你拖进随波逐流解密一下:
1 NEVERGONNEGIVEYOUUPNEVERGONNELETYOUDOWN
又是经典诈骗。
这题还有一个脚本解法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 s = "REOREREREREREOREREREREREREOOOREOREREREREOREREREOREREREOOREOREREREREOREREREREREOREOREREREREOOREOREOREOOREREOOREOOREOREOREREREREOOREOREREREREREOREOOOREOREREREOREOOOOREREOREOREOOOREOREREREREREOREOOREOOREREOOREREREREREOREOREOOOOREREOOREREREOREREOOREREREOREREOREOOOOREREOREREREOREOREOREREOREREREREOOREREOOOREOOOREOREOREOREREOOOOREOREOOREREREREOOREOOREREREOOREOOREREREOREOREOREREREOOREREOOREREO" def tokenize_oreo(s): i = 0 tokens = [] while i < len(s): if s[i] == 'O': tokens.append('O') i += 1 elif s[i:i+2] == 'RE': tokens.append('RE') i += 2 else: return None return tokens def bits_to_text(bits, width=8, msb_first=True): out = [] for i in range(0, len(bits) // width * width, width): chunk = bits[i:i+width] if not msb_first: chunk = chunk[::-1] val = int(chunk, 2) if 0 <= val < 256: out.append(chr(val)) else: out.append('?') return ''.join(out) tokens = tokenize_oreo(s) if not tokens: print("不能用 {O, RE} 完整切分") exit() candidates = [] for zero, one in [('O','RE'), ('RE','O')]: bits = ''.join('0' if t == zero else '1' for t in tokens) for width in (8,7): for msb in (True, False): txt = bits_to_text(bits, width=width, msb_first=msb) candidates.append((zero, one, width, msb, txt)) found = [c for c in candidates if "furryCTF{" in c[4]] if found: for z,o,w,m,txt in found: print(f"映射 {z}->0, {o}->1, width={w}, msb_first={m}") print(txt) else: # 额外尝试:反转整体位串再解一次 more = [] for zero, one in [('O','RE'), ('RE','O')]: bits = ''.join('0' if t == zero else '1' for t in tokens) bits = bits[::-1] for width in (8,7): for msb in (True, False): txt = bits_to_text(bits, width=width, msb_first=msb) more.append((zero, one, width, msb, txt)) found2 = [c for c in more if "furryCTF{" in c[4]] if found2: for z,o,w,m,txt in found2: print(f"[rev] 映射 {z}->0, {o}->1, width={w}, msb_first={m}") print(txt) else: # 打印可见文本里最像的几条,方便人工观察 def score(t): good = sum(c in "furryCTF{}_-:;,.@/=+[]()abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 " for c in t) return good / max(1,len(t)) best = sorted(candidates, key=lambda x: score(x[4]), reverse=True)[:5] for z,o,w,m,txt in best: print(f"候选 映射 {z}->0, {o}->1, width={w}, msb_first={m}, 可见率={score(txt):.2f}") print(txt[:200])
跑出来的结果是:
1 2 [rev] 映射 O->0, RE->1, width=8, msb_first=True furryCTF{Wanna_2_Eat_OReO_zwz?}
8.Miscode 题目描述:
1 2 3 这些文件好像都是乱码……? 不过,似乎有什么东西混进了.gitattributes? 温馨提示:请注意flag格式哦~
打开附件,.gitattributes 的内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 *.php linguist-language=7b *.swift linguist-language=61 *.scala linguist-language=48 *.py linguist-language=66 *.erl linguist-language=76 *.c linguist-language=72 *.f90 linguist-language=47 *.kt linguist-language=37 *.rb linguist-language=53 *.lua linguist-language=57 *.cs linguist-language=69 *.go linguist-language=43 *.m linguist-language=31 *.js linguist-language=74 *.ex linguist-language=36 *.pl linguist-language=31 *.clj linguist-language=68 *.vb linguist-language=5f *.sh linguist-language=46 *.cpp linguist-language=79 *.r linguist-language=21 *.ml linguist-language=7d *.ts linguist-language=5f *.hs linguist-language=6c *.java linguist-language=75 *.rs linguist-language=54
而random file 那个文件夹下有3000多个文件但是两种文件对比后可以发现random file下的文件后缀名就是.gitattributes有的,所以是有规律性的,大概率和次数有关,可能需要脚本。
背景:仓库用 .gitattributes 把每种扩展名映射到一个 1 字节十六进制值(linguist-language=xx),这其实就是把扩展名→ASCII 字符的字典藏在 GitHub Linguist 配置里;random_files/ 里放了大量占位文件,通过“每种扩展名出现的次数”来给字符排位。
思路:解析 .gitattributes 得到映射表 ext→char;统计 random_files/ 中每个扩展名的文件数;按“出现次数升序”排序(同次数按 .gitattributes 原顺序稳定打破平手);把对应字符连成串。题面还放了外层干扰前后缀(furryCTF 与 {FTCyruf}),以及可能的空 {},清理后规范输出 furryCTF{...}。
代码解释
题目的核心思路隐藏在 Git 仓库的两个部分:
.gitattributes 文件:定义了文件后缀名 (ext) 和一个 ASCII 字符之间的映射关系。
random_files/ 目录:包含了大量不同后缀名的文件,这些文件的数量决定了对应字符的排列顺序。
!!!注意:要把**.gitattributes和 random_files**和脚本放在同一个目录下。
脚本通过以下步骤来解出 Flag:
参数解析 (argparse)
脚本可以接受两个命令行参数:
path: 挑战仓库的根目录路径,默认为当前目录 (.)。
--mode: 拼接字符的顺序,fwd 代表正序(从前到后),rev 代表逆序(从后往前),默认是 rev,因为这正是本题的解法。
解析映射关系 (parse_mapping 函数)
此函数读取 .gitattributes 文件。
它使用正则表达式 ^\*\.(\w+)\s+linguist-language=([0-9a-fA-F]{2})\s*$ 来匹配类似 *.txt linguist-language=41 这样的行。
正则表达式解析 :
^\*\.:匹配以 *. 开头的行。
(\w+):捕获文件后缀名(如 txt)。
\s+linguist-language=:匹配中间的固定文本。
([0-9a-fA-F]{2}):捕获两位十六进制数(如 41)。
\s*$:匹配行尾的任意空格。
对于每个匹配行,它将后缀名存为 key,并将两位十六进制数解码为对应的 ASCII 字符(例如 41 -> 'A')作为 value,存入一个字典 mp 中并返回。
统计文件数量 (count_exts 函数)
此函数递归地遍历 random_files/ 目录下的所有文件。
对于每个文件,它提取其后缀名(如 .txt),去除开头的点号 .,并转换为小写,以保证计数的一致性。
使用 collections.Counter 对象来高效地统计每种后缀名出现的次数,并返回这个计数器。
主逻辑 (main 函数)
初始化和校验 :设置好文件路径,并检查 .gitattributes 和 random_files/ 是否存在,如果不存在则退出程序。
调用核心函数 :调用 parse_mapping 和 count_exts 获取映射表和计数值。
排序 :
将映射表中的后缀名和它们对应的计数值组合成一个元组列表 (ext, count)。
最关键的一步:items.sort(key=lambda t: t[1])。这行代码根据元组的第二个元素(也就是文件数量 count)对列表进行 升序 排序。Python 的 sort 是稳定的,意味着如果两个后缀名文件数量相同,它们在排序后的相对位置将保持不变(与它们在 .gitattributes 文件中的顺序一致)。
拼接 Flag :
根据 --mode 参数决定拼接顺序。默认是 rev,所以 reversed(items) 会将排好序的列表反转。这意味着,文件数量最多的后缀名对应的字符会排在最前面 。
最后,它遍历这个序列,从映射表 mp 中取出每个后缀名对应的字符,并将它们拼接成一个原始的字符串 s。
格式化输出 (normalize 函数)
此函数负责清理拼接好的原始字符串 s。
它移除了可能存在于字符串开头和结尾的干扰项(furryCTF 和 {FTCyruf})。
它还会移除空的花括号 {}。
最后,它提取出花括号内的核心内容,并用标准的 furryCTF{...} 格式包裹起来,打印到控制台。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 import re import argparse from pathlib import Path from collections import Counter def parse_mapping (gitattributes: Path ) -> dict : """ 解析 .gitattributes 文件,提取文件后缀名到 ASCII 字符的映射。 例如,解析行: *.ext linguist-language=41 -> {'ext': 'A'} Args: gitattributes (Path): .gitattributes 文件的路径对象。 Returns: dict: 一个从后缀名(小写)到对应字符的映射字典。 """ mp = {} pat = re.compile (r'^\*\.(\w+)\s+linguist-language=([0-9a-fA-F]{2})\s*$' ) with gitattributes.open ('r' , encoding='ascii' , errors='ignore' ) as f: for line in f: m = pat.match (line.strip()) if m: ext = m.group(1 ).lower() ch = bytes .fromhex(m.group(2 )).decode('ascii' ) mp[ext] = ch return mp def count_exts (random_dir: Path ) -> Counter: """ 递归统计指定目录中所有文件的后缀名出现次数。 Args: random_dir (Path): 要统计的目录路径对象。 Returns: Counter: 一个包含后缀名及其计数的 Counter 对象。 """ cnt = Counter() for p in random_dir.rglob('*' ): if p.is_file() and p.suffix: cnt[p.suffix[1 :].lower()] += 1 return cnt def normalize (raw: str ) -> str : """ 清理原始字符串,移除边缘的干扰项,并规范化为 furryCTF{...} 格式。 Args: raw (str): 原始拼接出的字符串。 Returns: str: 格式化后的 Flag 字符串。 """ t = raw if t.startswith('furryCTF' ): t = t[len ('furryCTF' ):] if t.endswith('{FTCyruf}' ): t = t[:-len ('{FTCyruf}' )] t = t.replace('{}' , '' ) m = re.search(r'\{([^{}]+)\}' , t) payload = m.group(1 ) if m else t.strip('{}' ).strip() return f'furryCTF{{{payload} }}' def main (): """ 主执行函数,协调整个解题流程。 """ ap = argparse.ArgumentParser(description="Solve the gitattributes CTF challenge." ) ap.add_argument('path' , nargs='?' , default='.' , help ="Path to the challenge repository root." ) ap.add_argument('--mode' , choices=['fwd' , 'rev' ], default='rev' , help ='Assembly order: fwd=forward (ascending count), rev=reverse (descending count, default)' ) args = ap.parse_args() base = Path(args.path).resolve() gitattributes = base / '.gitattributes' random_dir = base / 'random_files' if not gitattributes.is_file(): raise SystemExit(f'Error: Not found: {gitattributes} ' ) if not random_dir.is_dir(): raise SystemExit(f'Error: Not found: {random_dir} ' ) mp = parse_mapping(gitattributes) if not mp: raise SystemExit('Error: No mappings found in .gitattributes' ) cnt = count_exts(random_dir) if not cnt: raise SystemExit('Error: No files found in random_files' ) items = [(ext, cnt[ext]) for ext in mp.keys() if ext in cnt] items.sort(key=lambda t: t[1 ]) seq = items if args.mode == 'fwd' else reversed (items) s = '' .join(mp[ext] for ext, _ in seq) print (normalize(s)) if __name__ == '__main__' : main()
最后输出flag:
1 furryCTF{Sia7t_W1!H_G1lhv6}
这题既要统计各个后缀名次数,并把它们进行转换,还要注意得到的字符串是flag的反转形式,要逆回来。