本文最后更新于 2026年3月29日 晚上
前言 嗯….
这一篇wp本来应该及时发出的,但那时还没有blog😓,然后因为一些众所周知的原因,导致这次比赛在我心中的地位骤降,故没有搭理;后来想想还是补上吧,怎么也是我参加的第一个省B类竞赛,遂借着期末周忙里偷闲做一下。
不过还是忍不住想吐槽一句:
“致敬传奇比赛金盾杯”
原赛 llmlog 下方可以下载题目附件哦⬇️
📥 题目附件下载 文件名:llmlog.zip 大小:51 KB点我下载
很简单又很恶心的一道题。说简单是因为这题就是按图索骥,答案连个字都不用改就明明白白写在那里;说难是因为干扰项太多,比较考验阅读理解能力。由于结果需要转换成MD5,稍有不慎,便谬之千里。
话不多说,上截图!
如图所示,真的就是阅读理解
于是我们带着五个问题开始审查llm日志,分别在如下位置找到答案:
攻击者第一次冒充系统用户询问的时间
攻击者冒充后台管理用户询问的问题
攻击者得知完整手机号的时间
攻击者使用“特殊身份”询问得到回答的时间
邮箱被查询到的次数,复制到VS Code中ctrl+F查询或者直接在记事本里手动计数,反正也不多
需要注意的小细节挺多,比如“系统用户”真的就是系统用户,不是“system”;攻击者冒充后台管理用户询问的问题一定要加“我是后台管理用户”这半句(当时就因为这个卡了好久);遍历日志内容可以发现,攻击者先后获取了手机号的前七位和后四位,所以获取后四位时就已经得知完整密码了。而不是等到后来使用“金盾杯后台管理用户”这一“特殊身份”询问后才获知;输入法问题,等等。这道misc考验了审查大量数据的能力,也考验了我不断试错的耐心。
拿到五个问题的答案后就可以去赛博厨子那里获取flag啦🎉
赞美伟大的赛博厨子!
flag{1fcfbcd14f58c6b7add09ab13258ef14}
乱七八糟的意味 下方可以下载题目附件哦⬇️
📥 题目附件下载 文件名:附件.zip 大小:10726 KB点我下载
看到题目名称就有预感,出题人多少肯定带点..不过个人很喜欢这道题,比赛期间给了我很大的满足感。
拿到附件先解压,直接开幕雷击:
何意味?
字都没显示完整,朋友你疑似有点过于明显了😓
扔进010看一眼果然有CRC校验错误
正合我意
当时被llmlog磨灭了耐心,于是随波逐流一下直接生成修复宽高后的图片
无异味--随波逐流
下面露出来这一行,老实说我从来没见过,但第一眼就知道肯定是某种加密,第二眼发现好像都是wasd这四个字母,电脑玩家的DNA瞬间动了——这东西怕不是真和上下左右有关。
于是随手找来纸笔,随便取一个点为起点,按照w上s下a左d右,在纸上画线。
画出第一个数字的时候我就明白怎么回事了。就像这样👇
当时随手画的,不好看但能证明解题过程百分百纯原创
于是得到压缩包密码为972561,成功解压。
解压完成后得到两个文件:一个不知道是啥的高雅人士请打开,先扔一边;一张png图片,叫孩子们还认得出我吗
化成灰都认识
简单看一下结构,没啥问题。用Stegsolve进行位平面切片分析,在图像 RGB 通道的 LSB中发现了提示信息
红绿蓝三个通道都勾选0即可出现信息
这句英文(arnoldarnoldarnold,all parameters are set to 1)中,arnold提示我们是阿诺德猫脸变换,重复三次的意思就是要进行三次变换。很明显,这张图是被事先处理过的,只要按照提示给图片还原过来应该就能看到信息。在阿诺德变换的算法中,涉及到a和b两个参数。提示中说all parameters are set to 1,意思是所有参数都设置为1。由是,我们可以写出还原脚本:
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 import numpy as npfrom PIL import Imageimport osdef recover_arnold (image_path, iterations=3 ): if not os.path.exists(image_path): print (f"找不到文件: {image_path} " ) return img = Image.open (image_path).convert('RGB' ) img_array = np.array(img) height, width, channels = img_array.shape if height != width: print (f"警告:图像不是正方形 ({width} x{height} ),变换可能不符合标准阿诺德映射。" ) N = height current_array = img_array.copy() output_dir = os.path.join(os.path.dirname(image_path), "recovery_results" ) if not os.path.exists(output_dir): os.makedirs(output_dir) print (f"开始尝试逆变换,结果将保存在: {output_dir} " ) for i in range (1 , iterations + 1 ): new_array = np.zeros_like(current_array) for x in range (N): for y in range (N): nx = (2 * x - 1 * y) % N ny = (-1 * x + 1 * y) % N new_array[nx, ny] = current_array[x, y] current_array = new_array.copy() res_img = Image.fromarray(current_array) save_path = os.path.join(output_dir, f"recovered_step_{i} .png" ) res_img.save(save_path) print (f"第 {i} 次逆变换结果已保存。" )if __name__ == "__main__" : file_path = r"你的图片的实际路径" recover_arnold(file_path, iterations=3 )
有个小细节要注意,我们要对图片做逆变换来还原,因为原图实际上是被正向变换处理过的。不知道正逆也没关系,那就都试试,看哪个还原对了就是那个[doge],顺带一提,由于该变换具有周期性,正向一定次数也可以正确还原哦❤️
有关阿诺德变换的具体原理,感兴趣的话可以自行搜索学习,这里就不作解释啦~
运行脚本后成功得到了清晰的图片,仔细观察可以发现图中的密码👁️
注意大小写
密码为VCp@ssw0rd114514!@# 这个密码有什么用呢?唉🤓,注意到文件夹名称为do you know VeraCrypt,这不就是提示吗。
于是我们安装一个VeraCrypt,了解到它是一款开源磁盘加密软件,这下就好办了。刚刚的高雅人士请打开显然就是用这东西加密的。
打开VeraCrypt,点击选择文件,找到高雅人士请打开,打开,然后随便选个盘符,点击加载,就会弹出输入密码的弹窗。输入VCp@ssw0rd114514!@#并点击确定,之后你就能在此电脑中找到你刚才选的盘了。
VeraCrypt似乎无法被截屏,所以就直接拍屏了
凑合着看吧喵喵喵
打开这个盘更是两眼一黑
还有暗广
检查一番发现只有67号图片的大小是106KB,比其他的大了1KB,所以我们着重分析这张图片。放进010一看,原来末尾附加了一张png。
找到你啦
用binwalk或者foremost分离出这张png
你的身份是...
这不是残缺的二维码,而是残缺的Data Matrix码。由于左侧L形部分完好,随便找个在线网站就能扫出来
拿下!
flag{Y0u_@r3_gOOOOOOd_4t_m15c}
重赛 签到
如图所示
没啥说的,看见Zmxh一眼base64,赛博厨子直接带走
flag{fb243025-d204-4eda-b3dc-50fefa1089fd}
英勇投弹手 下方可以下载题目附件哦⬇️
📥 题目附件下载 文件名:英勇投弹手.zip 大小:14 KB点我下载
所有文件全加密,密码半天没找出来,以为漏了提示,结果就是直接爆破😓
那么我将拿出对密码爆破神器-ARCHPR 4.54
加载附件里的压缩包后直接弹出提示,要用担保WinZip恢复,5分钟左右就能爆出密码
想出这密码的家里请谁都没用了
成功解锁后我们挨个检查这些文件,发现它们似乎是一个网页小游戏飞机大战的组成部分。png图片很干净,css正常,html网页除了开始不了游戏以外也没啥问题(不影响做题)。着重检查js源码,感觉这个core.js相当奇怪。继续审查,作为游戏框架的game.js引入了player.js,bullet.js等其他几个js源码,唯独没提到core.js。这就说明core.js有问题。
通过这一段可知core.js不属于游戏本身
观察core.js
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 (function (_0x308471, _0x5643fe ) { const _0x5a606a = _0x2460, _0x33a4a5 = _0x308471 (); while (!![]) { try { const _0x2afb4f = parseInt (_0x5a606a (0x1b3 )) / (-0x19c * -0xd + -0xe77 + -0x674 ) * (-parseInt (_0x5a606a (0x1b7 )) / (0xb5 * 0x5 + 0x133f + -0x2 * 0xb63 )) + -parseInt (_0x5a606a (0x1ba )) / (-0x2644 + -0x68b + 0x2cd2 ) + parseInt (_0x5a606a (0x1bf )) / (0x66 + 0x46d + -0x4cf ) + parseInt (_0x5a606a (0x1b5 )) / (-0x32e + 0x2683 + -0x2350 ) + parseInt (_0x5a606a (0x1b4 )) / (0x513 * 0x1 + 0x1cce + -0x1b * 0x141 ) + parseInt (_0x5a606a (0x1bb )) / (0x1710 + 0x638 + -0x1 * 0x1d41 ) + -parseInt (_0x5a606a (0x1bd )) / (0x19 * 0x105 + -0xe * 0x193 + -0x36b ); if (_0x2afb4f === _0x5643fe) break ; else _0x33a4a5['push' ](_0x33a4a5['shift' ]()); } catch (_0x5220d0) { _0x33a4a5['push' ](_0x33a4a5['shift' ]()); } } }(_0x2f3d, -0x9dc5 + 0xf * 0x18fc + 0x49c84 ));let k = 0x166c + -0x7 * -0x4cf + -0x1 * 0x357b ;function _0x2460 (_0x111017, _0x5efafa ) { const _0x2f1f50 = _0x2f3d (); return _0x2460 = function (_0x2f7d39, _0x466e46 ) { _0x2f7d39 = _0x2f7d39 - (0x256e + -0x1a4d + -0x96e * 0x1 ); let _0x2b3801 = _0x2f1f50[_0x2f7d39]; return _0x2b3801; }, _0x2460 (_0x111017, _0x5efafa); }function core ( ) { const _0x47b80f = _0x2460, _0x3074a9 = { 'gFKKr' : function (_0xe55803, _0xa329bf ) { return _0xe55803 === _0xa329bf; }, 'HPqhV' : _0x47b80f (0x1b6 ) + _0x47b80f (0x1b8 ) + _0x47b80f (0x1be ) + 'a3' }; if (_0x3074a9[_0x47b80f (0x1b9 )](k, -0x295 * 0xf + -0x1 * 0x134b + -0x1 * -0x41ef )) return _0x3074a9[_0x47b80f (0x1bc )]; }function _0x2f3d ( ) { const _0x239d05 = [ '3088265aLnAAu' , '873bf00c57' , '25372BhvWzP' , '7c3f7bbf99' , 'gFKKr' , '821466gZMlek' , '2569812BGsTZj' , 'HPqhV' , '4040648FCWnre' , '76849b468b' , '1006744yccwII' , '25COywAc' , '1304166xohEjZ' ]; _0x2f3d = function ( ) { return _0x239d05; }; return _0x2f3d (); }
这是一个Obfuscator.io 混淆脚本,目的就是让代码变得极度难以阅读,但功能不变。前面那堆看不懂的东西可以不管,真正有用的部分从这里开始:
1 let k = 0x166c + -0x7 * -0x4cf + -0x1 * 0x357b ;
这里定义了一个变量k,计算它的初始值为 5740 + 8617 - 13691 = 666。
1 2 3 4 5 6 7 8 9 10 11 function core ( ) { const _0x47b80f = _0x2460, _0x3074a9 = { 'gFKKr' : function (_0xe55803, _0xa329bf ) { return _0xe55803 === _0xa329bf; }, 'HPqhV' : _0x47b80f (0x1b6 ) + _0x47b80f (0x1b8 ) + _0x47b80f (0x1be ) + 'a3' }; if (_0x3074a9[_0x47b80f (0x1b9 )](k, -0x295 * 0xf + -0x1 * 0x134b + -0x1 * -0x41ef )) return _0x3074a9[_0x47b80f (0x1bc )]; }
这里的gfKKr是用来执行===(严格相等运算符)比较的,目的是确保k的绝对精确。
HPqhV则是由三个字符串加上a3拼接而成的,这很可能就是flag。
大致意思就是,这个core函数会检查变量 k 是否等于特定的数值,如果匹配,则返回一串拼接好的加密字符串或 Key。
计算一下这个值:-9915 - 4939 + 16879 = 2025,我们需要让k的值达到2025才能触发这个函数,这样网页会返回HPqhV的值。但分析所有的js代码可知,我们在飞机大战的游戏中没有能够改变k值的用户行为。难道没有办法了吗?
其实不然。既然游戏内部毫无办法,我们可以在游戏外进行降维打击。
打开html网页,按F12打开控制台,直接输入以下内容:
1 2 k = 2025 ; console .log (core ());
直接就吐出了flag。没错,就这么简单。
用点web知识
flag{873bf00c577c3f7bbf9976849b468ba3}
data 下方可以下载题目附件哦⬇️
📥 题目附件下载 文件名:data.zip 大小:717 KB点我下载
解压题目附件发现一个没有扩展名的data,不知道是什么,先扔进010看一眼
验明真身
文件头是51 46 49 FB,加上dirty bit、corrupt bit等可读文本,可以确定这是一个 QCOW2 (QEMU Copy-On-Write) 磁盘镜像文件。
这时候就要用到R-STUDIO,一款功能极其强大的数据恢复和磁盘策略工具。注意是R-STUDIO,不是RStudio,这是两个东西!
为了防止你踩坑,这里提供正确的下载方式⬇️
点我打开下载界面
然后你会发现正版下载要50刀…富哥富姐们可以直接购买,像我一样喜欢白嫖的那就仁者见仁智者见智啦,这里不多赘述哦
双击启动R-STUDIO,点击打开镜像,在弹出的对话框中,定位到data的存储位置。记得右下角筛选要选全部文件(. ),不然不显示。点击打开后,就可以看到左侧设备/磁盘一栏多出了镜像文件。选中所有镜像文件点击扫描,出现了Recognized 分区和原始文件分区。在Recognized 分区中找到了7-zip归档,对对应的原始文件进行恢复。
对镜像文件扫描后得到了两个分区
恢复7z文件
两个7z文件内容是一样的,选一个恢复就行,记得指定恢复路径。恢复后解压7z,发现all.zip,里面有64个png。简单查看发现似乎是64个二维码碎片,我们需要用脚本将其拼接。但仔细观察,二维码的定位角出现了12次,说明实际上是4个二维码,每个被切成了16份。一般来说,这种碎片都是脚本处理后的结果,碎片的产生具有时间顺序。所以我们优先按照时间顺序恢复,如果时间相同(相差极短),就按照文件名排序,从左到右,从上到下依次恢复成4张4×4的图片。
脚本如下:
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 import osfrom PIL import Imagedef solve_quad_puzzles (): source_dir = '.' valid_extensions = ('.png' , '.jpg' , '.jpeg' , '.bmp' ) files = [] for f in os.listdir(source_dir): if f.lower().endswith(valid_extensions) and not f.startswith('result_' ): path = os.path.join(source_dir, f) mtime = os.path.getmtime(path) files.append({ 'name' : f, 'path' : path, 'mtime' : mtime }) files.sort(key=lambda x: (x['mtime' ], x['name' ])) if len (files) != 64 : print (f"警告:检测到 {len (files)} 个碎片。当前逻辑按 16 碎片/图处理。" ) rows, cols = 4 , 4 img_per_qr = rows * cols sample_img = Image.open (files[0 ]['path' ]) w, h = sample_img.size for i in range (0 , len (files), img_per_qr): qr_index = i // img_per_qr + 1 batch = files[i : i + img_per_qr] canvas = Image.new('RGB' , (w * cols, h * rows)) for index, file_info in enumerate (batch): img = Image.open (file_info['path' ]) r = index // cols c = index % cols canvas.paste(img, (c * w, r * h)) output_name = f'result_{qr_index} .png' canvas.save(output_name) print (f"已生成第 {qr_index} 张二维码: {output_name} (包含 {len (batch)} 个碎片)" )if __name__ == "__main__" : solve_quad_puzzles()
扔进all文件夹里运行一下得到四张图片,发现只有第四张二维码是完整的。
result_4.png是我们需要的
扫描得到以下内容:
1 K5LGIS2TGBIXSVKEJJKVCVJRJBKGW4CZKNDFUSKUIVTTAUSWIJFFC2SWKZLFKWTBK5LGIWSVGFUFKVTKJF4VO3DIIZLFMULZKIYGG6KRKVCXSVLKLJDFKVSGIRLDCVSKKZDHAUCTKZSEUV2GJZMU4222IJLEMZCTKMYFUS2XKVDFOVCVOBFFE2S2JBKWYVSTKBKDAOKQKQYDS = = =
接下来就是赛博厨子魅力时刻。经过不断尝试,解密顺序为base32–> base64 –> rot13 –> base32 –> base64
DDDDDecode
flag{4a154507-ba7d-3f79-8739-91533b2bafc7}
未解之谜 题目名称:流量包中的秘密(这下真成秘密了)
附件放在下面了,哪位misc大手子看到了有兴趣可以解一下👇
📥 题目附件下载 文件名:flag.zip 大小:20566 KB点我下载