首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
CTF对抗
发新帖
0
22
[原创]KCTF2026 - 第六题:酉时·书院迷局 题解(AI)
发表于: 2026-8-19 10:38
1692
[原创]KCTF2026 - 第六题:酉时·书院迷局 题解(AI)
星野安全
1
2026-8-19 10:38
1692
## Summary Android APK,`libkctf.so` 的 `nativeProcessInput` 校验 100 位小写 hex(50 字节)注册码。50 字节被拆成**偶数下标 25 字节走 Check A、奇数下标 25 字节走 Check B**,两条链靠一个字节 `v99` 耦合。 题目的难点在**反分析**上: 1. **回滚欺骗** —— 校验失败时静默把中间量恢复成无害默认值(S 盒退回恒等表、`rot` 归零)。用随机输入探测,一切都长得像"这段代码没作用",于是整条 Check A 被误判成 2^64 死结。 2. **Check B 的关键缓冲不是输入派生的**。`acc144` 那 8 个方程里被反编译显示成栈变量的`buffer`,实际指向常量数组 `v106[5..12]` 而非奇字节。看穿这一点后,64 位未知量 `w27`**被钉死成一个常数**,整题当场坍塌。 破掉这两点后,剩下的是一个 46 位稀疏布尔方程组,DFS 秒解。 ## Solution ### Step 1:拆出 .so,建仿真 oracle `AndroidManifest.xml` 里 `extractNativeLibs=false`,所以 `lib/arm64-v8a/libkctf.so` 在 APK 里是 **Stored 未压缩**的,可直接 dump。Java 层字符串用 `c()` 异或 `0xA5A5A5A5` 混淆、`d()` 是个被 jadx 展平成死循环的状态机(读 smali 还原即可),但都只是幌子, 校验全在 native。 so 有控制流平坦化 + 不透明谓词 + `BR X16` 间接派发,IDA 反编译多处失真(无独立 prologue 的函数被并进调用者)。 用 Unicorn 手写 harness 把 `nativeProcessInput` 单独跑起来(`solve/uc_oracle.py`): pyelftools 读 `.rela.dyn`/`.rela.plt` 做重定位(60 条,20 个导入桩)、 JNI vtable 打桩、以及**裸 `svc #0` 系统调用**(so 绕过 libc 直接 mmap,nr=222)。 angr 在这里走不通(NEON 的 `Iop_ZeroHI64ofV128` 未实现)。 ### Step 2:Check A —— 识破回滚欺骗 `sub_130CC` / `loc_1325C` / `loc_13878` 三段曾被写成"只维护不透明状态",实际都是真校验: - `loc_1325C` 用 `LE32(v117[9:13])` 作种子生成 256 字节 xorshift32 密钥流, 异或进 S 盒(偏移 `(0x8C+i)&0xff`),再校验 `sbox[0..2] == 85 26 27`。 **失败就重新载入恒等表** —— 这正是之前所有实验看到的现象。→ `sbox[0]=0x85`。  - `loc_13878` 的 `rot = (v117[21]&0x1F) ^ (在 sub_126B8 前 1KB 找到 BRK ? 1 : 0)` 是真反调试;八个方程 `ROL((x+(K>>5))^K^((x>>12)+(K<<4)), rot) == M[j%4]^const` 唯一解出 `rot=7`、`dword_16484=0x371342`。**失败则 rot 强制归零。**  解出偶字节与 Check A 对 Check B 的唯一要求: ``` EVEN25 = a77a78ffc894367d1bf5bb3faab6e2f4db0070533de8b73443 delta = 0x9E3779B9 LCG种子 = 0x5B28455B rot = 7 sbox[0] = 0x85 => 要求 v99 == 0x88 ``` ### Step 3:Check B —— 可逆海绵 + 把 w27 钉死 `sub_5FE8` 是个**rate 等于全状态**的 ARX 海绵(12 轮纯 ARX,挤出时直接吐整个 32 字节状态)。 这是致命设计缺陷:**KDF 可逆**,给定需要的 32 字节状态就能反推 25 个奇字节, 而 7 字节 `0x5A` 填充顺带成为一个 56 位一致性校验。 (`sub_6374` 里那套 NEON MixColumns + GF(2^8) 0x1b 的"AES"全部相消,实测 `sub_6374(x,y,k)=x^y`、 `sub_6614(x,y)=x+y`。)  状态四个字里 `A0/C0/D0` 由设备常量 `G`(`sub_10E18` 产出的 32 字节,真机测得)的三段比对钉死, 只剩 `b0` 一个 64 位未知。而 `acc144 == 0` 的 8 个方程里,除 `w27` 外**每一项都是常量**: ``` G[i] ^ G[8+i] ^ G[24+i] ^ v106[5+i] ^ (0xC3+0x29i) ^ (0xD7+0x17i) ```  `i=0..3` 与 `i=4..7` 两半独立自洽(2^-32 的巧合概率),所以 ``` w27 = 0x129B4A86 -> B = fmix64链(w27) = 0x996bd2f8c1b7dd80 (全程双射) ```  `B` 一固定,`sub_5998(b0,B,w27,0xD7) & 0x3FFFFFFFFFFF == 0x16EFF8C39C23` 这 46 个输出位**每位只依赖 b0 的 2~5 位**,DFS 直接解,再用 56 位填充筛出唯一解。  完整求解脚本(`solve/solve.py`): ```python #!/usr/bin/env python3 """KCTF2026 第六题 -- 端到端求解。python solve.py -> 100 位 hex 注册码""" import json, os, sys, time HERE = os.path.dirname(os.path.abspath(__file__)); sys.path.insert(0, HERE) from sponge import rounds, v99 as V99 # 12 轮 ARX 海绵,rate==全状态 => 可逆 from bmodel import B_from_w27 # w27 -> B 的 fmix64 双射链 EVEN25 = bytes.fromhex("a77a78ffc894367d1bf5bb3faab6e2f4db0070533de8b73443") # Check A 解 A0, C0, D0 = 0x58B67A47304291C1, 0x492f9ce7e59b3768, 0x69eec8c1ee727f73 # G 比对钉死 W27, TGT_X8, PAD = 0x129B4A86, 0x16EFF8C39C23, bytes([0x5A]) * 7 B = B_from_w27(W27) # 0x996bd2f8c1b7dd80 TAB = {int(k): v for k, v in json.load(open(f"{HERE}/tt5998.json"))["tables"].items()} red = [] # B 固定后化简成只含 b0 的布尔函数 for k, t in TAB.items(): ins, tt = t["inputs"], t["tt"] bbits = [b for b in ins if b < 64] # >=64 的位属于 B,现在是常数 fixed = sum(1 << j for j, b in enumerate(ins) if b >= 64 and (B >> (b - 64)) & 1) tab = {} for combo in range(1 << len(bbits)): m, bi = fixed, 0 for j, b in enumerate(ins): if b < 64: if (combo >> bi) & 1: m |= 1 << j bi += 1 tab[combo] = (tt >> m) & 1 red.append((k, bbits, tab)) want = {k: (TGT_X8 >> k) & 1 for k in TAB} used = sorted({b for _, bb, _ in red for b in bb}) pos = {b: i for i, b in enumerate(used)} # 贪心变量序:方程尽早触发才剪得动 trig = [[] for _ in used] for idx, (_, bb, _) in enumerate(red): trig[max(pos[b] for b in bb)].append(idx) def ev(idx, v): _, bb, tab = red[idx] return tab[sum(1 << j for j, b in enumerate(bb) if (v >> b) & 1)] sols = [] def dfs(i, v): if i == len(used): sols.append(v); return for bit in (0, 1): nv = v | (bit << used[i]) if all(ev(idx, nv) == want[red[idx][0]] for idx in trig[i]): dfs(i + 1, nv) dfs(0, 0) hits = [] # 56 位海绵填充筛唯一解 for b0 in sols: raw = b''.join(w.to_bytes(8, 'little') for w in rounds([A0, b0, C0, D0], 12, inv=True)) if raw[25:] == PAD: hits.append((b0, raw[:25])) assert len(hits) == 1 b0, odd25 = hits[0] assert V99(odd25) == 0x88 # Check A 的耦合条件 inp = bytearray(50) for k in range(25): inp[2*k], inp[2*k+1] = EVEN25[k], odd25[k] print(bytes(inp).hex()) ``` 运行结果: ``` $ python solve.py [*] w27 = 0x129b4a86 -> B = 0x996bd2f8c1b7dd80 [*] 46 个方程,约束到 b0 的 64 位,自由 0 位 [*] 每个输出位的扇入: [2, 3, 3, 4, 4, 4, 5, 5, 5, ... 5, 5] [*] DFS: 1130274 组解满足 46 位方程 (310.9s) [*] 填充筛选 1130274 个候选 ... [*] 幸存 1 个 (18s) [*] b0 = 0x9eb09ed6e443b907 [*] odd25 = 7ae31b94d256f80c41b7298e63a5df104bc8723d960fe458ad [*] v99 = 0x88 (Check A 要求 0x88) OK FLAG (100 hex): a77a7ae3781bff94c8d2945636f87d0c1b41f5b7bb293f8eaa63b6a5e2dff410db4b00c87072533d3d96e80fb7e4345843ad ``` 1130274 个候选经 56 位填充筛,**唯一幸存者**;`v99` 恰好等于 Check A 要求的 `0x88`。真机(frida hook `nativeProcessInput`)返回 `1`。 **关于变量序**:上面的 `order` 必须用贪心序(反复取未放置变量最少的方程)。 若按 bit 升序排,前 ~30 层没有任何方程可判,DFS 等于盲展开 2^30 个节点、根本跑不完—— 这是个很容易踩的坑。贪心序下纯 Python 约 5.5 分钟;同样算法的 C 版本(`b0.c`)0.59 秒。
回复或点赞可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
收藏
・
0
点赞
・
22
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
aut0run
非常支持你的观点!
3天前
git_51951meggadf3df
非常支持你的观点!
5天前
mb_dvqqrcce
感谢你的积极参与,期待更多精彩内容!
5天前
晨曦。
为你点赞!
2026-9-9 07:21
DannysMask
感谢你的贡献,论坛因你而更加精彩!
2026-8-31 18:33
mb_lthgjpwj
为你点赞!
2026-8-27 15:29
lihaiyanz
感谢你的贡献,论坛因你而更加精彩!
2026-8-24 04:04
bananaships
你的帖子非常有用,感谢分享!
2026-8-21 10:15
shuax
期待更多优质内容的分享,论坛有你更精彩!
2026-8-21 09:29
aeolusw
非常支持你的观点!
2026-8-20 20:27
逆向小玖
你的分享对大家帮助很大,非常感谢!
2026-8-20 18:47
dsfa
为你点赞!
2026-8-20 17:40
mb_haaygihg
这个讨论对我很有帮助,谢谢!
2026-8-20 16:17
梧桐生
感谢你的积极参与,期待更多精彩内容!
2026-8-20 15:32
mb_qxxdauxa
这个讨论对我很有帮助,谢谢!
2026-8-20 15:29
huangyalei
为你点赞!
2026-8-20 15:09
Magic丶
为你点赞!
2026-8-20 14:33
DNLINYJ
这个讨论对我很有帮助,谢谢!
2026-8-20 14:24
hyz111
感谢你分享这么好的资源!
2026-8-20 14:14
Hacker_0
为你点赞!
2026-8-20 14:06
阿捏利
这个讨论对我很有帮助,谢谢!
2026-8-20 13:55
教教我吧~
感谢你分享这么好的资源!
2026-8-20 13:49
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
9
)
xingbing
雪 币:
158
活跃值:
(5651)
能力值:
( LV2,RANK:10 )
在线值:
发帖
2
回帖
1373
粉丝
2
关注
私信
xingbing
2
楼
感谢分享。
2026-8-20 14:06
0
秋白
雪 币:
260
活跃值:
(110)
能力值:
( LV2,RANK:10 )
在线值:
发帖
6
回帖
12
粉丝
0
关注
私信
秋白
3
楼
感谢
2026-8-20 14:38
0
TimeQvQ
雪 币:
420
活跃值:
(65)
能力值:
( LV2,RANK:10 )
在线值:
发帖
1
回帖
15
粉丝
0
关注
私信
TimeQvQ
4
楼
1
2026-8-20 15:28
0
Imxz
雪 币:
112
活跃值:
(9395)
能力值:
( LV2,RANK:10 )
在线值:
发帖
6
回帖
749
粉丝
9
关注
私信
Imxz
5
楼
tql
2026-8-20 16:09
0
落音吹雪
雪 币:
205
活跃值:
(135)
能力值:
( LV2,RANK:10 )
在线值:
发帖
1
回帖
41
粉丝
0
关注
私信
落音吹雪
6
楼
nb
2026-8-20 17:52
0
Ahzn
雪 币:
130
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
54
粉丝
0
关注
私信
Ahzn
7
楼
NNX
2026-8-21 00:31
0
mb_pofaacey
雪 币:
0
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
3
粉丝
0
关注
私信
mb_pofaacey
8
楼
nb
2026-8-21 08:08
0
yegu
雪 币:
4114
活跃值:
(3129)
能力值:
( LV8,RANK:123 )
在线值:
发帖
26
回帖
573
粉丝
40
关注
私信
yegu
9
楼
nb
2026-8-24 01:37
0
mb_tlkgtbuf
雪 币:
0
能力值:
( LV1,RANK:0 )
在线值:
发帖
3
回帖
3
粉丝
0
关注
私信
mb_tlkgtbuf
10
楼
1
2026-8-24 17:56
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
星野安全
1
14
发帖
6
回帖
110
RANK
关注
私信
他的文章
[原创] 某 App 抽取壳的内存脱壳与请求签名逆向
13406
[原创] 基于某银行App的梆梆加固逆向实战
3628
[原创]KCTF2026 -第十题:卯时·曦光初现 题解(AI)
394
[原创]KCTF2026 - 第九题:丑寅同墟·星海抉择 题解(AI)
118
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部