* 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:
giantpune@gmail.com 2010-12-18 21:07:58 +00:00
parent 8eac10ea9e
commit 7e64a9f6b5
12 changed files with 1436 additions and 283 deletions

3
.gitattributes vendored
View File

@ -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

View File

@ -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;
}
}

View File

@ -1,7 +1,7 @@
#ifndef INCLUDES_H
#define INCLUDES_H
#include <QtGui/QApplication>
#include <QAction>
#include <QBuffer>
#include <QDialog>

View File

@ -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*)&current, 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;
}

View File

@ -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 );

View File

@ -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 );
}

View File

@ -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;
};

View File

@ -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;
}
}

View File

@ -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
View 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;
}

View 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
View 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