KashiCTF 2026 WriteUp
字数 6908
更新时间 2026-04-12 12:39:20
KashiCTF 2026 赛题详解与教学文档
文档概述
本教学文档基于先知社区发布的《KashiCTF 2026 WriteUp》内容整理而成。文档旨在系统性地梳理比赛中所涉及的各类赛题解题思路、技术与工具,为安全研究人员和CTF爱好者提供详尽的学习参考。内容涵盖数字取证(Forensics)、密码学(Cryptography)、杂项(Misc)、逆向工程(Reverse)、二进制利用(Pwn)、开源情报(Osint)和网络(Web)等多个方向。
数字取证(Forensics)
Easy Forensics
- 题目简述:提供一个流量包,要求从中提取隐藏信息。
- 关键点:
- 流量包中主要为DNS流量。
- DNS查询/响应的域名中包含经过编码的数据。
- 解题步骤:
- 使用Wireshark等工具分析流量,关注DNS协议。
- 从DNS查询的子域名或相关字段中提取出疑似编码的字符串。
- 识别编码类型为Base32并进行解码。
- 获取的Flag:
kashiCTF{dns_exfiltration_is_sneaky}
Mid Forensics
- 题目简述:提供一个包含大量ICMP Echo Request包的流量文件,TTL值异常。
- 核心原理:利用网络协议头部字段(如TTL)进行隐写(Steganography)。
- 解题步骤:
- 分析流量,发现几乎所有包都是ICMP Echo Request,且IPv4的TTL值仅在64和65之间切换。
- 建立映射关系:
TTL=64 -> 二进制0,TTL=65 -> 二进制1。 - 按照抓包顺序,将所有ICMP包的TTL值转换为比特流。
- 将比特流按8位一组转换为字节,解码为ASCII字符串。
- 获取的Flag:
kashiCTF{ttl_stego_is_evil}
Burnt Ashes
- 题目简述:分析一个
ext4文件系统镜像(kashi_ritual_ledger.img),寻找隐藏的“账本胶囊”。 - 涉及技术:文件系统分析、字符串提取、信息关联、AES-CBC解密、隐写分析。
- 解题步骤:
- 初步分析:确认文件系统类型,使用
strings命令全局搜索字符串,发现关键文件列表和邮件、聊天记录片段。 - 规则提取:从残留的邮件(
passphrase doctrine draft)中总结出口令规则:- 固定4个单词。
- 前两个单词是位置标签(
location labels)。 - 后两个单词来自仪式词表(
ritual_words.txt)。 - 单词间用连字符(
-)连接。
- 信息关联:从笔记(
ritual_index_notes.md)中得知最终口令前缀固定为ghat manjari,且不能复用第一阶段的口令。从聊天记录得知第一阶段口令trishul-lantern-braid及其AES参数是用于解密诱饵文件的。 - 关键突破:在镜像的未覆盖数据块中,直接找到了结构完整的JSON配置,其中包含第二阶段的口令(
stage2_phrase)和对应的AES参数。 - 解密获取Flag:最终口令为
ghat-manjari-copper-owl,对应的AES参数为:salt=d83f0a1e5bc94762,key=river-ink-oblation,iv=7f01ea25d6c59f4b38e4d0b451ccae12。使用此口令和参数解密隐藏的胶囊即可获得Flag。
- 初步分析:确认文件系统类型,使用
- 获取的Flag:
kashiCTF{ledger_ashes_remember_every_ritual}
Slavery
- 题目简述:一个1892年的梵文古籍PDF扫描件中隐藏了Flag。
- 核心思路:放弃对PDF文本内容的分析,聚焦于其作为图像容器的属性。
- 解题步骤:
- 识别真实格式:使用
pdfimages(Poppler工具集)提取PDF中嵌入的所有图像。 - 批量排查:将导出的数百张图片拼接成缩略图(Contact Sheet)进行快速浏览,寻找视觉异常(如灰条、边缘文字)。
- 定位异常:发现其中一张图片(如
page269.png)左侧有一条不自然的灰色竖条。 - 提取处理:裁剪出灰色竖条区域,将其顺时针旋转270度(或逆时针旋转90度)使其变为横向,然后进行灰度化、对比度增强等图像处理操作,使隐藏的文字清晰可读。
- 识别真实格式:使用
- 工具与命令示例:
pdfimages -j "Mantra Mahodadhi with Nauka Tika ... .pdf" output_dir/ - 获取的Flag:
kashiCTF{1r0nhex_1s_n07_4_m4n}
密码学(Cryptography)
Ancient Mystery
- 题目类型:编码
- 解题方法:对给出的密文进行64次Base64解码。
- 获取的Flag:
kashiCTF{th3_s3cr3t_0f_mah4bh4r4t4_fr0m_3136_BCE}
Broadcast
- 题目类型:RSA广播攻击(Håstad‘s Broadcast Attack)
- 攻击条件:相同的明文
m,用相同的小加密指数e(本题e=3),加密发送给多个接收者(每个接收者有不同的大模数n_i),得到多个密文c_i。 - 攻击原理:当
e很小且加密次数足够多时(满足m^e < ∏ n_i),可以利用中国剩余定理(CRT)从c_i ≡ m^e (mod n_i)中直接计算出m^e的精确整数值,然后对其开e次方根即可得到明文m。 - 解题步骤:
- 收集三组
(n_i, c_i)。 - 使用中国剩余定理求解
x,满足x ≡ c_i (mod n_i)。此时的x即为m^3。 - 对
x开三次方根,得到整数m。 - 将
m转换为字节串,即得Flag。
- 收集三组
- 获取的Flag:
kashiCTF{h4st4d_s4ys_sm4ll_3xp0n3nts_k1ll_RSA_br04dc4sts}
Efficient
- 题目类型:RSA特殊参数攻击
- 关键漏洞:模数
n并非两个大素数的乘积,而是一个素数的平方,即n = p^2。 - 攻击原理:
- 欧拉函数计算错误:对于
n = p^2,其正确的欧拉函数是φ(n) = p * (p-1)。如果错误地使用(p-1)^2或(p-1)*(q-1),将无法正确解密。 - 但在已知
n = p^2的情况下,可以很容易计算p = isqrt(n),进而算出正确的φ(n) = p*(p-1)。 - 随后计算私钥
d ≡ e^{-1} (mod φ(n)),即可正常解密RSA密文ct得到AES密钥。
- 欧拉函数计算错误:对于
- 解题步骤:
- 从
output.txt中提取n, e, ct, iv, flag_ct。 - 对
n开平方根得到p,验证p*p == n。 - 计算
φ(n) = p * (p-1)。 - 计算私钥
d = pow(e, -1, φ(n))。 - 解密RSA:
key_int = pow(ct, d, n),取其字节表示的最后16字节作为AES密钥。 - 使用该AES密钥和提供的
iv,以CBC模式解密flag_ct,得到最终Flag。
- 从
- 获取的Flag:
kashiCTF{wh3n_0n3_pr1m3_1s_n0t_3n0ugh_p_squared_1s_w0rs3}
杂项(Misc)
Sanity
- 题目类型:签到题
- 解题方法:加入比赛Discord服务器,在指定频道找到Flag。
- 获取的Flag:
kashiCTF{this_was_not_hard}
Sanity 1
- 题目类型:Web基础
- 解题方法:访问网站的
robots.txt文件,查看其中禁止爬虫访问的路径,通常Flag隐藏在其中。 - 关键点:
robots.txt是网站根目录下的一个标准文件,用于指示网络爬虫哪些目录不应被访问。
POEM
- 题目类型:隐写术
- 解题方法:使用雪花隐写(SNOW)工具对提供的文本文件进行解密。SNOW工具利用文本中行末空格数量的奇偶性来隐藏信息。
- 工具使用示例:
snow -C -p <可能的密码> poem.txt - 获取的Flag:
kashiCTF{1_like_poems_but_1_lik3_u_more<3}
Conquer
- 题目类型:文件格式分析
- 关键漏洞:文件头与文件实际内容不一致。
- 解题步骤:
- 检查名为
flag.pdf的文件,发现其文件头为P6,这是Netpbm格式中PPM(便携式像素图)的二进制格式标识。 - PPM (P6) 头部结构:
P6\n宽度 高度\n最大颜色值\n,后接二进制像素数据。 - 分析文件:头部声明图像尺寸为
284 150,但根据文件总大小计算,像素数据实际对应的高度为185。 - 修复文件:将文件头中的高度从
150修改为185。 - 用图片查看器打开修复后的PPM文件,在之前被截断的底部区域即可看到Flag。
- 检查名为
- 获取的Flag:
kashiCTF{iLOVEkashi}
Impossible CP
- 题目类型:交互式编程/算法漏洞利用
- 题目设定:有一个隐藏数组
A,允许用户查询A_i的某一位是否为1(? i k返回(A[i] & k) != 0),但限制1 ≤ i ≤ n-1,目标是求出A_n。 - 预期解法:通过巧妙组合对前
n-1个元素的查询,利用位运算性质推断出A_n。 - 非预期解法:检查发现交互程序的查询限制存在漏洞,并未严格执行
i ≤ n-1。因此可以直接对i = n进行查询。 - 利用步骤:对于每一位
k(如k=1,2,4,...,2^31),发送查询? n k。根据返回结果(0或1),即可逐位拼凑出A_n的完整值。 - exp核心逻辑:
ans = 0 for k in range(32): mask = 1 << k send_query(f'? {n} {mask}') if response == '1': ans |= mask - 获取的Flag:
kashiCTF{d8b4ea850c6d4d34f74f66f60efd2904e0WKz30Pmp}
逆向工程(Reverse)
Ghaat_MIrage
- 题目类型:逆向分析,包含UPX加壳和假Flag。
- 解题步骤:
- 脱壳:使用
upx -d prog对UPX加壳的程序进行脱壳。 - 识别假Flag:运行程序,发现对任意错误输入都会输出一个固定Flag
kashi{fr4ke_g4ng4_0ffering_lol},此为诱饵。 - 静态分析:使用IDA Pro/Ghidra等工具分析脱壳后的程序,定位核心验证函数。
- 分析验证逻辑:
- 长度校验:输入必须为32字节。
- FNV-1a哈希校验:对输入的前9个字节计算FNV-1a 32位哈希,要求
hash % 251 == 9。 - 分组滚动哈希校验:将输入字符串按下标模4的结果分成4组,每组独立计算一个滚动哈希(
acc = acc * 0x83 + char),计算结果必须分别等于四个固定魔数:0x6d4b8b11,0x1544fdb8,0x6442ef55,0x6b31a222。
- 求解/验证:根据上述逻辑,可以编写脚本暴力破解或直接验证候选字符串。满足所有条件的字符串即为真Flag。
- 脱壳:使用
- 获取的Flag:
kashi{Gh4t5_0f_K4sh1_Never_Di35}
MUSIC-for_Life
- 题目类型:音频隐写,频谱分析。
- 核心逻辑:程序将每个输入字符转换成一个特定频率的正弦波,持续0.12秒,然后拼接成完整的WAV音频。
- 频率映射公式:
freq = ((ch ^ 0xA5) + 0x11) * 9 + 500 - 解题步骤:
- 逆向程序:分析二进制文件,理解其将字符转换为音频样本的算法(
sin(2*pi*freq*t)*28000)。 - 频谱分析:读取提供的WAV文件,按0.12秒(
44100 Hz * 0.12 = 5292个样本)为一个片段进行切分。 - 提取频率:对每个音频片段进行快速傅里叶变换(FFT),找到能量最高的频率(主频)。
- 反向映射:根据找到的主频,反向求解字符。可以预先计算所有可打印ASCII字符对应的频率,然后为每个片段的主频寻找最接近的映射字符。
- 逆向程序:分析二进制文件,理解其将字符转换为音频样本的算法(
- Python关键代码(思路):
for i in range(0, len(audio), SEG_LEN): seg = audio[i:i+SEG_LEN] # 对seg做FFT spectrum = np.abs(np.fft.rfft(seg * np.hanning(len(seg)))) peak_freq = freqs[np.argmax(spectrum[1:]) + 1] # 在预计算的频率映射表中查找最接近peak_freq的字符 best_char = min(freq_map, key=lambda c: abs(freq_map[c] - peak_freq)) result.append(best_char) - 获取的Flag:
kashiCTF{MUSIC_VIBES_but_all_1_w4nt_15_a_MDrOrq}
二进制利用(Pwn)
Addrs hold the key
- 题目类型:栈溢出,返回地址覆盖。
- 漏洞分析:
程序允许用户多次修改一个整型数组arr的元素,但未对索引idx进行边界检查。// 伪代码 int arr[10]; scanf("%d", ×); for(i=0; i<times; i++){ scanf("%d", &idx); // 未检查 idx 范围 scanf("%d", &arr[idx]); // 越界写! } - 利用思路:
- 计算偏移:通过逆向分析或调试,确定
arr在栈上的起始地址(如rbp-0x30)与函数返回地址(rbp+0x8)之间的偏移。偏移为0x38字节。arr为int型(4字节),因此偏移索引为0x38 / 4 = 14。 - 定位目标函数:程序中存在一个隐藏函数
print_flag,地址为0x4011c9。由于程序未启用PIE,此地址固定。 - 构造利用:通过两次越界写,分别将
arr[14](覆盖返回地址低4字节)和arr[15](覆盖返回地址高4字节)修改为print_flag的地址。对于64位地址0x00000000004011c9,低4字节为0x4011c9,高4字节为0x0。
- 计算偏移:通过逆向分析或调试,确定
- 利用步骤:
- 发送修改次数
2。 - 第一次修改:索引
14,值0x4011c9。 - 第二次修改:索引
15,值0。 - 程序循环结束,执行
ret指令,跳转到print_flag函数,输出Flag。
- 发送修改次数
- 获取的Flag:
kashiCTF{made_u_return_lol_KrIl8mCTrH}
开源情报(Osint)
A Trip So peaceful
- 题目类型:地理定位与图片分析。
- 提供信息:两张旅行照片。
- 图1:从酒店房间窗户向外拍摄,可见寺庙尖塔。
- 图2:酒店外的街景,可见沿街店铺招牌。
- 解题步骤:
- 分析图2(街景):
- 识别店铺招牌上的文字,如
TAO BAO、ROYAL、CHEMIST & DRUGGIST、जन औषधि(Jan Aushadhi,印度药房)。 - 利用
TAO BAO等独特店名,在地图服务(如Google Maps)中进行搜索,定位到具体城市和街区。结合其他招牌信息,可确定地点为印度瓦拉纳西(Varanasi)的某个区域。
- 识别店铺招牌上的文字,如
- 结合图1(窗景):
- 目标酒店必须位于图2定位的街区附近。
- 从酒店房间的窗户必须能直接看到图1中的寺庙尖塔建筑。
- 筛选酒店:在定位的街区周围,通过地图、酒店预订网站、街景图片等,寻找符合“窗外可见寺庙尖塔”这一视角的酒店。最终匹配到符合条件的酒店。
- 分析图2(街景):
- 答案:
kashiCTF{HOTEL_KAVANA}
网络(Web)
Nexus 1
- 漏洞类型:服务端模板注入(SSTI),引擎为Jinja2。
- 漏洞点:网页前端有一个表单,提交
message参数。后端在渲染时,未对用户输入的message进行过滤,直接将其作为模板内容进行渲染。 - 测试与确认:提交
{{7*7}},返回49,确认SSTI存在。 - 绕过过滤:后端可能对
__、[]等字符进行了过滤。采用以下方法绕过:- 使用
|attr()过滤器来访问属性,代替点号。 - 使用字符串拼接
('_'*2)来动态生成__。 - 使用
.get()方法代替中括号[]来访问字典键。
- 使用
- 利用链构造:
- 通过Jinja2内置对象
cycler进入全局命名空间:
{{ cycler|attr(('_'*2) ~ 'init' ~ ('_'*2))|attr(('_'*2) ~ 'globals' ~ ('_'*2)) }} - 获取
__builtins__,进而获取open函数:
{{ ...|attr('get')(('_'*2) ~ 'builtins' ~ ('_'*2))|attr('get')('open') }} - 读取目标文件
/flag.txt:
{{ ...|attr('get')('open')('/flag.txt')|attr('read')() }}
- 通过Jinja2内置对象
- 完整Payload示例:
{{ cycler|attr(('_'*2) ~ 'init' ~ ('_'*2))|attr(('_'*2) ~ 'globals' ~ ('_'*2))|attr('get')(('_'*2) ~ 'builtins' ~ ('_'*2))|attr('get')('open')('/flag.txt')|attr('read')() }} - 修复建议:永远不要将用户输入作为模板渲染。如需动态内容,应使用安全的模板变量替换功能,并对所有用户输入进行严格的过滤和转义。
相似文章
相似文章