Added methods to fakesign a TMD or Ticket

This commit is contained in:
Campbell 2024-07-17 20:44:04 -04:00
parent 535de7f228
commit a56fa6e051
Signed by: NinjaCheetah
GPG Key ID: B547958AF96ED344
3 changed files with 77 additions and 4 deletions

View File

@ -5,6 +5,7 @@
import io import io
import binascii import binascii
import hashlib
from dataclasses import dataclass as _dataclass from dataclasses import dataclass as _dataclass
from .crypto import decrypt_title_key from .crypto import decrypt_title_key
from typing import List from typing import List
@ -162,8 +163,7 @@ class Ticket:
def dump(self) -> bytes: def dump(self) -> bytes:
""" """
Dumps the Ticket object back into bytes. This also sets the raw Ticket attribute of Ticket object to the Dumps the Ticket object back into bytes.
dumped data, and triggers load() again to ensure that the raw data and object match.
Returns Returns
------- -------
@ -226,6 +226,37 @@ class Ticket:
ticket_data += title_limit_data ticket_data += title_limit_data
return ticket_data return ticket_data
def fakesign(self) -> None:
"""
Fakesigns this Ticket for the trucha bug.
This is done by brute-forcing a Ticket body hash starting with 00, causing it to pass signature verification on
older IOS versions that incorrectly check the hash using strcmp() instead of memcmp(). The signature will also
be erased and replaced with all NULL bytes.
This modifies the Ticket object in place. You will need to call this method after any changes, and before
dumping the Ticket object back into bytes.
"""
# Clear the signature, so that the hash derived from it is guaranteed to always be
# '0000000000000000000000000000000000000000'.
self.signature = b'\x00' * 256
current_int = 0
test_hash = ''
while test_hash[:2] != '00':
current_int += 1
# We're using the first 2 bytes of this unused region of the Ticket as a 16-bit integer, and incrementing
# that to brute-force the hash we need.
data_to_edit = self.unknown2
data_to_edit = int.to_bytes(current_int, 2) + data_to_edit[2:]
self.unknown2 = data_to_edit
# Trim off the first 320 bytes, because we're only looking for the hash of the Ticket's body.
# This is a try-except because an OverflowError will be thrown if the number being used to brute-force the
# hash gets too big, as it is only a 16-bit integer. If that happens, then fakesigning has failed.
try:
test_hash = hashlib.sha1(self.dump()[320:]).hexdigest()
except OverflowError:
raise Exception("An error occurred during fakesigning. Ticket could not be fakesigned!")
def get_title_id(self) -> str: def get_title_id(self) -> str:
""" """
Gets the Title ID of the ticket's associated title. Gets the Title ID of the ticket's associated title.

View File

@ -234,3 +234,18 @@ class Title:
""" """
# Load the decrypted content. # Load the decrypted content.
self.content.load_content(dec_content, index, self.ticket.get_title_key()) self.content.load_content(dec_content, index, self.ticket.get_title_key())
def fakesign(self) -> None:
"""
Fakesigns this Title for the trucha bug.
This is done by brute-forcing a TMD and Ticket body hash starting with 00, causing it to pass signature
verification on older IOS versions that incorrectly check the hash using strcmp() instead of memcmp(). The TMD
and Ticket signatures will also be erased and replaced with all NULL bytes.
This modifies the TMD and Ticket objects that are part of this Title in place. You will need to call this method
after any changes to the TMD or Ticket, and before dumping the Title object into a WAD to ensure that the WAD
is properly fakesigned.
"""
self.tmd.fakesign()
self.ticket.fakesign()

View File

@ -5,6 +5,7 @@
import io import io
import binascii import binascii
import hashlib
import struct import struct
from typing import List from typing import List
from ..types import _ContentRecord from ..types import _ContentRecord
@ -159,8 +160,7 @@ class TMD:
def dump(self) -> bytes: def dump(self) -> bytes:
""" """
Dumps the TMD object back into bytes. This also sets the raw TMD attribute of TMD object to the dumped data, Dumps the TMD object back into bytes.
and triggers load() again to ensure that the raw data and object match.
Returns Returns
------- -------
@ -227,6 +227,33 @@ class TMD:
tmd_data += content_data tmd_data += content_data
return tmd_data return tmd_data
def fakesign(self) -> None:
"""
Fakesigns this TMD for the trucha bug.
This is done by brute-forcing a TMD body hash starting with 00, causing it to pass signature verification on
older IOS versions that incorrectly check the hash using strcmp() instead of memcmp(). The signature will also
be erased and replaced with all NULL bytes.
This modifies the TMD object in place. You will need to call this method after any changes, and before dumping
the TMD object back into bytes.
"""
# Clear the signature, so that the hash derived from it is guaranteed to always be
# '0000000000000000000000000000000000000000'.
self.signature = b'\x00' * 256
current_int = 0
test_hash = ''
while test_hash[:2] != '00':
current_int += 1
self.minor_version = current_int
# Trim off the first 320 bytes, because we're only looking for the hash of the TMD's body.
# This is a try-except because an OverflowError will be thrown if the number being used to brute-force the
# hash gets too big, as it is only a 16-bit integer. If that happens, then fakesigning has failed.
try:
test_hash = hashlib.sha1(self.dump()[320:]).hexdigest()
except OverflowError:
raise Exception("An error occurred during fakesigning. TMD could not be fakesigned!")
def get_title_region(self) -> str: def get_title_region(self) -> str:
""" """
Gets the region of the TMD's associated title. Gets the region of the TMD's associated title.