diff --git a/.gitattributes b/.gitattributes index 0ab1321..9e4215a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,6 +3,8 @@ WiiQt/aes.c -text WiiQt/aes.h -text WiiQt/ash.cpp -text WiiQt/ash.h -text +WiiQt/blocks0to7.cpp -text +WiiQt/blocks0to7.h -text WiiQt/includes.h -text WiiQt/lz77.cpp -text WiiQt/lz77.h -text @@ -35,6 +37,9 @@ WiiQt/wad.cpp -text WiiQt/wad.h -text nandExtract/black.png -text nandExtract/blue.png -text +nandExtract/boot2infodialog.cpp -text +nandExtract/boot2infodialog.h -text +nandExtract/boot2infodialog.ui -text nandExtract/green.png -text nandExtract/grey.png -text nandExtract/main.cpp -text diff --git a/WiiQt/blocks0to7.cpp b/WiiQt/blocks0to7.cpp new file mode 100644 index 0000000..4736a64 --- /dev/null +++ b/WiiQt/blocks0to7.cpp @@ -0,0 +1,562 @@ +#include "blocks0to7.h" +#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( QListblocks ) +{ + _ok = false; + if( !blocks.isEmpty() ) + SetBlocks( blocks ); +} + +bool Blocks0to7::SetBlocks( QListblocks ) +{ + _ok = false; + this->blocks.clear(); + boot2Infos.clear(); + quint16 cnt = blocks.size(); + if( cnt != 8 ) + return false; + for( quint16 i = 0; i < cnt; i++ ) + { + if( blocks.at( i ).size() != 0x20000 ) + { + qWarning() << "Blocks0to7::SetBlocks -> block" << i << "is" << hex << blocks.at( i ).size() << "bytes"; + return false; + } + } + this->blocks = blocks; + _ok = true; + return true; +} + +quint8 Blocks0to7::Boot1Version() +{ + if( blocks.size() != 8 ) + 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" ) ) + return BOOT_1_A; + if( hash == QByteArray( "\x2c\xdd\x5a\xff\xd2\xe7\x8c\x53\x76\x16\xa1\x19\xa7\xa2\xe1\xc5\x68\xe9\x1f\x22" ) ) + return BOOT_1_B; + if( hash == QByteArray( "\xf0\x1e\x8a\xca\x02\x9e\xe0\xcb\x52\x87\xf5\x05\x5d\xa1\xa0\xbe\xd2\xa5\x33\xfa" ) ) + return BOOT_1_C; + 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 + + return BOOT_1_UNK; +} + +//really ugly thing to get the different versions of boot2. +//this doesnt take into account the possibility that boot2 is bigger and takes up more than 2 blocks +//there are 0x40 blocks in the blockmap, but only 8 are used. maybe IOS has the authority to hijack the others if +//it runs out of room here. if that ever happns, this code will become quite wrong +QList Blocks0to7::Boot2Infos() +{ + if( !boot2Infos.isEmpty() ) + { + //qDebug() << "Blocks0to7::Boot2Infos() returning data from last time"; + return boot2Infos; + } + + QList< Boot2Info > ret; + if( blocks.size() != 8 ) + return ret; + + quint16 cnt = blocks.size(); + if( cnt != 8 ) + return ret; + + //get all the blockmaps + quint16 newest = 0; + quint8 lbm[ 8 ]; + for( quint8 i = 1; i < 8; i++ ) + { + Boot2Info info = GetBlockMap( blocks.at( i ) ); + if( info.state == BOOT_2_ERROR )//this block doesnt contain a decent blockmap + continue; + + info.secondBlock = i; //this one is easy enough + //find the first block that belongs to this second one + if( i > 4 )//blocks are backwards + { + bool found = false; + for( quint8 j = 7; j > i; j++ ) + { + if( info.blockMap[ j ] )//marked bad + continue; + + if( ( ( j > i + 1 ) && !info.blockMap[ i + 1 ] )//probably a much cleaner way to write this + || ( j == 6 && !info.blockMap[ 7 ] ) ) //but basically check for a couple stupid shit in the layout that really shouldnt ever happen + break; + + info.firstBlock = j; + found = true; + break; + } + if( !found ) + continue; + } + else//blocks are forwards + { + bool found = false; + for( quint8 j = 0; j < i; j++ ) + { + if( info.blockMap[ j ] )//marked bad + continue; + + info.firstBlock = j; + found = true; + break; + } + if( !found ) + continue; + found = false; + //probably a much cleaner way to write this + //but basically check for a couple stupid shit in the layout that really shouldnt ever happen + for( quint8 j = info.firstBlock + 1; j < info.secondBlock && !found; j++ ) + { + if( info.blockMap[ j ] )//marked bad + continue; + found = true; + } + if( found )//this means there is some other good block between the first block and this one that has the block map + continue; + } + //we made it this far, it means that so far we have a correct looking blockmap that points to this copy of boot2 + if( info.generation > newest ) + { + newest = info.generation; + for( quint8 j = 0; j < 8; j++ ) + lbm[ j ] = info.blockMap[ j ]; + } + + ret << info; + } + //qDebug() << "newest blockmap" << QByteArray( (const char*)&lbm, 8 ).toHex(); + + cnt = ret.size(); + bool foundBoot = false; + bool foundBackup = false; + for( quint8 i = 0; i < cnt; i++ ) + { + ret[ i ] = CheckHashes( ret[ i ] );//check all the hashes and stuff + if( !foundBoot && !lbm[ ret.at( i ).firstBlock ] && !lbm[ ret.at( i ).secondBlock ] ) + { + //qDebug() << "copy" << i << "is used when booting"; + ret[ i ].state |= BOOT_2_USED_TO_BOOT; + //ret[ i ].usedToBoot = true; + foundBoot = true; + } + else if( lbm[ ret.at( i ).firstBlock ] || lbm[ ret.at( i ).secondBlock ] ) + { + ret[ i ].state |= BOOT_2_MARKED_BAD; + } + } + for( quint8 i = ret.size(); !foundBackup && i > 0; i-- ) + { + if( !lbm[ ret.at( i - 1 ).firstBlock ] && !lbm[ ret.at( i - 1 ).secondBlock ] && ret.at( i - 1 ).firstBlock > ret.at( i - 1 ).secondBlock ) + { + //qDebug() << "copy" << i << "is used when booting from backup"; + ret[ i - 1 ].state |= BOOT_2_BACKUP_COPY; + foundBackup = true; + if( !foundBoot ) + ret[ i - 1 ].state |= BOOT_2_USED_TO_BOOT; + } + } + boot2Infos = ret; + return ret; +} + +Boot2Info Blocks0to7::GetBlockMap( QByteArray block ) +{ + Boot2Info ret; + ret.state = BOOT_2_ERROR; + if( block.size() != 0x20000 ) + return ret; + + QByteArray first = block.mid( 0x1f800, 0x4c ); + QByteArray second = block.mid( 0x1f84c, 0x4c ); + QByteArray third = block.mid( 0x1f898, 0x4c ); + QByteArray goodOne = QByteArray( "\x26\xF2\x9A\x40\x1E\xE6\x84\xCF" ); + if( first.startsWith( goodOne ) && ( first == second || first == third ) ) + goodOne = first; + else if( second.startsWith( goodOne ) && ( second == third ) ) + goodOne = second; + else + return ret; + + ret.generation = goodOne.mid( 8, 4 ).toHex().toInt( NULL, 16 ); + for( quint8 i = 0; i < 8; i++ ) + ret.blockMap[ i ] = goodOne.at( i + 12 ); + + ret.state = BOOT_2_TIK_SIG_OK; //just assign this for now. it will be corrected before this data leaves the class + return ret; +} + +Boot2Info Blocks0to7::CheckHashes( Boot2Info info ) +{ + Boot2Info ret = info; + ret.state = BOOT_2_ERROR_PARSING; + + QByteArray stuff = blocks.at( ret.firstBlock ); + QBuffer b( &stuff ); + b.open( QIODevice::ReadOnly ); + quint32 headerSize; + quint32 dataOff; + quint32 certSize; + quint32 ticketSize; + quint32 tmdSize; + quint32 tmp; + + b.read( (char*)&tmp, 4 ); + headerSize = qFromBigEndian( tmp ); + if( headerSize != 0x20 ) + return ret; + + b.read( (char*)&tmp, 4 ); + dataOff = qFromBigEndian( tmp ); + b.read( (char*)&tmp, 4 ); + certSize = qFromBigEndian( tmp ); + b.read( (char*)&tmp, 4 ); + ticketSize = qFromBigEndian( tmp ); + b.read( (char*)&tmp, 4 ); + tmdSize = qFromBigEndian( tmp ); + b.close(); + + QByteArray tikD = stuff.mid( headerSize + certSize, ticketSize ); + QByteArray tmdD = stuff.mid( headerSize + certSize + ticketSize, tmdSize ); + Tmd t( tmdD ); + Ticket ticket( tikD ); + if( t.Tid() != 0x100000001ull || ticket.Tid() != 0x100000001ull ) + { + qWarning() << "Blocks0to7::CheckHashes -> bad TID"; + return ret; + } + ret.state = 0; + int res = check_cert_chain( tikD ); + switch( res ) + { + default: + case ERROR_SIG_TYPE: + case ERROR_SUB_TYPE: + case ERROR_RSA_TYPE_UNKNOWN: + case ERROR_CERT_NOT_FOUND: + ret.state = BOOT_2_ERROR; + qWarning() << "check_cert_chain( tikD ):" << res; + return ret; + break; + case ERROR_RSA_TYPE_MISMATCH: + case ERROR_RSA_HASH: + ret.state = BOOT_2_BAD_SIGNATURE; + //qWarning() << "check_cert_chain( tikD ):" << res; + return ret; + break; + case ERROR_RSA_FAKESIGNED: + ret.state |= BOOT_2_TIK_FAKESIGNED; + break; + case ERROR_SUCCESS: + ret.state |= BOOT_2_TIK_SIG_OK; + break; + } + res = check_cert_chain( tmdD ); + switch( res ) + { + default: + case ERROR_SIG_TYPE: + case ERROR_SUB_TYPE: + case ERROR_RSA_TYPE_UNKNOWN: + case ERROR_CERT_NOT_FOUND: + ret.state = BOOT_2_ERROR; + //qWarning() << "check_cert_chain( tikD ):" << res; + return ret; + break; + case ERROR_RSA_TYPE_MISMATCH: + case ERROR_RSA_HASH: + ret.state = BOOT_2_BAD_SIGNATURE; + //qWarning() << "check_cert_chain( tikD ):" << res; + return ret; + break; + case ERROR_RSA_FAKESIGNED: + { + ret.state |= BOOT_2_TMD_FAKESIGNED; + if( tmdD.contains( "BM1.1" ) ) + ret.version = BOOTMII_11; + else if( tmdD.contains( "BM1.3" ) ) + ret.version = BOOTMII_13; + else + ret.version = BOOTMII_UNK; + } + break; + case ERROR_SUCCESS: + { + ret.state |= BOOT_2_TMD_SIG_OK; + ret.version = t.Version(); + } + break; + } + + //now decrypt boot2 and check the hash ( only checking 1 content because thats all there is ) + stuff += blocks.at( ret.secondBlock ); + + AesSetKey( ticket.DecryptedKey() ); + QByteArray decD = AesDecrypt( 0, stuff.mid( dataOff, RU( 0x40, t.Size( 0 ) ) ) ); + decD.resize( t.Size( 0 ) ); + QByteArray realHash = GetSha1( decD ); + if( realHash != t.Hash( 0 ) ) + ret.state |= BOOT_2_BAD_CONTENT_HASH; + 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; + } +} diff --git a/WiiQt/blocks0to7.h b/WiiQt/blocks0to7.h new file mode 100644 index 0000000..13fcb93 --- /dev/null +++ b/WiiQt/blocks0to7.h @@ -0,0 +1,92 @@ +#ifndef BLOCKS0TO7_H +#define BLOCKS0TO7_H + +#include "includes.h" +//for now im creating this class to handle getting info about the first 8 blocks of nand +//im not sure what all it should be doing, so it may become a clusterfuck +//for now, just pass it a list of 8 bytearrays, each 0x20000 bytes. then check IsOk(), and then you can check boot1version and Boot2Infos() + +//! currently it checks the hash of boot1 block against known versions, checks blocks 1-7 for valid blockmaps, +//! looks for copies of boot2 in those blocks, then finds the tmd, ticket and encrypted data. next it checks that +//! the tid in the tmd&ticket is correct, and verifies all the signatures and decrypts the actual content and checks its hash +//! finally it looks at the most recent valid blockmap and determines which copy would be used when booting & when booting from backup +//! Because it does all this, the first time requesting this data will cause the current thread to hang +//! ( checking the signatures takes the longest ) after the first time, the list returned by Boot2Infos() is remembered +//! and it will be returned any time after that when this function is called + +enum //returned by Boot1Version(). theres no official names for these, so ill just stick with lettering them +{ + BOOT_1_UNK = 0, + BOOT_1_A, + BOOT_1_B, + BOOT_1_C, + BOOT_1_D +}; + +#define BOOTMII_UNK 0x2110 +#define BOOTMII_11 0x2111 +#define BOOTMII_13 0x2113 +//TODO: what other versions of bootmii are there? + +//enum used to convey the state of each copy of boot2 on a nand +enum +{ + BOOT_2_ERROR_PARSING = -3, //error parsing the thing, cant really tell more info, since it wasnt parsed :P + BOOT_2_BAD_SIGNATURE = -2, //RSA signature of boot2 doesnt pass memcmp or strncmp + BOOT_2_ERROR = -1, //some other error + BOOT_2_BAD_CONTENT_HASH = 1, //sha1 of the content did not match what was in the TMD + BOOT_2_TMD_FAKESIGNED = 2, //tmd RSA seems fine, it passed strncmp but not memcmp ( probably bootmii ) + BOOT_2_TMD_SIG_OK = 4, //tmd RSA seems fine, signature passed memcmp ( probably official shit ) + BOOT_2_TIK_FAKESIGNED = 8, //tik RSA seems fine, it passed strncmp but not memcmp - not really sure why this would happen + BOOT_2_TIK_SIG_OK = 0x10, //tik RSA seems fine, signature passed memcmp + BOOT_2_MARKED_BAD = 0x20, //the most recent blockmap says this copy of boot2 falls on bad blocks + BOOT_2_USED_TO_BOOT = 0x40, //this is the copy used when trying to boot the wii + BOOT_2_BACKUP_COPY = 0x80 //this is the copy used when the first copy failed to boot +}; + + +struct Boot2Info //this little guy is just some container to hold the information about the state of boot2 in these blocks +{ //the above values are used for the "state". it will either be < 0 or it will be any of the other values |'d together + //if the state is above 0, version will either be the version from the TMD or one of the BOOTMII_... values + + quint8 firstBlock; //block that contains the header + quint8 secondBlock; //block that contains the blockmap + quint32 generation; //generation of the blockmap + quint8 blockMap[ 8 ]; //blockmap found on secondBlock ( only the first 8 blocks, as the rest belong to the encrypted FS ) + qint32 state; //information about this copy of boot2 + quint16 version; //whatversion of boot2 this is +}; + + +class Blocks0to7 +{ +public: + Blocks0to7( QListblocks = QList() ); + bool SetBlocks( QListblocks ); + bool IsOk(){ return _ok; } + + //check which version of boot1 we have + quint8 Boot1Version(); + + //get a list containing info for each copy of boot2 on the given blocks + QList Boot2Infos(); + +private: + bool _ok; + //should hold the blocks, without ecc + QListblocks; + + //after teh first time Boot2Infos() is called, store the result here so any subsequent calls can just return this for speed + QList< Boot2Info > boot2Infos; + + //this one doesnt really return a complete info, it only gets the block map from a block + //and returns it in an incomplete Boot2Info + Boot2Info GetBlockMap( QByteArray block ); + + //checks the hashes and whatnot in a copy of boot2 + //returns an incomplete Boot2Info + Boot2Info CheckHashes( Boot2Info info ); + +}; + +#endif // BLOCKS0TO7_H diff --git a/WiiQt/nandbin.cpp b/WiiQt/nandbin.cpp index 452deee..14b697b 100755 --- a/WiiQt/nandbin.cpp +++ b/WiiQt/nandbin.cpp @@ -24,7 +24,8 @@ NandBin::~NandBin() bool NandBin::SetPath( const QString &path ) { fstInited = false; - //nandPath = path; + nandPath = path; + bootBlocks = Blocks0to7(); if( f.isOpen() ) f.close(); @@ -235,7 +236,27 @@ bool NandBin::InitNand( QIcon dirs, QIcon files ) root = new QTreeWidgetItem( QStringList() << nandPath ); AddChildren( root, 0 ); - //ShowInfo(); + + //checkout the blocks for boot1&2 + QListblocks; + for( quint16 i = 0; i < 8; i++ ) + { + QByteArray block; + for( quint16 j = 0; j < 8; j++ ) + { + block += GetCluster( ( i * 8 ) + j, false ); + } + if( block.size() != 0x4000 * 8 ) + { + qDebug() << "wrong block size" << i; + return false; + } + blocks << block; + } + + if( !bootBlocks.SetBlocks( blocks ) ) + return false; + return true; } @@ -253,6 +274,22 @@ int NandBin::GetDumpType( quint64 fileSize ) return -1; } +const QList NandBin::Boot2Infos() +{ + if( !bootBlocks.IsOk() ) + return QList(); + + return bootBlocks.Boot2Infos(); +} + +quint8 NandBin::Boot1Version() +{ + if( !bootBlocks.IsOk() ) + return 0; + + return bootBlocks.Boot1Version(); +} + bool NandBin::GetKey( int type ) { switch( type ) @@ -471,7 +508,7 @@ QByteArray NandBin::GetCluster( quint16 cluster_entry, bool decrypt ) //1 key set at a time and it may be changed if some other object is decrypting something else AesSetKey( key ); - QByteArray ret = AesDecrypt( 0, cluster );//TODO... is IV really always 0? + QByteArray ret = AesDecrypt( 0, cluster ); return ret; } @@ -656,3 +693,308 @@ void NandBin::ShowInfo() << "\nbadBlocks:" << hex << badBlocks << badOnes << "\nreserved:" << hex << reserved; } + + +/* + structure of blocks 0 - 7 + + block 0 ( boot 1 ) + + 0 - 0x4320 + a bunch of encrypted gibberish. +the rest of the block as all 0s + + +sha1 of the whole block is + + 2cdd5affd2e78c537616a119a7a2e1c568e91f22 with boot1b + f01e8aca029ee0cb5287f5055da1a0bed2a533fa with boot1c + + {1, {0x4a, 0x7c, 0x6f, 0x30, 0x38, 0xde, 0xea, 0x7a, 0x07, 0xd3, 0x32, 0x32, 0x02, 0x4b, 0xe9, 0x5a, 0xfb, 0x56, 0xbf, 0x65}}, + {1, {0x2c, 0xdd, 0x5a, 0xff, 0xd2, 0xe7, 0x8c, 0x53, 0x76, 0x16, 0xa1, 0x19, 0xa7, 0xa2, 0xe1, 0xc5, 0x68, 0xe9, 0x1f, 0x22}}, + {0, {0xf0, 0x1e, 0x8a, 0xca, 0x02, 0x9e, 0xe0, 0xcb, 0x52, 0x87, 0xf5, 0x05, 0x5d, 0xa1, 0xa0, 0xbe, 0xd2, 0xa5, 0x33, 0xfa}}, + {0, {0x8d, 0x9e, 0xcf, 0x2f, 0x8f, 0x98, 0xa3, 0xc1, 0x07, 0xf1, 0xe5, 0xe3, 0x6f, 0xf2, 0x4d, 0x57, 0x7e, 0xac, 0x36, 0x08}}, + + + block 1 ( boot2 ) //just guessing at these for now + + u32 0x20 //header size + u32 0xf00 //data offset + u32 0xa00 //cert size + u32 0x2a4 //ticket size + u32 0x208 //tmd size + u32[ 3 ] 0s //padding till 0x20 + +0x20 - 0x9f0 +cert +root ca00000001 +Root-CA00000001 CP00000004 +Root-CA00000001 XS00000003 + +round up to 0xa20 +ticket for boot2 + +0xcc4 +tmd? + +0xecc - 0xf00 +gibberish padding + +0xf00 +start of boot2 contents? ( mine is 0x00023E91 bytes in the TMD for v2 ) +there is room for 0x1F100 bytes of it left in this block with 0x4D91 leftover + + +block 2 +bunch of gibberish till 0x4da0 +probably the rest of the contents of boot2, padded with gibberish + + +0x4da0 +0s till 0x5000 + +0x5000 - 0x1f800 +bunch of 0xffff +maybe this is untouched nand. never been written to + + + +0x1f800 - 0x1f8e4 blockmap? + +26F29A401EE684CF0000000201000000 +00000000010101010101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 +010101010101010101010101 + +26F29A401EE684CF0000000201000000 +00000000010101010101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 +010101010101010101010101 + +26F29A401EE684CF0000000201000000 +00000000010101010101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 +010101010101010101010101 + +0x1f8e4 - 0x20000 ( end of block ) +more 0s + + +block 2 ( looks a lot like block 1 ) +wad type header again with the same values + +same cert looking doodad again +same ticket again + +tmd is different ( zero'd RSA ) +1 content, 0x00027B5D bytes + + + + + +block 4 ( the rest of bootmii ) +0 - 0x8a60 +gibberish - rest of bootmii content still encrypted + +0x8a60 - 0x1f800 +0s + +0x1f800 +26F29A401EE684CF0000000501010100 +00000000010101010101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 +010101010101010101010101 + +26F29A401EE684CF0000000501010100 +00000000010101010101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 +010101010101010101010101 + +26F29A401EE684CF0000000501010100 +00000000010101010101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 +010101010101010101010101 + +then 0s till 0x20000 ( end of block ) + + + +block 5 +all 0xff + +block 6 +identical to block 2 except this only difference is the 0x02 is 0x03 +26F29A401EE684CF0000000301000000 +00000000010101010101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 +010101010101010101010101 + +26F29A401EE684CF0000000301000000 +00000000010101010101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 +010101010101010101010101 + +26F29A401EE684CF0000000301000000 +00000000010101010101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 +010101010101010101010101 + + +block 7 +identical to block 1 + + + + + + +/////////// +nand #2 ( no bootmii ) +/////////// + +block 0 ( boot 1c ) +gibberish till 0x43f0. then 0s for the rest of the block + +block 1 +same wad header as before, with the same values +cert doodad and ticket are the same +tmd is v4 and the content is 0x00027BE8 bytes + +block 2 +0 - 0x8af0 +the rest of the content from boot2 ( padded to 0x40 ) + +0s till 0x9000 +0xff till 0x18f00 + +26F29A401EE684CF0000000201000000 +00000000010101010101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 +010101010101010101010101 + +26F29A401EE684CF0000000201000000 +00000000010101010101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 +010101010101010101010101 + +26F29A401EE684CF0000000201000000 +00000000010101010101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 +010101010101010101010101 + + + +block 3 - all 0xff +block 4 - all 0xff +block 5 - all 0xff + +block 6 - identical to block 2 except the blockmap ( generation 3 ? ) + +26F29A401EE684CF0000000301000000 +00000000010101010101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 +010101010101010101010101 + +26F29A401EE684CF0000000301000000 +00000000010101010101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 +010101010101010101010101 + +26F29A401EE684CF0000000301000000 +00000000010101010101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 +010101010101010101010101 + +block 7 matches block 1 + + + + +///////////// +nand #3 +///////////// + +block 2 +26F29A401EE684CF +00000004 +01000000000000000101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 + +block 4 +26F29A401EE684CF +0000000F +01010100000000000101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 + +block 6 +26F29A401EE684CF +00000005 +01000000000000000101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 + + + + + + + + + + + + + + + + + + + + + + + + + + + +26F29A401EE684CF +00000002 + +01000000000000000101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 + + +26F29A401EE684CF +00000005 +01010100000000000101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 +01010101010101010101010101010101 + + + */ diff --git a/WiiQt/nandbin.h b/WiiQt/nandbin.h index 2a19781..37e95fa 100755 --- a/WiiQt/nandbin.h +++ b/WiiQt/nandbin.h @@ -2,6 +2,7 @@ #define NANDBIN_H #include "includes.h" +#include "blocks0to7.h" struct fst_t { quint8 filename[ 0xc ]; @@ -84,6 +85,10 @@ public: //get the fats for a given file const QList GetFatsForFile( quint16 i ); + const Blocks0to7 BootBlocks(){ return bootBlocks; } + const QList Boot2Infos(); + quint8 Boot1Version(); + private: QByteArray key; @@ -129,6 +134,9 @@ private: QTreeWidgetItem *ItemFromPath( const QString &path ); QTreeWidgetItem *FindItem( const QString &s, QTreeWidgetItem *parent ); + //holds info about boot1 & 2 + Blocks0to7 bootBlocks; + signals: //connect to these to receive messages from this object void SendError( QString ); diff --git a/nandExtract/boot2infodialog.cpp b/nandExtract/boot2infodialog.cpp new file mode 100644 index 0000000..5ad1c5d --- /dev/null +++ b/nandExtract/boot2infodialog.cpp @@ -0,0 +1,123 @@ +#include "boot2infodialog.h" +#include "ui_boot2infodialog.h" + +Boot2InfoDialog::Boot2InfoDialog( QWidget *parent, QList boot, quint8 boot1version ) : QDialog(parent), ui(new Ui::Boot2InfoDialog) +{ + ui->setupUi( this ); + ui->label_boot2Version->clear(); + + switch( boot1version ) + { + case BOOT_1_A: + ui->label_boot1->setText( "Boot1 A (vulnerable)"); + break; + case BOOT_1_B: + ui->label_boot1->setText( "Boot1 B (vulnerable)"); + break; + case BOOT_1_C: + ui->label_boot1->setText( "Boot1 C (fixed)"); + break; + case BOOT_1_D: + ui->label_boot1->setText( "Boot1 D (fixed)"); + break; + } + + quint8 c = boot.size(); + if( !c ) + return; + + ui->label_foundTxt->setText( QString( "Found %1 copies of boot2" ).arg( c ) ); + boot2s = boot; + + for( quint8 i = 0; i < c; i++ ) + { + QString entry = QString( "%1 - %2" ).arg( boot.at( i ).firstBlock ).arg( boot.at( i ).secondBlock ); + ui->comboBox_boot2->addItem( entry ); + if( boot.at( i ).state & BOOT_2_USED_TO_BOOT ) + { + ui->comboBox_boot2->setCurrentIndex( i ); + } + } +} + +Boot2InfoDialog::~Boot2InfoDialog() +{ + delete ui; +} + +void Boot2InfoDialog::on_comboBox_boot2_currentIndexChanged( int index ) +{ + if( index > boot2s.size() ) + return; + Boot2Info bi = boot2s.at( index ); + ui->label_boot2Version->clear(); + + ui->lineEdit_contentStatus->clear(); + ui->lineEdit_bootStatus->clear(); + ui->lineEdit_tikSig->setText( tr( "Not Bootable" ) ); + ui->lineEdit_tmdSig->clear(); + ui->lineEdit_gen->setText( tr( "Generation %1").arg( bi.generation ) ); + + + if( bi.state == BOOT_2_ERROR_PARSING || bi.state == BOOT_2_ERROR ) + ui->lineEdit_contentStatus->setText( tr( "Error parsing boot2" ) ); + else if( bi.state == BOOT_2_BAD_SIGNATURE ) + ui->lineEdit_contentStatus->setText( tr( "Bad RSA Signature" ) ); + else if( bi.state == BOOT_2_BAD_CONTENT_HASH ) + ui->lineEdit_contentStatus->setText( tr( "Content hash doesn't match TMD" ) ); + else if( bi.state == BOOT_2_ERROR_PARSING ) + ui->lineEdit_contentStatus->setText( tr( "Error parsing boot2" ) ); + else + { + + if( bi.state & BOOT_2_MARKED_BAD ) + ui->lineEdit_bootStatus->setText( tr( "Marked as bad blocks" ) ); + else if( bi.state & BOOT_2_USED_TO_BOOT ) + ui->lineEdit_bootStatus->setText( tr( "Used for booting" ) + " " );//in case the backup copy is used for booting + else if( bi.state & BOOT_2_BACKUP_COPY ) + ui->lineEdit_bootStatus->setText( ui->lineEdit_bootStatus->text() + tr( "Backup copy" ) ); + + + ui->lineEdit_contentStatus->setText( tr( "Content Sha1 matches TMD" ) ); + + if( bi.state & BOOT_2_TMD_FAKESIGNED ) + ui->lineEdit_tmdSig->setText( tr( "TMD is fakesigned" ) ); + else if( bi.state & BOOT_2_TMD_SIG_OK ) + ui->lineEdit_tmdSig->setText( tr( "TMD officially signed" ) ); + else + ui->lineEdit_tmdSig->setText( tr( "Error checking TMD" ) ); + + if( bi.state & BOOT_2_TIK_FAKESIGNED ) + ui->lineEdit_tikSig->setText( tr( "Ticket is fakesigned" ) ); + else if( bi.state & BOOT_2_TIK_SIG_OK ) + ui->lineEdit_tikSig->setText( tr( "Ticket officially signed" ) ); + else + ui->lineEdit_tikSig->setText( tr( "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( tr( "Version %1" ).arg( bi.version ) ); + break; + } + ui->label_boot2Version->setText( ver ); + } + + + /*QString bls = QByteArray( (const char*)&bi.blockMap, 8 ).toHex(); + QString str = QString( "copy:%1\tblocks%2 & %3 gen: %4 state: %5\n%7").arg( index ).arg( bi.firstBlock ) + .arg( bi.secondBlock ).arg( bi.generation, 8, 16, QChar( '0' ) ) + .arg( bi.state, 8, 16, QChar( ' ' ) ).arg( bls ); + + qDebug() << str;*/ +} diff --git a/nandExtract/boot2infodialog.h b/nandExtract/boot2infodialog.h new file mode 100644 index 0000000..5da8248 --- /dev/null +++ b/nandExtract/boot2infodialog.h @@ -0,0 +1,30 @@ +#ifndef BOOT2INFODIALOG_H +#define BOOT2INFODIALOG_H + +#include "../WiiQt/includes.h" +#include "../WiiQt/blocks0to7.h" + +namespace Ui { + class Boot2InfoDialog; +} + +class Boot2InfoDialog : public QDialog +{ + Q_OBJECT + +public: + explicit Boot2InfoDialog( QWidget *parent = 0, QList boot = QList(), quint8 boot1version = BOOT_1_UNK ); + ~Boot2InfoDialog(); + +private: + Ui::Boot2InfoDialog *ui; + + QList boot2s; + + //quint8 usedBlockmap[ 8 ]; + +private slots: + void on_comboBox_boot2_currentIndexChanged( int index ); +}; + +#endif // BOOT2INFODIALOG_H diff --git a/nandExtract/boot2infodialog.ui b/nandExtract/boot2infodialog.ui new file mode 100644 index 0000000..6b5cf2e --- /dev/null +++ b/nandExtract/boot2infodialog.ui @@ -0,0 +1,170 @@ + + + Boot2InfoDialog + + + + 0 + 0 + 428 + 310 + + + + Dialog + + + + + + Unknown boot1 version + + + + + + + Error getting boot2 info + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 100 + 16777215 + + + + + + + + version + + + + + + + + + + + + + 323 + 0 + + + + true + + + + + + + true + + + + + + + true + + + + + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Boot2InfoDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Boot2InfoDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/nandExtract/nandExtract.pro b/nandExtract/nandExtract.pro index 0d95f71..00dc94f 100755 --- a/nandExtract/nandExtract.pro +++ b/nandExtract/nandExtract.pro @@ -5,19 +5,25 @@ TARGET = nandExtract TEMPLATE = app SOURCES += main.cpp \ nandwindow.cpp \ + ../WiiQt/blocks0to7.cpp \ + ../WiiQt/tiktmd.cpp \ ../WiiQt/nandbin.cpp \ ../WiiQt/tools.cpp \ ../WiiQt/savebanner.cpp \ ../WiiQt/aes.c \ ../WiiQt/sha1.c \ - nandthread.cpp + nandthread.cpp \ + boot2infodialog.cpp HEADERS += nandwindow.h \ + ../WiiQt/tiktmd.h \ ../WiiQt/nandbin.h \ ../WiiQt/tools.h \ - nandthread.h + nandthread.h \ + boot2infodialog.h -FORMS += nandwindow.ui +FORMS += nandwindow.ui \ + boot2infodialog.ui RESOURCES += \ rc.qrc diff --git a/nandExtract/nandthread.cpp b/nandExtract/nandthread.cpp index 0b24e07..1b1adbf 100644 --- a/nandExtract/nandthread.cpp +++ b/nandExtract/nandthread.cpp @@ -43,6 +43,36 @@ bool NandThread::SetPath( const QString &path ) return nandBin.SetPath( path ); } +const Blocks0to7 NandThread::BootBlocks() +{ + if( isRunning() ) + { + emit SendError( tr( "Wait till the current job is done" ) ); + return Blocks0to7(); + } + return nandBin.BootBlocks(); +} + +const QList NandThread::Boot2Infos() +{ + if( isRunning() ) + { + emit SendError( tr( "Wait till the current job is done" ) ); + return QList(); + } + return nandBin.Boot2Infos(); +} + +quint8 NandThread::Boot1Version() +{ + if( isRunning() ) + { + emit SendError( tr( "Wait till the current job is done" ) ); + return 0; + } + return nandBin.Boot1Version(); +} + QTreeWidgetItem *NandThread::GetTree() { if( isRunning() ) diff --git a/nandExtract/nandthread.h b/nandExtract/nandthread.h index 0f7f3d3..a8ceae5 100644 --- a/nandExtract/nandthread.h +++ b/nandExtract/nandthread.h @@ -30,6 +30,10 @@ class NandThread : public QThread void Extract( QTreeWidgetItem *item, const QString &path ); void ForceQuit(); + const Blocks0to7 BootBlocks(); + const QList Boot2Infos(); + quint8 Boot1Version(); + protected: void run(); diff --git a/nandExtract/nandwindow.cpp b/nandExtract/nandwindow.cpp index 4a5303a..10c2f4d 100755 --- a/nandExtract/nandwindow.cpp +++ b/nandExtract/nandwindow.cpp @@ -1,5 +1,6 @@ #include "nandwindow.h" #include "ui_nandwindow.h" +#include "boot2infodialog.h" #include "../WiiQt/tools.h" NandWindow::NandWindow( QWidget *parent ) : QMainWindow( parent ), ui( new Ui::NandWindow ), nThread( this ) @@ -405,3 +406,17 @@ void NandWindow::on_treeWidget_currentItemChanged( QTreeWidgetItem* current, QTr } +//get boot2 info and show it as a dialog +void NandWindow::on_actionBoot2_triggered() +{ + QList b = nThread.Boot2Infos(); + if( b.isEmpty() ) + { + qDebug() << "!ok"; + return; + } + quint8 boot1 = nThread.Boot1Version(); + + Boot2InfoDialog d( this, b, boot1 ); + d.exec(); +} diff --git a/nandExtract/nandwindow.h b/nandExtract/nandwindow.h index 9261268..ec1211b 100755 --- a/nandExtract/nandwindow.h +++ b/nandExtract/nandwindow.h @@ -51,6 +51,7 @@ public slots: void ThreadIsDone(); private slots: + void on_actionBoot2_triggered(); void on_treeWidget_currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous); void on_actionShow_Usage_triggered(); void on_actionOpen_Nand_triggered(); diff --git a/nandExtract/nandwindow.ui b/nandExtract/nandwindow.ui index ae47522..65b770f 100755 --- a/nandExtract/nandwindow.ui +++ b/nandExtract/nandwindow.ui @@ -125,6 +125,7 @@ Info + @@ -160,6 +161,14 @@ Ctrl+U + + + Boot2 + + + Ctrl+B + +