ISCC 2026 移动安全题解教学文档
本文档基于 ISCC 2026 移动安全赛题官方题解整理,涵盖四道题目的完整解题思路与技术细节。
一、灰签名回廊
1.1 题目概述
典型 Android 多层验证逆向题。flag 未明文存放,而是通过 ContentProvider、BroadcastReceiver 和 Deep Link 三条独立隐藏通道分别下发不同片段,最终在 native 层完成链式收口校验。
核心特征:签名绑定机制,native 层 Part1 校验与 APK 证书 SHA-256 摘要绑定,防止重打包。
1.2 基本信息
| 项目 | 内容 |
|---|---|
| APK | attachment-68.apk |
| 包名 | com.example.gnd |
| 应用名 | SecureVault |
| Native 库 | lib/x86_64/libnative-verify.so |
关键组件:
com.example.gnd.hidden.SecretContentProvidercom.example.gnd.hidden.SecretReceivercom.example.gnd.hidden.SecretActivitycom.example.gnd.security.SecureValidator
1.3 隐藏片段触发与分发
ContentProvider 路径
- URI:
content://com.example.gnd.secret.provider/keys/fragment - 功能:生成并缓存 fragment2
- 注意事项:依赖内部计数器状态,需确保只触发一次查询
BroadcastReceiver 路径
- Action:
com.example.gnd.SECRET_ACTION - 两步验证机制:
- 先发认证广播:
code=auth+token=ISCC2026 - 再发取 key 广播:
code=getkey+part=3
- 先发认证广播:
- 功能:生成并缓存 fragment3
Deep Link 路径
- URI:
securevault://secret/unlock - 必需参数:
token=RC4_KEY_PART4 - 功能:写入 fragment4,若 token 错误则写入无效值
1.4 自定义 Base64 编码
字母表:
ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210+/
特点:大写字母反序、小写字母反序、数字反序、+/ 保持不变。
1.5 Java 层校验逻辑
入口:SecureValidator.c()
校验流程:
- flag 格式:
ISCC{...} - 内部字符串
inner = flag[5:-1] - 长度要求:
len(inner) == 24(由nativeGetMagicNumber()返回 12 决定) - 切分为 4 段:7 / 5 / 5 / 7 字节
- 前 3 段分别进入
nativeVerifyPart1/2/3 - 第 4 段进入
nativeVerifyPart4 - 中间经过
nativeComputeChainToken()串联结果
1.6 Native 层校验链
Part1(7 字节):e5JXv2T
- 结合 APK 证书 SHA-256 参与校验
- 通过 JNI 回调获取签名信息,与 so 中硬编码 hash 比较
Part2(5 字节):_qrGM
- 输入通过自定义 Base64 解码
- 与 Provider 产生的 fragment2 逐字节异或,检查结果是否符合预设常量
Part3(5 字节):_iJ5n
- 涉及线性反馈移位寄存器(LFSR)变换
- 输入字节经 LFSR 后与 fragment3 关联数据比较
Part4(7 字节):_bD24iR
- 使用前三段计算的 chain token
- 拼上 deep link 触发的 fragment4
- 进入 RC4 风格状态变换
- 最终与硬编码 SHA-256 值对比完成验证
1.7 最终 Flag
ISCC{e5JXv2T_qrGM_iJ5n_bD24iR}
二、Flag Shop
2.1 题目概述
flag 不在 Java 层明文,核心判定逻辑在 native 层 libflagshop.so。JNI 接口 NativeBridge.encryptFlag(String a, String b, String c, String d) 接收四元输入:商品编号、候选 flag、管理员账号、管理员密码。
2.2 商品结构
| 商品 | 类型 |
|---|---|
| fakeflag1 | 假 flag |
| fakeflag2 | 假 flag |
| fakeflag3 | 假 flag |
| realflag | 真 flag |
假 flag 明文统一为 ISCC{fakeflagfake}。
realflag 目标密文:
4653485084083011788fb5d735cfa35810b48a3433ced888c02965457ad21cf80e4936cc8a536fee26b3ffc2a64981a878511f0d3ab96cdd05879fac83f005f9b5e311fa07d299b0d0580b4611afd6c8a4db205c1f278134
2.3 管理员凭据恢复
- 用户名:
admin - 密码:
FlagShopAdmin2026(注意末尾6不可遗漏)
2.4 解题策略
- 使用 fake 商品校准仿真环境
- 构建测试输入:
fakeflag1+ISCC{fakeflagfake}+admin+FlagShopAdmin2026 - 验证 native oracle 正常工作
- 切换到 realflag 获取真实结果
2.5 本地 Oracle 搭建要点
- 抽取
libflagshop.so - 用户态 ARM64 仿真
- 需完成:PT_LOAD 段映射、重定位处理、JNIEnv 伪造、Java 字符串转换、导入函数 stub
2.6 最终 Flag
ISCC{7H15_1$_r431_f146_4nd_17_h45n'7_501d_0u7?!}
三、消失的喵星密使
3.1 题目概述
三层独立密码学设计叠加:ADFGVX 坐标矩阵、图片 LSB 隐写提取自定义 S 盒、基于该 S 盒的 AES-256 变体解密。
3.2 APK 结构
关键文件:
classes.dex、classes2.dexlib/arm64-v8a/libmobile03.soassets/hint.pngassets/sys/OBSERVER_PROTOCOL
核心类:
com.example.mobile03.MainActivitycom.example.mobile03._A、_B、_C、_P
3.3 Java 层前置处理
- 加载 native 库:
System.loadLibrary("mobile03") - native 函数:
_v(String flagBody, String path, Bitmap bitmap) - 读取
assets/hint.png传入 native - flag 格式:
ISCC{...},_B._g()通过异或还原前缀(0x0b, 0x11, 0x01, 0x01, 0x39异或0x42)
3.4 ADFGVX 坐标矩阵
坐标字母表:
| index | letter |
|---|---|
| 1 | V |
| 2 | F |
| 3 | D |
| 4 | G |
| 5 | A |
| 6 | X |
点击一个格子追加 rowLetter + colLetter,点击 6 个格子得到 12 字符路径。
3.5 MD5 前置校验
目标 MD5:0899ffac222b48317a57149d6df056c0
3.6 OBSERVER_PROTOCOL 约束推导
P1 求解:
- Ψ(r,c) = (r-c)² + (r+c-2)²
- 梯度为零得 r=1, c=1
- P1 = (1,1) = VV
P2 求解:
- 日志 tag = 0x1918
- T = 0x1918, τ = 1
- r_B = 2, c_B = 4
- P2 = (2,4) = FG
P3 求解:
- 数学推导得 (6,1),但实际受全局约束影响
- 结合 MD5 和路径约束暴力搜索得 P3 = (3,6) = DX
3.7 爆破点击序列
约束条件:
- Row_Total = 21
- Col_Total = 20
- P2.c > P1.c
- P6.c < P2.c
- MD5(sequence) = 目标值
唯一序列:VVFGDXGFAXXV
对应坐标:
| 点 | 坐标 | 字符 |
|---|---|---|
| P1 | (1,1) | VV |
| P2 | (2,4) | FG |
| P3 | (3,6) | DX |
| P4 | (4,2) | GF |
| P5 | (5,6) | AX |
| P6 | (6,1) | XV |
3.8 图片隐写
提取 hint.png 蓝色通道 LSB,得到以 MEOW 开头的数据块,后跟 256 字节 S-box(一一映射置换表)。
3.9 Native 层自定义 AES-256 解密
流程:
- 检查 flag body
- 从 hint.png LSB 提取 S-box
- 根据点击序列派生 32 字节 key
- 使用自定义 S-box 的 AES-256-like 算法解密密文
- 解出明文与输入比较
密钥派生:序列经交错重排 → 坐标对映射到 36 字符字母表 → 自定义 S 盒替换 → 与递增计数器异或
密文:
- Base64:
z5hNvYOCx3SmRtyC9LmQdCI8NG6wcQ4l63I8Jkmg6cY= - 异或 0x66 后:
a9fe2bdbe5e4a112c020bae492dff612445a5208d61768438d145a402fc68fa0
3.10 最终 Flag
ISCC{whi5ker_7r4ce_ayAP}
四、折叠回声
4.1 题目概述
flag 不是静态常量,而是由 APK 自身环境状态动态计算得出。第一层假 flag 用于筛选,真正校验在 EchoGate.verify() 中。
4.2 APK 结构
关键文件:
AndroidManifest.xmlMETA-INF/ECHOFOLD.RSAassets/sleep_loop.webpclasses.dexresources.arsc
特点:无 native 库,所有逻辑在 Java/DEX 层实现。
4.3 代码结构
核心类:
com.iscc.echofold.MainActivitycom.iscc.echofold.FirstEchoCodec(假 flag 校验)com.iscc.echofold.EchoGate(真 flag 校验)com.iscc.echofold.TraceRecordercom.iscc.echofold.R$styleable
4.4 假 flag
ISCC{this_is_only_the_first_echo}
4.5 WebP 中的 ECH0 Chunk
sleep_loop.webp 为 RIFF/WebP 容器,包含非标准 chunk ECH0(236 字节),为自定义 VM 数据块。
提取方法:按 RIFF chunk 结构遍历,查找 tag 为 ECH0 的 chunk。
4.6 EchoGate.verify 工作机制
输入构成:
key_material = sha256(classes.dex)
|| sha256(apk_signature_cert)
|| TraceRecorder.TRACE
|| R.styleable.EchoFoldPulse
处理流程:
- ECH0 chunk → EFVM → keystream
- cipher XOR keystream → flag body
TraceRecorder.TRACE:11 23 7a 42 51 66
R$styleable.EchoFoldPulse:32 个资源 ID,从 0x7f010000 递增至 0x7f01001f
4.7 最终 Flag
ISCC{f0lded_echo_is_a_state_not_a_string}
五、蔡文姬的胡笳琴音波谜阵
5.1 题目概述
Java 层生成 cw: tag,native 层用 tag 解密 assets/appview_cache.db,从解密后数据库提取种子初始化 RC6-like 加密器,反推 flag body。
5.2 APK 结构
关键文件:
classes.dexlib/arm64-v8a/libmobile04.soassets/appview_cache.db
包名:com.example.mobile04
5.3 皮肤与琴音序列
8 个 Skin 对应 8 个频率,按频率从低到高排序:
| 顺序 | 名称 | Code |
|---|---|---|
| 1 | 宫 | CWJ |
| 2 | 商 | XMAS |
| 3 | 角 | FLOW |
| 4 | 徵 | STAR |
| 5 | 羽 | ROSE |
| 6 | 变宫 | GOAL |
| 7 | 变徵 | DANCE |
| 8 | 清角 | MAGI |
正确点击序列:CWJXMASFLOWSTARROSEGOALDANCEMAGI(32 字节)
5.4 Tag 生成密码学链
流程:
- 获取点击序列
- 每个字节做 bit swap(相邻位交换:0↔1, 2↔3, 4↔5, 6↔7)
- FNV-1a 64 位哈希
- 拆分为低 32 位和高 32 位
- TEA-like 函数加密
- 结果按小端打包为 8 字节
- CRC32 处理
- 拼接得到
cw:%s:%08x格式 tag
TEA-like 密钥派生:native 层对包名字符串进行异或和移位操作生成 4 个 32 位常数。
5.5 Native 层校验
参数:cache.dat 路径、tag、flag body
body 长度要求:32 字节
5.6 解密 appview_cache.db
解密方式:
- 读取文件前 16 字节
- 计算
crc32(tag + first_16_bytes) - 用 CRC32 值对后续内容做异或解密
- 异或方式:4 字节循环密钥,字节偏移量
((i * 8) & 0x18),附加+ i扰动
解密成功后数据开头变为 SQLite format 3。
5.7 种子提取
从解密后数据库提取两个 32 位小端整数作为 seed,用于初始化 RC6-like 加密器。
5.8 RC6-like 加密与反推
关键常量:
- TGT(Base58 解码得 32 字节密文)
- K1(native 中硬编码的 32 字节异或密钥)
校验关系:
flag_body == RC6_decrypt(TGT XOR repeat(K1))
反推:
flag_body = RC6_decrypt(TGT XOR repeat(K1))
解出的 body:GuiHanQu8s9Dp68*G-ZN_#9Z3zLIbet6
5.9 最终 Flag
ISCC{GuiHanQu8s9Dp68*G-ZN_#9Z3zLIbet6}