Added fakesigning and fakesigning detection, command is in CLI

This commit is contained in:
2025-03-25 18:28:37 -04:00
parent 1bcc004af7
commit 839e33b911
11 changed files with 369 additions and 51 deletions

View File

@@ -20,6 +20,8 @@ pub enum TitleError {
BadTMD,
BadContent,
InvalidWAD,
TMDError(tmd::TMDError),
TicketError(ticket::TicketError),
WADError(wad::WADError),
IOError(std::io::Error),
}
@@ -31,6 +33,8 @@ impl fmt::Display for TitleError {
TitleError::BadTMD => "The provided TMD data was invalid.",
TitleError::BadContent => "The provided content data was invalid.",
TitleError::InvalidWAD => "The provided WAD data was invalid.",
TitleError::TMDError(_) => "An error occurred while processing TMD data.",
TitleError::TicketError(_) => "An error occurred while processing ticket data.",
TitleError::WADError(_) => "A WAD could not be built from the provided data.",
TitleError::IOError(_) => "The provided Title data was invalid.",
};
@@ -85,6 +89,17 @@ impl Title {
Ok(title)
}
pub fn is_fakesigned(&self) -> bool {
self.tmd.is_fakesigned() && self.ticket.is_fakesigned()
}
pub fn fakesign(&mut self) -> Result<(), TitleError> {
// Run the fakesign methods on the TMD and Ticket.
self.tmd.fakesign().map_err(TitleError::TMDError)?;
self.ticket.fakesign().map_err(TitleError::TicketError)?;
Ok(())
}
pub fn get_content_by_index(&self, index: usize) -> Result<Vec<u8>, content::ContentError> {
let content = self.content.get_content_by_index(index, self.ticket.dec_title_key())?;
Ok(content)

View File

@@ -7,11 +7,13 @@ use std::error::Error;
use std::fmt;
use std::io::{Cursor, Read, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use sha1::{Sha1, Digest};
use crate::title::crypto::decrypt_title_key;
#[derive(Debug)]
pub enum TicketError {
UnsupportedVersion,
CannotFakesign,
IOError(std::io::Error),
}
@@ -19,6 +21,7 @@ impl fmt::Display for TicketError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let description = match *self {
TicketError::UnsupportedVersion => "The provided Ticket is not a supported version (only v0 is supported).",
TicketError::CannotFakesign => "The Ticket data could not be fakesigned.",
TicketError::IOError(_) => "The provided Ticket data was invalid.",
};
f.write_str(description)
@@ -184,4 +187,37 @@ impl Ticket {
let issuer_str = String::from_utf8(Vec::from(&self.signature_issuer)).unwrap_or_default();
issuer_str.contains("Root-CA00000002-XS00000004") || issuer_str.contains("Root-CA00000002-XS00000006")
}
pub fn is_fakesigned(&self) -> bool {
// Can't be fakesigned without a null signature.
if self.signature != [0; 256] {
return false;
}
// Test the hash of the Ticket body to make sure it starts with 00.
let mut hasher = Sha1::new();
let ticket_body = self.to_bytes().unwrap();
hasher.update(&ticket_body[320..]);
let result = hasher.finalize();
if result[0] != 0 {
return false;
}
true
}
pub fn fakesign(&mut self) -> Result<(), TicketError> {
// Erase the signature.
self.signature = [0; 256];
let mut current_int: u16 = 0;
let mut test_hash: [u8; 20] = [255; 20];
while test_hash[0] != 0 {
if current_int == 255 { return Err(TicketError::CannotFakesign); }
current_int += 1;
self.unknown2 = current_int.to_be_bytes();
let mut hasher = Sha1::new();
let ticket_body = self.to_bytes().unwrap();
hasher.update(&ticket_body[320..]);
test_hash = <[u8; 20]>::from(hasher.finalize());
}
Ok(())
}
}

View File

@@ -3,8 +3,29 @@
//
// Implements the structures and methods required for TMD parsing and editing.
use std::error::Error;
use std::fmt;
use std::io::{Cursor, Read, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use sha1::{Sha1, Digest};
#[derive(Debug)]
pub enum TMDError {
CannotFakesign,
IOError(std::io::Error),
}
impl fmt::Display for TMDError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let description = match *self {
TMDError::CannotFakesign => "The TMD data could not be fakesigned.",
TMDError::IOError(_) => "The provided TMD data was invalid.",
};
f.write_str(description)
}
}
impl Error for TMDError {}
#[derive(Debug)]
#[derive(Clone)]
@@ -46,55 +67,55 @@ pub struct TMD {
impl TMD {
/// Creates a new TMD instance from the binary data of a TMD file.
pub fn from_bytes(data: &[u8]) -> Result<Self, std::io::Error> {
pub fn from_bytes(data: &[u8]) -> Result<Self, TMDError> {
let mut buf = Cursor::new(data);
let signature_type = buf.read_u32::<BigEndian>()?;
let signature_type = buf.read_u32::<BigEndian>().map_err(TMDError::IOError)?;
let mut signature = [0u8; 256];
buf.read_exact(&mut signature)?;
buf.read_exact(&mut signature).map_err(TMDError::IOError)?;
// Maybe this can be read differently?
let mut padding1 = [0u8; 60];
buf.read_exact(&mut padding1)?;
buf.read_exact(&mut padding1).map_err(TMDError::IOError)?;
let mut signature_issuer = [0u8; 64];
buf.read_exact(&mut signature_issuer)?;
let tmd_version = buf.read_u8()?;
let ca_crl_version = buf.read_u8()?;
let signer_crl_version = buf.read_u8()?;
let is_vwii = buf.read_u8()?;
buf.read_exact(&mut signature_issuer).map_err(TMDError::IOError)?;
let tmd_version = buf.read_u8().map_err(TMDError::IOError)?;
let ca_crl_version = buf.read_u8().map_err(TMDError::IOError)?;
let signer_crl_version = buf.read_u8().map_err(TMDError::IOError)?;
let is_vwii = buf.read_u8().map_err(TMDError::IOError)?;
let mut ios_tid = [0u8; 8];
buf.read_exact(&mut ios_tid)?;
buf.read_exact(&mut ios_tid).map_err(TMDError::IOError)?;
let mut title_id = [0u8; 8];
buf.read_exact(&mut title_id)?;
buf.read_exact(&mut title_id).map_err(TMDError::IOError)?;
let mut title_type = [0u8; 4];
buf.read_exact(&mut title_type)?;
let group_id = buf.read_u16::<BigEndian>()?;
buf.read_exact(&mut title_type).map_err(TMDError::IOError)?;
let group_id = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?;
// Same here...
let mut padding2 = [0u8; 2];
buf.read_exact(&mut padding2)?;
let region = buf.read_u16::<BigEndian>()?;
buf.read_exact(&mut padding2).map_err(TMDError::IOError)?;
let region = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?;
let mut ratings = [0u8; 16];
buf.read_exact(&mut ratings)?;
buf.read_exact(&mut ratings).map_err(TMDError::IOError)?;
// ...and here...
let mut reserved1 = [0u8; 12];
buf.read_exact(&mut reserved1)?;
buf.read_exact(&mut reserved1).map_err(TMDError::IOError)?;
let mut ipc_mask = [0u8; 12];
buf.read_exact(&mut ipc_mask)?;
buf.read_exact(&mut ipc_mask).map_err(TMDError::IOError)?;
// ...and here.
let mut reserved2 = [0u8; 18];
buf.read_exact(&mut reserved2)?;
let access_rights = buf.read_u32::<BigEndian>()?;
let title_version = buf.read_u16::<BigEndian>()?;
let num_contents = buf.read_u16::<BigEndian>()?;
let boot_index = buf.read_u16::<BigEndian>()?;
let minor_version = buf.read_u16::<BigEndian>()?;
buf.read_exact(&mut reserved2).map_err(TMDError::IOError)?;
let access_rights = buf.read_u32::<BigEndian>().map_err(TMDError::IOError)?;
let title_version = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?;
let num_contents = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?;
let boot_index = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?;
let minor_version = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?;
// Build content records by iterating over the rest of the data num_contents times.
let mut content_records = Vec::with_capacity(num_contents as usize);
for _ in 0..num_contents {
let content_id = buf.read_u32::<BigEndian>()?;
let index = buf.read_u16::<BigEndian>()?;
let content_type = buf.read_u16::<BigEndian>()?;
let content_size = buf.read_u64::<BigEndian>()?;
let content_id = buf.read_u32::<BigEndian>().map_err(TMDError::IOError)?;
let index = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?;
let content_type = buf.read_u16::<BigEndian>().map_err(TMDError::IOError)?;
let content_size = buf.read_u64::<BigEndian>().map_err(TMDError::IOError)?;
let mut content_hash = [0u8; 20];
buf.read_exact(&mut content_hash)?;
buf.read_exact(&mut content_hash).map_err(TMDError::IOError)?;
content_records.push(ContentRecord {
content_id,
index,
@@ -167,4 +188,37 @@ impl TMD {
}
Ok(buf)
}
pub fn is_fakesigned(&self) -> bool {
// Can't be fakesigned without a null signature.
if self.signature != [0; 256] {
return false;
}
// Test the hash of the TMD body to make sure it starts with 00.
let mut hasher = Sha1::new();
let tmd_body = self.to_bytes().unwrap();
hasher.update(&tmd_body[320..]);
let result = hasher.finalize();
if result[0] != 0 {
return false;
}
true
}
pub fn fakesign(&mut self) -> Result<(), TMDError> {
// Erase the signature.
self.signature = [0; 256];
let mut current_int: u16 = 0;
let mut test_hash: [u8; 20] = [255; 20];
while test_hash[0] != 0 {
if current_int == 255 { return Err(TMDError::CannotFakesign); }
current_int += 1;
self.minor_version = current_int;
let mut hasher = Sha1::new();
let ticket_body = self.to_bytes().unwrap();
hasher.update(&ticket_body[320..]);
test_hash = <[u8; 20]>::from(hasher.finalize());
}
Ok(())
}
}

View File

@@ -9,10 +9,14 @@ use std::str;
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use crate::title::{tmd, ticket, content};
use crate::title::ticket::TicketError;
use crate::title::tmd::TMDError;
#[derive(Debug)]
pub enum WADError {
BadType,
TMDError(TMDError),
TicketError(TicketError),
IOError(std::io::Error),
}
@@ -20,6 +24,8 @@ impl fmt::Display for WADError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let description = match *self {
WADError::BadType => "An invalid WAD type was specified.",
WADError::TMDError(_) => "An error occurred while loading TMD data.",
WADError::TicketError(_) => "An error occurred while loading Ticket data.",
WADError::IOError(_) => "The provided WAD data was invalid.",
};
f.write_str(description)
@@ -68,7 +74,7 @@ impl WADHeader {
pub fn from_body(body: &WADBody) -> Result<WADHeader, WADError> {
// Generates a new WADHeader from a populated WADBody object.
// Parse the TMD and use that to determine if this is a standard WAD or a boot2 WAD.
let tmd = tmd::TMD::from_bytes(&body.tmd).map_err(WADError::IOError)?;
let tmd = tmd::TMD::from_bytes(&body.tmd).map_err(WADError::TMDError)?;
let wad_type = match hex::encode(tmd.title_id).as_str() {
"0000000100000001" => WADType::ImportBoot,
_ => WADType::Installable,