0x00 前言
本来这只是一个社团的招新 CTF, 难度可能比较低, 不过我实在是闲的发霉就稍微来玩了一下, 顺手水一篇 Writeup.
文章内图片较多, 请注意流量. 此外由于题目是每日上新, 文章中题目出现的顺序并不能代表难度.
由于本次比赛是社团内部招新, 不对非 SEU 人员开放, 涉及到的服务器地址均进行了打码.
此外, 由于未经过多次审阅, 文章中可能存在错漏, 如发现问题请通过邮件联系我或直接在评论区留言.
0x01 Hello_SUS
直接输入Flag, 完成 这也要水一个题么
0x02 misc_hhh
提示就不管了, 都是后面加上去的, 懒得再看
下载到一个 hello.gif, 先丢给 binwalk 跑一下, 发现里面藏了个 zip
解压后得到两个小zip, 一个有密码一个提示文件损坏
损坏那个很清楚的提示了 fix_head, 用 HEX 编辑器打开, 第一眼就看到一个 50 4a.
改成 50 4b 保存, 检查一下后面的头是没问题的(否则可能打开连目录都显示不了), 解压后得到一张图片, 图片名称本身提示了一个网站
在该网站中找到一个解密工具, 丢进去获得 Flag 后半段
前半段应该藏在那个 12_letters.zip 里面, 这个12提示的应该是指密码长度, 所以不太可能是假加密. 研究了一会这个 png 大概是没啥东西了, 只好把原来那张gif丢去 Stegsolve 里面看看.
发现里面藏了个二维码, 扫出来的 Base64 解码后得到 …. .. … … -. --- - ..-. .-.. .- --.
扔进 Morse Decoder 得到一个字符串, thissnotflag
, 刚好12个字符. 用这个作为密码解压 12_letters.zip 得到 Flag 前半段, 完成.
0x03 misc_half
提示给的已经很明显了…下面连CSDN都找好了. 这题一部分肯定是和高度有关的. 不过拿到文件名称是 look_attribute, 我们还是先看一下属性
第一个是暂时不知道干啥的 HEX Offset, 第二个是图片的 data url, 随便找个 有img 元素的网页 复制进去发现是个二维码, 得到了 Flag 前半段
后半段肯定就是在被改高度截断的部分了. 丢进 HEX 编辑器后发现图片前面还有一段数据, 暂时没找到 JPEG 头, 跳到之前的 Offset 看看.
很明显了, 这玩意就是 JPEG 头. 再看一眼后面的高度是 01 4C, 结合之前给的 01->02 这个提示, 都不用试着改直接就能复原高度.
扫一下得到一串核心价值观, 拿到 这个工具 解码得到 Flag 后半段, 完成.
0x04 OpenCV_easy
因为我这只是玩玩, 所以解法就比较随意了. 要什么 opencv, 丢进 PS, 替换颜色, 容差1直接搞定
0x05 checkin
丢进 IDA, 发现是一个某些物联网公司的代码水平的字符串匹配.
一样的整点骚操作, 复制进NPP, 先把空格去掉然后稍微整几个正则替换一下.
最后替换成了这样: 83,85,83,67,84,70,123,87,101,108,99,48,109,101,95,116,111,95,83,85,83,67,84,70,95,50,48,50,48,125,30
然后随便找个网页打开 DevTools, 怎么简单怎么来
0x06 ez_x1r
嗯? 你们社团还上课的么, 反正我不知道.jpg
丢进IDA, 发现就是简单的把输入 XOR 1 后和一个全局变量进行比较.
拿到这个字符串, 然后, F12.jpg
代码我没细看, 不知道为啥掉了个S, 反正手动补上就好了.
0x07 wait_for_AA
照样丢进IDA, 发现是一个超长 sleep 后面输出 Flag
这里当然可以选择把 calc_flag 单独剥离出来跑一遍, 不过我懒, 直接打补丁
然后丢进 Linux 虚拟机跑一下, 完成
0x08 bbbb4444se
一开始还以为就是个 Base64, 不过解码了一下发现事情好像并没有这么简单.
感觉改算法的可能性不大, 先看看它的 Base Table 是不是改过.
注意红框中部分, 正常的 Base64 表是 A-Za-z0-9+/
, 这里明显把 a-z 和 A-Z 调了一下位置. 那这怎么办?
还记得我的 F12 么, 先随便找一个 JavaScript Base64实现, 把它的表稍微改一下, 解码, 完成.
0x09 mengxin_lfsr
一开始拿到这题我是拒绝的, 因为要研究新的加密算法, 我前几天才在 [数据删除] 折腾加密算法折腾到吐.
不过大概看了一下, 你这个题好像 Flag 不长啊?
嗯, 咱不会逆算法但咱会爆破. 不要吐槽为什么最大值要设为 500000, 顺手敲的, 反正取第一个值就好了.
然后丢进计算器, 得到 Flag. 你问我为什么不用 Python 直接输出? 因为我并不熟悉这语言, 就是不会的意思.
0x0A ez_http_header
第一个挺明显的, 加个 Referer 就好了.
第二个翻了半天RFC, 试了 Authentication, UA, XFF 都不对, 结果是要在 Cookie 里整一个 user=admin
, 真就比脑洞.
这个更简单了, UA 里直接设成 SUS
第四个, 明显是 Client-IP 或者 XFF, 两个都打上就能 Bypass (其实只需要 XFF), 完成
0x0B easy_http
打开发现一个 “检测系统”, Ctrl+U 先看看源码.
在底部发现一段代码, 应该就是点击提交的时候 POST 到的 ctf.php
的源码, 我不是很懂为啥它要写个 /admin.php, 误导了我半天.
看这源码还不支持 HTTPS 的, 有够丢人. 只好随便找个服务器配置一下记录日志.
填入对应的URL, 提交, 完成.
0x0C Easy_php
这个题嘛, 当然可以写个 PHP 进行碰撞, 原理很明显是让两个 MD5 都变成 0e一串数字
的形式, 然后用两个等号比较的时候就等价于 0==0
. 不过我为啥不直接 Google 一个现成的值呢
页面上还有一个前端限制最大长度为5, 照样 F12 改一下, 提交, 完成.
0x0D 武林秘籍
打开页面就有一个不工作的按钮和下面不知道飞哪去的 Copyright. 只好丢进 Postman 提交试试.
随便写个数字会提示 x 不能是数字, 而随便写个字符串会提示 x 太小, 估计是下面这种代码
if(is_numeric($_POST['x'])) die('XD');
if($_POST['x'] < 100) die('XDD');
这种情况应该就只能考虑用提交数组绕过了, 可能还能提交其他值不过我没想到.
绕过后得到一个奇怪的 Flag, 提交了一下提示 Flag 错误, 看来是得检查 robots.txt
解码得到一个字符串 th1s_1s_y0ur_fl4g.php
, 访问一下康康.
进来又是老套路, 整个 Cookie, user=admin
, 完成.
0x0E upload
进来看到一个简单的上传 Form, 简单的测试后发现只允许上传图片. 康康源码, 下面直接给了一个 include, 这难度压的确实很低. 不管怎么说, 先找个图片加段一句话扔进去吧.
结果这玩意还不让我直接写 <?
这个标签, 只好改用 <script language=php>
然后丢进 Postman 读一下 Flag, 完成.
0x0F Have_Fun
打开看是一个简单的登录页面, 使用 admin/admin 可以登录成功但没什么卵用.
康康源码, 居然是把用户凭据装在 XML 里提交的? 再结合这玩意是 PHP, 并且会在返回信息中附上提交的用户名, 想到可能是 XXE Attack.
构造一个 Payload 进行测试, 果然就打出了 SUS2019.php
的内容. 这里还有个坑, 页面提示里文件名是全大写的, 结果实际的文件拓展名是小写
看一下这个文件应该是用 parse_str
覆盖全局变量实现任意包含, 不过 SUS2020.php
是不存在的, 试了下 /flag
也不存在, 只好考虑远程包含.
构造一个写一句话的 PHP, 执行即可. 当然我省略了判断是否可写之类的, 因为之前已经做过了.
列一下根目录, 发现一个 flag 和 readflag. 但是刚刚为什么直接包含 /flag 失败呢?
进一步查看权限, 原来 flag 被设成了100… 直接执行 readflag, 完成. 顺手删了一下 shell 文件, 不留垃圾.
0x10 Download_ctf
拿到题目, 相当的简洁, 只有一个 URL 告诉你下一步. 初步分析是将文件名 Base64 编码后提交. 此时访问一下 /flag.php, 发现不是 404, 文件是存在的.
把文件名改成 flag.php
试试, 发现 php 三个字被删掉了. 这里还不能确定是什么过滤, 需要进一步验证.
提交 flag.pphphp
, 发现显示出了正常的 flag.php, 看来只是简单的 str_replace. 但是仍然提示 flag.php 无法找到, 可能是跑去子文件夹里找了. 先采用一层一层往上翻目录尝试读取 /etc/passwd 的方式先看看这文件夹有几级.
- ../etc/passwd
- ../../etc/passwd
- ../../../etc/passwd
- ………
- ../../../../../../../etc/passwd
找到目录级数后, 再一级一级的往下读 flag.php
结果发生了有趣的事情, 只到了第一级目录提示信息就从 can’t find 变成了 can’t down, 这也许暗示了路径是对的, 但我们需要采用其他方法读取 flag.
于是改成读取 index.php, 打开发现正确获取 flag 的姿势是设置 Cookie 并访问 ?method=login
, 完成.
0x11 Hello_pwn
nc 连上, 送分题.
0x12 use_pwntools
简单的写个小脚本连接, 这里是不能用 Python Interactive Shell 的, 会造成无法显示 Flag. 如果不喜欢被刷屏这里也可以加句 r.recvuntil('SUS')
, 我倒是不介意.
直接拿到 Flag, 也是送分题.
0x13 pwd
我 PWN 超蔡的, 如果有写错了请各位指正.
看一眼题, 教科书式的溢出, 还送了一个 gift 函数直接运行 shell.
直接上 GDB, 先把程序炸了再说.
现在程序跑到 main 函数的 ret 了, 看一下寄存器发现 RSP 被淹了, 直接找到 Offset. 顺便用 info address gift
看一眼 gift 函数的地址.
简单构造 Payload, 完成.
0x14 input
你们说这个炒鸡简单, 但是我炒了好久啊… 我还是太蔡了.
丢进 IDA 看一眼, 大概是读了一个 position, 然后从 v7[pos]
开始写输入 a 的数据. 这里 a 的长度被卡的很死, 刚好 8 bytes 够塞一个指针, 看来只能从这个 position 做手脚.
这里先贴一个 64 位的栈帧结构, 来源见水印(右下角). 我们的目的就是把返回地址淹掉, 跳转到 gift 函数(这个文件也有 gift)
切回 IDA 进行分析, 发现主函数分了 96 bytes 的栈.
然后查看主函数的栈分布情况, 可以发现我们要写入的 char 数组就位于栈顶, 因此我们只要让一开始的 position 为 96 即可直接写到 RBP, 但是我们要淹的是 RBP 前面的返回地址, 因此 position 还需要再加 8.
由于这里读入的 position 后面会转成 ushort, 而且在程序开头有输入大小不能超过 100 的限制, 考虑利用负数溢出到我们想要的值. -65536 + 104 = -65432
完成, 这大概我玩 PWN 的第二个程序吧…
0x15 Pwn3-format
这题解的真是一言难尽…首先拿到文件丢进 IDA, 一眼就看到左边有个 getflag
函数, 然后, 就研究了两天到底怎么让这个 printf 写到返回地址跳转去 getflag
….
不料下面还有个 when(year)
长这样. 所以只要能让传入的参数 a1 == 2021
成立, 即之前传入的全局变量 year 为 2021 即可解出.
先用 gdb 取得 year 的地址: 0x804C040
然后构建一个简单的脚本. pwntools 的自动测试 offset 功能很方便, 都不需要去分析 binary.
直接执行, 完成.
0x16 迷宫
一开始还以为图片里藏了啥, 不过后来研究了一圈啥都没有, 想想既然放到 crypto 了应该是什么移位密码.
稍微数了一下图片里的格子刚好是25个, 然后密码字符串也是25个字, 那无非就是两种可能, 一是将密码按顺序填入格子然后走一遍迷宫得到解, 二是按着走迷宫顺序填入密码然后得到解.
观察一下密码, 很幸运, 尾端出现了 SUS 三个字, 而这恰好是 Flag 的开头. 整个密码中只有这三个大写 SUS, 绝对是它没错. 再按照迷宫布局大概试一下无疑是从左下开始顺着迷宫填字.
在格子里按之前找的顺序填字, 再把下面的图层扔掉, 完成, 拿到个 FB 很开心.
0x17 rand
这题卡了我好几天了… 题出的很简单, 就是让你和服务器对一下时间然后按着 C++ 的伪随机数生成器生成100个随机数.
但是 pwntools 是 Python 写的, 而 Python 生成的随机数和 rand()
的结果相差甚远.
不过后来我发现了 这个 Gist, 能在 Python 中直接调用 libc 来生成随机数, 事情一下就变得简单了起来.
构造这样一个简单的脚本, 就可以成功拿到 shell. 但是在远程执行的时候碰到了一些问题, 初步猜测是时间与服务器不同步. 如果是因为 libc 版本不同而导致 rand 实现不同, 那这麻烦就大了.
稍微把脚本改一下, 给时间加一个偏移量, 然后我们来爆破这个偏移.
跑脚本拿 Flag, 完成. 这题真不应该扔到 pwn, 我觉得就是个 misc 题.
0x18 ez_sql
这题, 搞清楚过滤了啥然后丢进 sqlmap 就出来了. 不过那个没啥意思, 还是表演一个手工注入.
打开题目发现是一个简单查询页面, 通过整数ID查询用户.
但是初步测试的时候发现过滤了空格…这里考虑用 /**/
代替空格, 当然你也可以用 Tab 和其他字符.
先把数据库名字整出来
然后再看看表名, 这里用的是 1/**/AND/**/0/**/UNION/**/SELECT/**/table_schema,/**/table_name/**/FROM/**/information_schema.tables/**/where/**/table_schema='security'/**/limit/**/1,1
总共发现了 emails
,referers
,uagents
,users
四个表, 先看看这些表里有啥. 同理把各个 column 列出来, 用的 payload 是 1/**/AND/**/0/**/UNION/**/SELECT/**/table_name,column_name/**/FROM/**/information_schema.columns/**/where/**/table_schema='security'/**/limit/**/1,1
后面就是重复的扒数据时间, 扒到第8条的时候发现了 flag, 运气真好啊(迫真) 还是推荐直接上 sqlmap, 把整个裤子拖下来慢慢看 csv 比较舒服.
0x19 遇事不决?
拿到题目发现最外面是一个 “加密” 压缩包, 但是用 HEX 编辑器打开查看发现数据区两个文件的加密位都是 00 00, 典型的假加密压缩包.
拉到下面的目录区域, 找到加密标志 09 00
均修改为 00 00
即可解压两个文件.
解压后得到另外一个加密的压缩包, 大致检查了一下是真加密. 注意到有一个文件和之前明文压缩包内的文件是相同的, 这里就回到了新年红包那会玩的 zip plain text attack了.
具体的攻击细节请参考 奶冰的 2020 新年红包 Writeup, 这里因为已经提供了明文压缩包, 就不存在压缩软件不同导致无法破解的问题.
不过这里还有一个小问题是 rbkrack 是不支持 GBK 编码的, 中文文件名称传入会导致无法识别, 我们可以考虑采用 -a 参数让程序自动找出相同的文件进行攻击.
这个压缩包还是挺难跑的, 跑了四分钟左右才攻击成功. 然后同样的套路进行解压, 第二层就解开了.
第三层里的压缩包换成了 RAR, 而且开了目录名加密甚至没办法看到文件. 提示还给了个佛言加密.
到 这个在线工具 解码给的提示, 就能得到密码, 第三层就解开了.
然后第四层又是一个加密压缩包, 这次只给了密码长度和结构的提示.
先试一下爆破, 由于 Kali 自带的 rarcrack 是不支持指定密码结构的, 这里先打个字典然后上其他工具. 我看了一下发现 rarcrack 可以通过改进度文件实现指定伪密码结构, 下面直接开始爆破.
跑了半天终于跑出来了, 然后得到了一个文件名带 emoji 的图片.
打开发现是一个被抹掉定位点的二维码.
丢进 PS 修一下, 扫出来就是 flag. 这题没抢到 FB, 主要是中间跑去买了个 010 Editor 然后还水了会群 Frhed 实在是太难用了.
0x1A 氪金让你变强
访问题目看一眼, 是服务端用 curl 请求了一个地址然后比对返回数据. 这里我最开始能想到的有三个解法. 如果你有更多 idea 欢迎在下面留言.
0x1A-1 使用 File 协议直接读 Flag
这个思路很简单, 直接使用 file:///
伪协议读取 flag.php 就行了, 有难度的主要是怎么获取 Web 路径.
但是这场比赛的路径几乎都是默认的 /var/www/html
. 很幸运的, 这台服务器也是, 轻松拿到 flag.
0x1A-2 使用自己的服务器返回数据
这个算是比较直白的解法了, 只要你手上服务器多有服务器, 随便找一台丢个 PHP 进去返回 $_POST['x']
即可. 这大概就是题目暗示的 “氪金”?
0x1A-3 白嫖比赛题中的服务器
这个思路和上面的差不多, 不过这是我们没有钱又想做题的情况. 比赛题中有几个上传漏洞, 我们只要随便找一个题传一个 PHP, 再让这边请求一下就好了.
0x1B easy_unserialize
教科书似的反序列化, 连 flag 在哪都说了…
我不会手敲序列化字符串, 所以在本地写一个 PHP 文件稍微输出一下.
然后直接提交就完成了, 送分题.
0x1C where_1s_AA
这道题确实比较不一样, 丢进 IDA 先看到的就是一个逗人玩的 main 函数, main 上面的 check 就是实际输出 flag 的函数, 不过当时我没注意到这个
最开始分析的时候注意到一个 _init_proc 函数可能会有关, 刚好手上建好了 Debug 环境所以直接下断点跟进去 (其实这里不需要断点, 直接静态分析追进 base_init 后面就很清晰了)
进了 base_init 可以清晰的看到这里取了 CWD 然后直接传入 check 函数, 继续追进去
最开始 IDA 转出来的伪代码并不是这样的. 现在的是稍微修复后的结果.
由于字符串里塞了大量的 ‘\0’, 一开始 IDA 显示的数组定义看起来就是这种↑
拉到下面看, 对 a1 的引用很明显是字符串取索引 i, 同理下面的 v7 是我们这一堆数据的开头, 很容易看出其实是一整个字符数组. 然后用 IDA 的新建 array 功能稍微修复一下就好了.
修复后的代码大概是这样的, 原来的 f()
实际上就是将参数原样返回. 可以注意到这里有个 4 * i
, 每个 0 都被跳过了.
然后我们要做的就是将这个字符数组弄出来. 简单的骚操作可以参考上面的 0x05 checkin, 最后得到的字符串是 %jcf%77U'iU_dUOekhU^[Whj
注意到下面 v2 在比较前还有个 + ‘\n’ 的操作, 所以我们再按一下 F12.jpg
这样就把满足 v6 == 1
条件的 CWD 找到了.
如果你疑惑为什么我不直接爆破要去找 CWD, 看后面的引用中有 CWD, 就算我们爆破了后面输出的 flag 也不太可能是正确的.
然后把程序放到我们找到的目录里跑一下, 完成.
0x1D 音游
拿到题目发现是一个在线的音游, 判定很严格而且做工看起来不是很好…大致翻一圈请求没有直接搜到 Flag.
追踪失败关键字 “菜” 我们可以看到判断游戏失败实际上是在判断 models.cube
和 models.auto_cube
的位置偏差是否过大.
虽然我现在也不知道 Flag 在哪, 反正时间很充裕, 现在的思路大致是先整一个自动过关把游戏打通了再说. 我们可以想办法让 cube 和 auto_cube 的位置更新同步, 这样就可以实现全自动过关了.
继续分析之前加载 Cube 的部分, 可以发现这个部分给 auto_cube, cube1, cube2 都加载了 KeyFrames
, 初步猜测这个 KeyFrames
很可能就是 Beatmap.
由于这些 Cube 都采用了几乎相同的初始化方案, 不妨试一下直接给 cube.KeyFrames
赋个值.
所以就变成全自动了 , 一路上都没见到啥特别的, 但是在最后我们可以看到路上躺了一个蜜汁字符串.
不难看出这个字符串是一个 3D 对象, 所以我们来请求里面找一下, 发现了一个很可疑的 secret.obj
在 WebGL 里加载的 .obj 文件八成是 3D 模型, 直接扔进 Blender 我也不知道为什么我 Steam 库里有这玩意反正就是有, 得到 Flag, 完成.
0x1E xuanjiang
宣讲? 那是什么东西我不知道啊.jpg 不过题还是要做的.
看一眼题, 大概有三个地方需要绕过, 都是很简单的玩意. 这个也是送分题.
第一处, 利用 PHP 在比较 int 和 string 的时候会截断 string 最前面的数字进行比较就可以过.
第二处整个 URL 编码就可以过了… #'&
-> %23%27%26
第三处, 由于 stripos 返回的是字符串首次出现的位置, 只要我们提交的 Payload 里开头就是 flag, 这个判断条件就变成了 0 > 1, 就可以到下一步, 完成.
0x1F rop_1
啊, 懒得写了, 大致思路就是找块空内存, 调用 read 读一下 /bin/sh
然后跳到 system 执行就好了. 中间踩了不少坑, 我的 PWN 还是蔡.
直接放 Payload 吧
0x20 消失的flag
拿到程序先跑一下, 发现输出了一些很像 ASCII 字符画的东西.
丢进 IDA 看一眼, 发现这里用随机数输出的方式来打印各行, 算法比较绕不好分析.
但是我们可以根据题目意思将种子 Patch 为 0, 然后直接运行就可以出结果了.
如果屏幕不够宽可以把自动换行关掉查看.
0x21 ez_cpp
拿到程序丢进 IDA, 这不像是 cpp 啊… 掏出 DIE 看一眼, 原来是外面套了个 UPX, 直接使用 upx -d
就可以解开.
解开后直接分析 main 函数, 前面的都是一些输出, 可以看到在这里构建了一个一看就不是STL函数 的 Str1ng
. 并且将 SUSCTF{this^1s^n0t^fl4g}
作为参数之一传入.
追进 Str1ng 可以看到这个函数中读取了我们的输入内容, 然后作为参数传给 Str2ng::trans
函数.
之前传入的那个字符串被作为 Str2ng::Str2ng
的参数之一. 这个函数只是简单的进行了赋值, 没什么特别的.
下一步追进 Str2ng::trans
函数, 发现之前传入的 a2 其实只是一个 char, 并且使用的时候进行了一个 a2 - '0'
的操作, 很容易联想到可能是要输入一个数字.
继续分析代码, 代码将 this[i]
进行 XOR 操作, 并且是从 i = 7
开始的, 刚好跳过了上面字符串中的 SUSCTF{
这几个字.
接下来就简单了, 暂时不去分析 out 中判断输入是否正确的操作, 我们先把数字 0-9 试一遍. 运气还不错, 在输入 1 的时候就拿到了 flag.
0x22 ret2moon
拿到题目跑 checksec 的时候发现开了一堆保护, 初步估计是不能直接写返回地址跳转了.
丢进 IDA, 检查字符串的时候发现一个 system("/bin/sh")
, 这块代码本来是没分析出函数的, 就紧跟在 main 下面. 总之思路肯定是跳转到这个函数, 只是暂时不确定如何操作.
检查输入函数的时候发现了一个可控的偏移量 target, 这里就比较有意思了.
如果我们的 target 足够大, 就可以超过 buf 然后淹到堆栈上面. 注意到下面又输入了一个 char, 再结合上面 main 函数的返回地址和 moon 函数的入口地址只差了最后一个字节, 大致就有了利用思路.
查看反汇编, 计算出我们需要的 target 值是 0x30 + 8
, 接下来的事情就简单了.
构造一个简单的脚本, 执行即可获得 shell, 完成.