MOBILE-深海金库

ISCC2026 WriteUp 提交模板

MOBILE-深海金库

解题思路

1.假逻辑

image.png

这一部分是个假逻辑,无论你输入什么点击后都是输出签名错误

image.png

追踪这个函数

内部逻辑:

  1. updateState() 每次点击将 CLICK_TRIGGER 递增
  2. prepareContext() 检查两个条件,任一成立就直接 return:
    • Debug.isDebuggerConnected() — 检测到调试器则静默退出
    • CLICK_TRIGGER.get() < 3 — 点击次数少于 3 次也退出

也就是说只有非调试环境 + 至少点击 3 次按钮后,才会真正调用 CoreProtocolDelegate.dispatchConfiguration(view, onLongClickListener)。

image.png

image.png

第一层校验,并不复杂

image.png

Snipaste_2026-05-18_08-00-14.png

第二层是这个

接着看so层文件,找到对应so的函数是verify_bridge

image.png

image.png

具体实现要追踪SV函数:

核心函数 SV::v (0x222B0) 的验证逻辑:

  1. 拷贝输入数据
  2. 调用 process_fib_stream(dest, length) — 用 Fibonacci 流变换输入
  3. 调用 encode_b64(&n2, &dest_2) — base64 编码变换后的数据
  4. 将 base64 结果与硬编码字符串 "cUdltutGeWWIUm+7OE4ce3wi" 比较

所以验证链为:

输入 → process_fib_stream → encode_b64 → 比较 "cUdltutGeWWIUm+7OE4ce3wi"

值得注意的是,base64是个自定义的。

运行脚本

image.png

ISCC{n2R#7_pQ!9vL*5mWnp}

Exp

from __future__ import annotations

import base64
import sys
import zipfile
from pathlib import Path

DEFAULT_BUNDLE = Path(__file__).with_name("app-release-01.apk")
SO_MEMBER = "lib/arm64-v8a/libsecure_verify.so"

OBFUSCATED_ALPHABET = "zKJUExRaVtM3Ydv5TQIsWD1frnHC78Lckl6euPh9AGoj0SgN4Zp+OwXi2F/ybmBq"
REFERENCE_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
TARGET_SEGMENTS = ("cUdltutGeWWI", "Um+7OE4ce3wi")
SYMBOL_REMAP = str.maketrans(OBFUSCATED_ALPHABET, REFERENCE_ALPHABET)

APK_FINGERPRINT = (
    b"nativeVerify",
    b"com/example/mobile02/MainActivity",
    OBFUSCATED_ALPHABET.encode("ascii"),
    *(segment.encode("ascii") for segment in TARGET_SEGMENTS),
)


def read_verifier_image(bundle_file: Path) -> bytes:
    with zipfile.ZipFile(bundle_file) as archive:
        return archive.read(SO_MEMBER)


def assert_expected_build(so_blob: bytes) -> None:
    absent = [needle for needle in APK_FINGERPRINT if needle not in so_blob]
    if absent:
        detail = ", ".join(repr(needle.decode("ascii", errors="ignore")) for needle in absent)
        raise ValueError(f"unexpected verifier image, missing markers: {detail}")


def decode_embedded_goal() -> bytes:
    encoded_goal = "".join(TARGET_SEGMENTS)
    normalized_goal = encoded_goal.translate(SYMBOL_REMAP)
    return base64.b64decode(normalized_goal)


def peel_fibonacci_xor(ciphertext: bytes) -> bytes:
    plain = bytearray()
    left = 1
    right = 1
    for cipher_byte in ciphertext:
        plain.append(cipher_byte ^ (left & 0xFF))
        left, right = right, (left + right) & 0xFF
    return bytes(plain)


def replay_java_stage(candidate_inner: str) -> bytes:
    emitted = bytearray()
    rolling = 0
    for slot, raw_char in enumerate(candidate_inner.encode("ascii")):
        rotated = ((raw_char - 25) % 95) + 32
        bias = 8 if slot % 2 == 0 else -8
        rolling = (rotated + bias + (rolling % 4)) & 0xFF
        emitted.append(rolling)
    return bytes(emitted)


def restore_inner_text(java_bytes: bytes) -> str:
    recovered = bytearray()
    previous_byte = 0
    for slot, output_byte in enumerate(java_bytes):
        bias = 8 if slot % 2 == 0 else -8
        rotated = (output_byte - bias - (previous_byte % 4)) & 0xFF
        original_byte = ((rotated - 32 - 7) % 95) + 32
        recovered.append(original_byte)
        previous_byte = output_byte
    return recovered.decode("ascii")


def derive_flag(bundle_file: Path) -> str:
    so_blob = read_verifier_image(bundle_file)
    assert_expected_build(so_blob)

    native_goal = decode_embedded_goal()
    java_goal = peel_fibonacci_xor(native_goal)
    inner_text = restore_inner_text(java_goal)

    if replay_java_stage(inner_text) != java_goal:
        raise ValueError("java stage round-trip check failed")

    return f"ISCC{{{inner_text}}}"


def main() -> None:
    bundle_file = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else DEFAULT_BUNDLE.resolve()
    print(derive_flag(bundle_file))


if __name__ == "__main__":
    main()

posted @ 2026-05-19 16:29  MillionMind  阅读(13)  评论(0)    收藏  举报