GeekChallenge题目难度好像都不算太大,思路也很清晰,咱得多学学怎么样能出出这样的题(而不是各种猜谜)
cheekin(不是checkin?)
关注公众号,给了张图片,拉进Stegsolve,直接LSB解了
ez_smilemo(一开始做的时候还叫ez_smile来着)
给了个data.win和smilemo这款虐心小游戏,既然是游戏,那基本往拆包的方向想
搜data.win,明白是GameMaker拆包,工具用的是UndertaleModTools
直接搜flag,双击就找到了
B64解个码出了
DEATH_N0TE
给了张kamisama.png,LSB直接拿到B64加密过的flag1
Stegsolve打开图片的时候刚好是放大的,发现有些奇奇怪怪的像素点,看起来是哥特体字母(其实搜”死亡笔记 字母表“就能找到一样的字体了)
简单用脚本提取一下,方便看
1 | from PIL import Image |
得到图片是长这样的:
读出来TkFNRV9vMnRha3VYWH0=,自己可以对照一下
两个flag拼一块,结束
下一站是哪儿呢
表情包里面藏了zip,zip里面给了指挥官基恩的文字,翻译一下就知道目的地是Liquor City
Liquor指的是烈酒,所以“酒城”指的是哪里,搜一下“中国 酒城”就知道答案是泸州
给的机场图片是深圳宝安国际机场的图片(蜂窝状的天花板太有辨识度了),那么只需要找在泸州和深圳往返的航班就可以了
FlightAware搜一下,发现只有两架航班,开爆!
爆出来结果是CZ8579(我当时提交了好几次CSN8579,死都不对,晕死)
这里解释一下为什么我会说爆(破),因为我查过两个航班在聊天记录当日的航班信息,结果时间基本与聊天时间相差1hr以上,所以无法确定“马上上飞机”指的是哪一架航班,总之就两架,大不了爆了
Qingwan心都要碎了
下面的图片确认地点成都,上面的图片确认在博物馆
仔细看能看到展品是有关三峡的文献资料,确认地点在重庆中国三峡博物馆,结束
xqr
给了张扫不出来的“QR”,扔进010里面发现文件尾还存在里图
两张图片提取出来,再根据题目名称xqr(=XOR+QR),将两个QR进行异或运算:
1 | from PIL import Image |
我得到的图片是反色的QR,那就再反下色就行了,操作完成之后扫就行了
DEATH_N1TE
给了个webp动图,先把所有帧提取出来再说:
1 | import os |
稍微用magick看了下帧时间,好像没啥东西,那就直接拼接图片吧:
1 | from PIL import Image |
得到的图片却是这样的…
这个时候就不得不请出我们的gaps来帮我们拼了:
1 | gaps run output.png output_fin.png --generations=30 --population=880 --size=48 |
population指的是拼图碎片的个数;size就是大小,这里碎片都是48*48的,所以size=48;generations就是迭代几次,这个参数调大一点,多了它会直接结束并告诉你已经没法再优化了
最后得到图片:
至于那个mp3,最后面有SSTV,一扫就出来了,没啥好说的
窃听风云
流量分析,看到下面聊天记录说密码是在rockyou里面的,咱们先记下来
上面有个NTLMv2的认证过程,那我们就去搜一下NTLMv2…
搜索栏很智能的告诉我”NTLMv2 hashcat”,那么就是可以爆的咯
跟着搜索,就可以知道有个东西叫NTLM哈希爆破
由于NTLMv2的哈希是和Server Challenge经过加密运算得到Response的,而v2的加密用到了用户名,域/服务器名和NTProofString,所以我们需要提交的信息就显而易见了(详细的NTLM加密过程可以看这里)
在流量包7获取到Server Challenge,流量包8获取到其他信息,最后我们得到如下的信息:
1 | jack::WIDGETLLC:2af71b5ca7246268:2d1d24572b15fe544043431c59965d30:0101000000000000040d962b02edd901e6994147d6a34af200000000020012005700490044004700450054004c004c004300010008004400430030003100040024005700690064006700650074004c004c0043002e0049006e007400650072006e0061006c0003002e0044004300300031002e005700690064006700650074004c004c0043002e0049006e007400650072006e0061006c00050024005700690064006700650074004c004c0043002e0049006e007400650072006e0061006c0007000800040d962b02edd90106000400020000000800300030000000000000000000000000300000078cdc520910762267e40488b60032835c6a37604d1e9be3ecee58802fb5f9150a001000000000000000000000000000000000000900200048005400540050002f003100390032002e003100360038002e0030002e0031000000000000000000 |
把这个装成文本文件(这里称作crackme.txt),然后用hashcat以rockyou作为字典进行爆破:
1 | ./hashcat -m 5600 crackme.txt rockyou.txt |
过段时间就爆出来了
extractMe
有笨蛋居然忘记了CRC爆破(没错,就是我)
文件夹里面的flag都只有4个字符,足够短,可以进行CRC爆破:
1 | import binascii |
拼起来结束
时代的眼泪
一看就知道是WinXP取证,个人习惯用DiskGenius打开磁盘
打开磁盘,直冲Administrator(C:\Documents and Settings\Administrator),看看最近都在干些什么(查看Recent文件夹)
看到有两个链接(.lnk),指向的是C:\Documents and Settings\All Users\Documents\My Pictures\Sample Pictures\bliss.jpg
我们直冲这个目录去,一打开图片,flag就映入眼帘啦
SimpleConnect
简单的合约交互,没啥难度,和我出的题目“Five Nights at Sepolia’s”异曲同工,此处不过多赘述
give_me_Goerlieth
小狐狸转账,结束
DEATH_N2TE
一个简单的mp4提取影像流和像素提取
先拆帧:
1 | # 导入所有必要的库 |
然后和死亡笔记0那题一样的位置,提取特殊点:
1 | from PIL import Image |
然后就读flag,结束
窃听风云-V2
窃听风云套了个盒子而已
表面上看是SMTP流量分析,但是发现里面传输的数据B64解码之后就是NTLMv2
那有什么好说的,同理可爆(只是注意一下域名是WidgetLLC.Internal而不是WidgetLLC)
stage
这题挺好的,先放题目合约:
1 | ///SPDX-License-Identifier: MIT |
先分析一下具体的合约:我们需要的是让flag[tx.origin],也就是flag[自己账户地址]=true,而为了达成这个目标我们得完成3个stage
stage1是一个简单的extcodesize判断,如果是已经创建好的合约就无法通过stage1,只有账户地址和还在构造中的合约才能做到extcodesize=0
stage2就是一个猜数字,不过由于在区块链这种一切公开的地方,以块/交易特征生成的伪随机数是可预测的,这里就是个最简单的例子:生成的伪随机数就是块的时间戳%100然后+1
_stage3一开头又确认了extcodesize>0,这种情况只有已创建好的合约能通过判断;接下来就是两次对调用stage2(3)的合约(即我们的攻击合约)的getNumber函数进行一个call和一个staticcall,然后比对两者返回的bytes是否一致,只有不一致才能通过判断
最上面就一个接口,不用怎么管
难点不多,stage1就是一个extcodesize绕过,由于_stage3又需要msg.sender是合约,所以第一步就是一个构造器绕过:
1 | constructor(){ |
stage2直接设一个uint guess=block.timestamp%100+1就好了,没什么难的
重头戏在_stage3这里,如果只是单纯的call,那么我们自然可以在第一次call之后修改合约状态然后再第二次call,但是staticcall就有个问题:它不允许被call的合约发生状态变化,如果发生了就会直接revert,导致交易失败,所以设定全局变量进行更改这一步算是走到底了
但是假如我们call一个外部变量,又会怎么样呢?因为即使我们外部call,最多也只是返回一个状态码0,不会阻碍我们原来合约的一个交互,所以我们可以从这里下手:
比如,我们新设一个Dummy自毁合约:
1 | ///SPDX-License-Identifier: MIT |
由于这个合约会进行自毁,合约状态会发生变化,所以如果是staticcall,那么返回的肯定是false;而call的话由于没有上面的限制,返回的自然是true
所以我们的getNumber函数就可以这样写:
1 | function getNumber() external payable returns(uint256){ |
我们再加一个check函数的调用和攻击合约创建者的声明,于是最后我们得到了这样的poc:
1 | //SPDX-License-Identifier:MIT |
1 | ///SPDX-License-Identifier: MIT |
同类型的题目还有paradigmCTF 2021的babysandbox
- 本文作者: 9C±Void
- 本文链接: https://cauliweak9.github.io/2023/11/10/GeekChallenge2023-Writeup/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!