* added a class to deal with getting data from the first 8 nand blocks ( ones not belonging to the filesystem )

* really fix the bug that caused the nandBin class not to be able to open keys.bin
This commit is contained in:
giantpune@gmail.com 2010-12-14 05:58:44 +00:00
parent 1b86844eb4
commit 63f58938fe
14 changed files with 1403 additions and 6 deletions

5
.gitattributes vendored
View File

@ -3,6 +3,8 @@ WiiQt/aes.c -text
WiiQt/aes.h -text WiiQt/aes.h -text
WiiQt/ash.cpp -text WiiQt/ash.cpp -text
WiiQt/ash.h -text WiiQt/ash.h -text
WiiQt/blocks0to7.cpp -text
WiiQt/blocks0to7.h -text
WiiQt/includes.h -text WiiQt/includes.h -text
WiiQt/lz77.cpp -text WiiQt/lz77.cpp -text
WiiQt/lz77.h -text WiiQt/lz77.h -text
@ -35,6 +37,9 @@ WiiQt/wad.cpp -text
WiiQt/wad.h -text WiiQt/wad.h -text
nandExtract/black.png -text nandExtract/black.png -text
nandExtract/blue.png -text nandExtract/blue.png -text
nandExtract/boot2infodialog.cpp -text
nandExtract/boot2infodialog.h -text
nandExtract/boot2infodialog.ui -text
nandExtract/green.png -text nandExtract/green.png -text
nandExtract/grey.png -text nandExtract/grey.png -text
nandExtract/main.cpp -text nandExtract/main.cpp -text

562
WiiQt/blocks0to7.cpp Normal file
View File

@ -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( QList<QByteArray>blocks )
{
_ok = false;
if( !blocks.isEmpty() )
SetBlocks( blocks );
}
bool Blocks0to7::SetBlocks( QList<QByteArray>blocks )
{
_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<Boot2Info> 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;
}
}

92
WiiQt/blocks0to7.h Normal file
View File

@ -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( QList<QByteArray>blocks = QList<QByteArray>() );
bool SetBlocks( QList<QByteArray>blocks );
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<Boot2Info> Boot2Infos();
private:
bool _ok;
//should hold the blocks, without ecc
QList<QByteArray>blocks;
//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

View File

@ -24,7 +24,8 @@ NandBin::~NandBin()
bool NandBin::SetPath( const QString &path ) bool NandBin::SetPath( const QString &path )
{ {
fstInited = false; fstInited = false;
//nandPath = path; nandPath = path;
bootBlocks = Blocks0to7();
if( f.isOpen() ) if( f.isOpen() )
f.close(); f.close();
@ -235,7 +236,27 @@ bool NandBin::InitNand( QIcon dirs, QIcon files )
root = new QTreeWidgetItem( QStringList() << nandPath ); root = new QTreeWidgetItem( QStringList() << nandPath );
AddChildren( root, 0 ); AddChildren( root, 0 );
//ShowInfo();
//checkout the blocks for boot1&2
QList<QByteArray>blocks;
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; return true;
} }
@ -253,6 +274,22 @@ int NandBin::GetDumpType( quint64 fileSize )
return -1; return -1;
} }
const QList<Boot2Info> NandBin::Boot2Infos()
{
if( !bootBlocks.IsOk() )
return QList<Boot2Info>();
return bootBlocks.Boot2Infos();
}
quint8 NandBin::Boot1Version()
{
if( !bootBlocks.IsOk() )
return 0;
return bootBlocks.Boot1Version();
}
bool NandBin::GetKey( int type ) bool NandBin::GetKey( int type )
{ {
switch( 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 //1 key set at a time and it may be changed if some other object is decrypting something else
AesSetKey( key ); AesSetKey( key );
QByteArray ret = AesDecrypt( 0, cluster );//TODO... is IV really always 0? QByteArray ret = AesDecrypt( 0, cluster );
return ret; return ret;
} }
@ -656,3 +693,308 @@ void NandBin::ShowInfo()
<< "\nbadBlocks:" << hex << badBlocks << badOnes << "\nbadBlocks:" << hex << badBlocks << badOnes
<< "\nreserved:" << hex << reserved; << "\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
<sven> {1, {0x4a, 0x7c, 0x6f, 0x30, 0x38, 0xde, 0xea, 0x7a, 0x07, 0xd3, 0x32, 0x32, 0x02, 0x4b, 0xe9, 0x5a, 0xfb, 0x56, 0xbf, 0x65}},
<sven> {1, {0x2c, 0xdd, 0x5a, 0xff, 0xd2, 0xe7, 0x8c, 0x53, 0x76, 0x16, 0xa1, 0x19, 0xa7, 0xa2, 0xe1, 0xc5, 0x68, 0xe9, 0x1f, 0x22}},
<sven> {0, {0xf0, 0x1e, 0x8a, 0xca, 0x02, 0x9e, 0xe0, 0xcb, 0x52, 0x87, 0xf5, 0x05, 0x5d, 0xa1, 0xa0, 0xbe, 0xd2, 0xa5, 0x33, 0xfa}},
<sven> {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
*/

View File

@ -2,6 +2,7 @@
#define NANDBIN_H #define NANDBIN_H
#include "includes.h" #include "includes.h"
#include "blocks0to7.h"
struct fst_t struct fst_t
{ {
quint8 filename[ 0xc ]; quint8 filename[ 0xc ];
@ -84,6 +85,10 @@ public:
//get the fats for a given file //get the fats for a given file
const QList<quint16> GetFatsForFile( quint16 i ); const QList<quint16> GetFatsForFile( quint16 i );
const Blocks0to7 BootBlocks(){ return bootBlocks; }
const QList<Boot2Info> Boot2Infos();
quint8 Boot1Version();
private: private:
QByteArray key; QByteArray key;
@ -129,6 +134,9 @@ private:
QTreeWidgetItem *ItemFromPath( const QString &path ); QTreeWidgetItem *ItemFromPath( const QString &path );
QTreeWidgetItem *FindItem( const QString &s, QTreeWidgetItem *parent ); QTreeWidgetItem *FindItem( const QString &s, QTreeWidgetItem *parent );
//holds info about boot1 & 2
Blocks0to7 bootBlocks;
signals: signals:
//connect to these to receive messages from this object //connect to these to receive messages from this object
void SendError( QString ); void SendError( QString );

View File

@ -0,0 +1,123 @@
#include "boot2infodialog.h"
#include "ui_boot2infodialog.h"
Boot2InfoDialog::Boot2InfoDialog( QWidget *parent, QList<Boot2Info> 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;*/
}

View File

@ -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<Boot2Info> boot = QList<Boot2Info>(), quint8 boot1version = BOOT_1_UNK );
~Boot2InfoDialog();
private:
Ui::Boot2InfoDialog *ui;
QList<Boot2Info> boot2s;
//quint8 usedBlockmap[ 8 ];
private slots:
void on_comboBox_boot2_currentIndexChanged( int index );
};
#endif // BOOT2INFODIALOG_H

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Boot2InfoDialog</class>
<widget class="QDialog" name="Boot2InfoDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>428</width>
<height>310</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_boot1">
<property name="text">
<string>Unknown boot1 version</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_foundTxt">
<property name="text">
<string>Error getting boot2 info</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="comboBox_boot2">
<property name="maximumSize">
<size>
<width>100</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_boot2Version">
<property name="text">
<string>version</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_gen"/>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_tmdSig">
<property name="minimumSize">
<size>
<width>323</width>
<height>0</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_tikSig">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_contentStatus">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_bootStatus">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Boot2InfoDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Boot2InfoDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -5,19 +5,25 @@ TARGET = nandExtract
TEMPLATE = app TEMPLATE = app
SOURCES += main.cpp \ SOURCES += main.cpp \
nandwindow.cpp \ nandwindow.cpp \
../WiiQt/blocks0to7.cpp \
../WiiQt/tiktmd.cpp \
../WiiQt/nandbin.cpp \ ../WiiQt/nandbin.cpp \
../WiiQt/tools.cpp \ ../WiiQt/tools.cpp \
../WiiQt/savebanner.cpp \ ../WiiQt/savebanner.cpp \
../WiiQt/aes.c \ ../WiiQt/aes.c \
../WiiQt/sha1.c \ ../WiiQt/sha1.c \
nandthread.cpp nandthread.cpp \
boot2infodialog.cpp
HEADERS += nandwindow.h \ HEADERS += nandwindow.h \
../WiiQt/tiktmd.h \
../WiiQt/nandbin.h \ ../WiiQt/nandbin.h \
../WiiQt/tools.h \ ../WiiQt/tools.h \
nandthread.h nandthread.h \
boot2infodialog.h
FORMS += nandwindow.ui FORMS += nandwindow.ui \
boot2infodialog.ui
RESOURCES += \ RESOURCES += \
rc.qrc rc.qrc

View File

@ -43,6 +43,36 @@ bool NandThread::SetPath( const QString &path )
return nandBin.SetPath( 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<Boot2Info> NandThread::Boot2Infos()
{
if( isRunning() )
{
emit SendError( tr( "Wait till the current job is done" ) );
return QList<Boot2Info>();
}
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() QTreeWidgetItem *NandThread::GetTree()
{ {
if( isRunning() ) if( isRunning() )

View File

@ -30,6 +30,10 @@ class NandThread : public QThread
void Extract( QTreeWidgetItem *item, const QString &path ); void Extract( QTreeWidgetItem *item, const QString &path );
void ForceQuit(); void ForceQuit();
const Blocks0to7 BootBlocks();
const QList<Boot2Info> Boot2Infos();
quint8 Boot1Version();
protected: protected:
void run(); void run();

View File

@ -1,5 +1,6 @@
#include "nandwindow.h" #include "nandwindow.h"
#include "ui_nandwindow.h" #include "ui_nandwindow.h"
#include "boot2infodialog.h"
#include "../WiiQt/tools.h" #include "../WiiQt/tools.h"
NandWindow::NandWindow( QWidget *parent ) : QMainWindow( parent ), ui( new Ui::NandWindow ), nThread( this ) 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<Boot2Info> b = nThread.Boot2Infos();
if( b.isEmpty() )
{
qDebug() << "!ok";
return;
}
quint8 boot1 = nThread.Boot1Version();
Boot2InfoDialog d( this, b, boot1 );
d.exec();
}

View File

@ -51,6 +51,7 @@ public slots:
void ThreadIsDone(); void ThreadIsDone();
private slots: private slots:
void on_actionBoot2_triggered();
void on_treeWidget_currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous); void on_treeWidget_currentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous);
void on_actionShow_Usage_triggered(); void on_actionShow_Usage_triggered();
void on_actionOpen_Nand_triggered(); void on_actionOpen_Nand_triggered();

View File

@ -125,6 +125,7 @@
<string>Info</string> <string>Info</string>
</property> </property>
<addaction name="actionShow_Usage"/> <addaction name="actionShow_Usage"/>
<addaction name="actionBoot2"/>
</widget> </widget>
<addaction name="menuFile"/> <addaction name="menuFile"/>
<addaction name="menuInfo"/> <addaction name="menuInfo"/>
@ -160,6 +161,14 @@
<string>Ctrl+U</string> <string>Ctrl+U</string>
</property> </property>
</action> </action>
<action name="actionBoot2">
<property name="text">
<string>Boot2</string>
</property>
<property name="shortcut">
<string>Ctrl+B</string>
</property>
</action>
</widget> </widget>
<layoutdefault spacing="6" margin="11"/> <layoutdefault spacing="6" margin="11"/>
<resources/> <resources/>