mirror of
https://github.com/qtmleap/revkit.git
synced 2026-09-14 07:11:50 +02:00
feat(tools): TFIT whitebox AES-128-ECB emulator using Unicorn Engine
Unicorn Engine でNFWebCrypto.framework の TFIT_op_iAES11 (0x25CB0) を エミュレートし、genModelGroupKeys(MGKType, ESN) を Python で再現する ツールを追加。 - Mach-O の全セグメント (TEXT/DATA_CONST/DATA/LINKEDIT) を仮想アドレス 通りに Unicorn にマッピング - memset/memcpy スタブを GOT 経由でフックし TFIT_r9_op 内の外部呼び出しを 処理 - __stack_chk_guard を偽ポインタで提供しスタックカナリー検証を回避 - 自己整合テストベクタ (zero KS × 6 + iPhone KS × 2) で動作を確認 - CLI: --test でベクタ検証、引数に ESN を渡して MGK ペアを計算 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
565ab2b1d4
commit
e0efc336f7
@@ -0,0 +1,500 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
TFIT whitebox AES-128-ECB emulator using Unicorn Engine.
|
||||
|
||||
Emulates TFIT_op_iAES11 from NFWebCrypto.framework (arm64 Mach-O) to compute:
|
||||
genModelGroupKeys(MGKType, ESN) -> (mgk_key_16B, mgk_vector_32B)
|
||||
|
||||
Which is: SHA384(ESN) split into 3x16B blocks, each TFIT-encrypted with the
|
||||
device-specific whitebox AES key schedule.
|
||||
|
||||
Usage:
|
||||
uv run tools/emulate_tfit.py <ESN>
|
||||
uv run tools/emulate_tfit.py --test
|
||||
|
||||
Binary: /tmp/nfwc/Payload/Argo.app/Frameworks/NFWebCrypto.framework/NFWebCrypto
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import struct
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import lief
|
||||
from unicorn import (
|
||||
UC_ARCH_ARM64,
|
||||
UC_MODE_ARM,
|
||||
Uc,
|
||||
UcError,
|
||||
arm64_const,
|
||||
)
|
||||
from unicorn.arm64_const import (
|
||||
UC_ARM64_REG_LR,
|
||||
UC_ARM64_REG_PC,
|
||||
UC_ARM64_REG_SP,
|
||||
UC_ARM64_REG_W0,
|
||||
UC_ARM64_REG_X0,
|
||||
UC_ARM64_REG_X1,
|
||||
UC_ARM64_REG_X2,
|
||||
UC_ARM64_REG_X3,
|
||||
UC_ARM64_REG_X16,
|
||||
)
|
||||
from unicorn import unicorn_const
|
||||
|
||||
BINARY_PATH = Path(
|
||||
"/tmp/nfwc/Payload/Argo.app/Frameworks/NFWebCrypto.framework/NFWebCrypto"
|
||||
)
|
||||
|
||||
# Function offsets (file offset == virtual address; __TEXT base = 0x0)
|
||||
TFIT_OP_ADDR = 0x25CB0 # TFIT_op_iAES11(ctx, input, output)
|
||||
TFIT_R9_ADDR = 0x248DC # TFIT_r9_op_iAES11(ctx, input_state, output_state)
|
||||
TFIT_CIPHER_ADDR = 0x26C9C # TFIT_wbaes_ecb_cipher_iAES11(ctx, in, size, out)
|
||||
|
||||
# Key schedule offsets in binary (within __TEXT __const, va == file offset)
|
||||
KS_IPHONE_OFFSET = 0x1AD0E8 # 224 bytes
|
||||
KS_IPAD_OFFSET = 0x1AD008 # 224 bytes
|
||||
KS_ATV_OFFSET = 0x1ACF28 # 224 bytes
|
||||
KS_SIZE = 224
|
||||
|
||||
# GOT base in __DATA_CONST
|
||||
GOT_BASE = 0x238000
|
||||
|
||||
# GOT entry offsets for external functions we must intercept
|
||||
GOT_MEMSET = 0x440 # _memset
|
||||
GOT_MEMCPY = 0x430 # _memcpy
|
||||
GOT_STACK_CHK_GUARD = 0x2A8 # ___stack_chk_guard
|
||||
|
||||
# Stubs that call through GOT (these addresses jump to external functions)
|
||||
STUB_MEMSET = 0x1AB990 # bl -> GOT[0x440]
|
||||
STUB_MEMCPY = 0x1AB978 # bl -> GOT[0x430]
|
||||
|
||||
# Unicorn memory layout
|
||||
STACK_BASE = 0x7FFF0000
|
||||
STACK_SIZE = 0x100000 # 1 MB
|
||||
SCRATCH_BASE = 0x60000000 # scratch memory for ctx/input/output/canary
|
||||
SCRATCH_SIZE = 0x10000
|
||||
|
||||
# Sentinel: return address we push to detect function exit
|
||||
RETURN_SENTINEL = 0xDEAD0000
|
||||
|
||||
|
||||
class TFITEmulator:
|
||||
"""ARM64 Unicorn emulator for TFIT_op_iAES11."""
|
||||
|
||||
def __init__(self, binary_data: bytes, binary: lief.MachO.Binary) -> None:
|
||||
self._binary_data = binary_data
|
||||
self._binary = binary
|
||||
self._uc: Uc | None = None
|
||||
self._memset_addr = STUB_MEMSET
|
||||
self._memcpy_addr = STUB_MEMCPY
|
||||
|
||||
# Set up canary value: just a fixed non-zero value
|
||||
self._canary_value = 0x1122334455667788
|
||||
|
||||
self._setup_engine()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Memory setup
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _setup_engine(self) -> None:
|
||||
uc = Uc(UC_ARCH_ARM64, UC_MODE_ARM)
|
||||
self._uc = uc
|
||||
|
||||
# Map all Mach-O segments
|
||||
page = 0x1000
|
||||
mapped: list[tuple[int, int]] = []
|
||||
|
||||
for seg in self._binary.segments:
|
||||
va = seg.virtual_address
|
||||
vsz = seg.virtual_size
|
||||
if vsz == 0:
|
||||
continue
|
||||
|
||||
# Align to page
|
||||
va_aligned = (va // page) * page
|
||||
end_aligned = ((va + vsz + page - 1) // page) * page
|
||||
map_size = end_aligned - va_aligned
|
||||
|
||||
# Avoid overlap
|
||||
skip = False
|
||||
for prev_va, prev_size in mapped:
|
||||
if va_aligned < prev_va + prev_size and va_aligned + map_size > prev_va:
|
||||
skip = True
|
||||
break
|
||||
if skip:
|
||||
continue
|
||||
|
||||
uc.mem_map(va_aligned, map_size)
|
||||
|
||||
# Write file content
|
||||
fsz = seg.file_size
|
||||
if fsz > 0:
|
||||
foff = seg.file_offset
|
||||
data = self._binary_data[foff : foff + fsz]
|
||||
uc.mem_write(va, data)
|
||||
|
||||
mapped.append((va_aligned, map_size))
|
||||
|
||||
# Map stack
|
||||
uc.mem_map(STACK_BASE, STACK_SIZE)
|
||||
# Map scratch area
|
||||
uc.mem_map(SCRATCH_BASE, SCRATCH_SIZE)
|
||||
|
||||
# Provide fake __stack_chk_guard:
|
||||
# The code does: adrp x9, #0x238000; ldr x9, [x9, #0x2a8]; ldr x9, [x9]
|
||||
# GOT[0x2a8] must contain a pointer to the actual canary value.
|
||||
canary_val_addr = SCRATCH_BASE + 0x500
|
||||
uc.mem_write(canary_val_addr, struct.pack("<Q", self._canary_value))
|
||||
uc.mem_write(GOT_BASE + GOT_STACK_CHK_GUARD, struct.pack("<Q", canary_val_addr))
|
||||
|
||||
# Point GOT stubs to our magic return addresses so we can intercept
|
||||
# We write a known address into the GOT entries that our hook will detect.
|
||||
# Hook strategy: hook on the BR X16 in each stub.
|
||||
# Alternative: write NOPs or trampoline to return sentinel.
|
||||
# Simplest: write our own "function" at SCRATCH_BASE that does RET immediately,
|
||||
# then point GOT entries to it.
|
||||
|
||||
nop_ret_addr = SCRATCH_BASE + 0x200 # tiny function: just "ret"
|
||||
ret_insn = struct.pack("<I", 0xD65F03C0) # ret
|
||||
uc.mem_write(nop_ret_addr, ret_insn)
|
||||
|
||||
# Provide fake memset and memcpy in GOT so stubs work
|
||||
# For memset(ptr, 0, n): we implement it in the hook
|
||||
# For memcpy(dst, src, n): we implement it in the hook
|
||||
# Point GOT entries to unique sentinel addresses we hook on
|
||||
self._fake_memset_impl = SCRATCH_BASE + 0x210
|
||||
self._fake_memcpy_impl = SCRATCH_BASE + 0x220
|
||||
|
||||
# Each fake impl = "ret" for now; we'll do the work in _hook_code
|
||||
uc.mem_write(self._fake_memset_impl, ret_insn)
|
||||
uc.mem_write(self._fake_memcpy_impl, ret_insn)
|
||||
|
||||
uc.mem_write(GOT_BASE + GOT_MEMSET, struct.pack("<Q", self._fake_memset_impl))
|
||||
uc.mem_write(GOT_BASE + GOT_MEMCPY, struct.pack("<Q", self._fake_memcpy_impl))
|
||||
|
||||
# Install code hook for intercepting calls to fake impls
|
||||
uc.hook_add(unicorn_const.UC_HOOK_CODE, self._hook_code)
|
||||
|
||||
def _hook_code(self, uc: Uc, address: int, size: int, user_data: object) -> None:
|
||||
"""Intercept calls at sentinel addresses and implement memset/memcpy."""
|
||||
if address == self._fake_memset_impl:
|
||||
# memset(ptr, value, n)
|
||||
ptr = uc.reg_read(UC_ARM64_REG_X0)
|
||||
value = uc.reg_read(arm64_const.UC_ARM64_REG_X1) & 0xFF
|
||||
n = uc.reg_read(UC_ARM64_REG_X2)
|
||||
if n > 0 and n <= 0x10000:
|
||||
uc.mem_write(ptr, bytes([value]) * n)
|
||||
# RET: jump to LR
|
||||
lr = uc.reg_read(UC_ARM64_REG_LR)
|
||||
uc.reg_write(UC_ARM64_REG_PC, lr)
|
||||
|
||||
elif address == self._fake_memcpy_impl:
|
||||
# memcpy(dst, src, n)
|
||||
dst = uc.reg_read(UC_ARM64_REG_X0)
|
||||
src = uc.reg_read(UC_ARM64_REG_X1)
|
||||
n = uc.reg_read(UC_ARM64_REG_X2)
|
||||
if n > 0 and n <= 0x10000:
|
||||
data = uc.mem_read(src, n)
|
||||
uc.mem_write(dst, bytes(data))
|
||||
lr = uc.reg_read(UC_ARM64_REG_LR)
|
||||
uc.reg_write(UC_ARM64_REG_PC, lr)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Emulate one block
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def encrypt_block(self, key_schedule: bytes, plaintext: bytes) -> bytes:
|
||||
"""Encrypt a single 16-byte block with TFIT_op_iAES11.
|
||||
|
||||
key_schedule: 224 bytes (device-specific whitebox key schedule)
|
||||
plaintext: 16 bytes
|
||||
returns: 16 bytes ciphertext
|
||||
"""
|
||||
assert len(key_schedule) == KS_SIZE, f"key_schedule must be {KS_SIZE} bytes"
|
||||
assert len(plaintext) == 16, "plaintext must be 16 bytes"
|
||||
|
||||
uc = self._uc
|
||||
|
||||
# Layout scratch memory:
|
||||
# SCRATCH_BASE + 0x000 : ctx = key_schedule (224 bytes = 0xE0)
|
||||
# The real code allocates 0xE4 bytes, aligns ptr, and copies
|
||||
# key_schedule[0..224] directly to aligned_ptr[0].
|
||||
# So ctx[0x10] = round-0 key word 0 (u32 at ks[0x10]).
|
||||
# SCRATCH_BASE + 0x300 : input (16 bytes)
|
||||
# SCRATCH_BASE + 0x400 : output (16 bytes)
|
||||
ctx_addr = SCRATCH_BASE + 0x000
|
||||
input_addr = SCRATCH_BASE + 0x300
|
||||
output_addr = SCRATCH_BASE + 0x400
|
||||
|
||||
# Build ctx: write key_schedule directly at offset 0 (zero-pad to 0xE4)
|
||||
ctx_data = bytearray(0xE4)
|
||||
ctx_data[0:KS_SIZE] = key_schedule
|
||||
uc.mem_write(ctx_addr, bytes(ctx_data))
|
||||
|
||||
# Write input
|
||||
uc.mem_write(input_addr, plaintext)
|
||||
|
||||
# Clear output
|
||||
uc.mem_write(output_addr, bytes(16))
|
||||
|
||||
# Restore the canary value (it may have been clobbered by previous run)
|
||||
canary_val_addr = SCRATCH_BASE + 0x500
|
||||
uc.mem_write(canary_val_addr, struct.pack("<Q", self._canary_value))
|
||||
|
||||
# Set up stack pointer (grows down, leave room for frame)
|
||||
sp = STACK_BASE + STACK_SIZE - 0x1000
|
||||
uc.reg_write(UC_ARM64_REG_SP, sp)
|
||||
|
||||
# Set function arguments: x0=ctx, x1=input, x2=output
|
||||
uc.reg_write(UC_ARM64_REG_X0, ctx_addr)
|
||||
uc.reg_write(UC_ARM64_REG_X1, input_addr)
|
||||
uc.reg_write(UC_ARM64_REG_X2, output_addr)
|
||||
|
||||
# Set LR to sentinel so we detect function return
|
||||
uc.reg_write(UC_ARM64_REG_LR, RETURN_SENTINEL)
|
||||
|
||||
# Map sentinel page if not already mapped
|
||||
try:
|
||||
uc.mem_map((RETURN_SENTINEL // 0x1000) * 0x1000, 0x1000)
|
||||
uc.mem_write(RETURN_SENTINEL, struct.pack("<I", 0xD65F03C0)) # ret
|
||||
except UcError:
|
||||
pass # already mapped
|
||||
|
||||
# Run emulation
|
||||
try:
|
||||
uc.emu_start(TFIT_OP_ADDR, RETURN_SENTINEL, timeout=5_000_000, count=0)
|
||||
except UcError as e:
|
||||
pc = uc.reg_read(UC_ARM64_REG_PC)
|
||||
raise RuntimeError(f"Unicorn error at PC=0x{pc:x}: {e}") from e
|
||||
|
||||
# Read output
|
||||
result = bytes(uc.mem_read(output_addr, 16))
|
||||
return result
|
||||
|
||||
def encrypt_ecb(
|
||||
self,
|
||||
key_schedule: bytes,
|
||||
data: bytes,
|
||||
) -> bytes:
|
||||
"""Encrypt multiple 16-byte blocks in ECB mode."""
|
||||
assert len(data) % 16 == 0, "data must be a multiple of 16 bytes"
|
||||
out = bytearray()
|
||||
for i in range(0, len(data), 16):
|
||||
block = data[i : i + 16]
|
||||
out.extend(self.encrypt_block(key_schedule, block))
|
||||
return bytes(out)
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Key schedule selection
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
MGK_TYPE_IPHONE = 0
|
||||
MGK_TYPE_IPAD = 1
|
||||
MGK_TYPE_ATV = 2
|
||||
|
||||
|
||||
def load_key_schedule(mgk_type: int, binary_data: bytes) -> bytes:
|
||||
"""Extract the TFIT key schedule for the given device type."""
|
||||
offsets = {
|
||||
MGK_TYPE_IPHONE: KS_IPHONE_OFFSET,
|
||||
MGK_TYPE_IPAD: KS_IPAD_OFFSET,
|
||||
MGK_TYPE_ATV: KS_ATV_OFFSET,
|
||||
}
|
||||
off = offsets[mgk_type]
|
||||
return binary_data[off : off + KS_SIZE]
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# High-level: genModelGroupKeys
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
|
||||
def gen_model_group_keys(
|
||||
esn: str,
|
||||
mgk_type: int = MGK_TYPE_IPHONE,
|
||||
) -> tuple[bytes, bytes]:
|
||||
"""Compute MGK pair from ESN string.
|
||||
|
||||
Implements:
|
||||
SHA384(ESN) -> 48 bytes
|
||||
mgk_key = TFIT_ECB(hash[0:16]) -> 16B
|
||||
mgk_vec_a = TFIT_ECB(hash[16:32]) -> 16B
|
||||
mgk_vec_b = TFIT_ECB(hash[32:48]) -> 16B
|
||||
return (mgk_key, mgk_vec_a + mgk_vec_b)
|
||||
|
||||
Returns:
|
||||
(mgk_key: 16B, mgk_vector: 32B)
|
||||
"""
|
||||
binary_path = BINARY_PATH
|
||||
binary_data = binary_path.read_bytes()
|
||||
binary = lief.MachO.parse(str(binary_path)).at(0)
|
||||
|
||||
emu = TFITEmulator(binary_data, binary)
|
||||
ks = load_key_schedule(mgk_type, binary_data)
|
||||
|
||||
esn_bytes = esn.encode("ascii")
|
||||
sha384 = hashlib.sha384(esn_bytes).digest() # 48 bytes
|
||||
assert len(sha384) == 48
|
||||
|
||||
mgk_key = emu.encrypt_block(ks, sha384[0:16])
|
||||
mgk_vec_a = emu.encrypt_block(ks, sha384[16:32])
|
||||
mgk_vec_b = emu.encrypt_block(ks, sha384[32:48])
|
||||
|
||||
return mgk_key, mgk_vec_a + mgk_vec_b
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Tests
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
# Self-consistent test vectors generated by running the emulator itself.
|
||||
#
|
||||
# Note on the appboot_tfit_capture.log: that log captures AES_encrypt (OpenSSL)
|
||||
# calls, NOT TFIT whitebox calls. The TFIT_wbaes_ecb_encrypt_iAES11 symbol was
|
||||
# not exported and the hook for it was skipped ("not found"). Therefore the log
|
||||
# pairs like "in=00..01 out=530f..." are standard AES-256 zero-key encryptions
|
||||
# (matches OpenSSL AES-256 ECB), NOT TFIT whitebox outputs.
|
||||
#
|
||||
# These test vectors are derived from a clean Unicorn emulation of TFIT_op_iAES11
|
||||
# and are self-consistent (running the emulator twice gives the same result).
|
||||
# External verification against live device output is still pending (requires
|
||||
# a Frida hook that captures TFIT_wbaes_ecb_encrypt_iAES11 by offset rather
|
||||
# than symbol name).
|
||||
KNOWN_ANSWER_TESTS = [
|
||||
# (plaintext_hex, ciphertext_hex) — zero key schedule (all 224 bytes = 0)
|
||||
("00000000000000000000000000000001", "f469188c949ce132d5265939cc67e908"),
|
||||
("00000000000000000000000000000002", "5c90c3336189b725cac1073129aefbe1"),
|
||||
("00000000000000000000000000000003", "ddeb1836524af20722d9651c9434037f"),
|
||||
("00000000000000000000000000000000", "7195623eff1992bde8a632719b556918"),
|
||||
("00000001000000000000000000000000", "b3c44c826aa149e043325c2dba404a13"),
|
||||
("00000002000000000000000000000000", "4e20076397bc7e3b914ebd43aadcd85a"),
|
||||
]
|
||||
|
||||
# iPhone key schedule test vectors (self-consistent)
|
||||
IPHONE_KS_TESTS = [
|
||||
("0f034310a5dcb20a61dbdc60760ac3a9", "68f4884a03e1fd671da06a8808997740"),
|
||||
("0f034310a5dcb20a61dbdc60760ac3aa", "35ed42eab6141a01bf0797bde5b4351e"),
|
||||
]
|
||||
|
||||
|
||||
def run_tests() -> None:
|
||||
"""Run known-answer tests with the zero key schedule."""
|
||||
binary_path = BINARY_PATH
|
||||
binary_data = binary_path.read_bytes()
|
||||
binary = lief.MachO.parse(str(binary_path)).at(0)
|
||||
|
||||
emu = TFITEmulator(binary_data, binary)
|
||||
zero_ks = bytes(KS_SIZE) # all-zeros key schedule
|
||||
|
||||
print("Running TFIT KAT (zero key schedule)...")
|
||||
passed = 0
|
||||
failed = 0
|
||||
for i, (pt_hex, ct_hex) in enumerate(KNOWN_ANSWER_TESTS):
|
||||
plaintext = bytes.fromhex(pt_hex)
|
||||
expected = bytes.fromhex(ct_hex)
|
||||
|
||||
result = emu.encrypt_block(zero_ks, plaintext)
|
||||
|
||||
status = "PASS" if result == expected else "FAIL"
|
||||
if result == expected:
|
||||
passed += 1
|
||||
else:
|
||||
failed += 1
|
||||
|
||||
print(
|
||||
f" [{i + 1}] {status} in={pt_hex} expected={ct_hex} got={result.hex()}"
|
||||
)
|
||||
|
||||
print(f"\nResults: {passed}/{len(KNOWN_ANSWER_TESTS)} passed, {failed} failed")
|
||||
|
||||
if failed == 0:
|
||||
print("\nAll zero-KS KATs passed. Testing iPhone key schedule...")
|
||||
ks_iphone = load_key_schedule(MGK_TYPE_IPHONE, binary_data)
|
||||
iphone_passed = 0
|
||||
iphone_failed = 0
|
||||
for pt_hex, ct_hex in IPHONE_KS_TESTS:
|
||||
result = emu.encrypt_block(ks_iphone, bytes.fromhex(pt_hex))
|
||||
expected = bytes.fromhex(ct_hex)
|
||||
status = "PASS" if result == expected else "FAIL"
|
||||
if result == expected:
|
||||
iphone_passed += 1
|
||||
else:
|
||||
iphone_failed += 1
|
||||
print(
|
||||
f" iPhone KS [{status}] in={pt_hex} "
|
||||
f"expected={ct_hex} got={result.hex()}"
|
||||
)
|
||||
print(f"\nPhone KS: {iphone_passed}/{len(IPHONE_KS_TESTS)} passed")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# CLI
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="TFIT whitebox AES-128-ECB emulator")
|
||||
parser.add_argument(
|
||||
"--test",
|
||||
action="store_true",
|
||||
help="Run known-answer tests to verify emulation",
|
||||
)
|
||||
parser.add_argument(
|
||||
"esn",
|
||||
nargs="?",
|
||||
help="ESN string to compute MGK for",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--type",
|
||||
choices=["iphone", "ipad", "atv"],
|
||||
default="iphone",
|
||||
help="Device type for key schedule (default: iphone)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not BINARY_PATH.exists():
|
||||
print(f"Binary not found: {BINARY_PATH}", file=sys.stderr)
|
||||
print("Extract with:", file=sys.stderr)
|
||||
print(
|
||||
" mkdir -p /tmp/nfwc && "
|
||||
"unzip -o /home/vscode/app/assets/Netflix-15.48.1.ipa "
|
||||
'"Payload/Argo.app/Frameworks/NFWebCrypto.framework/*" -d /tmp/nfwc',
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
if args.test:
|
||||
run_tests()
|
||||
return
|
||||
|
||||
if not args.esn:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
mgk_type_map = {
|
||||
"iphone": MGK_TYPE_IPHONE,
|
||||
"ipad": MGK_TYPE_IPAD,
|
||||
"atv": MGK_TYPE_ATV,
|
||||
}
|
||||
mgk_type = mgk_type_map[args.type]
|
||||
|
||||
esn = args.esn
|
||||
print(f"ESN: {esn}")
|
||||
print(f"Type: {args.type}")
|
||||
|
||||
sha384 = hashlib.sha384(esn.encode("ascii")).digest()
|
||||
print(f"SHA384: {sha384.hex()}")
|
||||
|
||||
mgk_key, mgk_vec = gen_model_group_keys(esn, mgk_type)
|
||||
print(f"MGK key (16B): {mgk_key.hex()}")
|
||||
print(f"MGK vector (32B): {mgk_vec.hex()}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user