mirror of
https://github.com/martravi/wiiqt.git
synced 2024-11-25 10:36:56 +01:00
* fixed bugs in nandBin dealing with getting the supercluster
* adding stuff for writing a supercluster with updates information, using correct rotation * fixed a few more bugs in this class forgot about * move the "fakesign checker" stuff to a more appropriate place * add a couple small functions to TMD, ticket, shardeMap * add a new "demo" program/project nandBinCheck. it does lots of useful integrety checks on a nand.bin that will be needed when starting to actually write these things
This commit is contained in:
parent
8eac10ea9e
commit
7e64a9f6b5
3
.gitattributes
vendored
3
.gitattributes
vendored
@ -37,6 +37,9 @@ WiiQt/uidmap.cpp -text
|
||||
WiiQt/uidmap.h -text
|
||||
WiiQt/wad.cpp -text
|
||||
WiiQt/wad.h -text
|
||||
nandBinCheck/main.cpp -text
|
||||
nandBinCheck/nandBinCheck.pro -text
|
||||
nandBinCheck/readmii.txt -text
|
||||
nandExtract/black.png -text
|
||||
nandExtract/blue.png -text
|
||||
nandExtract/boot2infodialog.cpp -text
|
||||
|
@ -2,21 +2,6 @@
|
||||
#include "tools.h"
|
||||
#include "tiktmd.h"
|
||||
|
||||
int check_cert_chain( const QByteArray data );
|
||||
|
||||
enum
|
||||
{
|
||||
ERROR_SUCCESS = 0,
|
||||
ERROR_SIG_TYPE,
|
||||
ERROR_SUB_TYPE,
|
||||
ERROR_RSA_FAKESIGNED,
|
||||
ERROR_RSA_HASH,
|
||||
ERROR_RSA_TYPE_UNKNOWN,
|
||||
ERROR_RSA_TYPE_MISMATCH,
|
||||
ERROR_CERT_NOT_FOUND,
|
||||
ERROR_COUNT
|
||||
};
|
||||
|
||||
Blocks0to7::Blocks0to7( QList<QByteArray>blocks )
|
||||
{
|
||||
_ok = false;
|
||||
@ -26,6 +11,7 @@ Blocks0to7::Blocks0to7( QList<QByteArray>blocks )
|
||||
|
||||
bool Blocks0to7::SetBlocks( QList<QByteArray>blocks )
|
||||
{
|
||||
//qDebug() << "Blocks0to7::SetBlocks" << blocks.size();
|
||||
_ok = false;
|
||||
this->blocks.clear();
|
||||
boot2Infos.clear();
|
||||
@ -48,7 +34,10 @@ bool Blocks0to7::SetBlocks( QList<QByteArray>blocks )
|
||||
quint8 Blocks0to7::Boot1Version()
|
||||
{
|
||||
if( blocks.size() != 8 )
|
||||
{
|
||||
qWarning() << "Blocks0to7::Boot1Version -> not enough blocks" << blocks.size();
|
||||
return BOOT_1_UNK;
|
||||
}
|
||||
QByteArray hash = GetSha1( blocks.at( 0 ) );
|
||||
|
||||
if( hash == QByteArray( "\x4a\x7c\x6f\x30\x38\xde\xea\x7a\x07\xd3\x32\x32\x02\x4b\xe9\x5a\xfb\x56\xbf\x65" ) )
|
||||
@ -60,6 +49,8 @@ quint8 Blocks0to7::Boot1Version()
|
||||
if( hash == QByteArray( "\x8d\x9e\xcf\x2f\x8f\x98\xa3\xc1\x07\xf1\xe5\xe3\x6f\xf2\x4d\x57\x7e\xac\x36\x08" ) )
|
||||
return BOOT_1_D; //displayed as "boot1?" in ceilingcat
|
||||
|
||||
qWarning() << "Blocks0to7::Boot1Version -> unknown boot1 hash:" << hash.toHex();
|
||||
|
||||
return BOOT_1_UNK;
|
||||
}
|
||||
|
||||
@ -324,239 +315,3 @@ Boot2Info Blocks0to7::CheckHashes( Boot2Info info )
|
||||
return ret;
|
||||
}
|
||||
|
||||
//theres probably a better place to put all this stuff. but until then, just put it here
|
||||
#define BE32(a) (((a)[0] << 24)|((a)[1] << 16)|((a)[2] << 8)|(a)[3])
|
||||
|
||||
#define bn_zero(a,b) memset(a,0,b)
|
||||
#define bn_copy(a,b,c) memcpy(a,b,c)
|
||||
#define bn_compare(a,b,c) memcmp(a,b,c)
|
||||
|
||||
// calc a = a mod N, given n = size of a,N in bytes
|
||||
static void bn_sub_modulus( quint8 *a, const quint8 *N, const quint32 n )
|
||||
{
|
||||
quint32 dig;
|
||||
quint8 c = 0;
|
||||
|
||||
for( quint32 i = n - 1; i < n; i-- )
|
||||
{
|
||||
dig = N[ i ] + c;
|
||||
c = ( a [ i ] < dig );
|
||||
a[ i ] -= dig;
|
||||
}
|
||||
}
|
||||
|
||||
// calc d = (a + b) mod N, given n = size of d,a,b,N in bytes
|
||||
static void bn_add( quint8 *d, const quint8 *a, const quint8 *b, const quint8 *N, const quint32 n )
|
||||
{
|
||||
quint32 i;
|
||||
quint32 dig;
|
||||
quint8 c = 0;
|
||||
|
||||
for( i = n - 1; i < n; i--)
|
||||
{
|
||||
dig = a[ i ] + b[ i ] + c;
|
||||
c = ( dig >= 0x100 );
|
||||
d[ i ] = dig;
|
||||
}
|
||||
if( c )
|
||||
bn_sub_modulus( d, N, n );
|
||||
|
||||
if( bn_compare( d, N, n ) >= 0 )
|
||||
bn_sub_modulus( d, N, n );
|
||||
}
|
||||
|
||||
// calc d = (a * b) mod N, given n = size of d,a,b,N in bytes
|
||||
static void bn_mul( quint8 *d, const quint8 *a, const quint8 *b, const quint8 *N, const quint32 n )
|
||||
{
|
||||
quint8 mask;
|
||||
bn_zero( d, n );
|
||||
|
||||
for( quint32 i = 0; i < n; i++ )
|
||||
for( mask = 0x80; mask != 0; mask >>= 1 )
|
||||
{
|
||||
bn_add( d, d, d, N, n );
|
||||
if( ( a[ i ] & mask ) != 0 )
|
||||
bn_add( d, d, b, N, n );
|
||||
}
|
||||
}
|
||||
|
||||
// calc d = (a ^ e) mod N, given n = size of d,a,N and en = size of e in bytes
|
||||
static void bn_exp( quint8 *d, const quint8 *a, const quint8 *N, const quint32 n, const quint8 *e, const quint32 en )
|
||||
{
|
||||
quint8 t[ 512 ];
|
||||
quint8 mask;
|
||||
|
||||
bn_zero( d, n );
|
||||
d[ n-1 ] = 1;
|
||||
for( quint32 i = 0; i < en; i++ )
|
||||
for( mask = 0x80; mask != 0; mask >>= 1 )
|
||||
{
|
||||
bn_mul( t, d, d, N, n );
|
||||
if( ( e [ i ] & mask ) != 0 )
|
||||
bn_mul( d, t, a, N, n );
|
||||
else
|
||||
bn_copy( d, t, n );
|
||||
}
|
||||
}
|
||||
|
||||
static int get_sig_len( const quint8 *sig )
|
||||
{
|
||||
quint32 type;
|
||||
type = BE32( sig );
|
||||
switch( type - 0x10000 )
|
||||
{
|
||||
case 0:
|
||||
return 0x240;
|
||||
case 1:
|
||||
return 0x140;
|
||||
case 2:
|
||||
return 0x80;
|
||||
}
|
||||
return -ERROR_SIG_TYPE;
|
||||
}
|
||||
|
||||
static int get_sub_len( const quint8 *sub )
|
||||
{
|
||||
quint32 type;
|
||||
type = BE32( sub + 0x40 );
|
||||
switch( type )
|
||||
{
|
||||
case 0: return 0x2c0;
|
||||
case 1: return 0x1c0;
|
||||
case 2: return 0x100;
|
||||
}
|
||||
return -ERROR_SUB_TYPE;
|
||||
}
|
||||
|
||||
static int check_rsa( QByteArray h, const quint8 *sig, const quint8 *key, const quint32 n )
|
||||
{
|
||||
quint8 correct[ 0x200 ], x[ 0x200 ];
|
||||
static const quint8 ber[ 16 ] = { 0x00,0x30,0x21,0x30,0x09,0x06,0x05,0x2b,
|
||||
0x0e,0x03,0x02,0x1a,0x05,0x00,0x04,0x14 };
|
||||
correct[ 0 ] = 0;
|
||||
correct[ 1 ] = 1;
|
||||
memset( correct + 2, 0xff, n - 38 );
|
||||
memcpy( correct + n - 36, ber, 16 );
|
||||
memcpy( correct + n - 20, h.constData(), 20 );
|
||||
|
||||
bn_exp( x, sig, key, n, key + n, 4 );
|
||||
|
||||
//qDebug() << "Decrypted signature hash:" << QByteArray( (const char*)&x[ n-20 ], 20 ).toHex();
|
||||
//qDebug() << " SHA1 hash:" << h.toHex();
|
||||
|
||||
if( memcmp( correct, x, n ) == 0 )
|
||||
return 0;
|
||||
|
||||
if( strncmp( (char*)h.constData(), (char*) x + n - 20, 20 ) == 0 )
|
||||
return ERROR_RSA_FAKESIGNED;
|
||||
|
||||
return ERROR_RSA_HASH;
|
||||
}
|
||||
|
||||
static int check_hash( QByteArray h, const quint8 *sig, const quint8 *key )
|
||||
{
|
||||
quint32 type;
|
||||
type = BE32( sig ) - 0x10000;
|
||||
if( (qint32)type != BE32( key + 0x40 ) )
|
||||
return ERROR_RSA_TYPE_MISMATCH;
|
||||
|
||||
if( type == 1 )
|
||||
return check_rsa( h, sig + 4, key + 0x88, 0x100 );
|
||||
return ERROR_RSA_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
static const quint8* find_cert_in_chain( const quint8 *sub, const quint8 *cert, const quint32 cert_len, int *err )
|
||||
{
|
||||
char parent[ 64 ], *child;
|
||||
int sig_len, sub_len;
|
||||
const quint8 *p, *issuer;
|
||||
|
||||
strncpy( parent, (char*)sub, sizeof parent );
|
||||
parent[ sizeof parent - 1 ] = 0;
|
||||
child = strrchr( parent, '-' );
|
||||
if( child )
|
||||
*child++ = 0;
|
||||
else
|
||||
{
|
||||
*parent = 0;
|
||||
child = (char*)sub;
|
||||
}
|
||||
|
||||
*err = -ERROR_CERT_NOT_FOUND;
|
||||
|
||||
for( p = cert; p < cert + cert_len; p += sig_len + sub_len )
|
||||
{
|
||||
sig_len = get_sig_len( p );
|
||||
if( sig_len < 0 )
|
||||
{
|
||||
*err = sig_len;
|
||||
break;
|
||||
}
|
||||
issuer = p + sig_len;
|
||||
sub_len = get_sub_len( issuer );
|
||||
if( sub_len < 0 )
|
||||
{
|
||||
*err = sub_len;
|
||||
break;
|
||||
}
|
||||
if( strcmp( parent, (char*)issuer ) == 0 && strcmp( child, (char*)issuer + 0x44 ) == 0 )
|
||||
return p;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int check_cert_chain( const QByteArray data )
|
||||
{
|
||||
int cert_err;
|
||||
const quint8* key;
|
||||
const quint8 *sig, *sub, *key_cert;
|
||||
int sig_len, sub_len;
|
||||
QByteArray h;
|
||||
int ret;
|
||||
const quint8 *certificates = certs_dat;
|
||||
|
||||
sig = (const quint8*)data.constData();
|
||||
sig_len = get_sig_len( sig );
|
||||
if( sig_len < 0 )
|
||||
return -sig_len;
|
||||
|
||||
sub = (const quint8*)( data.data() + sig_len );
|
||||
sub_len = data.size() - sig_len;
|
||||
if( sub_len <= 0 )
|
||||
return ERROR_SUB_TYPE;
|
||||
|
||||
for( ; ; )
|
||||
{
|
||||
//qDebug() << "Verifying using" << QString( (const char*) sub );
|
||||
if( strcmp((char*)sub, "Root" ) == 0 )
|
||||
{
|
||||
key = root_dat;
|
||||
h = GetSha1( QByteArray( (const char*)sub, sub_len ) );
|
||||
if( BE32( sig ) != 0x10000 )
|
||||
return ERROR_SIG_TYPE;
|
||||
return check_rsa( h, sig + 4, key, 0x200 );
|
||||
}
|
||||
|
||||
key_cert = find_cert_in_chain( sub, certificates, CERTS_DAT_SIZE, &cert_err );
|
||||
if( key_cert )
|
||||
cert_err = get_sig_len( key_cert );
|
||||
|
||||
if( cert_err < 0 )
|
||||
return -cert_err;
|
||||
key = key_cert + cert_err;
|
||||
|
||||
h = GetSha1( QByteArray( (const char*)sub, sub_len ) );
|
||||
ret = check_hash( h, sig, key );
|
||||
// remove this if statement if you don't want to check the whole chain
|
||||
if( ret != ERROR_SUCCESS )
|
||||
return ret;
|
||||
sig = key_cert;
|
||||
sig_len = get_sig_len( sig );
|
||||
if( sig_len < 0 )
|
||||
return -sig_len;
|
||||
sub = sig + sig_len;
|
||||
sub_len = get_sub_len( sub );
|
||||
if( sub_len < 0 )
|
||||
return -sub_len;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
#ifndef INCLUDES_H
|
||||
#define INCLUDES_H
|
||||
|
||||
|
||||
#include <QtGui/QApplication>
|
||||
#include <QAction>
|
||||
#include <QBuffer>
|
||||
#include <QDialog>
|
||||
|
@ -1,6 +1,8 @@
|
||||
#include "nandbin.h"
|
||||
#include "tools.h"
|
||||
|
||||
//#define NAND_BIN_CAN_WRITE
|
||||
|
||||
NandBin::NandBin( QObject * parent, const QString &path ) : QObject( parent )
|
||||
{
|
||||
type = -1;
|
||||
@ -30,7 +32,12 @@ bool NandBin::SetPath( const QString &path )
|
||||
f.close();
|
||||
|
||||
f.setFileName( path );
|
||||
bool ret = !f.exists() || !f.open( QIODevice::ReadOnly );
|
||||
bool ret = !f.exists() ||
|
||||
#ifdef NAND_BIN_CAN_WRITE
|
||||
!f.open( QIODevice::ReadWrite );
|
||||
#else
|
||||
!f.open( QIODevice::ReadOnly );
|
||||
#endif
|
||||
if( ret )
|
||||
{
|
||||
emit SendError( tr( "Cant open %1" ).arg( path ) );
|
||||
@ -224,6 +231,7 @@ bool NandBin::ExtractFile( fst_t fst, QString parent )
|
||||
bool NandBin::InitNand( QIcon dirs, QIcon files )
|
||||
{
|
||||
fstInited = false;
|
||||
memset( (void*)&fsts, 0, sizeof( fst_t ) * 0x17ff );
|
||||
fats.clear();
|
||||
type = GetDumpType( f.size() );
|
||||
if( type < 0 || type > 3 )
|
||||
@ -324,18 +332,41 @@ bool NandBin::InitNand( QIcon dirs, QIcon files )
|
||||
}*/
|
||||
//GetFile( 369 );
|
||||
|
||||
/*QByteArray scl;
|
||||
for( quint16 i = 0; i < 16; i++ )
|
||||
{
|
||||
scl += GetCluster( currentSuperCluster + i, false );
|
||||
}
|
||||
WriteFile( "./aaaa_superCluster.bin", scl );
|
||||
qDebug() << "starting scl" << hex << currentSuperCluster;*/
|
||||
|
||||
//testing creating new items
|
||||
/*quint16 n = CreateEntry( "/test1", 0x4444, 0x5555, 2, 1, 2, 0 );
|
||||
qDebug() << "created item" << n;
|
||||
|
||||
n = CreateEntry( "/test1/test2", 0x4444, 0x5555, 1, 1, 2, 3 );
|
||||
n = CreateEntry( "/test1/test4", 0x4444, 0x5555, 1, 1, 2, 3 );
|
||||
qDebug() << "created item" << n;
|
||||
if( n )
|
||||
{
|
||||
bool dd = SetData( n, QByteArray( 0x3000000, '\x69') );
|
||||
bool dd = SetData( n, QByteArray( 0x4000000, '\x69') );
|
||||
qDebug() << "added data" << dd;
|
||||
|
||||
dd = WriteMetaData();
|
||||
qDebug() << "write meta" << dd;
|
||||
}*/
|
||||
|
||||
/*scl.clear();
|
||||
for( quint16 i = 0; i < 16; i++ )
|
||||
{
|
||||
scl += GetCluster( currentSuperCluster + i, false );
|
||||
}
|
||||
qDebug() << "tests" << scl.count( "test" ) << "in scl" << hex << currentSuperCluster;
|
||||
WriteFile( "./aaaa_superCluster2.bin", scl );*/
|
||||
|
||||
//QByteArray fu = GetData( "/test1/test2" );
|
||||
//if( !fu.isEmpty() )
|
||||
//hexdump( fu, 0, 0x30 );
|
||||
|
||||
/*ShowLostClusters();
|
||||
bool x= Delete( "/ticket" );
|
||||
qDebug() << "delete" << x;
|
||||
@ -345,6 +376,9 @@ bool NandBin::InitNand( QIcon dirs, QIcon files )
|
||||
//root = new QTreeWidgetItem( QStringList() << nandPath );
|
||||
//AddChildren( root, 0 );
|
||||
|
||||
//bool l = TestMetaData();
|
||||
//qDebug() << "test meta" << l;
|
||||
|
||||
|
||||
if( !bootBlocks.SetBlocks( blocks ) )
|
||||
return false;
|
||||
@ -382,7 +416,8 @@ void NandBin::ShowLostClusters()
|
||||
break;
|
||||
}
|
||||
}
|
||||
qDebug() << "found" << lost << "lost clusters\n0xffffs" << hex << ffs.size() << ffs << "\nfree" << frs.size();
|
||||
qDebug() << "found" << lost << "lost clusters\nUNK ( 0xffff )" << hex << ffs.size() << ffs <<
|
||||
"\nfree " << frs.size();
|
||||
}
|
||||
|
||||
int NandBin::GetDumpType( quint64 fileSize )
|
||||
@ -507,30 +542,46 @@ qint32 NandBin::FindSuperblock()
|
||||
emit SendError( tr( "Tried to get superblock of unopened dump" ) );
|
||||
return -1;
|
||||
}
|
||||
quint32 loc = 0, current = 0, last = 0;
|
||||
quint32 loc = 0, current = 0, magic = 0;
|
||||
superClusterVersion = 0;
|
||||
quint32 n_start[] = { 0x1FC00000, 0x20BE0000, 0x20BE0000 },
|
||||
n_end[] = { 0x20000000, 0x21000000, 0x21000000 },
|
||||
n_len[] = { 0x40000, 0x42000, 0x42000 };
|
||||
|
||||
|
||||
for( loc = n_start[ type ]; loc < n_end[ type ]; loc += n_len[ type ] )
|
||||
quint8 rewind = 1;
|
||||
for( loc = n_start[ type ], currentSuperCluster = 0x7f00; loc < n_end[ type ]; loc += n_len[ type ], currentSuperCluster += 0x10 )
|
||||
{
|
||||
f.seek( loc );
|
||||
//QByteArray sss = f.peek( 0x50 );
|
||||
f.read( (char*)&magic, 4 );//no need to switch endian here
|
||||
if( magic != 0x53464653 )
|
||||
{
|
||||
qWarning() << "oops, this isnt a supercluster" << hex << loc << magic << currentSuperCluster;
|
||||
rewind++;
|
||||
//hexdump( sss );
|
||||
continue;
|
||||
}
|
||||
|
||||
f.read( (char*)¤t, 4 );
|
||||
current = qFromBigEndian( current );
|
||||
|
||||
//qDebug() << "superblock" << hex << current;
|
||||
//qDebug() << "superblock" << hex << current << currentSuperCluster << loc;
|
||||
|
||||
if( current > last )
|
||||
last = current;
|
||||
if( current > superClusterVersion )
|
||||
superClusterVersion = current;
|
||||
else
|
||||
{
|
||||
//qDebug() << "superblock loc" << hex << loc - n_len[ type ];
|
||||
return loc - n_len[ type ];
|
||||
//qDebug() << "using superblock" << hex << superClusterVersion << currentSuperCluster - 0x10 << f.pos() - n_len[ type ];
|
||||
currentSuperCluster -= ( 0x10 * rewind );
|
||||
loc -= ( n_len[ type ] * rewind );
|
||||
break;
|
||||
}
|
||||
|
||||
f.seek( n_len[ type ] - 4 );
|
||||
}
|
||||
return -1;//hmmmm what happens if the last supercluster is the latest one? seems like a bug to fix at a later date...
|
||||
if( !superClusterVersion )
|
||||
return -1;
|
||||
|
||||
//qDebug() << "using superblock" << hex << superClusterVersion << currentSuperCluster << "page:" << ( loc / 0x840 );
|
||||
return loc;
|
||||
}
|
||||
|
||||
fst_t NandBin::GetFST( quint16 entry )
|
||||
@ -627,6 +678,7 @@ QByteArray NandBin::GetPage( quint32 pageNo, bool withEcc )
|
||||
return QByteArray();
|
||||
}
|
||||
f.seek( pageNo * n_pagelen[ type ] ); //seek to the beginning of the page to read
|
||||
//qDebug() << "reading page from" << hex << (quint32)f.pos();
|
||||
QByteArray page = f.read( ( type && withEcc ) ? n_pagelen[ type ] : 0x800 );
|
||||
return page;
|
||||
}
|
||||
@ -648,6 +700,7 @@ QByteArray NandBin::GetCluster( quint16 cluster_entry, bool decrypt )
|
||||
for( int i = 0; i < 8; i++ )
|
||||
{
|
||||
f.seek( ( cluster_entry * n_clusterlen[ type ] ) + ( i * n_pagelen[ type ] ) ); //seek to the beginning of the page to read
|
||||
//qDebug() << "reading page from" << hex << (quint32)f.pos();
|
||||
//QByteArray page = f.read( n_pagelen[ type ] ); //read the page, with ecc
|
||||
QByteArray page = f.read( 0x800 ); //read the page, skip the ecc
|
||||
//hexdump( page.mid( 0x800, 0x40 ) );//just here for debugging purposes
|
||||
@ -682,7 +735,7 @@ QByteArray NandBin::GetFile( quint16 entry )
|
||||
|
||||
QByteArray NandBin::GetFile( fst_t fst_ )
|
||||
{
|
||||
qDebug() << "NandBin::GetFile" << (const char*)fst_.filename;
|
||||
//qDebug() << "NandBin::GetFile" << QByteArray( (const char*)fst_.filename, 12 );
|
||||
if( !fst_.size )
|
||||
return QByteArray();
|
||||
|
||||
@ -691,7 +744,8 @@ QByteArray NandBin::GetFile( fst_t fst_ )
|
||||
|
||||
QByteArray data;
|
||||
|
||||
//int idx = 0;
|
||||
//QList<quint16> readFats;
|
||||
//int idx = 0;a
|
||||
for (int i = 0; fat < 0xFFF0; i++)
|
||||
{
|
||||
QByteArray cluster = GetCluster( fat );
|
||||
@ -702,8 +756,11 @@ QByteArray NandBin::GetFile( fst_t fst_ )
|
||||
//WriteDecryptedCluster( 0, cluster, fst_, idx++ );
|
||||
|
||||
data += cluster;
|
||||
//readFats << fat;
|
||||
fat = GetFAT( fat );
|
||||
}
|
||||
//qDebug() << "actually read data from fats\n" << hex << readFats;
|
||||
//qDebug() << "stopped reading because of" << hex << fat;
|
||||
|
||||
//this check doesnt really seem to matter, it always appears to be 1 extra cluster added to the end
|
||||
//of the file and that extra bit is dropped in this function before the data is returned.
|
||||
@ -967,11 +1024,11 @@ bool NandBin::WriteCluster( quint32 pageNo, const QByteArray data, const QByteAr
|
||||
|
||||
bool NandBin::WriteDecryptedCluster( quint32 pageNo, const QByteArray data, fst_t fst, quint16 idx )
|
||||
{
|
||||
qDebug() << "NandBin::WriteDecryptedCluster";
|
||||
//qDebug() << "NandBin::WriteDecryptedCluster";
|
||||
QByteArray hmac = spare.Get_hmac_data( data, fst.uid, (const unsigned char *)&fst.filename, fst.fst_pos, fst.x3, idx );
|
||||
|
||||
hexdump( hmac );
|
||||
return true;
|
||||
//hexdump( hmac );
|
||||
//return true;
|
||||
|
||||
AesSetKey( key );
|
||||
QByteArray encData = AesEncrypt( 0, data );
|
||||
@ -980,8 +1037,13 @@ bool NandBin::WriteDecryptedCluster( quint32 pageNo, const QByteArray data, fst_
|
||||
|
||||
bool NandBin::WritePage( quint32 pageNo, const QByteArray data )
|
||||
{
|
||||
//return true;
|
||||
#ifndef NAND_BIN_CAN_WRITE
|
||||
qWarning() << __FILE__ << "was built without write support";
|
||||
return false;
|
||||
#endif
|
||||
//qDebug() << "NandBin::WritePage(" << hex << pageNo << ")";
|
||||
return true;
|
||||
//return true;
|
||||
quint32 n_pagelen[] = { 0x800, 0x840, 0x840 };
|
||||
if( (quint32)data.size() != n_pagelen[ type ] )
|
||||
{
|
||||
@ -994,7 +1056,9 @@ bool NandBin::WritePage( quint32 pageNo, const QByteArray data )
|
||||
emit SendError( tr( "Tried to write page past size of nand.bin" ) );
|
||||
return false;
|
||||
}
|
||||
f.seek( pageNo * n_pagelen[ type ] ); //seek to the beginning of the page to write
|
||||
f.seek( (quint64)pageNo * (quint64)n_pagelen[ type ] ); //seek to the beginning of the page to write
|
||||
//qDebug() << "writing page at:" << f.pos() << hex << (quint32)f.pos();
|
||||
//hexdump( data, 0, 0x20 );
|
||||
return f.write( data );
|
||||
}
|
||||
|
||||
@ -1002,7 +1066,7 @@ quint16 NandBin::CreateNode( const QString &name, quint32 uid, quint16 gid, quin
|
||||
{
|
||||
attr = attr | ( ( user_perm & 3 ) << 6 ) | ( ( group_perm & 3 ) << 4 ) | ( ( other_perm & 3 ) << 2 );
|
||||
|
||||
quint32 i;//TODO: maybe add in some sort of wear-leveling emulation so all new entries arent created in sequential order
|
||||
quint32 i;
|
||||
for( i = 1; i < 0x17ff; i++ )//cant be entry 0 because that is the root
|
||||
{
|
||||
fst_t fst = fsts[ i ];
|
||||
@ -1013,8 +1077,10 @@ quint16 NandBin::CreateNode( const QString &name, quint32 uid, quint16 gid, quin
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
QByteArray n = name.toLatin1().data();
|
||||
n.resize( 12 );
|
||||
//qDebug() << "will add entry for" << n << "at" << hex << i;
|
||||
memcpy( &fsts[ i ].filename, n, 12 );
|
||||
fsts[ i ].attr = attr;
|
||||
fsts[ i ].wtf = 0;
|
||||
@ -1031,6 +1097,7 @@ quint16 NandBin::CreateNode( const QString &name, quint32 uid, quint16 gid, quin
|
||||
fsts[ i ].uid = uid;
|
||||
fsts[ i ].gid = gid;
|
||||
fsts[ i ].x3 = 0;
|
||||
//hexdump( (const void*)&fsts[ i ], sizeof( fst_t ) );
|
||||
|
||||
return i;
|
||||
}
|
||||
@ -1284,6 +1351,11 @@ bool NandBin::SetData( quint16 idx, const QByteArray data )
|
||||
//grab a random cluster from the list
|
||||
quint16 idx = qrand() % freeClusters.size();
|
||||
quint16 cl = freeClusters.takeAt( idx ); //remove this number from the list
|
||||
/*if( freeClusters.contains( cl ) )
|
||||
{
|
||||
qDebug() << "wtf4";
|
||||
return false;
|
||||
}*/
|
||||
fts << cl; //add this one to the clusters that will be used to hold the data
|
||||
quint16 block = cl / 8; //try to find other clusters in the same block
|
||||
for( quint16 i = block * 8; i < ( ( block + 1 ) * 8 ) && fts.size() < clCnt; i++ )
|
||||
@ -1294,7 +1366,12 @@ bool NandBin::SetData( quint16 idx, const QByteArray data )
|
||||
if( fats.at( i ) == 0xfffe ) //theres more free clusters in this same block, grab them
|
||||
{
|
||||
fts << i;
|
||||
freeClusters.removeAt( freeClusters.indexOf( i, MAX( cl - 8, 0 ) ) );
|
||||
freeClusters.removeAt( freeClusters.indexOf( i, 0 ) );
|
||||
/*if( freeClusters.contains( i ) )
|
||||
{
|
||||
qDebug() << "wtf5";
|
||||
return false;
|
||||
}*/
|
||||
}
|
||||
}
|
||||
//read the spare data to see that the cluster is good - removed for now. but its probably not a bad idea to do this
|
||||
@ -1315,7 +1392,7 @@ bool NandBin::SetData( quint16 idx, const QByteArray data )
|
||||
}
|
||||
}
|
||||
//qDebug() << "about to writing shit" << clCnt << fts.size();
|
||||
//qDebug() << "file will be on clusters" << hex << fts;
|
||||
//qDebug() << "file will be on clusters\n" << hex << fts;
|
||||
for( quint32 i = 0; i < clCnt; i++ )
|
||||
{
|
||||
QByteArray cluster = pData.mid( i * 0x4000, 0x4000 );
|
||||
@ -1326,17 +1403,52 @@ bool NandBin::SetData( quint16 idx, const QByteArray data )
|
||||
//all the data has been written, now make sure the fats are correct
|
||||
fsts[ idx ].sub = fts.at( 0 );
|
||||
|
||||
/*QList<quint16>bugFix = fts;
|
||||
for( quint16 i = 0; i < fts.size(); i++ )
|
||||
{
|
||||
if( bugFix.at( i ) != fts.at( i ) )
|
||||
{
|
||||
qDebug() << "wwwwtttf?" << i << hex << bugFix.at( i ) << fts.at( i );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
quint16 te = fsts[ idx ].sub;*/
|
||||
|
||||
for( quint16 i = 0; i < clCnt - 1; i++ )
|
||||
{
|
||||
//qDebug() << "replacing fat" << hex << fts.at( 0 );
|
||||
fats.replace( fts.at( 0 ), fts.at( 1 ) );
|
||||
/*qDebug() << "replacing fat" << hex << fts.at( 0 ) << "to point to" << fts.at( 1 ) << "actual:" << fats.at( fts.at( 0 ) );
|
||||
if( te != fts.at( 0 ) || te != bugFix.at( i ) )
|
||||
{
|
||||
qDebug() << "failed" << i << hex << te << fts.at( 0 ) << bugFix.at( i );
|
||||
return false;
|
||||
}*/
|
||||
fts.takeFirst();
|
||||
//te = GetFAT( te );
|
||||
}
|
||||
//follow the fat chain and make sure it is as expected
|
||||
/*quint16 num = 0;
|
||||
te = fsts[ idx ].sub;
|
||||
while( te < 0xfff0 )
|
||||
{
|
||||
if( te != bugFix.at( num ) )
|
||||
{
|
||||
qDebug() << "mismatch" << num << hex << te << bugFix.at( num );
|
||||
break;
|
||||
}
|
||||
te = GetFAT( te );
|
||||
num++;
|
||||
}*/
|
||||
|
||||
//qDebug() << "1 followed the chain to" << num << "items. expected" << clCnt;
|
||||
//qDebug() << "loop is done";
|
||||
fats.replace( fts.at( 0 ), 0xfffb );//last cluster in chain
|
||||
fts.takeFirst();
|
||||
//qDebug() << "fixed the last one" << hex << fts;
|
||||
// if the new data uses less clusters than the previous data, mark the extra ones as free
|
||||
//if( !fts.isEmpty() )
|
||||
//qDebug() << "need to mark" << fts.size() << "clusters free";
|
||||
|
||||
while( !fts.isEmpty() )
|
||||
{
|
||||
fats.replace( fts.at( 0 ), 0xfffe );
|
||||
@ -1344,6 +1456,22 @@ bool NandBin::SetData( quint16 idx, const QByteArray data )
|
||||
}
|
||||
//qDebug() << "2nd loop is done";
|
||||
|
||||
//follow the fat chain and make sure it is as expected
|
||||
/*num = 0;
|
||||
te = fsts[ idx ].sub;
|
||||
while( te < 0xfff0 )
|
||||
{
|
||||
if( te != bugFix.at( num ) )
|
||||
{
|
||||
qDebug() << "mismatch" << num << hex << te << bugFix.at( num );
|
||||
break;
|
||||
}
|
||||
te = GetFAT( te );
|
||||
num++;
|
||||
}
|
||||
|
||||
qDebug() << "2 followed the chain to" << num << "items. expected" << clCnt;*/
|
||||
|
||||
fsts[ idx ].size = data.size();
|
||||
|
||||
QTreeWidgetItem *i = ItemFromEntry( idx, root );
|
||||
@ -1353,3 +1481,247 @@ bool NandBin::SetData( quint16 idx, const QByteArray data )
|
||||
i->setText( 2, QString( "%1" ).arg( data.size(), 0, 16 ) );
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NandBin::WriteMetaData()
|
||||
{
|
||||
//make sure the currect cluster is sane
|
||||
if( currentSuperCluster < 0x7f00 || currentSuperCluster > 0x7ff0 || currentSuperCluster % 16 || fats.size() != 0x8000 )
|
||||
return false;
|
||||
quint16 nextSuperCluster = currentSuperCluster + 0x10;
|
||||
if( nextSuperCluster > 0x7ff0 )
|
||||
nextSuperCluster = 0x7f00;
|
||||
|
||||
quint32 nextClusterVersion = superClusterVersion + 1;
|
||||
QByteArray scl( 0x4000 * 16, '\0' ); //this will hold all the data
|
||||
//qDebug() << "created the meta block buffer" << hex << scl.size();
|
||||
QBuffer b( &scl );
|
||||
b.open( QIODevice::WriteOnly );
|
||||
quint32 tmp;
|
||||
quint16 t;
|
||||
|
||||
b.write( "SFFS" ); //magic word
|
||||
tmp = qFromBigEndian( nextClusterVersion );
|
||||
b.write( (const char*)&tmp, 4 ); //version
|
||||
tmp = qFromBigEndian( (quint32)0x10 );
|
||||
b.write( (const char*)&tmp, 4 ); //wiibrew says its always 0x10. but mine is 0
|
||||
qDebug() << "writing the fats at" << hex << (quint32)b.pos();
|
||||
|
||||
//write all the fats
|
||||
for( quint16 i = 0; i < 0x8000; i++ )
|
||||
{
|
||||
t = qFromBigEndian( fats.at( i ) );
|
||||
b.write( (const char*)&t, 2 );
|
||||
}
|
||||
|
||||
qDebug() << "writing the fsts at" << hex << (quint32)b.pos();
|
||||
//write all the fst entries
|
||||
for( quint16 i = 0; i < 0x17ff; i++ )
|
||||
{
|
||||
fst_t fst = fsts[ i ];
|
||||
|
||||
b.write( (const char*)&fst.filename, 0xc );
|
||||
b.write( (const char*)&fst.attr, 1 );
|
||||
b.write( (const char*)&fst.wtf, 1 );
|
||||
|
||||
t = qFromBigEndian( fst.sub );
|
||||
b.write( (const char*)&t, 2 );
|
||||
|
||||
t = qFromBigEndian( fst.sib );
|
||||
b.write( (const char*)&t, 2 );
|
||||
|
||||
tmp = qFromBigEndian( fst.size );
|
||||
b.write( (const char*)&tmp, 4 );
|
||||
|
||||
tmp = qFromBigEndian( fst.uid );
|
||||
b.write( (const char*)&tmp, 4 );
|
||||
|
||||
t = qFromBigEndian( fst.gid );
|
||||
b.write( (const char*)&t, 2 );
|
||||
|
||||
tmp = qFromBigEndian( fst.x3 );
|
||||
b.write( (const char*)&tmp, 4 );
|
||||
}
|
||||
|
||||
|
||||
//qDebug() << "done adding shit" << hex << (quint32)b.pos();
|
||||
b.close();
|
||||
//qDebug() << "tests" << scl.count( "test" );
|
||||
//get hmac data
|
||||
QByteArray hmR = spare.Get_hmac_meta( scl, nextSuperCluster );
|
||||
qDebug() << "about to write the meta block" << hex << nextSuperCluster << nextClusterVersion << "to page" << (quint32)( nextSuperCluster * 8 );
|
||||
|
||||
for( quint8 i = 0; i < 0x10; i++ )
|
||||
{
|
||||
bool ret = WriteCluster( (quint32)( ( nextSuperCluster + i ) * 8 ), scl.mid( 0x4000 * i, 0x4000 ), ( i == 15 ? hmR : QByteArray() ) );
|
||||
if( !ret )
|
||||
{
|
||||
qWarning() << "failed to write the metadata. this nand may be broken now :(" << i;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
currentSuperCluster = nextSuperCluster;
|
||||
superClusterVersion = nextClusterVersion; //probably need to put some magic here in case the version wraps around back to 0
|
||||
|
||||
|
||||
//testing 1,2
|
||||
/*QByteArray tt;
|
||||
for( quint16 i = 0; i < 16; i++ )
|
||||
{
|
||||
tt += GetCluster( currentSuperCluster + i, false );
|
||||
}
|
||||
if( tt != scl )
|
||||
qDebug() << "shit doesnt match :(";
|
||||
else
|
||||
qDebug() << "wrote what was expected :)";*/
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NandBin::CheckEcc( quint32 pageNo )
|
||||
{
|
||||
if( !type )
|
||||
return false;
|
||||
|
||||
QByteArray whole = GetPage( pageNo, true );
|
||||
if( whole.size() != 0x840 )
|
||||
return false;
|
||||
|
||||
QByteArray data = whole.left( 0x800 );
|
||||
QByteArray ecc = whole.right( 0x10 );
|
||||
QByteArray calc = spare.CalcEcc( data );
|
||||
return ( ecc == calc );
|
||||
}
|
||||
|
||||
bool NandBin::CheckHmacData( quint16 entry )
|
||||
{
|
||||
if( entry > 0x17fe )
|
||||
{
|
||||
qDebug() << "bad entry #" << hex << entry;
|
||||
return false;
|
||||
}
|
||||
|
||||
fst_t fst = fsts[ entry ];
|
||||
if( ( fst.attr & 3 ) != 1 )
|
||||
{
|
||||
qDebug() << "bad attributes" << ( fst.attr & 3 );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( !fst.size )
|
||||
return true;
|
||||
|
||||
quint16 clCnt = ( RU( 0x4000, fst.size ) / 0x4000 );
|
||||
//qDebug() << FstName( fst ) << "is" << hex << fst.size << "bytes (" << clCnt << ") clusters";
|
||||
|
||||
quint16 fat = fst.sub;
|
||||
QByteArray sp1;
|
||||
QByteArray sp2;
|
||||
QByteArray hmac;
|
||||
//qDebug() << "fat" << hex << fat;
|
||||
for( quint32 i = 0; i < clCnt; i++ )
|
||||
{
|
||||
if( fat > 0x7fff )
|
||||
{
|
||||
qDebug() << "fat is out of range" << hex << fat;
|
||||
return false;
|
||||
}
|
||||
QByteArray cluster = GetCluster( fat ); //hmac is calculated with the decrypted cluster data
|
||||
if( cluster.size() != 0x4000 )
|
||||
{
|
||||
qDebug() << "error reading cluster";
|
||||
return false;
|
||||
}
|
||||
sp1 = GetPage( ( fat * 8 ) + 6, true ); //the spare data of these 2 pages hold the hmac data for the cluster
|
||||
sp2 = GetPage( ( fat * 8 ) + 7, true );
|
||||
if( sp1.isEmpty() || sp2.isEmpty() )
|
||||
{
|
||||
qDebug() << "error getting spare data";
|
||||
return false;
|
||||
}
|
||||
|
||||
sp1 = sp1.right( 0x40 ); //only keep the spare data and drop the data
|
||||
sp2 = sp2.right( 0x40 );
|
||||
|
||||
hmac = spare.Get_hmac_data( cluster, fst.uid, (const unsigned char*)&fst.filename, entry, fst.x3, i );
|
||||
|
||||
//this part is kinda ugly, but this is how it is layed out by big N
|
||||
if( sp1.mid( 1, 0x14 ) != hmac )
|
||||
{
|
||||
qDebug() << "hmac bad (1)";
|
||||
goto error;
|
||||
}
|
||||
if( sp1.mid( 0x15, 0xc ) != hmac.left( 0xc ) )
|
||||
{
|
||||
qDebug() << "hmac bad (2)";
|
||||
goto error;
|
||||
}
|
||||
if( sp2.mid( 1, 8 ) != hmac.right( 8 ) )
|
||||
{
|
||||
qDebug() << "hmac bad (3)";
|
||||
goto error;
|
||||
}
|
||||
//qDebug() << "hmac ok for cluster" << i;
|
||||
//data += cluster;
|
||||
fat = GetFAT( fat );
|
||||
}
|
||||
return true;
|
||||
|
||||
error:
|
||||
qDebug() << FstName( fst ) << "is" << hex << fst.size << "bytes (" << clCnt << ") clusters";
|
||||
hexdump( sp1 );
|
||||
hexdump( sp2 );
|
||||
hexdump( hmac );
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
bool NandBin::CheckHmacMeta( quint16 clNo )
|
||||
{
|
||||
if( clNo < 0x7f00 || clNo > 0x7ff0 || clNo % 0x10 )
|
||||
return false;
|
||||
|
||||
QByteArray data;
|
||||
for( quint8 i = 0; i < 0x10; i++ )
|
||||
{
|
||||
data += GetCluster( ( clNo + i ), false );
|
||||
}
|
||||
QByteArray hmac = spare.Get_hmac_meta( data, clNo );
|
||||
quint32 baseP = ( clNo + 15 ) * 8;
|
||||
//qDebug() << "baseP" << hex << baseP << ( baseP + 6 ) << ( baseP + 7 );
|
||||
QByteArray sp1 = GetPage( baseP + 6, true ); //the spare data of these 2 pages hold the hmac data for the supercluster
|
||||
QByteArray sp2 = GetPage( baseP + 7, true );
|
||||
if( sp1.isEmpty() || sp2.isEmpty() )
|
||||
{
|
||||
qDebug() << "error getting spare data";
|
||||
return false;
|
||||
}
|
||||
|
||||
sp1 = sp1.right( 0x40 ); //only keep the spare data and drop the data
|
||||
sp2 = sp2.right( 0x40 );
|
||||
|
||||
//this part is kinda ugly, but this is how it is layed out by big N
|
||||
if( sp1.mid( 1, 0x14 ) != hmac )
|
||||
{
|
||||
qDebug() << "hmac bad (1)";
|
||||
goto error;
|
||||
}
|
||||
if( sp1.mid( 0x15, 0xc ) != hmac.left( 0xc ) )
|
||||
{
|
||||
qDebug() << "hmac bad (2)";
|
||||
goto error;
|
||||
}
|
||||
if( sp2.mid( 1, 8 ) != hmac.right( 8 ) )
|
||||
{
|
||||
qDebug() << "hmac bad (3)";
|
||||
goto error;
|
||||
}
|
||||
return true;
|
||||
|
||||
error:
|
||||
qDebug() << "supercluster" << hex << clNo;
|
||||
hexdump( sp1 );
|
||||
hexdump( sp2 );
|
||||
hexdump( hmac );
|
||||
return false;
|
||||
|
||||
}
|
||||
|
@ -116,12 +116,25 @@ public:
|
||||
//overloads the above function
|
||||
bool SetData( const QString &path, const QByteArray data );
|
||||
|
||||
//write the current changes to the metadata( if you dont do this, then none of the other stuff youve done wont be saved )
|
||||
bool WriteMetaData();
|
||||
|
||||
//functions to verify spare data
|
||||
bool CheckEcc( quint32 pageNo );
|
||||
bool CheckHmacData( quint16 entry );
|
||||
|
||||
//verify hmac stuff for a given supercluster
|
||||
//expects 0x7f00 - 0x7ff0
|
||||
bool CheckHmacMeta( quint16 clNo );
|
||||
|
||||
|
||||
private:
|
||||
QByteArray key;
|
||||
qint32 loc_super;
|
||||
qint32 loc_fat;
|
||||
qint32 loc_fst;
|
||||
quint16 currentSuperCluster;
|
||||
quint32 superClusterVersion;
|
||||
QString extractPath;
|
||||
QString nandPath;
|
||||
QFile f;
|
||||
@ -181,6 +194,8 @@ private:
|
||||
QTreeWidgetItem *ItemFromEntry( quint16 i, QTreeWidgetItem *parent = NULL );
|
||||
QTreeWidgetItem *ItemFromEntry( const QString &i, QTreeWidgetItem *parent = NULL );
|
||||
|
||||
|
||||
|
||||
signals:
|
||||
//connect to these to receive messages from this object
|
||||
void SendError( QString );
|
||||
|
@ -117,6 +117,22 @@ QString SharedContentMap::GetNextEmptyCid()
|
||||
void SharedContentMap::AddEntry( const QString &app, const QByteArray &hash )
|
||||
{
|
||||
data += app.toAscii() + hash;
|
||||
//qDebug() << "SharedContentMap::AddEntry -> added entry, rechecking this beast";
|
||||
//Check();
|
||||
}
|
||||
|
||||
const QByteArray SharedContentMap::Hash( quint16 i )
|
||||
{
|
||||
if( Count() < i )
|
||||
return QByteArray();
|
||||
return data.mid( ( i * 28 ) + 8, 20 );
|
||||
}
|
||||
|
||||
const QString SharedContentMap::Cid( quint16 i )
|
||||
{
|
||||
if( Count() < i )
|
||||
return QByteArray();
|
||||
return QString( data.mid( ( i * 28 ), 8 ) );
|
||||
}
|
||||
quint16 SharedContentMap::Count()
|
||||
{
|
||||
return ( data.size() / 28 );
|
||||
}
|
||||
|
@ -28,6 +28,10 @@ public:
|
||||
//get the entire data ready for writing to a wii nand
|
||||
const QByteArray Data(){ return data; }
|
||||
|
||||
const QByteArray Hash( quint16 i );
|
||||
const QString Cid( quint16 i );
|
||||
quint16 Count();
|
||||
|
||||
private:
|
||||
QByteArray data;
|
||||
};
|
||||
|
262
WiiQt/tiktmd.cpp
262
WiiQt/tiktmd.cpp
@ -36,6 +36,29 @@ bool Tmd::SetTid( quint64 tid )
|
||||
return true;
|
||||
}
|
||||
|
||||
quint64 Tmd::IOS()
|
||||
{
|
||||
if( !p_tmd )
|
||||
return 0;
|
||||
return qFromBigEndian( p_tmd->sys_version );
|
||||
}
|
||||
|
||||
bool Tmd::SetIOS( quint64 ios )
|
||||
{
|
||||
if( !p_tmd )
|
||||
return false;
|
||||
|
||||
p_tmd->sys_version = qFromBigEndian( ios );
|
||||
return true;
|
||||
}
|
||||
|
||||
quint16 Tmd::Gid()
|
||||
{
|
||||
if( !p_tmd )
|
||||
return 0;
|
||||
return qFromBigEndian( p_tmd->group_id );
|
||||
}
|
||||
|
||||
QString Tmd::Cid( quint16 i )
|
||||
{
|
||||
if( !p_tmd || i > qFromBigEndian( p_tmd->num_contents ) )
|
||||
@ -276,3 +299,242 @@ bool Ticket::FakeSign()
|
||||
while( ++i );
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
//theres probably a better place to put all this stuff. but until then, just put it here
|
||||
//and this BE() probably isnt too good for cross-platformyitutiveness
|
||||
#define BE32(a) (((a)[0] << 24)|((a)[1] << 16)|((a)[2] << 8)|(a)[3])
|
||||
|
||||
#define bn_zero(a,b) memset(a,0,b)
|
||||
#define bn_copy(a,b,c) memcpy(a,b,c)
|
||||
#define bn_compare(a,b,c) memcmp(a,b,c)
|
||||
|
||||
// calc a = a mod N, given n = size of a,N in bytes
|
||||
static void bn_sub_modulus( quint8 *a, const quint8 *N, const quint32 n )
|
||||
{
|
||||
quint32 dig;
|
||||
quint8 c = 0;
|
||||
|
||||
for( quint32 i = n - 1; i < n; i-- )
|
||||
{
|
||||
dig = N[ i ] + c;
|
||||
c = ( a [ i ] < dig );
|
||||
a[ i ] -= dig;
|
||||
}
|
||||
}
|
||||
|
||||
// calc d = (a + b) mod N, given n = size of d,a,b,N in bytes
|
||||
static void bn_add( quint8 *d, const quint8 *a, const quint8 *b, const quint8 *N, const quint32 n )
|
||||
{
|
||||
quint32 i;
|
||||
quint32 dig;
|
||||
quint8 c = 0;
|
||||
|
||||
for( i = n - 1; i < n; i--)
|
||||
{
|
||||
dig = a[ i ] + b[ i ] + c;
|
||||
c = ( dig >= 0x100 );
|
||||
d[ i ] = dig;
|
||||
}
|
||||
if( c )
|
||||
bn_sub_modulus( d, N, n );
|
||||
|
||||
if( bn_compare( d, N, n ) >= 0 )
|
||||
bn_sub_modulus( d, N, n );
|
||||
}
|
||||
|
||||
// calc d = (a * b) mod N, given n = size of d,a,b,N in bytes
|
||||
static void bn_mul( quint8 *d, const quint8 *a, const quint8 *b, const quint8 *N, const quint32 n )
|
||||
{
|
||||
quint8 mask;
|
||||
bn_zero( d, n );
|
||||
|
||||
for( quint32 i = 0; i < n; i++ )
|
||||
for( mask = 0x80; mask != 0; mask >>= 1 )
|
||||
{
|
||||
bn_add( d, d, d, N, n );
|
||||
if( ( a[ i ] & mask ) != 0 )
|
||||
bn_add( d, d, b, N, n );
|
||||
}
|
||||
}
|
||||
|
||||
// calc d = (a ^ e) mod N, given n = size of d,a,N and en = size of e in bytes
|
||||
static void bn_exp( quint8 *d, const quint8 *a, const quint8 *N, const quint32 n, const quint8 *e, const quint32 en )
|
||||
{
|
||||
quint8 t[ 512 ];
|
||||
quint8 mask;
|
||||
|
||||
bn_zero( d, n );
|
||||
d[ n-1 ] = 1;
|
||||
for( quint32 i = 0; i < en; i++ )
|
||||
for( mask = 0x80; mask != 0; mask >>= 1 )
|
||||
{
|
||||
bn_mul( t, d, d, N, n );
|
||||
if( ( e [ i ] & mask ) != 0 )
|
||||
bn_mul( d, t, a, N, n );
|
||||
else
|
||||
bn_copy( d, t, n );
|
||||
}
|
||||
}
|
||||
|
||||
static int get_sig_len( const quint8 *sig )
|
||||
{
|
||||
quint32 type;
|
||||
type = BE32( sig );
|
||||
switch( type - 0x10000 )
|
||||
{
|
||||
case 0:
|
||||
return 0x240;
|
||||
case 1:
|
||||
return 0x140;
|
||||
case 2:
|
||||
return 0x80;
|
||||
}
|
||||
return -ERROR_SIG_TYPE;
|
||||
}
|
||||
|
||||
static int get_sub_len( const quint8 *sub )
|
||||
{
|
||||
quint32 type;
|
||||
type = BE32( sub + 0x40 );
|
||||
switch( type )
|
||||
{
|
||||
case 0: return 0x2c0;
|
||||
case 1: return 0x1c0;
|
||||
case 2: return 0x100;
|
||||
}
|
||||
return -ERROR_SUB_TYPE;
|
||||
}
|
||||
|
||||
static int check_rsa( QByteArray h, const quint8 *sig, const quint8 *key, const quint32 n )
|
||||
{
|
||||
quint8 correct[ 0x200 ], x[ 0x200 ];
|
||||
static const quint8 ber[ 16 ] = { 0x00,0x30,0x21,0x30,0x09,0x06,0x05,0x2b,
|
||||
0x0e,0x03,0x02,0x1a,0x05,0x00,0x04,0x14 };
|
||||
correct[ 0 ] = 0;
|
||||
correct[ 1 ] = 1;
|
||||
memset( correct + 2, 0xff, n - 38 );
|
||||
memcpy( correct + n - 36, ber, 16 );
|
||||
memcpy( correct + n - 20, h.constData(), 20 );
|
||||
|
||||
bn_exp( x, sig, key, n, key + n, 4 );
|
||||
|
||||
//qDebug() << "Decrypted signature hash:" << QByteArray( (const char*)&x[ n-20 ], 20 ).toHex();
|
||||
//qDebug() << " SHA1 hash:" << h.toHex();
|
||||
|
||||
if( memcmp( correct, x, n ) == 0 )
|
||||
return 0;
|
||||
|
||||
if( strncmp( (char*)h.constData(), (char*) x + n - 20, 20 ) == 0 )
|
||||
return ERROR_RSA_FAKESIGNED;
|
||||
|
||||
return ERROR_RSA_HASH;
|
||||
}
|
||||
|
||||
static int check_hash( QByteArray h, const quint8 *sig, const quint8 *key )
|
||||
{
|
||||
quint32 type;
|
||||
type = BE32( sig ) - 0x10000;
|
||||
if( (qint32)type != BE32( key + 0x40 ) )
|
||||
return ERROR_RSA_TYPE_MISMATCH;
|
||||
|
||||
if( type == 1 )
|
||||
return check_rsa( h, sig + 4, key + 0x88, 0x100 );
|
||||
return ERROR_RSA_TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
static const quint8* find_cert_in_chain( const quint8 *sub, const quint8 *cert, const quint32 cert_len, int *err )
|
||||
{
|
||||
char parent[ 64 ], *child;
|
||||
int sig_len, sub_len;
|
||||
const quint8 *p, *issuer;
|
||||
|
||||
strncpy( parent, (char*)sub, sizeof parent );
|
||||
parent[ sizeof parent - 1 ] = 0;
|
||||
child = strrchr( parent, '-' );
|
||||
if( child )
|
||||
*child++ = 0;
|
||||
else
|
||||
{
|
||||
*parent = 0;
|
||||
child = (char*)sub;
|
||||
}
|
||||
|
||||
*err = -ERROR_CERT_NOT_FOUND;
|
||||
|
||||
for( p = cert; p < cert + cert_len; p += sig_len + sub_len )
|
||||
{
|
||||
sig_len = get_sig_len( p );
|
||||
if( sig_len < 0 )
|
||||
{
|
||||
*err = sig_len;
|
||||
break;
|
||||
}
|
||||
issuer = p + sig_len;
|
||||
sub_len = get_sub_len( issuer );
|
||||
if( sub_len < 0 )
|
||||
{
|
||||
*err = sub_len;
|
||||
break;
|
||||
}
|
||||
if( strcmp( parent, (char*)issuer ) == 0 && strcmp( child, (char*)issuer + 0x44 ) == 0 )
|
||||
return p;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int check_cert_chain( const QByteArray data )
|
||||
{
|
||||
int cert_err;
|
||||
const quint8* key;
|
||||
const quint8 *sig, *sub, *key_cert;
|
||||
int sig_len, sub_len;
|
||||
QByteArray h;
|
||||
int ret;
|
||||
const quint8 *certificates = certs_dat;
|
||||
|
||||
sig = (const quint8*)data.constData();
|
||||
sig_len = get_sig_len( sig );
|
||||
if( sig_len < 0 )
|
||||
return -sig_len;
|
||||
|
||||
sub = (const quint8*)( data.data() + sig_len );
|
||||
sub_len = data.size() - sig_len;
|
||||
if( sub_len <= 0 )
|
||||
return ERROR_SUB_TYPE;
|
||||
|
||||
for( ; ; )
|
||||
{
|
||||
//qDebug() << "Verifying using" << QString( (const char*) sub );
|
||||
if( strcmp((char*)sub, "Root" ) == 0 )
|
||||
{
|
||||
key = root_dat;
|
||||
h = GetSha1( QByteArray( (const char*)sub, sub_len ) );
|
||||
if( BE32( sig ) != 0x10000 )
|
||||
return ERROR_SIG_TYPE;
|
||||
return check_rsa( h, sig + 4, key, 0x200 );
|
||||
}
|
||||
|
||||
key_cert = find_cert_in_chain( sub, certificates, CERTS_DAT_SIZE, &cert_err );
|
||||
if( key_cert )
|
||||
cert_err = get_sig_len( key_cert );
|
||||
|
||||
if( cert_err < 0 )
|
||||
return -cert_err;
|
||||
key = key_cert + cert_err;
|
||||
|
||||
h = GetSha1( QByteArray( (const char*)sub, sub_len ) );
|
||||
ret = check_hash( h, sig, key );
|
||||
// remove this if statement if you don't want to check the whole chain
|
||||
if( ret != ERROR_SUCCESS )
|
||||
return ret;
|
||||
sig = key_cert;
|
||||
sig_len = get_sig_len( sig );
|
||||
if( sig_len < 0 )
|
||||
return -sig_len;
|
||||
sub = sig + sig_len;
|
||||
sub_len = get_sub_len( sub );
|
||||
if( sub_len < 0 )
|
||||
return -sub_len;
|
||||
}
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ typedef struct _cert_ecdsa {
|
||||
class Ticket
|
||||
{
|
||||
public:
|
||||
Ticket( QByteArray stuff );
|
||||
Ticket( QByteArray stuff = QByteArray() );
|
||||
|
||||
//expose the tik data to the rest of the code so it cas read directly from the p_tmd instead of having to add a function to access all the data
|
||||
//the pointer should be good until "data" is changed
|
||||
@ -172,7 +172,7 @@ private:
|
||||
class Tmd
|
||||
{
|
||||
public:
|
||||
Tmd( QByteArray stuff );
|
||||
Tmd( QByteArray stuff = QByteArray() );
|
||||
|
||||
//expose the tmd data to the rest of the code so it cas read directly from the p_tmd instead of having to add a function to access all the data
|
||||
//the pointer should be good until "data" is changed
|
||||
@ -188,6 +188,9 @@ public:
|
||||
quint64 Size( quint16 i );
|
||||
quint16 Type( quint16 i );
|
||||
quint64 Tid();
|
||||
quint64 IOS();
|
||||
quint16 Gid();
|
||||
|
||||
|
||||
//gets the number of contents
|
||||
quint16 Count();
|
||||
@ -202,6 +205,7 @@ public:
|
||||
bool SetType( quint16 i, quint16 type );
|
||||
bool SetSize( quint16 i, quint32 size );
|
||||
bool SetHash( quint16 i, const QByteArray hash );
|
||||
bool SetIOS( quint64 ios );
|
||||
|
||||
bool FakeSign();
|
||||
|
||||
@ -227,4 +231,21 @@ private:
|
||||
tmd *p_tmd;
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
ERROR_SUCCESS = 0,
|
||||
ERROR_SIG_TYPE,
|
||||
ERROR_SUB_TYPE,
|
||||
ERROR_RSA_FAKESIGNED,
|
||||
ERROR_RSA_HASH,
|
||||
ERROR_RSA_TYPE_UNKNOWN,
|
||||
ERROR_RSA_TYPE_MISMATCH,
|
||||
ERROR_CERT_NOT_FOUND,
|
||||
ERROR_COUNT
|
||||
};
|
||||
|
||||
//checks the signatures in a tmd/ticket
|
||||
//returns 1 of the above enums
|
||||
int check_cert_chain( const QByteArray data );
|
||||
|
||||
#endif // TIKTMD_H
|
||||
|
662
nandBinCheck/main.cpp
Normal file
662
nandBinCheck/main.cpp
Normal file
@ -0,0 +1,662 @@
|
||||
#include "../WiiQt/includes.h"
|
||||
#include "../WiiQt/nandbin.h"
|
||||
#include "../WiiQt/sharedcontentmap.h"
|
||||
#include "../WiiQt/uidmap.h"
|
||||
#include "../WiiQt/tools.h"
|
||||
#include "../WiiQt/tiktmd.h"
|
||||
|
||||
|
||||
//yippie for global variables
|
||||
NandBin nand;
|
||||
SharedContentMap sharedM;
|
||||
UIDmap uidM;
|
||||
QList< quint64 > tids;
|
||||
QList< quint64 > validIoses;//dont put stubs in this list.
|
||||
QTreeWidgetItem *root;
|
||||
QList<quint16> fats;
|
||||
|
||||
|
||||
bool CheckTitleIntegrity( quint64 tid );
|
||||
|
||||
void Usage()
|
||||
{
|
||||
qDebug() << "usage" << QCoreApplication::arguments().at( 0 ) << "nand.bin" << "<other options>";
|
||||
qDebug() << "\nOther options:";
|
||||
qDebug() << " -boot shows information about boot 1 and 2";
|
||||
qDebug() << " -fs verify the filesystem is in tact";
|
||||
qDebug() << " verifies presence of uid & content.map & checks the hashes in the content.map";
|
||||
qDebug() << " check installed titles for RSA & sha1 validity";
|
||||
qDebug() << " check installed titles for required IOS, proper uid & gid";
|
||||
qDebug() << " -clInfo shows free, used, and lost ( marked used, but dont belong to any file ) clusters";
|
||||
qDebug() << " -spare calculate & compare ecc for all pages in the nand";
|
||||
qDebug() << " calculate & compare hmac signatures for all files and superblocks";
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
void Fail( const QString& str )
|
||||
{
|
||||
qDebug() << str;
|
||||
exit( 1 );
|
||||
}
|
||||
|
||||
QString TidTxt( quint64 tid )
|
||||
{
|
||||
return QString( "%1" ).arg( tid, 16, 16, QChar( '0' ) );
|
||||
}
|
||||
|
||||
void ShowBootInfo( quint8 boot1, QList<Boot2Info> boot2stuff )
|
||||
{
|
||||
switch( boot1 )
|
||||
{
|
||||
case BOOT_1_A:
|
||||
qDebug() << "Boot1 A (vulnerable)";
|
||||
break;
|
||||
case BOOT_1_B:
|
||||
qDebug() << "Boot1 B (vulnerable)";
|
||||
break;
|
||||
case BOOT_1_C:
|
||||
qDebug() << "Boot1 C (fixed)";
|
||||
break;
|
||||
case BOOT_1_D:
|
||||
qDebug() << "Boot1 D (fixed)";
|
||||
break;
|
||||
default:
|
||||
qDebug() << "unrecognized boot1 version";
|
||||
break;
|
||||
}
|
||||
quint16 cnt = boot2stuff.size();
|
||||
if( !cnt )
|
||||
Fail( "didnt find any boot2. this nand wont work" );
|
||||
|
||||
qDebug() << "found" << cnt << "copies of boot2";
|
||||
for( quint16 i = 0; i < cnt; i++ )
|
||||
{
|
||||
Boot2Info bi = boot2stuff.at( i );
|
||||
QString str = QString( "blocks %1 & %2: " ).arg( bi.firstBlock ).arg( bi.secondBlock );
|
||||
if( bi.state == BOOT_2_ERROR_PARSING || bi.state == BOOT_2_ERROR )
|
||||
str += "parsing error";
|
||||
else if( bi.state == BOOT_2_BAD_SIGNATURE )
|
||||
str += "Bad RSA Signature";
|
||||
else if( bi.state == BOOT_2_BAD_CONTENT_HASH )
|
||||
str += "Content hash doesn't match TMD";
|
||||
else if( bi.state == BOOT_2_ERROR_PARSING )
|
||||
str += "Error parsing boot2";
|
||||
else
|
||||
{
|
||||
|
||||
if( bi.state & BOOT_2_MARKED_BAD )
|
||||
str += "Marked as bad blocks; ";
|
||||
else if( bi.state & BOOT_2_USED_TO_BOOT )
|
||||
{
|
||||
str += "Used for booting; ";
|
||||
if( boot1 != BOOT_1_A && boot1 != BOOT_1_B
|
||||
&& (( bi.state & BOOT_2_TIK_FAKESIGNED) ||
|
||||
( bi.state & BOOT_2_TMD_FAKESIGNED ) ) )
|
||||
Fail( "The version of boot1 installed doesn't have the strncmp bug and the copy of boot2 used for booting is fakesigned" );
|
||||
}
|
||||
else if( bi.state & BOOT_2_BACKUP_COPY )
|
||||
str += "Backup copy; ";
|
||||
|
||||
|
||||
str += "Content Sha1 matches TMD; ";
|
||||
|
||||
if( bi.state & BOOT_2_TMD_FAKESIGNED )
|
||||
str += "TMD is fakesigned; ";
|
||||
else if( bi.state & BOOT_2_TMD_SIG_OK )
|
||||
str += "TMD officially signed; ";
|
||||
else
|
||||
str += "Error checking TMD; " ;
|
||||
|
||||
if( bi.state & BOOT_2_TIK_FAKESIGNED )
|
||||
str += "Ticket is fakesigned; ";
|
||||
else if( bi.state & BOOT_2_TIK_SIG_OK )
|
||||
str += "Ticket officially signed; ";
|
||||
else
|
||||
str += "Error checking ticket; ";
|
||||
|
||||
QString ver;
|
||||
switch( bi.version )
|
||||
{
|
||||
case BOOTMII_11:
|
||||
ver = "BootMii 1.1";
|
||||
break;
|
||||
case BOOTMII_13:
|
||||
ver = "BootMii 1.3";
|
||||
break;
|
||||
case BOOTMII_UNK:
|
||||
ver = "BootMii (Unk)";
|
||||
break;
|
||||
default:
|
||||
ver = QString( "Version %1" ).arg( bi.version );
|
||||
break;
|
||||
}
|
||||
str += ver;
|
||||
}
|
||||
qDebug() << str;
|
||||
}
|
||||
}
|
||||
|
||||
QTreeWidgetItem *FindItem( const QString &s, QTreeWidgetItem *parent )
|
||||
{
|
||||
int cnt = parent->childCount();
|
||||
for( int i = 0; i <cnt; i++ )
|
||||
{
|
||||
QTreeWidgetItem *r = parent->child( i );
|
||||
if( r->text( 0 ) == s )
|
||||
{
|
||||
return r;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
QTreeWidgetItem *ItemFromPath( const QString &path )
|
||||
{
|
||||
QTreeWidgetItem *item = root;
|
||||
if( !path.startsWith( "/" ) || path.contains( "//" ))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
int slash = 1;
|
||||
while( slash )
|
||||
{
|
||||
int nextSlash = path.indexOf( "/", slash + 1 );
|
||||
QString lookingFor = path.mid( slash, nextSlash - slash );
|
||||
item = FindItem( lookingFor, item );
|
||||
if( !item )
|
||||
{
|
||||
qWarning() << "ItemFromPath ->item not found" << path;
|
||||
return NULL;
|
||||
}
|
||||
slash = nextSlash + 1;
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
QString PathFromItem( QTreeWidgetItem *item )
|
||||
{
|
||||
QString ret;
|
||||
while( item )
|
||||
{
|
||||
ret.prepend( "/" + item->text( 0 ) );
|
||||
item = item->parent();
|
||||
if( item->text( 0 ) == "/" )// dont add the root
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
//get the installed titles in the nand - first check for tickets and then check for TMDs that match that ticket
|
||||
QList< quint64 > InstalledTitles()
|
||||
{
|
||||
QList< quint64 > ret;
|
||||
QTreeWidgetItem *tikFolder = ItemFromPath( "/ticket" );
|
||||
if( !tikFolder )
|
||||
Fail( "Couldnt find a ticket folder in this nand" );
|
||||
|
||||
quint16 subfc = tikFolder->childCount();
|
||||
for( quint16 i = 0; i < subfc; i++ )//check all subfolders of "/ticket"
|
||||
{
|
||||
QTreeWidgetItem *subF = tikFolder->child( i );
|
||||
//qDebug() << "checking folder" << subF->text( 0 );
|
||||
bool ok = false;
|
||||
quint32 upper = subF->text( 0 ).toInt( &ok, 16 );//make sure it can be converted to int
|
||||
if ( !ok )
|
||||
continue;
|
||||
|
||||
quint16 subfc2 = subF->childCount();//get all entries in this subfolder
|
||||
for( quint16 j = 0; j < subfc2; j++ )
|
||||
{
|
||||
QTreeWidgetItem *tikI = subF->child( j );
|
||||
QString name = tikI->text( 0 );
|
||||
//qDebug() << "checking item" << subF->text( 0 ) + "/" + name;
|
||||
if( !name.endsWith( ".tik" ) )
|
||||
{
|
||||
//qDebug() << "!tik";
|
||||
continue;
|
||||
}
|
||||
|
||||
name.resize( 8 );
|
||||
quint32 lower = name.toInt( &ok, 16 );
|
||||
if( !ok )
|
||||
{
|
||||
//qDebug() << "!ok";
|
||||
continue;
|
||||
}
|
||||
|
||||
//now see if there is a tmd
|
||||
QTreeWidgetItem *tmdI = ItemFromPath( "/title/" + subF->text( 0 ) + "/" + name + "/content/title.tmd" );
|
||||
if( !tmdI )
|
||||
{
|
||||
//qDebug() << "no tmd";
|
||||
continue;
|
||||
}
|
||||
|
||||
quint64 tid = (( (quint64)upper << 32) | lower );
|
||||
//qDebug() << "adding item to list" << TidTxt( tid );
|
||||
ret << tid;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void CheckShared()
|
||||
{
|
||||
QByteArray ba = nand.GetData( "/shared1/content.map" );
|
||||
if( ba.isEmpty() )
|
||||
Fail( "No content map found in the nand" );
|
||||
|
||||
sharedM = SharedContentMap( ba );
|
||||
quint16 cnt = sharedM.Count();
|
||||
for( quint16 i = 0; i < cnt; i++ )
|
||||
{
|
||||
QString path = "/shared1/" + sharedM.Cid( i ) + ".app";
|
||||
qDebug() << "checking" << path << "...";
|
||||
QByteArray stuff = nand.GetData( path );
|
||||
if( stuff.isEmpty() )
|
||||
Fail( "One of the shared contents in this nand is missing" );
|
||||
|
||||
QByteArray realHash = GetSha1( stuff );
|
||||
if( realHash != sharedM.Hash( i ) )
|
||||
Fail( "The hash for at least 1 content is bad" );
|
||||
}
|
||||
}
|
||||
|
||||
void BuildGoodIosList()
|
||||
{
|
||||
foreach( quint64 tid, tids )
|
||||
{
|
||||
quint32 upper = ( tid >> 32 ) & 0xffffffff;
|
||||
if( upper != 1 )
|
||||
continue;
|
||||
|
||||
quint32 lower = tid & 0xffffffff;
|
||||
|
||||
if( lower < 3 || lower > 255 )
|
||||
continue;
|
||||
|
||||
QString tmdp = TidTxt( tid );
|
||||
tmdp.insert( 8, "/" );
|
||||
tmdp.prepend( "/title/" );
|
||||
tmdp += "/content/title.tmd";
|
||||
QByteArray ba = nand.GetData( tmdp );
|
||||
if( ba.isEmpty() )
|
||||
continue;
|
||||
|
||||
Tmd t( ba ); //skip stubbzzzzz
|
||||
if( !( t.Version() % 0x100 ) && //version is a nice pretty round number
|
||||
t.Count() == 3 && //3 contents, 1 private and 2 shared
|
||||
t.Type( 0 ) == 1 &&
|
||||
t.Type( 1 ) == 0x8001 &&
|
||||
t.Type( 2 ) == 0x8001 )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if( !CheckTitleIntegrity( tid ) )
|
||||
continue;
|
||||
|
||||
validIoses << tid;//seems good enough. add it to the list
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckTitleIntegrity( quint64 tid )
|
||||
{
|
||||
if( validIoses.contains( tid ) )//this one has already been checked
|
||||
return true;
|
||||
qDebug() << "Checking" << TidTxt( tid ).insert( 8, "-" ) << "...";
|
||||
QString p = TidTxt( tid );
|
||||
p.insert( 8 ,"/" );
|
||||
QString tikp = p;
|
||||
tikp.prepend( "/ticket/" );
|
||||
tikp += ".tik";
|
||||
|
||||
QString tmdp = p;
|
||||
tmdp.prepend( "/title/" );
|
||||
tmdp += "/content/title.tmd";
|
||||
|
||||
Tmd t;
|
||||
//check the presence of the tmd & ticket. also check their signatures
|
||||
//remember the tmd for checking the actual contents
|
||||
for( quint8 i = 0; i < 2; i++ )
|
||||
{
|
||||
QByteArray ba = nand.GetData( i ? tmdp : tikp );
|
||||
if( ba.isEmpty() )
|
||||
{
|
||||
if( i )
|
||||
qDebug() << "error getting tmd data";
|
||||
else
|
||||
qDebug() << "error getting ticket data";
|
||||
return false;
|
||||
}
|
||||
switch( check_cert_chain( ba ) )
|
||||
{
|
||||
case ERROR_SIG_TYPE:
|
||||
case ERROR_SUB_TYPE:
|
||||
case ERROR_RSA_TYPE_UNKNOWN:
|
||||
case ERROR_RSA_TYPE_MISMATCH:
|
||||
case ERROR_CERT_NOT_FOUND:
|
||||
qDebug() << "the RSA signature isn't even close";
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if( i )
|
||||
{
|
||||
t = Tmd( ba );
|
||||
if( t.Tid() != tid )
|
||||
{
|
||||
qDebug() << "the TMD contains the wrong TID";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Ticket ticket( ba );
|
||||
if( ticket.Tid() != tid )
|
||||
{
|
||||
qDebug() << "the ticket contains the wrong TID";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
quint16 cnt = t.Count();
|
||||
for( quint16 i = 0; i < cnt; i++ )
|
||||
{
|
||||
if( t.Type( i ) == 0x8001 )//shared
|
||||
{
|
||||
if( sharedM.GetAppFromHash( t.Hash( i ) ).isEmpty() )
|
||||
{
|
||||
qDebug() << "one of the shared contents is missing";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else//private
|
||||
{
|
||||
QString cid = t.Cid( i );
|
||||
QString pA = tmdp;
|
||||
pA.resize( 33 );
|
||||
pA += cid + ".app";
|
||||
QByteArray ba = nand.GetData( pA );
|
||||
if( ba.isEmpty() )
|
||||
{
|
||||
qDebug() << "one of the private contents is missing" << pA;
|
||||
return false;
|
||||
}
|
||||
QByteArray realH = GetSha1( ba );
|
||||
if( realH != t.Hash( i ) )
|
||||
{
|
||||
qDebug() << "one of the private contents' hash doesnt check out" << i << pA <<
|
||||
"\nexpected" << t.Hash( i ).toHex() <<
|
||||
"\nactual " << realH.toHex();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
quint64 ios = t.IOS();
|
||||
if( ios && !validIoses.contains( ios ) )
|
||||
{
|
||||
qDebug() << "the IOS for this title is not bootable";
|
||||
return false;
|
||||
}
|
||||
quint32 uid = uidM.GetUid( tid, false );
|
||||
if( !uid )
|
||||
{
|
||||
qDebug() << "this title has no UID entry";
|
||||
return false;
|
||||
}
|
||||
|
||||
//make sure all the stuff in this title's data directory belongs to it ( im looking at you priibricker & dop-mii )
|
||||
QString dataP = tmdp;
|
||||
dataP.resize( 25 );
|
||||
dataP += "data";
|
||||
QTreeWidgetItem *dataI = ItemFromPath( dataP );
|
||||
if( dataI )
|
||||
{
|
||||
quint16 gid = t.Gid();
|
||||
QString uidS = QString( "%1" ).arg( uid, 8, 16, QChar( '0' ) );
|
||||
QString gidS = QString( "%1" ).arg( gid, 4, 16, QChar( '0' ) );
|
||||
if( dataI->text( 3 ) != uidS || !dataI->text( 4 ).startsWith( gidS ) )//dont necessarily fail for this. the title will still be bootable without its data
|
||||
qDebug() << "incorrect uid/gid for data folder";
|
||||
quint16 cnt = dataI->childCount();
|
||||
for( quint16 i = 0; i < cnt; i++ )
|
||||
{
|
||||
QTreeWidgetItem *item = dataI->child( i );
|
||||
if( item->text( 3 ) != uidS || !item->text( 4 ).startsWith( gidS ) )
|
||||
qDebug() << "incorrect uid/gid for" << QString( "data/" + item->text( 0 ) );
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CheckLostClusters()
|
||||
{
|
||||
QList<quint16> u = nand.GetFatsForEntry( 0 );//all clusters actually used for a file
|
||||
qDebug() << "total used clusters" << hex << u.size() << "of 0x8000";
|
||||
quint16 lost = 0;
|
||||
QList<quint16> ffs;
|
||||
QList<quint16> frs;
|
||||
fats = nand.GetFats();
|
||||
for( quint16 i = 0; i < 0x8000; i++ )
|
||||
{
|
||||
if( u.contains( fats.at( i ) ) )//this cluster is really used
|
||||
continue;
|
||||
switch( fats.at( i ) )
|
||||
{
|
||||
case 0xFFFB:
|
||||
case 0xFFFC:
|
||||
case 0xFFFD:
|
||||
break;
|
||||
case 0xFFFE:
|
||||
frs << i;
|
||||
break;
|
||||
case 0xFFFF:
|
||||
ffs << i;
|
||||
break;
|
||||
default:
|
||||
lost++;
|
||||
//qDebug() << hex << i << fats.at( i );
|
||||
break;
|
||||
}
|
||||
}
|
||||
qDebug() << "found" << lost << "lost clusters\nUNK ( 0xffff )" << hex << ffs.size() << ffs <<
|
||||
"\nfree " << frs.size();
|
||||
}
|
||||
|
||||
void CheckEcc()
|
||||
{
|
||||
QList< quint32 > bad;
|
||||
QList< quint16 > clusters;
|
||||
fats = nand.GetFats();
|
||||
quint32 checked = 0;
|
||||
quint16 badClustersNotSpecial = 0;
|
||||
for( quint16 i = 0; i < 0x8000; i++ )
|
||||
{
|
||||
if( fats.at( i ) == 0xfffd || fats.at( i ) == 0xfffe )
|
||||
continue;
|
||||
//qDebug() << hex << i;
|
||||
|
||||
for( quint8 j = 0; j < 8; j++, checked += 8 )
|
||||
{
|
||||
quint32 page = ( i * 8 ) + j;
|
||||
if( !nand.CheckEcc( page ) )
|
||||
{
|
||||
bad << page;
|
||||
if( !clusters.contains( i ) )
|
||||
clusters << i;
|
||||
}
|
||||
}
|
||||
}
|
||||
QList< quint16 > blocks;
|
||||
QList< quint16 > clustersCpy = clusters;
|
||||
|
||||
while( clustersCpy.size() )
|
||||
{
|
||||
quint16 p = clustersCpy.takeFirst();
|
||||
if( fats.at( p ) < 0xfff0 )
|
||||
badClustersNotSpecial ++;
|
||||
//qDebug() << p << hex << fats.at( p );
|
||||
quint16 block = p/8;
|
||||
if( !blocks.contains( block ) )
|
||||
blocks << block;
|
||||
}
|
||||
/*QList< quint32 > badCpy = bad;
|
||||
while( badCpy.size() )
|
||||
{
|
||||
quint16 p = badCpy.takeFirst();
|
||||
quint16 block = p/64;
|
||||
if( !blocks.contains( block ) )
|
||||
blocks << block;
|
||||
}*/
|
||||
qDebug() << bad.size() << "out of" << checked << "pages had incorrect ecc.\nthey were spread through"
|
||||
<< clusters.size() << "clusters in" << blocks.size() << "blocks:\n" << blocks;
|
||||
qDebug() << badClustersNotSpecial << "of those clusters are non-special (they belong to the fs)";
|
||||
//qDebug() << bad;
|
||||
}
|
||||
|
||||
void SetUpTree()
|
||||
{
|
||||
if( root )
|
||||
return;
|
||||
QTreeWidgetItem *r = nand.GetTree();
|
||||
if( r->childCount() != 1 || r->child( 0 )->text( 0 ) != "/" )
|
||||
Fail( "The nand FS is seriously broken. I Couldn't even find a correct root" );
|
||||
|
||||
root = r->takeChild( 0 );
|
||||
delete r;
|
||||
}
|
||||
|
||||
int RecurseCountFiles( QTreeWidgetItem *dir )
|
||||
{
|
||||
int ret = 0;
|
||||
quint16 cnt = dir->childCount();
|
||||
for( quint16 i = 0; i < cnt; i++ )
|
||||
{
|
||||
QTreeWidgetItem *item = dir->child( i );
|
||||
if( item->text( 7 ).startsWith( "02" ) )
|
||||
ret += RecurseCountFiles( item );
|
||||
else
|
||||
{
|
||||
ret++;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int RecurseCheckHmac( QTreeWidgetItem *dir )
|
||||
{
|
||||
int ret = 0;
|
||||
quint16 cnt = dir->childCount();
|
||||
for( quint16 i = 0; i < cnt; i++ )
|
||||
{
|
||||
QTreeWidgetItem *item = dir->child( i );
|
||||
if( item->text( 7 ).startsWith( "02" ) )
|
||||
ret += RecurseCheckHmac( item );
|
||||
else
|
||||
{
|
||||
bool ok = false;
|
||||
quint16 entry = item->text( 1 ).toInt( &ok );
|
||||
if( !ok )
|
||||
continue;
|
||||
|
||||
if( !nand.CheckHmacData( entry ) )
|
||||
{
|
||||
qDebug() << "bad HMAC for" << PathFromItem( item );
|
||||
ret++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void CheckHmac()
|
||||
{
|
||||
quint16 total = RecurseCountFiles( root );
|
||||
qDebug() << "verifying hmac for" << total << "files";
|
||||
quint16 bad = RecurseCheckHmac( root );
|
||||
qDebug() << bad << "files had bad HMAC data";
|
||||
qDebug() << "checking HMAC for superclusters...";
|
||||
QList<quint16> sclBad;
|
||||
for( quint16 i = 0x7f00; i < 0x8000; i += 0x10 )
|
||||
{
|
||||
if( !nand.CheckHmacMeta( i ) )
|
||||
sclBad << i;
|
||||
}
|
||||
qDebug() << sclBad.size() << "superClusters had bad HMAC data";
|
||||
if( sclBad.size() )
|
||||
qDebug() << sclBad;
|
||||
}
|
||||
|
||||
int main( int argc, char *argv[] )
|
||||
{
|
||||
QCoreApplication a( argc, argv );
|
||||
QStringList args = QCoreApplication::arguments();
|
||||
if( args.size() < 2 )
|
||||
Usage();
|
||||
|
||||
if( !QFile( args.at( 1 ) ).exists() )
|
||||
Usage();
|
||||
|
||||
if( !nand.SetPath( args.at( 1 ) ) || !nand.InitNand() )
|
||||
Fail( "Error setting path to nand object" );
|
||||
|
||||
root = NULL;
|
||||
|
||||
//these only serve to show info. no action is taken
|
||||
if( args.contains( "-boot", Qt::CaseInsensitive ) )
|
||||
{
|
||||
qDebug() << "checking boot1 & 2...";
|
||||
ShowBootInfo( nand.Boot1Version(), nand.Boot2Infos() );
|
||||
}
|
||||
|
||||
if( args.contains( "-fs", Qt::CaseInsensitive ) )
|
||||
{
|
||||
qDebug() << "checking uid.sys...";
|
||||
QByteArray ba = nand.GetData( "/sys/uid.sys" );
|
||||
if( ba.isEmpty() )
|
||||
Fail( "No uid map found in the nand" );
|
||||
uidM = UIDmap( ba );
|
||||
uidM.Check(); //dont really take action, the check should spit out info if there is an error. not all errors are fatal
|
||||
|
||||
qDebug() << "checking content.map...";
|
||||
CheckShared(); //check for the presence of the content.map as well as verify all the hashes
|
||||
|
||||
SetUpTree();
|
||||
|
||||
tids = InstalledTitles();
|
||||
|
||||
qDebug() << "found" << tids.size() << "titles installed";
|
||||
BuildGoodIosList();
|
||||
qDebug() << "found" << validIoses.size() << "good IOS";
|
||||
foreach( quint64 tid, tids )
|
||||
{
|
||||
CheckTitleIntegrity( tid );
|
||||
//if( !CheckTitleIntegrity( tid ) && tid == 0x100000002ull ) //well, this SHOULD be the case. but nintendo doesnt care so much about
|
||||
//Fail( "The System menu isnt valid" ); //checking signatures & hashes as the rest of us.
|
||||
}
|
||||
}
|
||||
|
||||
if( args.contains( "-clInfo", Qt::CaseInsensitive ) )
|
||||
{
|
||||
qDebug() << "checking for lost clusters...";
|
||||
CheckLostClusters();
|
||||
}
|
||||
|
||||
if( args.contains( "-spare", Qt::CaseInsensitive ) )
|
||||
{
|
||||
qDebug() << "verifying ecc...";
|
||||
CheckEcc();
|
||||
|
||||
SetUpTree();
|
||||
qDebug() << "verifying hmac...";
|
||||
CheckHmac();
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
35
nandBinCheck/nandBinCheck.pro
Normal file
35
nandBinCheck/nandBinCheck.pro
Normal file
@ -0,0 +1,35 @@
|
||||
#-------------------------------------------------
|
||||
#
|
||||
# Project created by QtCreator 2010-12-17T16:22:53
|
||||
#
|
||||
#-------------------------------------------------
|
||||
|
||||
#QT += core\
|
||||
# gui
|
||||
|
||||
TARGET = nandBinCheck
|
||||
#CONFIG += console
|
||||
#CONFIG -= app_bundle
|
||||
|
||||
TEMPLATE = app
|
||||
|
||||
|
||||
SOURCES += main.cpp \
|
||||
../WiiQt/blocks0to7.cpp \
|
||||
../WiiQt/tiktmd.cpp \
|
||||
../WiiQt/nandbin.cpp \
|
||||
../WiiQt/tools.cpp \
|
||||
../WiiQt/savebanner.cpp \
|
||||
../WiiQt/aes.c \
|
||||
../WiiQt/sha1.c \
|
||||
../WiiQt/uidmap.cpp \
|
||||
../WiiQt/sharedcontentmap.cpp \
|
||||
../WiiQt/nandspare.cpp
|
||||
|
||||
HEADERS += ../WiiQt/tiktmd.h \
|
||||
../WiiQt/nandbin.h \
|
||||
../WiiQt/tools.h \
|
||||
../WiiQt/blocks0to7.h \
|
||||
../WiiQt/uidmap.h \
|
||||
../WiiQt/sharedcontentmap.h \
|
||||
../WiiQt/nandspare.h
|
8
nandBinCheck/readmii.txt
Normal file
8
nandBinCheck/readmii.txt
Normal file
@ -0,0 +1,8 @@
|
||||
this is a simple cli program to check a nand.bin for various things...
|
||||
boot1/2 info, fs integrity, signatures, hashes, ecc, hmac, lost clusters, and title sanity.
|
||||
|
||||
i have written this as an aide to help debugging the write functions in the nand.bin class. but it likely will serve other purposes.
|
||||
|
||||
run it with no args to see the usage/options
|
||||
|
||||
giantpune
|
Loading…
Reference in New Issue
Block a user