0xGame 2025 Week1 WriteUp
0xGame 2025 Week1 全部题解(WP) by 正规子群 https://github.com/n-WN 观察 这些要素与日本静冈县伊东市著名的火山渣锥「大室山」($\text{Oomuroyama}$)高度吻合。该地常被中文游客称为“抹茶山”,因其夏季整座山被绿色草地覆盖,外形如抹茶蛋糕。网上实景图亦可见山脊步道与鸟居,和本题图像构图相符。 因此山名取为: 我们对 得到(关键字段): 将度分秒换算为十进制度: $$ \begin{aligned} \varphi_{\text{deg}}&=d+m/60+s/3600,\ \lambda_{\text{deg}}&=d+m/60+s/3600. \end{aligned} $$ 代入数值: $$ \begin{aligned} \varphi_{\text{deg}}&=32+\tfrac{7}{60}+\tfrac{8.98}{3600}=32.119161\dots,\ \lambda_{\text{deg}}&=118+\tfrac{55}{60}+\tfrac{35.69}{3600}=118.926580\dots. \end{aligned} $$ “不进位保留四位小数” 等价于向下截断至 $10^{-4}$ 位: $$ \operatorname{trunc}_{4}(x)=\left\lfloor x\cdot 10^4 \right\rfloor / 10^4. $$ 因此: $$ \varphi=32.1191,\qquad \lambda=118.9265. $$ 最终 flag: 备注:第二张图为室内拍摄的卡通头套,但其 EXIF 保留了拍摄位置坐标,这是 OSINT 中常见的“元数据泄露”。 若需验证读取到的 DMS 值,可使用: 页面直接 因此可以构造以下调用链: 只需把 利用 PHP 的类型转换即可轻松满足:令 脚本会自动构造 payload,触发链条,并从响应中的环境变量提取 flag。 后台源码未公开,但通过交互可推断处理流程: 初次访问页面可见提示:“用 GET 传递 hello=web”。按照提示逐步构造请求: 服务以逐步提示的方式引导我们补全 HTTP 请求要素。可以理解为一个多条件验证链,只有全部满足后才会执行最终分支返回 flag。 脚本路径: 核心逻辑: 运行方式: 输出将包含完整页面内容及 flag。 源码(首页直接 要点: 综合得到安全过检的 payload: 满足条件的整体请求: 已编写脚本: 运行(建议使用 uv 以独立环境运行): 输出示例(截断): 本题是一道典型“对象属性污染(object attribute pollution)→ 文件读取(LFI)”的 Flask/Jinja 利用。核心并非 JavaScript 的“原型链污染”,而是 Python 语境下通过不安全的递归 merge 将用户提供的 JSON 写入到应用对象图,进而篡改 Flask 应用的内部配置(如 本文面向教学,分三部分: 题面(简化排版,完整见 可见: 用函数 $\operatorname{M}(\cdot, \cdot)$ 表示合并: 因此,给定键序列 $(k_1, k_2, \dots, k_n)$ 与叶值 $v$,只要沿途总能走到“可递归/可写”的对象 $o$,就有: $$ \operatorname{M}({k_1:{\cdots {k_n: v}}}, ; \texttt{Game0x}) ;\Longrightarrow; \texttt{setattr}(o, k_n, v). $$ 这等价于“用户可对对象图中任意可达节点成功写属性”。 在 Python 中,函数对象有 即可令 对照文档: 用 Jinja 思路:将 步骤: 实测返回: 在容器/CTF 环境中,1 号进程的根目录符号链接 先把静态根映射到 若可读,说明对象污染链生效且静态路由可用。 (以上链接与说明已通过 MCP context7 抽取核实要点:函数对象的 本文记录一次基于 Flask/Jinja 与“对象属性合并”污染(pollution)的利用过程。目标为一段简化的 Flask 服务,核心漏洞在于对用户提供的 JSON 递归合并到应用全局对象上,导致可写入 Flask 实例内部属性,从而改写模板或静态资源的加载根目录,进而实现任意文件读取(LFI)。 服务核心代码如下(节选并适当排版): 要点: 通过 解释: 因此,我们的两步利用为: 记运行目标为 将静态目录映射到 若返回 passwd 内容,说明污染链生效且可经静态路由读取。 把 实际输出示例: 这一步绕开了 形式化地看,若我们构造键序列 $k_1, k_2, \dots, k_n$ 以及叶子值 $v$,使得在归并路径上始终满足“可递归”或“可写”条件,则最终有 $$\operatorname{merge}({k_1: {\cdots {k_n: v}}}, \mathrm{Game0x}) \implies \mathrm{setattr}(o, k_n, v)$$ 其中 $o$ 是通过前缀 $k_1,\dots,k_{n-1}$ 导航到的中间对象(如 原本也可将 许多容器化/CTF 环境中,1 号进程的根( 建议使用独立虚拟环境(如 脚本逻辑: Flag: 最初访问 http://80-29913a1b-3472-4ff2-87f9-bafd4a69ee14.challenge.ctfplus.cn,页面仅显示一行歌词,并通过 JavaScript 监听 在命令行中执行: 返回的 HTML 中包含注释 为便于复现,我在 脚本输出 文件: 逻辑: 使用方式: 输出示例: 题面强调: 据此推断,真正的唯一性不在于字符串本身,而在于“来源位置与语义”。 镜像分块 假旗的海洋 唯一性线索:禁区与真实挂载 结论 路径: 逻辑: 运行: 运行示例输出: 位置: 依赖: 用法: 输出: Office OpenXML 文件本质是 zip 压缩包,只要定位到隐藏的 运行脚本: 输出: 位置: 依赖: 用法: 输出示例: 核心逻辑位于 运行: 得到终极答案: 题目考察了对基础编码与经典移位密码的识别。一步步拆解编码层次、验证偏移量即可快速复原 flag。 偏移 $= 0x30 + 8 = 0x38$,构造: 数学化: $$ \text{ret} \to (\text{pop rdi;ret}) \Rightarrow rdi \leftarrow \text{addr}(“$0”),\ \text{ret} \to system. $$ 得到 shell 后,直接 见 即使二进制中没有 因此,偏移 $= 0x20 + 8 = 0x28$;链路: 设返回地址覆写为序列 $[G, A, S]$,其中 $$ \text{ret} \to G \Rightarrow rdi \leftarrow A,\ \text{ret} \to S \Rightarrow system(A). $$ 见 成功弹 shell 并 当前目录附带可执行文件 因此最小利用为 ret2func:把返回地址改为 注意:由于 I/O 交错, 若返回地址位于栈帧布局: $$ \underbrace{\text{buf}[0..0x2F]}{0x30\ \text{bytes}};\Vert;\underbrace{\text{saved\ RBP}}{8\ \text{bytes}};\Vert;\underbrace{\text{RET}}_{8\ \text{bytes}} $$ 则向 路径: 运行: 输出示例: 本题为经典 ret2func 基础练习:找到可调用的“后门”函数 远程服务在连接后会连续抛出 1000 道四则题目(仅含 $+$、$-$、$x$ 三种运算)。每答对一题反馈 对任意题目,记 $A,B\in\mathbb{Z}$,运算符 $\circ\in{+, -, \times}$,目标即计算 $$ \text{ans} = \begin{cases} A + B, & \circ = +,\ A - B, & \circ = -,\ A \times B, & \circ = \times. \end{cases} $$ 乘法题数量明显最多(约占 $2/3$),若单轮耗时 $t$ 秒,全部完成需要 $1000t$ 秒。故必须自动化避免人工输入。实现关键点: 源码: 首轮运行会自动在当前目录创建 这是一个典型的“连问”脚本题,核心在于: 题虽标注为 Pwn,但本质上是简单的交互自动化练习,难点在耐心与脚本正确性。 上述过程可以抽象成一次简单的命令注入模型。设服务端执行逻辑为 $$ \text{output} = \text{sh}(\text{user“+“cmd}) $$ 只要我们提供的 源码位于 理论上 flag 长度有限(这里为 28 字符),一次 第一次执行 该题本质是确认远程服务直接执行我们输入的命令。在没有额外黑名单或沙箱限制的情况下,最直接的策略就是列目录并 在当前场景下,最优方案就是直接读取 flag,简单高效。0xGame 2025 Week1 Writeup
OSINT
Week1 OSINT
0xGame{山的名字_纬度_经度},经纬度保留四位小数且不进位(截断)。0xGame{大室山_32.1191_118.9265}题目与附件
task/OSINT1-1.jpg:识别图片中的山名。task/OSINT1-2.jpg:从图片(或其 EXIF)推导拍摄点经纬度。解题思路
1. 山名识别(1-1)
OSINT1-1.jpg:大室山(俗称“抹茶山”)。2. EXIF 定位(1-2)
OSINT1-2.jpg 提取 EXIF: |
GPSLatitude = 32 deg 7' 8.98"(北纬)GPSLongitude = 118 deg 55' 35.69"(东经)GPSAltitude 与 GPSDateStamp 等无关字段0xGame{大室山_32.1191_118.9265}
|
尝试与坑点记录
参考要点(知识性)
LatitudeRef 与 LongitudeRef,需在南纬/西经时赋负号。Web
Rubbish_Unser Writeup
题目信息
http://8000-29b93fdb-616e-477c-a19f-27457c7d6042.challenge.ctfplus.cn代码概览
highlight_file(__FILE__),随后仅在收到 $_GET['0xGame'] 时执行 unserialize() 并抛出异常。脚本中预置了多个带魔术方法的类:ZZZ::__destruct():在析构时输出 "破绽,在这里!" . $this->yuzuha;若 $yuzuha 是对象,则触发其 __toString()。Mi::__toString():尝试调用 $this->game->tks()。GI::__call():拦截未定义方法,取 $this->furina()(可调用对象)。HI3rd::__invoke():若满足 $kiana !== $RaidenMei 且 md5/sha1 完全相等,则返回 $this->guanxing->Elysia。HSR::__get():访问不存在属性时取 $this->robin 并 eval()。ZZZ->__destruct
-> Mi::__toString
-> GI::__call (tks)
-> HI3rd::__invoke
-> HSR::__get("Elysia")
-> eval($this->robin)
$robin 设置为任意字符串即可执行任意 PHP 代码。哈希约束绕过
HI3rd::__invoke() 中的判断要求:$kiana !== $RaidenMei;md5($kiana) === md5($RaidenMei);sha1($kiana) === sha1($RaidenMei)。$kiana = 0(整型)与 $RaidenMei = "0"(字符串)。两者严格不相等(类型不同),但在 md5 / sha1 时都会被转成字符串 "0",哈希值完全一致。Payload 设计
HSR::$robin = 'echo file_get_contents("/proc/self/environ");'。HI3rd 同时持有 $kiana = 0、$RaidenMei = "0"、$guanxing = $hsr。GI::$furina = $hi3rd(可调用对象)。Mi::$game = $gi。ZZZ::$yuzuha = $mi(析构时触发整条链)。O:3:"ZZZ":1:{s:6:"yuzuha";O:2:"Mi":1:{s:4:"game";O:2:"GI":1:{
s:6:"furina";O:5:"HI3rd":3:{s:9:"RaidenMei";s:1:"0";s:5:"kiana";i:0;
s:8:"guanxing";O:3:"HSR":1:{s:5:"robin";s:45:"echo file_get_contents("/proc/self/environ");";}}}}}
/?0xGame=<序列化字符串>,链条展开后 eval 会打印 environ,其中包含 flag=0xGame{Really_Rubbish_Unser!}。自动化脚本
[0xGame2025]rubbish_unser/solution/solution.pyBASE 环境变量切换。
Flag
$0xGame{Really_Rubbish_Unser!}$复盘
0 与 "0" 绕过 md5/sha1 的同时,不需要预制碰撞,思路清晰直接。留言板_reVenge Writeup
题目信息
http://8000-8d86851f-921f-43e7-aa4a-bb059a158d51.challenge.ctfplus.cn入口侦察
login.php。admin / admin123,登录成功会弹窗后跳转至 xxxxmleee.php。xxxxmleee.php 无需会话即可打开,页面是一个接收 XML 并回显解析结果的留言板,提示标题 “Ez_XXE”。漏洞分析
POST 时读取原始请求体并使用 DOMDocument::loadXML() 加载,随后 simplexml_import_dom() 转换,再把节点内容输出到页面;LIBXML_NONET / LIBXML_NOENT 防护),因此存在 XXE;/flag 会触发 Start tag expected 警告 —— 因为外部实体被当作“已解析实体”,内容需要是合法的 XML 片段;<data>%file;</data>,用 <data>...</data> 包裹 Base64 后的 flag,即可满足 Well-formed 要求并顺利回显。利用 payload
&exfil;
%file 参数实体拉取 /flag,经过 php://filter 做 Base64 包装;%eval 参数实体来自 data:// URI,动态声明 exfil 通用实体,把 %file; 填入 <data>...</data> 标签内部,使解析器得到合法 XML;&exfil; 展开后,服务端再将 SimpleXML 导出的字符串嵌入 <pre>,因此最终响应中能看到 Base64 文本,解码即可得到 flag。自动化脚本
[0xGame2025]guestbook-revenge/solution/solution.py
Flag
$0xGame{1a903b96-173a-8b3d-8a37-a81934dc4187_xxe1919810}$复盘
reVenge 修补了“非预期”路径,但主逻辑依然允许外部实体。Start tag expected 报错。Http的真理,我已解明 Writeup
题目信息
http://80-ad98a018-e0f5-46c5-87bb-dce59a2ba4ec.challenge.ctfplus.cn交互探测
GET /?hello=web POST /?hello=web,表单数据 http=good Sean=god。Cookie: Sean=god User-Agent: Safari Referer: www.mihoyo.com Via: clash 自动化脚本
[0xGame2025]http-truth/solution/solution.py =
=
=
=
结果
$0XGame{Congratuation_You_Are_Http_God!!!}$复盘小结
curl/requests 自动化构造请求即可稳定拿flag。RCE1 Writeup
题目信息
http://80-9e19ce66-5345-45b1-a0df-9990629073c0.challenge.ctfplus.cn题目源码与审计
highlight_file(__FILE__))关键片段:eval($real_code)。check($real_code) 黑名单限制十分苛刻:任意数字、$%@* 以及常见命令与函数名(system、cat、ls、echo、strings 等)均被禁止。绕过思路
md5() 传入数组参数会产生一条 Warning,但在 error_reporting(0) 下被隐藏,同时 md5($arr) 的返回值为 NULL(未开启严格类型错误)。rce1 与 rce2 都为数组,则有: md5($rce1) === md5($rce2) 等价于 NULL === NULL,成立;[1] 与 [2]),所以 $rce1 !== $rce2 也成立。flag 与 cat、ls、echo 等关键词与字符,但并未禁止反引号执行(\``)及die()`。die() 输出执行结果。flag 字面量,可使用单字符通配符 ?:/fl?g。tac 读取文件:tac /fl?g。rce1[]=1(数组)rce2[]=2(数组)、rce3=die(tac /fl?g);自动化脚本
[0xGame2025]RCE1/solution/solution.py
0xGame{This_is_Your_First_Stop_to_RCE!!!}
关键细节与推导
===,常见的“0e 魔法哈希”仅在 == 时才会绕过,比对为数值 $0$。因此这里不适用“魔法哈希”,需要利用传入数组触发 `md5($arr) \to NULL$ 的行为(在该环境配置下成立)。\d 禁止任何数字,阻断了 chr(… ) 一类构造;[\$%@*] 禁止若干常见特殊字符;tac、die、以及反引号执行。echo 被禁,但 die($s) 会输出参数后终止脚本,满足需求。尝试记录
rce1=r& rce2=r 无法满足 rce1 !== rce2 条件;=== 下失败;md5() 不会触发致命错误(被 error_reporting(0) 隐去),返回值为 NULL,于是想到使用数组构造“相等的 md5 值、但不等的原值”。cat/ls/rev/nl/sort/strings 等黑名单项,采用 tac /fl?g 并通过通配符规避 flag 常量。最终答案
$0xGame{This_is_Your_First_Stop_to_RCE!!!}$0xGame2025 Lemon_Rev(Web)
static_folder 或 Jinja loader),实现任意文件读取。__globals__、内置 setattr、os.path.exists 与 Flask static_folder、Jinja FileSystemLoader 等关键点。1. 题目与代码复盘
task/attachment.txt):
=
=
pass
=
return
return , 404
return
POST / 的 JSON 都会被递归 merge 进全局对象 Game0x;/<path> 路由会在渲染前检查 os.path.exists('templates/' + path);2. 根因分析与形式化抽象
2.1 merge 的危险分支
dst 类映射结构且 v 是字典,则递归:将 $v$ 合入 dst[k];dst 有名为 $k$ 的属性且 $v$ 是字典,则递归到 getattr(dst, k);setattr(dst, k, v)。2.2 可达 app:
__init__.__globals____globals__ 属性,指向其定义模块的全局字典(官方文档:Function Objects)。Game0x.__init__ 的 __globals__ 中就包含了 app 实例。因此构造:
setattr(app, 'static_folder', '/proc/1/root') 生效。__globals__):Python 官方文档(Data model → Function objects)。setattr(obj, name, value):Python 官方文档(Built-in Functions)。2.3 路由前置检查与取舍
/<path> 的 render_page 先做 os.path.exists('templates/' + path);仅当“templates/ + path”本地确实存在时才渲染,这对单纯改 Jinja searchpath 的玩法构成干扰。os.path.exists(path) 仅检查文件是否存在(官方文档:os.path.exists)。FileSystemLoader.searchpath 仍可做 LFI,但需与该 exists 的拼接规则配合路径穿越;而 Flask 的静态路由没有这层 exists 拼接,故更稳妥的做法是污染 static_folder。3. 稳定利用链(两步)
/static 挂到“进程视角的真实根目录”,即 /proc/1/root,之后直接 GET /static/flag。/proc/1/root:
0xGame{Welcome_to_Easy_Pollute~}
为什么选
/proc/1/root/proc/1/root 通常指向容器真实根,这样我们无需猜测工作目录或模板层级,等价把 /static 绑定到容器根目录。能力自测(可选)
/etc,尝试读取 /etc/passwd:
|
4. 尝试过程与对比方案(取舍记录)
app.jinja_env.loader.searchpath=["/"] 再走 /<path> 渲染,但受 os.path.exists('templates/' + path) 限制,路径穿越需要与工作目录/模板目录实际层级吻合,实战中不稳定。%2e%2e/、双重编码、混合分隔符)能提升成功率,但复杂度高,不利于教学;static_folder 则无需关心 exists 拼接与层级,命中率更高、思路更清晰。6. 与“原型链污染”的关系
Object.prototype 等原型,让属性查找链受到影响;7. 防护建议(工程视角)
setattr 写入实例/模块等内建对象;__class__、__init__、__globals__、__dict__、__mro__ 等;app.jinja_env、app.static_folder 等敏感配置;将其固化于启动配置;8. 官方文档对照(便于延伸阅读)
__globals__) setattr static_folder/static_url_path) FileSystemLoader) __globals__ 定义、setattr 的赋值语义、os.path.exists 的存在性检查、Flask static_folder 的含义与 Jinja FileSystemLoader 多路径支持等。)9. Flag
0xGame{Welcome_to_Easy_Pollute~}题目描述与源码摘录
=
=
pass
=
return
return , 404
return
merge 会把任意 JSON 递归“合并”到目标对象上;当 dst 不是映射时会直接 setattr(dst, k, v)。这使得我们可以构造键路径去触达 Flask 实例 app 的内部对象。FileSystemLoader.searchpath 改到根目录/,再利用 /<path> 路由中的 render_template(path) 读取文件。但它前有 os.path.exists('templates/' + path) 的存在性检查,单纯的 ../ 有时难以命中真实路径。static_folder,走框架内置的静态文件路由 /static/...,从而绕过上面的 exists 检查。进而配合 /proc/1/root 指针把静态根指向“进程视角的根文件系统”,实现对容器真实根路径的直接读取。利用链总体思路
POST / 发送 JSON,利用 Game0x.__init__.__globals__ 链拿到模块全局,再取到 app 实例并修改其属性。例如:
Game0x 是 Dst() 的实例,Python 实例有 __class__,类有 __init__,函数对象有 __globals__ 可达模块字典;模块字典中保存了 app。merge 在路径上遇到对象就递归,最终以 setattr 写入属性值。app.static_folder 作为 /static 路由的根(配合 static_url_path,默认 /static)。app.static_folder 改为目标目录后,GET /static/<name> 即尝试读取 <dir>/<name>。/proc/1/root/proc/1/root 是“1 号进程”的根目录的符号链接。将 static_folder 设置为该路径,可以稳定将静态根锚定到容器真实根,而无需猜测应用工作目录与层级。POST / 设置 app.static_folder = "/proc/1/root"。GET /static/flag 直接读取容器根下的 /flag。详细操作与尝试过程
BASE = http://nc1.ctfplus.cn:19346(实际以赛题为准)。/etc,读取 /etc/passwd:
|
/proc 可用static_folder 设置为 /proc/1,读取 cmdline/environ 等:
| |
| |
/proc/1/root
0xGame{Welcome_to_Easy_Pollute~}
/<path> 路由前置的 os.path.exists('templates/' + path) 检查,不需要层数穿越与编码绕过。原理推导与安全分析
merge 的伪代码行为可抽象为:给定 src(用户 JSON)与 dst(任意对象),若 dst 类字典则递归合并键;否则若 dst 有属性 k 且 v 是字典,递归;否则直接 setattr(dst, k, v)。这等价于暴露了“写任意属性”的能力。Game0x.__init__.__globals__['app'])。static_folder 而非 Jinja searchpathapp.jinja_env.loader.searchpath = ['/'],再用 render_template(path) 读取。但该路由有 os.path.exists('templates/' + path) 前置检查,要求 'templates/' + path 在文件系统上可达;实践中常被工作目录与层级差异卡住。相较之下,Flask 的静态文件路由不会附带该检查,污染 static_folder 更稳。/proc/1/root 的优势/proc/1/root)反映了容器真实文件系统根。将静态根指向这个符号链接,相当于将 /static 绑定到容器根,不再需要计算相对层数或关心应用工作目录,成功率极高。自动化脚本(requests 版)
uv)运行:
POST / 污染 static_folder 为 /proc/1/root;/static/flag 输出;/etc/passwd 以确认能力。参考与加固建议
setattr 改写实例内部状态。__globals__)需要屏蔽。0xGame{Welcome_to_Easy_Pollute~}Lemon Writeup
题目信息
初步侦查
contextmenu 与 keydown 事件,阻止右键和 F12 调试器。阻断调试界面属于常见的“刷存在感”手段,并不会限制我们直接拉取网页源码。因此我尝试:view-source: 功能(在禁用右键的页面中仍然有效)。curl 等命令行工具直接获取 HTML。源代码提取
<!-- 0xGame{Welc0me_t0_0xG@me_2025_Web!!!} -->。由注释形式可知 flag 已被嵌在源码里。题目没有进一步的跳转或加密逻辑,因此 flag 直接取值即可。自动化脚本
solution/solution.py 中使用标准库 urllib 抓取页面,并用正则表达式提取满足模式 0xGame{.*} 的字符串。脚本通过 uv run 调起独立的 Python 环境,避免直接依赖系统解释器:
$0xGame{Welc0me_t0_0xG@me_2025_Web!!!}$,与手工分析得到的结果一致。最终答案
$0xGame{Welc0me_t0_0xG@me_2025_Web!!!}$复盘
misc
ezShell_PLUS 解题报告
题目信息
welcome/hacker,端口 49313解题流程
/home/welcome/challenge 下发现 hash_value、decrypt.sh 以及 files 目录。cat hash_value 得到目标 SHA256 —— cdae1e73…997b553。files/*.dat 批量执行 sha256sum,定位到哈希匹配的文件 558c1a43e0ce90df.dat。./decrypt.sh files/558c1a43e0ce90df.dat,脚本会读取 /etc/secret_key.backup 作为密码,最终输出 flag。自动化脚本
solution/solution.pyhash_value,遍历 files 下的 .dat 文件计算 SHA256。decrypt.sh 执行解密。
目标 SHA256: cdae1e7300420ddf2500f05c1247f572f06eade2862fb1c56ffd25812997b553
匹配文件: /home/welcome/challenge/files/558c1a43e0ce90df.dat
0xGame{Welc0me_to_H@ckers_w0r1d}
Flag
0xGame{Welc0me_to_H@ckers_w0r1d}Do_not_enter 解题报告
题目摘要
do_not_enter.dd.gz(MBR 整盘镜像,解压得 do_not_enter.dd)环境与工具
p7zip(7z)与 ripgrep 验证;本仓库已提供自动化脚本。task/do_not_enter.dd:题目镜像solution/solution.py:一键抽取唯一正确 flag 的脚本总体思路
技术拆解
7z l task/do_not_enter.dd 可直接列出 6 个“分块”:0.img, 1.img, 2.img, 3.img, 4, 5。file/7z l 识别: 0.img/1.img/2.img/3.img 为 ext4 文件系统镜像,卷标分别为: UserShare、Do_not_enter、WebServer、SysLogs4 为全零数据;5 是零填充中掺杂 ASCII 文本(大量假旗)。auth.log/syslog/dpkg.log)以及分块 5 中塞满了形如 0xGame{WoW_y0u_fouNd_1t?_NNNNNN} 的“编号态 flag”。0.img:50 个2.img:50 个3.img:50 个5:30 个1.img 的卷标即为 Do_not_enter,且 7z l 1.img 可见 Last Mounted = /mnt/real_target。1.img 中仅出现 1 条编号态 flag。1.img → syslog 唯一出现:0xGame{WoW_y0u_fouNd_1t?_114514}。自动化脚本
solution/solution.py7z 将 task/do_not_enter.dd 解出到工作目录;1.img(禁区分区)展开,读取 syslog/auth.log/dpkg.log;
0xGame{WoW_y0u_fouNd_1t?_114514}
误区与排除(记录尝试过程)
5 为零填充中穿插 ASCII 的诈骗层,出现 30 条假旗;其结构非文件系统,也非真实目标。最终答案
0xGame{WoW_y0u_fouNd_1t?_114514}Do_not_enter(Last Mounted: /mnt/real_target)中出现一次,满足“唯一正确”的题意约束。Zootopia 解题报告
题目信息摘录
Zootopia.zip → Zootopia.png尝试过程
task 目录,确认只有一张 PNG 图片。binwalk、exiftool 等常规检测未发现额外文件或文本 chunk,考虑 LSB 隐写。zsteg 验证,b1,rgb,lsb,xy 渠道出现可读文本 0xGame{...}。\0 结束。推导细节
自动化脚本
solution/solution.pypillow
0xGame{W1_Need_t0_t@k3_a_break}
结果
0xGame{W1_Need_t0_t@k3_a_break}。公众号原稿 解题报告
题目信息摘录
公众号.docx尝试过程
task 目录,使用 uv init 创建独立 Python 环境。unzip -l 查看 docx 内部结构,发现非常规条目 docProps/gift.xml。unzip -p 公众号.docx docProps/gift.xml 得到完整 flag。solution/solution.py 使用 zipfile 模块解压目标文件并输出内容。推导与验证
gift.xml 即可。
0xGame{omg!Y0u_f0und_m3!_C0ngr4tul4t10ns!}
结果
0xGame{omg!Y0u_f0und_m3!_C0ngr4tul4t10ns!}。ez_Shell 解题报告
题目信息摘录
whoami、pwd、当前路径下的文件夹名以及两个 flag 文件内容,再按题面格式拼装。尝试记录
task/attachment.txt 保存题面,并用 uv init 初始化脚本环境。nc1.ctfplus.cn:47957,编写简短 paramiko 脚本验证连接,确认初始工作目录 /home/hacker 下只有隐藏目录 .mysecret,其中包含 flag1.txt。cat flag1.txt 导致报错,改用 find 自动定位 .mysecret/flag1.txt 后成功读取第一段。root/Y0u_@re_root 直接 SSH 登录失败,推测禁用了 root 远程登录。改为在 hacker 会话内执行 su - root -c 'cat /root/flag2.txt',并通过 stdin 传入密码获取第二段。su 输出进行清洗,去除 Password: 提示后,脚本能够一次性生成完整 flag。解题思路概述
hacker/h@cker_it 登录,采集下列信息: whoamipwd. 与 ..,实际为 .mysecret).mysecret/flag1.txt 的内容su 切换 root,读取 /root/flag2.txt。'_' 连接并包裹进 0xGame{...}。自动化脚本
solution/solution.pyparamiko
0xGame{hacker_/home/hacker_.mysecret_It_is_funny_right?_You_hacked_me!!!}
结果
0xGame{hacker_/home/hacker_.mysecret_It_is_funny_right?_You_hacked_me!!!}。Sign_in 解题报告
题目信息摘录
MGhRa3dve0dvdm0wd29fZDBfMGhRNHczXzJ5MjVfQHhuX3JAbXVfUHliX3BlWH0=
尝试过程
task/attachment.txt 记录原始字符串后,使用 uv init 初始化题目标准的 Python 环境,避免直接调用系统 Python。uv run python 解码得到中间结果:0hQkwo{Govm0wo_d0_0hQ4w3_2y25_@xn_r@mu_Pyb_peX}。0hQkwo 形似 0xGame。对照 ASCII 发现大小写字母均被循环右移了 $10$ 位。推导细节
关键脚本
solution/solution.py:caesar_shift,对字母和数字执行模运算逆变换。
0xGame{Welc0me_t0_0xG4m3_2o25_@nd_h@ck_For_fuN}
总结
RE
BaseUpx
UPX 后,主函数读取输入并执行 base64_encode,与常量 MHhHYW1le1cwd191XzRyM183aDNfRzBkXzBmX3VweCZiNHMzNjRfRDNzMWdufQ 比较。== 还原 base64,即得 0xGame{W0w_u_4r3_7h3_G0d_0f_upx&b4s364_D3s1gn}。BaseUpx/。DyDebug
F(R,K)=(R*K)⊕ROL(R,17);轮密钥通过线性同余生成。.rdata 中的密文分块解密,得到 0xGame{91f2c64e-057d-4191-8868-9a8c0847b2c0}。EasyXor
((flag[i] ^ key[i mod 16]) + i) == table[i]。0xGame{6c74d39f-723f-42e7-9d7a-18e9508a655b}。SignIn
.rdata。strings 直接读取到 0xGame{G00d$!gn1n_&_N0w_5t4rt_y0ur_R3V3R5E}。SignIn2
decrypt 实际上是 ROT94/47 风格的表循环减法,枚举 key 即可。0xGame 对应 key=16,解出 0xGame{We1c0m3_2_xiaoxinxie_qq_1060449509}。ZZZ
sscanf("%8x%8x%8x%8x") 读取四个 32 位数,再验证 4 个模 $2^{32}$ 方程。z3-solver 搜索满足约束的 4 组解,匹配题面给出的 SHA-256 仅有一组: 0xGame{99482fd0b95440870e990f7aa0514982}。脚本使用
solution/solution.py。ZZZ 题依赖 z3-solver:已通过 uv pip install z3-solver 安装,如在其他环境运行请提前安装。Pwn
0xGame ROP2 Write-up
目标
sh//bin/sh 字符串的情况下,依旧通过 ROP 调用 system 实现 RCE。静态分析要点
setbuf、system、printf、read;main: init 关闭缓冲;system("echo Start your attack");read(0, buf, 0x100) 读入到 buf(大小 0x30),存在溢出。pop rdi ; ret @ 0x40119e,以及 leave ; ret @ 0x40124b。.text 段 0x401200 处有一条 movabs rax, 0x9173003024,其立即数的最低三字节即 0x24 0x30 0x00,也就是 "$0\x00"!因此内存地址 0x401202 起正好是一段以 0 结尾的 "$0" 字符串。利用思路(“神奇参数”)
system(cmd) 实际会执行 /bin/sh -c cmd。若令 cmd = "$0",那么在子 shell 中 $0 的值为 "sh",因此实际等价执行 sh,从而获得交互 shell。这就是题面所说“除了 sh 之外的神奇参数”。ROP 链
pop rdi ; ret (0x40119e)"$0\x00" 地址 (0x401202)system@plt (0x401080)cat flag。脚本
solution/solution.py,已内置短超时与缓冲读取,默认静默输出 flag,可开启 debug=True 观察交互。运行
## 填好脚本中的 HOST/PORT 后
结论
sh//bin/sh 常量,也可以借助代码段中的 "$0\x00" 作为 system 的参数来获得 shell。这是 ROP 控参思路在实战中的一个小技巧。0xGame ROP1 Write-up
目标
system 的参数,实现远程命令执行(RCE)。静态分析要点
gadget@0x401176、help@0x401183、main@0x40119d、system@plt@0x401070;main 调用 read(0, buf, 0x100) 且 buf 大小仅 0x20,可溢出覆盖返回地址。.rodata 含有字符串 echo Maybe you need this: sh,其中 "sh" 子串位于 0x40201e。gadget 汇编: push rbp; mov rbp, rsp; pop rdi; ret,效果等同 pop rdi; ret(用于布置 system 的第 1 参)。gadget -> "sh" -> system@plt。ROP 链表达
Exploit 脚本
solution/solution.py:cat flag;运行
## 填好脚本中的 HOST/PORT 后
结果
cat flag,完成 RCE。0xGame nc1.ctfplus.cn:49641 基础栈溢出 Write-up
nc1.ctfplus.cn 49641题目与附件
task/stack-overflow(64 位 ELF,非 PIE)。我们只需直接打远程即可,不需要本地调试也能完成;但为了稳妥,我先做了快速静态分析确定思路与偏移。快速静态分析
checksec 结果:No PIE、NX 启用、Canary 关闭,ELF 含 CET 标记(IBT/SHSTK),但远端通常不强制 Shadow Stack。backdoor@0x4011d6whhhat@0x4011f7main@0x40122amain 在栈上开辟 $0x30$ 字节缓冲区,并调用 read(0, buf, 0x100),存在明显溢出。whhhat@0x4011f7: puts,实测为 good work!);execve("/bin/sh", NULL, NULL),相当于帮我们起一个交互 shell(继承同一套标准 I/O)。whhhat(0x4011F7),无需 ROP 链也无需泄露地址。利用思路
Just say something... 并阻塞读入;payload = b"A"*0x38 + p64(0x4011F7) 触发返回至 whhhat;whhhat 后先打印 good work! 再 execve("/bin/sh"),这会直接把当前进程镜像替换为 /bin/sh,我们获得交互;cat flag 即可拿到 flag。good work! 或命令输出可能与提示行交织。脚本里采用短超时、多次读取并在缓冲中查找 0xGame{,确保稳健拿 flag。缓冲区覆写
read 写入长度 $L\ge 0x38+8$ 的数据,令 RET=\text{addr}(\text{whhhat})$,返回即跳转至whhhat执行。因为目标函数本身完成了寄存器参数设置和execve`,我们不必搭建 ROP。自动化脚本(Pwntools)
solution/solution.pydebug 开关,便于观察远程交互细节;pwntools,通过 uv 管理环境。
0xGame{W0w_y0u_kn0w_h0w_t0_h1j@ck_3x3cut10n_fl0w}
远程调试建议
solve(debug=True) 观察: good work!/换行);cat flag 后的流式数据;overall_timeout;pwd && ls -la 确认 flag 是否在当前目录(实测在根目录 /)。结论
whhhat,通过溢出直接将返回地址劫持过去,即可一把到 shell 并读取 flag。无需泄露地址、无需复杂 ROP,是非常适合入门的栈溢出题。0xGame nc1.ctfplus.cn:14164 数学连问 Write-up
题意概述
Good work! 并立刻给出下一题;全部答完后提示 Congratulations on completing the challenge,此时再输入 cat flag 即可获得最终 flag:0xGame{7h3_m4573r_0f_m47h!!!}
探测与信息收集
nc 连上 nc1.ctfplus.cn 14164,立即收到 “Are you good at math?”、“Kore wa shiren da!” 和首道题。例如:733 - 400 = ?。Good work! 并刷新题目。错误或超时会断开,需重新过题。cat flag(或任意能读取文件的指令)即可得到 flag,说明后端实际上直接暴露了 shell。数学与实现思路
(-?\d+)\s*([+\-x])\s*(-?\d+) 捕获 $A$、运算符、$B$;sendline 回传答案,循环直到检测到祝贺语;cat flag,再读取包含 0xGame{ 的行并返回。自动化脚本
solution/solution.pypwntoolsuv 创建虚拟环境并安装依赖evaluate(expr: str) -> int:解析题目并返回计算结果;solve(debug: bool = False) -> str:完成 1000 道题并执行 cat flag。复现步骤
.venv 并安装 pwntools。脚本会输出最终 flag。过程记录
pwntools.remote 重复答题 1000 次,首次仅拿到祝贺语,随后推测服务开放了 shell,尝试发送 cat flag 成功得到 flag。结论
0xGame nc1.ctfplus.cn:14099 Write-up
题目复现环境
nc1.ctfplus.cn14099uv run python solution/solution.py信息收集与推理过程
nc 发送任意字符串;服务端回显 /bin/sh: 1: <cmd>: not found,表明后台直接把我们的输入喂给了一个 /bin/sh 子进程。ls 得到根目录内容,出现 flag 文件,推测 flag 即保存在此处。cat flag,无任何额外限制,直接返回字符串 0xGame{test_your_nc_first}。这说明服务端对子命令没有额外过滤,可以直接读取目标文件。cmd 在 shell 中合法,该服务就会执行并返回结果。由于没有对敏感命令做黑名单拦截,直接读取 flag 即可。Exploit 步骤
nc nc1.ctfplus.cn 14099lscat flag自动化脚本
solution/solution.py,核心逻辑:socket.create_connection 建立 TCP 连接。cat flag\n。recv(4096) 足够覆盖全部输出;若服务存在延迟,可在脚本中添加循环读取直到换行结束。运行方法
uv run 会自动在当前目录创建 .venv 并下载兼容的 Python 解释器。输出结果
0xGame{test_your_nc_first}
总结
cat flag。虽然题目被归类为 Pwn,但实现上等价于裸命令执行。若面对更严苛的环境,可考虑:Crypto
Prime-Modulus-RSA
题目分析
flag || random 共 253 字节,随后按大端转换为整数 $m$。sha256(XXXX + suffix) 的 PoW,不影响核心解法。推导
0xGame{...} 段即为 flag。尝试过程
解题脚本
solution/solution.py 提供完整流程:读取 $n, e, c_{\text{hex}}$,解密并搜索 flag。Flag
nc1.ctfplus.cn 47036 获取到的密文解密得到 0xGame{d4cf5685-acf2-43c3-806d-10c86934be91}。Mixed-Encoding
题目分析
flag[0:25] 使用 Base64。flag[25:50] 转为十六进制字符串。flag[50:75] 经过自定义 awaqaq 映射:把字节串当作大端整数,用三进制表示,再把余数 $0,1,2$ 映射为 a,w,q。flag[75:100] 视作 little-endian 整数后求七次幂。gb2312,并被随机填充到 100 字节。逆向思路
awaqaq: } 为止,即 flag。尝试记录
gb2312 解码失败,排查发现随机填充落在 } 之后,引起非法字节。},再进行解码,问题解决。len(flag) = 100,与题面约束一致。解题脚本
solution/solution.py 将四段解码流程整合,并带有整数七次根函数,执行后直接输出旗帜。Flag
0xGame{欢迎来到0xGame2025,现在你已经学会Crypto的基本知识了,快来试试更难的挑战吧!}Vigenere-Nonlinear
题目分析
0xGame{ 开头,以 } 结尾,中间全部为小写字母。思路推导
0xGame{。}。a\ldots z。尝试过程
excellent 获得最高得分,且能重加密回原密文,验证无误。解题脚本
solution/solution.py 完整实现上述枚举与评分逻辑,输出唯一的最佳候选。Flag
0xGame{excellent}Diffie-Hellman-Backdoor
题目分析
核心漏洞
数学推导
实际利用流程
1 作为 Bob 的公钥。unpad。尝试记录
secret.flag,验证提交 1 后可成功解密得到原 flag。解题脚本
solution/solution.py 会从标准输入读取密文十六进制串,内部固定密钥后输出明文。pwntools/telnet 先拿到密文,再喂给脚本解密。Flag
nc1.ctfplus.cn 29844 返回的密文解密得到 0xGame{70276d88-2972-4bbc-9bcf-429e1f17540a}。Vigenere-Classic
题目分析
digits + ascii_letters + punctuation,共 10 + 52 + 32 = 94 个字符。Welcome-2025-0xGame 做经典维吉尼亚加密:$c_i = \text{alphabet}[(p_i + k_j) \bmod 94]$。推导与解法
尝试记录
0,符合 CTF flag 风格。0xGame{...},与期待一致。解题脚本
solution/solution.py 实现上述解密流程,直接输出 flag。Flag
0xGame{you_learned_vigenere_cipher_2df4b1c2e3}RSA-FactorDB
题目分析
解决思路与推导
Crypto.Util.number.inverse 计算。long_to_bytes(m) 还原字节串。尝试过程
解题脚本
solution/solution.py,直接写死题面给出的 $n, c, p, q$,执行即可复现。Flag
0xGame{F4ct0rDB_1s_usefu1_r19ht?}