Files
Serdtse-Sysoly/qualifiers/mileage-mdm-ios-reverse-hard/WRITEUP.md
T
2026-09-17 00:50:07 +03:00

2.5 KiB
Raw Blame History

Mileage MDM

Reverse hard

В раздатке есть MileageMDM.ipa и NOTES.md со схемой маскирования. MDM-агент автопарка хранит офлайн-доказательство подлинности профиля и карту API-селекторов. Нужно связать параметры профиля с блоком данных в Mach-O.

Решение

Распаковываем IPA как ZIP и смотрим Payload/MileageMDM.app/:

Файл Что ищем
Info.plist Значение AttestSeed
embedded.mobileprovision Тот же seed для сверки
MileageMDM Маркер MDMPROOF и зашифрованную карту

Согласно NOTES.md, ключ состоит из первых 16 байт SHA-256 от AttestSeed, а данные скрыты повторяющимся XOR. Seed лежит в Info.plist и дублируется в профиле, так что секретным он не является.

В бинаре находим MDMPROOF. Следующие четыре байта — длина блока в little-endian, дальше идёт сам блок. Читаем AttestSeed из plist в переменную seed, а зашифрованные байты — в blob, затем расшифровываем:

import hashlib
import json
import plistlib
import zipfile

with zipfile.ZipFile("public/MileageMDM.ipa") as ipa:
    data = ipa.read("Payload/MileageMDM.app/MileageMDM")
    profile = plistlib.loads(ipa.read("Payload/MileageMDM.app/Info.plist"))
seed = profile["AttestSeed"]
pos = data.index(b"MDMPROOF") + len(b"MDMPROOF")
size = int.from_bytes(data[pos:pos + 4], "little")
blob = data[pos + 4:pos + 4 + size]
key = hashlib.sha256(seed.encode()).digest()[:16]
plain = bytes(b ^ key[i % len(key)] for i, b in enumerate(blob))
proof = json.loads(plain)
print(proof["flag"])

Важно брать байты хеша через digest(), а не первые 16 символов hex-представления. С правильным ключом получается JSON-карта селекторов, в которой поле flag содержит искомое доказательство.

Солвер.

Флаг

caplag{selector_hashes_opened_the_mdm_proof}