MOBILE-深潮协定
ISCC2026 WriteUp 提交模板
MOBILE-深潮协定
解题思路
1.初步分析
MainActivity函数

继续看


放到so层了。
先看CryptoEngine.transform(context, str.substring(5, str.length() - 1)


Telemetry.load() 会把 assets/telemetry.bin 读出来,然后按换行切成 4 段:
0b04110916060d
1337c0de42a5197f
Qh2s7Ca9VxN4fKpB8J1mL+zoF0ewAyOu3DrRYSgPItU6McdkXl/WTnGEZ5ivbHjq
T8GHtJzYDRX7siXzWKLJCGlrpi1sVwnvMhZCPj3jwDWYU4Y=
对应关系如下:
- 第 1 行:
shifts,十六进制转字节,长度 7 - 第 2 行:
mask,十六进制转字节,长度 8 - 第 3 行:自定义 Base64 字母表,长度 64
- 第 4 行:目标串
Cyberengine做的就是:
- 对可打印 ASCII 做一个
mod 95的循环位移 - 再和
mask做循环异或 - 再加上前一个输出字节
prev - 再额外加上
3 * i
2.分析so文件

这段是在检查切分后的字符串数量是否达到 4。也就是说,native 期望:
- 至少有 4 个字段
- 后面会从其中取出两项做进一步处理

可以看到:
- 初始状态是
0x5D - 每轮都在更新 8-bit 状态
- 异或密钥不是固定值,而是
state + 7*i

- 用两个字符串参数调用一个解码函数
- 把解码结果拿到
r15 - 先比较长度
- 再做逐字节比较
结合前面的取值位置,可以确认这两个字符串就是:
- 第 3 行:自定义 Base64 字母表
- 第 4 行:目标串
所以 native 的职责是:
- 先把 Java 输出改写一遍
- 再把
telemetry.bin里的目标串按自定义 Base64 解码 - 最后比较两边是否一致
4.还原思路
到这里,整个校验链就已经完整了:
flag body
-> CryptoEngine.transform()
-> native keystream xor
-> compare(decoded_target)
所以我们需要:
- 先把目标串按“自定义字母表 Base64”解码
- 再逆掉 native 的滚动异或
- 最后逆掉 Java 的状态变换

ISCC{abyssal_vows_bind_the_last_protocol}
Exp
from __future__ import annotations
import base64
import zipfile
from pathlib import Path
APK_PATH = Path(__file__).with_name("app-release.apk")
STANDARD_B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
def load_telemetry(apk_path: Path) -> tuple[bytes, bytes, str, str]:
with zipfile.ZipFile(apk_path) as zf:
shifts_hex, mask_hex, alphabet, target = zf.read("assets/telemetry.bin").decode().splitlines()
return bytes.fromhex(shifts_hex), bytes.fromhex(mask_hex), alphabet, target
def decode_custom_base64(payload: str, alphabet: str) -> bytearray:
translated = payload.translate(str.maketrans(alphabet, STANDARD_B64))
return bytearray(base64.b64decode(translated))
def undo_native_keystream(data: bytearray) -> bytearray:
state = 0x5D
for index in range(len(data)):
feedback = ((state >> 2) ^ (state >> 3) ^ (state >> 5) ^ state) & 1
state = ((state >> 1) | (feedback << 7)) & 0xFF
data[index] ^= (state + 7 * index) & 0xFF
return data
def undo_java_transform(transformed: bytes, shifts: bytes, mask: bytes) -> str:
previous = 0
recovered = bytearray()
for index, current in enumerate(transformed):
mixed = (current - previous - 3 * index) & 0xFF
rotated = mixed ^ mask[index % len(mask)]
if not 32 <= rotated <= 126:
raise ValueError(f"unexpected non-printable intermediate byte at index {index}: 0x{rotated:02x}")
plain = ((rotated - 32 - shifts[index % len(shifts)]) % 95) + 32
recovered.append(plain)
previous = current
return recovered.decode("ascii")
def main() -> None:
shifts, mask, alphabet, target = load_telemetry(APK_PATH)
decoded_target = decode_custom_base64(target, alphabet)
java_output = undo_native_keystream(decoded_target)
body = undo_java_transform(java_output, shifts, mask)
print(f"[+] APK: {APK_PATH}")
print(f"[+] shifts length: {len(shifts)}")
print(f"[+] mask length: {len(mask)}")
print(f"[+] decoded target bytes: {len(decoded_target)}")
print(f"[+] flag body: {body}")
print(f"[+] flag: ISCC{{{body}}}")
if __name__ == "__main__":
main()

浙公网安备 33010602011771号