fix: option versions are different; fix header metadat, formatting

This commit is contained in:
beerpsi 2024-07-18 03:31:34 +07:00
parent 8ca3bc37ba
commit 5c40c357c6

View File

@ -9,7 +9,16 @@ import zlib
from Crypto.Cipher import AES, PKCS1_OAEP
from Crypto.Hash import HMAC, SHA1
from Crypto.PublicKey import RSA
from construct import Bytes, Const, Int16ul, Int32ul, Int64ul, Int8ul, Struct
from construct import (
Bytes,
Const,
IfThenElse,
Int16ul,
Int32ul,
Int64ul,
Int8ul,
Struct,
)
# ---- Configuration
ENCRYPTION_KEY = bytes.fromhex("")
@ -28,11 +37,7 @@ BOOTID = {
"second": 43,
"milli": 0,
},
"game_version": {
"release": 0,
"minor": 30,
"major": 1,
},
"game_version": b"A041",
"block_size": 0x40000,
"header_block_count": 8,
"unk1": 0,
@ -57,7 +62,8 @@ BOOTID = {
"minor": 54,
"major": 80,
},
"strings": b"\x00" * 0x27AC, # Depending on the app/opt/pack, this might have some text in it.
"strings": b"\x00"
* 0x27AC, # Depending on the app/opt/pack, this might have some text in it.
}
# ----
@ -65,7 +71,9 @@ BOOTID = {
# The BootID (app/opt/pack header) encryption key and IV.
BTKEY = bytes.fromhex("09ca5efd30c9aaef3804d0a7e3fa7120")
BTIV = bytes.fromhex("b155c22c2e7f0491fa7f0fdc217aff90")
SIGKEY = bytes.fromhex("e1bdcb2d5e9ed3b5de234364dfa4d126849edff769fc6c28fba5f43bc482bd7479d676afce8188e1d3a6852f4ebce45cde46bd15e8ee5fe84d197f945a54518f")
SIGKEY = bytes.fromhex(
"e1bdcb2d5e9ed3b5de234364dfa4d126849edff769fc6c28fba5f43bc482bd7479d676afce8188e1d3a6852f4ebce45cde46bd15e8ee5fe84d197f945a54518f"
)
HEADER_META_PUBKEY = RSA.import_key("""-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsRMLnJuczNpfoqPpHQ3o
5XNkjKXO6P3ToV/45Az5dNaHVL7uEu9vPI7a2KYFQnNYgD3UUHFahfTcljzLOkcH
@ -81,7 +89,7 @@ uwIDAQAB
# ---- Constants. Don't edit.
EXFAT_HEADER = bytes.fromhex("EB769045584641542020200000000000")
OPTION_IV = bytes.fromhex("C063BF6F562D084D7963C987F5281761")
# ----
# ----
# ---- Script
Timestamp = Struct(
@ -107,7 +115,7 @@ BootID = Struct(
"sequence_number" / Int16ul,
"game_id" / Bytes(4),
"game_timestamp" / Timestamp,
"game_version" / Version,
"game_version" / IfThenElse(lambda ctx: ctx.type == 0x0201, Bytes(4), Version),
"block_count" / Int64ul,
"block_size" / Int64ul,
"header_block_count" / Int64ul,
@ -120,6 +128,7 @@ BootID = Struct(
"strings" / Bytes(0x27AC),
)
def get_page_iv(iv: bytes, offset: int):
return bytes(x ^ (offset >> (8 * (i % 8))) & 0xFF for (i, x) in enumerate(iv))
@ -134,11 +143,27 @@ print(f"Generated IV: {iv.hex()}")
filesize = os.stat(INPUT_FILE).st_size
BOOTID["block_count"] = ceil(filesize / BOOTID["block_size"]) + 8
header_meta = struct.pack("<Q", time.time()) + os.path.abspath(INPUT_FILE).encode("utf-8")
header_meta += secrets.token_bytes(BOOTID["block_size"] - len(header_meta))
header_meta = PKCS1_OAEP.new(HEADER_META_PUBKEY).encrypt(header_meta)
key = secrets.token_bytes(16)
iv = secrets.token_bytes(16)
encrypted_keypair = PKCS1_OAEP.new(HEADER_META_PUBKEY).encrypt(key + iv)
header_meta = struct.pack("<Q", int(time.time())) + os.path.abspath(INPUT_FILE).encode(
"utf-8"
)
header_meta += secrets.token_bytes(
BOOTID["block_size"] - len(header_meta) - len(encrypted_keypair)
)
header_meta = encrypted_keypair + AES.new(key, AES.MODE_CBC, iv).encrypt(header_meta)
header_meta_crc32 = zlib.crc32(header_meta)
block_crc32s = [0, header_meta_crc32, header_meta_crc32, header_meta_crc32, header_meta_crc32, header_meta_crc32, header_meta_crc32, header_meta_crc32]
block_crc32s = [
0,
header_meta_crc32,
header_meta_crc32,
header_meta_crc32,
header_meta_crc32,
header_meta_crc32,
header_meta_crc32,
header_meta_crc32,
]
with open(INPUT_FILE, "rb") as fin, open(OUTPUT_FILE, "w+b") as fout:
# Write the bootID.
@ -170,7 +195,7 @@ with open(INPUT_FILE, "rb") as fin, open(OUTPUT_FILE, "w+b") as fout:
if contents_len < 4096:
contents += b"\x00" * (4096 - contents_len)
cipher = AES.new(ENCRYPTION_KEY, AES.MODE_CBC, page_iv)
encrypted = cipher.encrypt(contents)
total_written += fout.write(encrypted)
@ -179,11 +204,11 @@ with open(INPUT_FILE, "rb") as fin, open(OUTPUT_FILE, "w+b") as fout:
if (total_written % BOOTID["block_size"]) == 0:
block_crc32s.append(block_crc32)
block_crc32 = 0
# Pad with null bytes if we have an unfinished block.
if (total_written % BOOTID["block_size"]) != 0:
null_byte_count = BOOTID["block_size"] - (total_written % BOOTID["block_size"])
while null_byte_count > 0:
page_iv = get_page_iv(iv, total_written)
cipher = AES.new(ENCRYPTION_KEY, AES.MODE_CBC, page_iv)
@ -192,7 +217,7 @@ with open(INPUT_FILE, "rb") as fin, open(OUTPUT_FILE, "w+b") as fout:
block_crc32 = zlib.crc32(encrypted, block_crc32)
null_byte_count -= 4096
block_crc32s.append(block_crc32)
block_crc32 = 0
@ -207,8 +232,10 @@ with open(INPUT_FILE, "rb") as fin, open(OUTPUT_FILE, "w+b") as fout:
# Skip the HMAC signature and the first CRC32, which we're trying to calculate.
_ = fout.seek(0x204, os.SEEK_CUR)
block_0_crc32 = zlib.crc32(fout.read(BOOTID["block_size"] - 0x2800 - 0x204), block_0_crc32)
block_0_crc32 = zlib.crc32(
fout.read(BOOTID["block_size"] - 0x2800 - 0x204), block_0_crc32
)
_ = fout.seek(0x2A00)
_ = fout.write(struct.pack("<I", block_0_crc32))
@ -224,7 +251,6 @@ with open(INPUT_FILE, "rb") as fin, open(OUTPUT_FILE, "w+b") as fout:
_ = hmac.update(block)
to_read -= len(block)
_ = fout.seek(0x2800)
_ = fout.write(hmac.digest())