#!perl -w # (C) 2003-2007 Willem Jan Hengeveld # Web: http://www.xs4all.nl/~itsme/ # http://wiki.xda-developers.com/ # # $Id$ # # these are some card id's i often use: # cc ymm. ssssssss rr pppppppppp aaaa mm # 55 4500 accf6300 55 3832314453 4453 03 'UE...c.U821DSDS.' .. my minisd # 3f 5100 09531f40 03 424d383231 4e49 18 '?Q..S.@.BM821NI.' .. my kingston # 3f 3c00 65ba4764 07 3832314453 4d54 02 '?<.e.Gd.821DSMT.' .. my daneelec # 00 4200 0f588942 41 4238323153 4150 01 .... bjorns sdcard # .. the first byte seems dependend on who has read it. # 0x55 - smartphone # 0x3f - pocketpc # 0x00 - bootloader <--- you have to set the first byte to this for the goldcard to work. # # ... this id can be found in the magician rom 1.12: # at offset 0x0849d4d8 ... 'CMDS'+0x48 # or at offset 0x0849cfc5 ... 'SBDS'+0x75 # examples: # # perl typhoonnbfdecode.pl sd.img # # will try to determine what type of sdcard sd.img is, tell you the # seclevel, if it can find it. # # to create a 'goldcard': # # use 'magic=xxx' to prevent the sdcard from being seen as containing a flash image. # # perl typhoonnbfdecode.pl -d sd80.img -p magic=xxx -p cardid=004500accf6300553832314453445303 -p keys=typhoon -p seclevel=0 # then psdwrite f: sd80.img # # or ... for a 'e0' based goldcard. # perl typhoonnbfdecode.pl -d sde0.img -p magic=xxx -p cardid=004500accf6300553832314453445303 -p cid=CDL___02 # # perl typhoonnbfdecode.pl -d sd80.img -p cardid=00510009531f4003424d3832314e4918 -p keys=magician -p seclevel=0 # ... then psdwrite f: sd80.img 0x80 -s 0x80 # todo: add option to create pocketpc version of header. # # for the magician: 'task 32' shows the securitylevel. # sub usage { "Usage: typhoonnbfdecode.pl filename\n" ." lists contents of filename\n" ."\n" ." or typhoonnbfdecode.pl [-i] [-v] -x infile\n" ." extract modules\n" ." -i decode as sd header image\n" ." -t file is tornado nbf, with large header\n" ." -v also list headers\n" ." -s convert SPL to memory image\n" ."\n" ." or typhoonnbfdecode.pl [-r module=file] [-p param=value] -c outfile\n" ." create new nbf\n" ." -s convert SPL memory image to nbf image\n" ." -t file is tornado nbf, with large header\n" ." -r specify the modules to read, module can be one of:\n" ." os, ipl, spl, splash or gsm\n" ." -p specify the header values, param can be one of:\n" ." device, language, magic, operator, flags, version or blversion\n" ."\n" ." or typhoonnbfdecode.pl [-r module=file] [-p param=value] -d outfile\n" ." create sdcard image\n" ." -r specify the modules to read, module can be one of:\n" ." os, ipl, spl, splash or gsm\n" ." -p specify the header values, param can be one of:\n" ." docuniqueid, blversion, cid, seclevel, timestart, timeend\n" ." keys, cardid\n" } my $verbose= 0; package SeclevelDecoder; use XdaDevelopers::NbfUtils; our @EXPORT= qw(decode_seclevel encode_seclevel decode_cid encode_cid decode_times encode_times); # these keys are usually found near the end of the copy-data section # stored as 3 lists of 8 dword values. our %deskeys= ( alpine=>[ pack("C*", map { $_-0x60 } ( 0xD0, 0xBD, 0xDB, 0x83, 0xD1, 0x92, 0xB5, 0x8A )), pack("C*", map { $_-0x60 } ( 0xBE, 0x99, 0xBE, 0x83, 0x88, 0x8C, 0x92, 0x8C )), pack("C*", map { $_-0x60 } ( 0xA1, 0xD0, 0x85, 0xDE, 0x97, 0xA0, 0x9C, 0xAE )), ], himalaya=>[ pack("C*", map { $_-0x60 } ( 0x85, 0xA1, 0xC7, 0x92, 0xC7, 0xB7, 0xD0, 0x84 )), pack("C*", map { $_-0x60 } ( 0x95, 0x88, 0xA5, 0xD6, 0xAF, 0xBE, 0x99, 0x8C )), pack("C*", map { $_-0x60 } ( 0xD2, 0xA7, 0x8A, 0x93, 0x94, 0x94, 0xA0, 0xB4 )), ], harrier=>[ pack("C*", map { $_-0x60 } ( 0xC8, 0x90, 0xC6, 0x83, 0x93, 0x85, 0xA9, 0xA0 )), pack("C*", map { $_-0x60 } ( 0x98, 0x84, 0x83, 0xB0, 0xB7, 0x92, 0x8A, 0x91 )), pack("C*", map { $_-0x60 } ( 0x8A, 0x97, 0xA5, 0xD6, 0x92, 0x88, 0x8C, 0xC7 )), ], blueangel=>[ pack("C*", map { $_-0x60 } ( 0x81, 0x8B, 0xC2, 0x93, 0x86, 0x92, 0x89, 0xC1 )), pack("C*", map { $_-0x60 } ( 0xBE, 0xAF, 0xC5, 0x93, 0x83, 0xC8, 0x90, 0xD1 )), pack("C*", map { $_-0x60 } ( 0x8C, 0x92, 0x97, 0xC6, 0x86, 0xD7, 0xB0, 0xA0 )), ], magician=>[ #pack("C*", map { $_-0x60 } ( 0x9d, 0xa1, 0x86, 0x93, 0xcf, 0x85, 0x9e, 0x99 )), pack("C*", map { $_-0x60 } ( 0x81, 0xA1, 0x86, 0x93, 0xCF, 0x85, 0x9E, 0x99 )), pack("C*", map { $_-0x60 } ( 0x91, 0xB6, 0xC7, 0x8A, 0xA0, 0x99, 0x83, 0x8C )), pack("C*", map { $_-0x60 } ( 0x83, 0x83, 0x98, 0x90, 0xA7, 0xD0, 0xBB, 0xDB )), ], typhoon=>[ pack("C*", map { $_-0x60 } ( 0xBE, 0xC8, 0x88, 0xAB, 0xCF, 0xA0, 0x90, 0x81 )), pack("C*", map { $_-0x60 } ( 0x94, 0xA2, 0xC5, 0x8B, 0x97, 0xA7, 0x88, 0xB1 )), pack("C*", map { $_-0x60 } ( 0xDE, 0xAC, 0xC4, 0xCE, 0x85, 0x95, 0x83, 0xB5 )), ], tornado=>[ # same as charmer, prodigy, wizard, prophet, startrek, herald pack("C*", map { $_-0x60 } ( 0xA0, 0xAD, 0xC4, 0xA3, 0xD3, 0xC5, 0xA8, 0xA9 )), pack("C*", map { $_-0x60 } ( 0xB4, 0xCF, 0xA5, 0xD2, 0xB3, 0xA1, 0xC5, 0xAF )), pack("C*", map { $_-0x60 } ( 0xC1, 0xAE, 0xD2, 0xA5, 0xA8, 0xD4, 0xB2, 0xD6 )), ], universal=>[ pack("C*", map { $_-0x60 } ( 0xDD, 0xD2, 0x97, 0x83, 0x88, 0x8B, 0xC9, 0x9E )), pack("C*", map { $_-0x60 } ( 0xBE, 0xC6, 0x94, 0xA0, 0xD6, 0x8A, 0xC1, 0x83 )), pack("C*", map { $_-0x60 } ( 0xB5, 0xCE, 0x93, 0xBB, 0x8C, 0xC5, 0x84, 0xD3 )), ], # these are unverified: maybe the key order is wrong. charmer=>[ pack("C*", map { $_-0x60 } ( 0xA0, 0xAD, 0xC4, 0xA3, 0xD3, 0xC5, 0xA8, 0xA9 )), pack("C*", map { $_-0x60 } ( 0xB4, 0xCF, 0xA5, 0xD2, 0xB3, 0xA1, 0xC5, 0xAF )), pack("C*", map { $_-0x60 } ( 0xC1, 0xAE, 0xD2, 0xA5, 0xA8, 0xD4, 0xB2, 0xD6 )), ], prodigy=>[ pack("C*", map { $_-0x60 } ( 0xA0, 0xAD, 0xC4, 0xA3, 0xD3, 0xC5, 0xA8, 0xA9 )), pack("C*", map { $_-0x60 } ( 0xB4, 0xCF, 0xA5, 0xD2, 0xB3, 0xA1, 0xC5, 0xAF )), pack("C*", map { $_-0x60 } ( 0xC1, 0xAE, 0xD2, 0xA5, 0xA8, 0xD4, 0xB2, 0xD6 )), ], apache=>[ pack("C*", map { $_-0x60 } ( 0xDB, 0x93, 0xBE, 0xA1, 0x8B, 0xDD, 0xC3, 0xDE )), pack("C*", map { $_-0x60 } ( 0xD0, 0x89, 0xD3, 0x90, 0xD4, 0x83, 0xC8, 0xD2 )), pack("C*", map { $_-0x60 } ( 0xC1, 0x84, 0xC8, 0xA5, 0x85, 0xD4, 0x92, 0xC3 )), ], nasa=>[ pack("C*", map { $_-0x60 } ( 0xa0, 0xa8, 0xca, 0x92, 0xa8, 0xb4, 0xd0, 0x84 )), pack("C*", map { $_-0x60 } ( 0x93, 0x88, 0xa8, 0xd4, 0xd5, 0x8b, 0x98, 0x8b )), pack("C*", map { $_-0x60 } ( 0xb4, 0xd1, 0x8a, 0x86, 0x90, 0x86, 0x90, 0xd4 )), ], vivida=>[ pack("C*", map { $_-0x60 } ( 0xa5, 0x92, 0x85, 0x88, 0xd1, 0x94, 0xde, 0xc9 )), pack("C*", map { $_-0x60 } ( 0x83, 0xc7, 0x98, 0xd4, 0x88, 0x86, 0x93, 0xc5 )), pack("C*", map { $_-0x60 } ( 0x92, 0xb2, 0x8a, 0xc7, 0xc8, 0x93, 0xb1, 0xbf )), ], diamond=>[ pack("C*", map { $_-0x60 } ( 0xC6, 0x90, 0x85, 0xD5, 0x93, 0xA9, 0xD3, 0x9B )), pack("C*", map { $_-0x60 } ( 0xD4, 0xBB, 0x8C, 0xB6, 0xC7, 0xD0, 0x94, 0x81 )), pack("C*", map { $_-0x60 } ( 0xA7, 0xAF, 0xD4, 0xB4, 0x92, 0xC4, 0xDE, 0x84 )), ], photon=>[ pack("C*", map { $_-0x60 } ( 0xA5, 0xA6, 0x94, 0xA4, 0x98, 0x96, 0x95, 0xA4 )), pack("C*", map { $_-0x60 } ( 0x96, 0x95, 0x95, 0x97, 0x98, 0xA5, 0x95, 0x91 )), pack("C*", map { $_-0x60 } ( 0xA3, 0xA3, 0x92, 0x96, 0xA1, 0x91, 0x96, 0x96 )), ], ); # for sd card headers our @ofs80list= ( 1, 4, 9, 15, 19, 21, 28, 33, 34, 45, 46, 57, 58, 59, 60, 73); our @ofse0list= ( 1, 3, 4, 5, 7, 10, 11, 13, 14, 17, 18, 19, 21, 25, 29, 30 ); ################################################################# # sd card header functions sub reversestring { return pack("C*", reverse unpack("C*", shift)); } # in: 96 bytes hdr 0x80-0xdf # out: 16 bytes sub unpack_80 { my $hdr= shift; #printf("packed hdr80 = %s\n", unpack("H*", $hdr)); my $data= "\x00" x 16; for (my $i=0 ; $i<16 ; $i++) { substr($data, 15-$i, 1)= substr($hdr, $ofs80list[$i]+4, 1); } my $calccrc= XdaDevelopers::NbfUtils::crc32(substr($hdr, 0, 0x5c), 0); my $storedcrc= unpack("V", substr($hdr, 0x5c, 4)); if ($calccrc!=$storedcrc) { printf("hdr80: crc mismatch: stored=%08lx calc=%08lx\n", $storedcrc, $calccrc); } return $data; } # in: 16 bytes # out: 96 bytes sub pack_80 { my $data= shift; my $hdr= "SA00" . "\x00" x 0x58; for (my $i=0 ; $i<16 ; $i++) { substr($hdr, $ofs80list[$i]+4, 1)= substr($data, 15-$i, 1); } #printf("packed hdr80 = %s\n", unpack("H*", $hdr)); my $calccrc= XdaDevelopers::NbfUtils::crc32($hdr, 0); return $hdr.pack("V", $calccrc); } # in: 32 bytes hdr 0xe0-0xff # out: 16 bytes sub unpack_e0 { my $hdr= shift; my $data= "\x00" x 16; for (my $i=0 ; $i<16 ; $i++) { substr($data, 15-$i,1) = substr($hdr, $ofse0list[$i], 1); } return $data; } # in: 16 bytes # out: 32 bytes sub pack_e0 { my $data= shift; my $hdr= "\x00" x 32; for (my $i=0 ; $i<16 ; $i++) { substr($hdr, $ofse0list[$i], 1)= substr($data, 15-$i, 1); } return $hdr; } # in: 2 dwords # out: 16 bytes sub pack_100 { return pack("V*", 0, $_[0], $_[1], 0); } # in: 16 bytes hdr 0x100-0x10f # out: 2 dwords : tstart, tend sub unpack_100 { my @vals= unpack("V*", shift); return ($vals[1] ,$vals[2]); } sub decode_times { return unpack_100(shift); } sub encode_times { return pack_100(@_); } sub decode_seclevel { my ($sd80, $keys, $cardid)= @_; my $hdr80= unpack_80($sd80); my $crc_cardid= defined $cardid ? XdaDevelopers::NbfUtils::crc32($cardid, 0) : undef; for my $keytype ( defined $keys ? $keys : keys %deskeys) { for (my $keyid=0 ; $keyid<=2 ; $keyid++) { my $hdrlo= substr($hdr80, 0, 8); my $hdrhi= substr($hdr80, 8, 8); my $dechi= DesCrypt::des_decrypt($hdrlo, $deskeys{$keytype}[$keyid]); my $enclo= DesCrypt::des_encrypt($hdrhi, $deskeys{$keytype}[$keyid]); printf("%-15s %d %s:%s - %s:%s\n", $keytype, $keyid, unpack("H*", $hdrlo), unpack("H*", $hdrhi), unpack("H*", $enclo), unpack("H*", $dechi)) if ($verbose>1); if ($dechi eq $hdrhi) { my $val= unpack("V", $dechi); if (defined $crc_cardid && $val == $crc_cardid) { return $keyid; } printf("seclevel probably %s: %d, crc(cardid)=%08lx\n", $keytype, $keyid, $val); } } } # todo: match with crc of card-id return 0xff; } sub encode_seclevel { my ($seclevel, $keys, $cardid)= @_; if ($seclevel>=0 && $seclevel<=2) { my $cardidcrc= XdaDevelopers::NbfUtils::crc32($cardid, 0); printf("cardcrc=%08lx\n", $cardidcrc); my $hdrhi= pack("VV", $cardidcrc, 0); my $hdrlo= DesCrypt::des_encrypt($hdrhi, $deskeys{$keys}[$seclevel]); return pack_80($hdrlo.$hdrhi); } else { return pack_80("\x00" x 16); } } sub decode_cid { my ($sde0, $cardid)= @_; my $hdre0= unpack_e0($sde0); my $decryptede0= reversestring(DesCrypt::des_decrypt16($hdre0, reversestring($cardid))); #printf("dec-e0= %s\n", unpack("H*", $decryptede0)); return substr($decryptede0, 0, 8); } sub encode_cid { my ($cid, $cardid)= @_; return pack_e0(DesCrypt::des_encrypt16(reversestring(pack("a16", $cid)), reversestring($cardid))); } sub test { my $tv= pack("C*", 0..15); print "not " if ($tv ne unpack_e0(pack_e0($tv))); print "ok 1\n"; print "not " if ($tv ne unpack_80(pack_80($tv))); print "ok 2\n"; } package DesCrypt; use integer; use Crypt::DES; our @EXPORT= qw(des_encrypt16 des_decrypt16 des_encrypt des_decrypt); sub des_encrypt16 { my ($data, $key)= @_; my $result= ""; for (my $i=0 ; $inew($key); my $result= ""; for (my $i=0 ; $iencrypt(substr($data,$i,8)); } return $result; } sub des_decrypt { my ($data, $key)= @_; #$key &= "\xfe" x 8; my $des= Crypt::DES->new($key); my $result= ""; for (my $i=0 ; $idecrypt(substr($data,$i,8)); } return $result; } package main; use strict; use IO::File; use Getopt::Long; use XdaDevelopers::NbfUtils; #use DataDecoder; #use CrcCalc; # DataDecoder::encodedatachunk -> XdaDevelopers::NbfUtils::encodenbfchunk # CrcCalc::crc32 -> XdaDevelopers::NbfUtils::crc32 # my $doExtract= 0; my $splspecial= 0; my $doCreateNbf= 0; my $doCreateSd= 0; my $g_is_sdimage= 0; my $g_tornado= 0; my $g_prodigy= 0; my $g_nbfheadersize= 0x200; my $g_maxentries= 6; my $g_entrysize= 28; my $g_use_file_anyway= 0; my %inputfiles; my %properties; my $g_override_sdofs; # rid range # 1 91000000 91100000 spl # 2 82040000 84040000 os # 3 96000000 96290000 gsm # 4 92000000 92020000 splash # 5 90000000 90000800 ipl sub validate_extrom { 1; } sub validate_xsvg { 1; } sub validate_htclogo { 1; } # note: a 'MFG' section, is loaded in memory at 0x8c080000, and executed. # note: my %entryinfo= ( ipl=> { sdidx=>0, sdofs=>0x80000000, length=>0x0000800, nbfidx=>2, nbfofs=>0x90000000, nbfname=>"IPL", verify=>\&validate_ipl }, spl=> { sdidx=>1, sdofs=>0x80000800, length=>0x00c0000, nbfidx=>1, nbfofs=>0x91000000, nbfname=>"SPL", verify=>\&validate_spl }, splash=> { sdidx=>2, sdofs=>0x800C0800, length=>0x0040000, nbfidx=>5, nbfofs=>0x92000000, nbfname=>"Splash Screen", verify=>\&validate_splash }, os=> { sdidx=>3, sdofs=>0x04E3D4C0, length=>0x2600000, nbfidx=>4, nbfofs=>0x82040000, nbfname=>"OS" , verify=>\&validate_os }, extrom=> { sdidx=>9, sdofs=>0x0743d4c0, length=>0x0a00000, nbfidx=>6, nbfofs=>0x9B000000, nbfname=>"EXTROM" , verify=>\&validate_extrom }, xsvf=> { sdidx=>10,sdofs=>0xffffffff, length=>0x0a00000, nbfidx=>7, nbfofs=>0x9C000000, nbfname=>"XSVF" , verify=>\&validate_xsvg}, htclogo=>{ sdidx=>11,sdofs=>0x07e3d4c0, length=>0x0a00000, nbfidx=>8, nbfofs=>0x9D000000, nbfname=>"HTCLOGO" , verify=>\&validate_htclogo }, gsm=> { sdidx=>4, sdofs=>0x800E0800, length=>0x0280000, nbfidx=>3, nbfofs=>0x96000000, nbfname=>"GSM", verify=>\&validate_gsm }, ffs=> { sdidx=>5, sdofs=>0x8C080000, length=>0x0200000 }, #all => { sdidx=>6, }, mfg=> { sdidx=>7, sdofs=>0x802E4800, length=>0x0060000 }, gsmdata=>{ sdidx=>8, sdofs=>0x802E0800, length=>0x0004000, verify=>\&validate_gsmdata }, ); # -rs source nbf - base new nbf on this file. #-rm : GetOptions( "s"=> \$splspecial, "x"=> \$doExtract, "c"=> \$doCreateNbf, "d"=> \$doCreateSd, "i"=> \$g_is_sdimage, "v+"=> \$verbose, "t"=> \$g_tornado, "tp"=> \$g_prodigy, "h=s"=> sub { $g_nbfheadersize= eval($_[1]); }, "r=s"=> \%inputfiles, "p=s"=> \%properties, "o=s"=> sub { $g_override_sdofs= eval($_[1]); }, "f" => \$g_use_file_anyway, ) or die usage(); set_globalparams(); sub firstdef { for (@_) { return $_ if defined $_; } return undef; } sub set_globalparams { $g_nbfheadersize= 0x400 if ($g_tornado); $g_nbfheadersize= 0x800 if ($g_prodigy); $g_maxentries= 18 if ($g_tornado); $g_maxentries= 28 if ($g_prodigy); $g_entrysize= 36 if ($g_prodigy); } if (@ARGV!=1) { die usage(); } verifyproperties(%properties) or die usage(); sub verifyproperties { my %props= @_; for (qw(magic device version language blversion flags operator docuniqueid cardid cid seclevel timestart timeemd keys)) { delete $props{$_}; } if (keys %props) { print "unknown properties: ", join(", ", keys %props), "\n"; return 0; } return 1; } $properties{docuniqueid}= pack("H*", $properties{docuniqueid}) if (exists $properties{docuniqueid}); $properties{cardid}= pack("H*", $properties{cardid}) if (exists $properties{cardid}); if ($doExtract) { ##################################################################### my $ifn= shift; my $ifh= IO::File->new($ifn, "r") or die "$ifn: $!\n"; binmode $ifh; my $hdr= readheader($ifh); printheader($hdr) if ($verbose); for my $entry (@{$hdr->{entries}}) { if ($splspecial && lc($entry->{name}) eq "spl") { savespl($ifh, $entry); } else { decodenbfdata($ifh, $entry); } } $ifh->close(); } elsif ($doCreateNbf) { ##################################################################### my $ofn= shift; my $ofh= IO::File->new($ofn, "w") or die "opening output file $ofn\n"; binmode $ofh; my %inputinfo; # read file data, calc checksums, duplicate blocks for SPL. $inputinfo{$_}= ProcessInputFile($_, $inputfiles{$_}) for keys %inputfiles; my @nonnbf= grep { !exists $entryinfo{$_}{nbfidx} } keys %inputinfo; if (@nonnbf) { die "These sections cannot be in a NBF file: @nonnbf\n"; } my $hdr= MakeNbfHeader(\%properties, \%inputinfo); printheader($hdr); my $hdrdata= PackNbfHeader($hdr); my $enchdr= encodeheader($hdrdata); $ofh->print($enchdr); $ofh->print(pack("V", XdaDevelopers::NbfUtils::crc32($hdrdata, 0))); for (@{$hdr->{entries}}) { encodenbfdata($ofh, $_); } $ofh->close(); } elsif ($doCreateSd) { ##################################################################### my $ofn= shift; my $ofh= IO::File->new($ofn, "w") or die "opening output file $ofn\n"; binmode $ofh; my %inputinfo; # read file data, calc checksums, duplicate blocks for SPL. $inputinfo{$_}= ProcessInputFile($_, $inputfiles{$_}) for keys %inputfiles; my $hdr= MakeSdHeader(\%properties, \%inputinfo); printheader($hdr); my $hdrdata= PackSdHeader($hdr); my $enchdr= encodeheader(substr($hdrdata, 0, 0x7c)); $ofh->print($enchdr); $ofh->print(pack("V", XdaDevelopers::NbfUtils::crc32(substr($enchdr,0,0x7c), 0))); if (exists $properties{seclevel} && exists $properties{cardid} && exists $properties{keys}) { die "unknown key type $properties{keys}\n" if (!exists $deskeys{$properties{keys}}); $ofh->print(SeclevelDecoder::encode_seclevel($properties{seclevel}, $properties{keys}, $properties{cardid})); } else { $ofh->print("\x00" x 0x60); } if (exists $properties{cid} && exists $properties{cardid}) { $ofh->print(SeclevelDecoder::encode_cid($properties{cid}, $properties{cardid})); } else { $ofh->print("\x00" x 0x20); } $ofh->print(SeclevelDecoder::encode_times($properties{timestart} || -1, $properties{timeend} || -1)); $ofh->print("\x00" x 0xf0); for (@{$hdr->{entries}}) { encodenbfdata($ofh, $_); } $ofh->close(); } else { ##################################################################### my $ifn= shift; my $ifh= IO::File->new($ifn, "r") or die "$ifn: $!\n"; binmode $ifh; my $hdr= readheader($ifh); printheader($hdr); $ifh->close(); } exit(0); ############################################################################## sub ProcessInputFile { my ($name, $filename)= @_; if (!exists $entryinfo{$name}) { die "invalid section name $name\n"; } my $data; my $ifh= IO::File->new($filename, "r") or die "opening input file $name - $filename: $!\n"; binmode $ifh; $ifh->read($data, -s $filename) or die "reading input file $name - $filename: $!\n"; $ifh->close(); printf("read %08lx for %s from %s\n", length($data), $name, $filename); if ($splspecial && lc($name) eq "spl") { $data= CreateSplImage($data); printf("spl image: %08lx bytes\n", length($data)); } if (exists $entryinfo{$name}{verify}) { if (!$entryinfo{$name}{verify}(\$data) && !$g_use_file_anyway) { exit(1); } } return { name=>$name, data=>$data, offset=> $entryinfo{$name}{nbfofs}, # $entry->{offset} || sdoffset=> $g_override_sdofs || $entryinfo{$name}{sdofs}, # $entry->{offset} || length=> length($data), # $entry->{length} || checksum=> XdaDevelopers::NbfUtils::crc32($data, 0), }; } sub CreateSplImage { my $data= shift; my $spldata= ""; for (my $ofs= 0 ; $ofs < length($data) ; $ofs += 512) { my $sector= substr($data, $ofs, 512); if (length($sector)<512) { $sector .= "\x00" x (512-length($sector)); } $spldata .= $sector; $spldata .= $sector; } return $spldata; } sub MakeNbfHeader { my ($props, $info)= @_; return { magic=> firstdef($props->{magic}, "HTC"), device=> firstdef($props->{device}, "SP3i"), version=> firstdef($props->{version}, "1.0.0.0"), language=> firstdef($props->{language}, "ENGLISH"), blversion=> firstdef($props->{blversion}, "1.0.0.0"), flags=> firstdef($props->{flags}, 0x11), operator=> firstdef($props->{operator}, "CDL___02"), entrycount=> scalar keys %$info, entries=> [ map { $info->{$_} } CreateNbfEntryNamelist($info) ], $g_tornado?(flags2=> firstdef($props->{flags2}, 0x0c)):(), $g_tornado?(rest2=> firstdef($props->{rest2}, "")):(), }; } sub CreateNbfEntryNamelist { my ($info)= @_; return sort { $entryinfo{$a}{nbfidx} <=> $entryinfo{$b}{nbfidx} || $a cmp $b } keys %$info; } sub MakeSdHeader { my ($props, $info)= @_; my $id= 6; if ((scalar keys %$info)==1) { my ($entry)= values %$info; $id= $entryinfo{$entry->{name}}{sdidx}; } my $magic= sprintf('HTC$TYPH-%d%d%d', $id, $id, $id); return { magic=> firstdef($props->{magic}, $magic), blversion=> firstdef($props->{blversion}, "1.0.0.0"), docuniqueid=> firstdef($props->{docuniqueid}, ""), entrycount=> scalar keys %$info, entries=> [ map { $info->{$_} } CreateSdEntryNamelist($info) ], }; } sub CreateSdEntryNamelist { my ($info)= @_; return sort { $entryinfo{$a}{sdidx} <=> $entryinfo{$b}{sdidx} || $a cmp $b } keys %$info; } ############################################################################## sub encodenbfdata { my ($ofh, $entry)= @_; printf("enc: %08lx %08lx %08lx %s\n", $entry->{offset} || $entry->{sdoffset}, $entry->{length}, $entry->{checksum}, $entry->{name}); my $index=0; for (my $ofs= 0 ; $ofs < $entry->{length} ; $ofs+=0x178) { my $wanted= $entry->{length}-$ofs; $wanted = 0x178 if ($wanted>0x178); $ofh->print(XdaDevelopers::NbfUtils::encodenbfchunk(substr($entry->{data}, $ofs, $wanted), $wanted!=0x178?$wanted : $index)); $index++; } } sub makeentrysavename { my ($entry)= @_; my $name= $entry->{name}; $name =~ s/\s.*//; # remove ' screen' from 'splash screen' return sprintf("%08lx-%s.nb", $entry->{offset} || $entry->{sdoffset}, $name); } sub decodenbfdata { my ($ifh, $entry)= @_; my $crc= 0; my $fn= makeentrysavename($entry); my $oh= IO::File->new($fn, "w") or die "$fn: $!\n"; binmode $oh; my $index=0; for (my $ofs= 0 ; $ofs < $entry->{length} ; $ofs+=0x178) { my $wanted= $entry->{length}-$ofs; $wanted = 0x178 if ($wanted>0x178); my $data; $ifh->read($data, $wanted); my $decodeddata= XdaDevelopers::NbfUtils::decodenbfchunk($data, length($data)!=0x178?length($data) : $index); $crc= XdaDevelopers::NbfUtils::crc32($decodeddata, $crc); $oh->print($decodeddata); $index++; } $oh->close(); if ($crc != $entry->{checksum}) { warn sprintf("crc mismatch: calced=%08lx stored=%08lx %s\n", $crc, $entry->{checksum}, $entry->{name}); } printf("extracted %s\n", $entry->{name}); } sub findnonnulldw { my $data= shift; my $xofs; for (my $i= 0 ; $inew($fn, "w") or die "$fn: $!\n"; binmode $oh; my $outchunksize= 0x200; my $outchunk=""; my $index=0; for (my $ofs= 0 ; $ofs < $entry->{length} ; $ofs+=0x178) { my $wanted= $entry->{length}-$ofs; $wanted = 0x178 if ($wanted>0x178); my $data; $fh->read($data, $wanted); $outchunk .= XdaDevelopers::NbfUtils::decodenbfchunk($data, $wanted!=0x178?$wanted :$index); if (length($outchunk)>=2*$outchunksize) { if (substr($outchunk,0,$outchunksize) ne substr($outchunk,$outchunksize,$outchunksize)) { warn_of_diff($oh->tell(), substr($outchunk,0,$outchunksize), substr($outchunk,$outchunksize,$outchunksize)); } $oh->print(substr($outchunk,0,$outchunksize)); $outchunk = substr($outchunk, 2*$outchunksize); } $index++; } if (length($outchunk)>0) { if (substr($outchunk,0,$outchunksize) ne substr($outchunk,$outchunksize,$outchunksize)) { warn_of_diff($oh->tell(), substr($outchunk,0,$outchunksize), substr($outchunk,$outchunksize,$outchunksize)); } $oh->print(substr($outchunk,0,$outchunksize)); $outchunk=""; } $oh->close(); } sub warn_of_diff { my ($ofs, $s1, $s2)= @_; my $diff= $s1 ^ $s2; if ($diff =~ /\x00+/) { warn sprintf("SPL difference at %08lx\n", $ofs+length($&)); } } sub skipentry { my ($fh, $entry)= @_; $fh->seek($entry->{length}, SEEK_CUR); } ############################################################################## sub printheader { my $hdr= shift; for my $key (sort keys %$hdr) { next if ($key eq "entries"); if ($key eq "hdrcrc" || $key eq "unknown") { printf("%-10s 0x%08lx\n", $key, $hdr->{$key}); } elsif ($key =~ /^sd[0-9a-f]+$/ || $key eq "cardid" || $key eq "docuniqueid" || ($key eq "blversion" && $hdr->{$key} !~ /^[[:print:]]{4}/)) { printf("%-10s %s\n", $key, unpack("H*", $hdr->{$key})); } else { printf("%-10s %s\n", $key, $hdr->{$key}); } } print "\n"; for my $entry (@{$hdr->{entries}}) { printheaderentry($entry); } } sub printheaderentry { my $entry= shift; printf("%08lx %08lx %08lx %s\n", $entry->{offset} || $entry->{sdoffset}, $entry->{length}, $entry->{checksum}, $entry->{name}); } sub hexdump { join "\n", map { my ($a, $b)=($_,$_); $a=~s/[\x00-\x1f\x7f-]/./g; sprintf("%s - %s", unpack("H*", $b), $a); } map { substr($_[0],$_*16,16) } (0..(length($_[0])-1)/16); } ############################################################################## sub readheader { my $ifh= shift; $ifh->seek(0, SEEK_SET); my $enchdrdata; $ifh->read($enchdrdata, $g_nbfheadersize) or die "reading header: $!\n"; if ($g_is_sdimage || $enchdrdata =~ /^HIMALAYAS/ || $enchdrdata =~ /^MAGICIAN/ || $enchdrdata =~ /^UNIVERSAL/) { my $hdr= {}; if (exists $properties{cardid}) { $hdr->{cid}= SeclevelDecoder::decode_cid(substr($enchdrdata, 0xe0, 0x20), $properties{cardid}); } $hdr->{seclevel}= SeclevelDecoder::decode_seclevel(substr($enchdrdata, 0x80, 0x60), $properties{keys}, $properties{cardid}); ($hdr->{timestart}, $hdr->{timeend}) = SeclevelDecoder::decode_times(substr($enchdrdata, 0x100, 0x10)); $hdr->{magic}= substr($enchdrdata, 0, 16); return $hdr; } if (!$g_prodigy && !$g_tornado) { if (substr($enchdrdata, 0xf8, 1) eq "\x02") { $g_prodigy= 1; set_globalparams(); } elsif (substr($enchdrdata, 0xf8, 1) eq "\x03") { $g_tornado= 1; set_globalparams(); } $ifh->seek(0, SEEK_SET); $ifh->read($enchdrdata, $g_nbfheadersize) or die "reading header: $!\n"; } my $hdrdata= decodeheader($enchdrdata) or die "error decoding header\n"; print hexdump($hdrdata)."\n" if ($verbose); if ($hdrdata =~ /^HTC\$/) { # is sd card image my $storedcrc= unpack("V", substr($enchdrdata, 0x7c,4)); my $calccrc= XdaDevelopers::NbfUtils::crc32(substr($enchdrdata,0,0x7c), 0); my $hdr= unpacksdheader(substr($hdrdata, 0, 0x7c)); if ($storedcrc!=$calccrc) { warn sprintf("!!! storedcrc=%08lx calculatedcrc=%08lx\n", $storedcrc, $calccrc); } $hdr->{hdrcrc}= $storedcrc; if (exists $properties{cardid}) { $hdr->{cid}= SeclevelDecoder::decode_cid(substr($enchdrdata, 0xe0, 0x20), $properties{cardid}); } $hdr->{seclevel}= SeclevelDecoder::decode_seclevel(substr($enchdrdata, 0x80, 0x60), $properties{keys}, $properties{cardid}); ($hdr->{timestart}, $hdr->{timeend}) = SeclevelDecoder::decode_times(substr($enchdrdata, 0x100, 0x10)); return $hdr; } elsif ($hdrdata =~ /^HTC\x00/) { # is NBF file my $hdrsize= $g_nbfheadersize-4; my $storedcrc= unpack("V", substr($enchdrdata, $hdrsize, 4)); my $calccrc= XdaDevelopers::NbfUtils::crc32(substr($hdrdata,0,$hdrsize), 0); if ($storedcrc!=$calccrc) { warn sprintf("!!! storedcrc=%08lx calculatedcrc=%08lx\n", $storedcrc, $calccrc); } my $hdr= unpacknbfheader(substr($hdrdata,0,$hdrsize)); $hdr->{hdrcrc}= $storedcrc; if ($hdrsize==0x7fc) { printf("figuring out the 0x800 sized nbf header crc's:\n"); printf("stored: enc: %08lx %08lx, dec: %08lx %08lx\n", unpack("V*", substr($enchdrdata, 0x3fc, 4).substr($enchdrdata, 0x7fc, 4).substr($hdrdata, 0x3fc, 4).substr($hdrdata, 0x7fc, 4))); printf("calced: enc: %08lx %08lx/%08lx, dec: %08lx %08lx/%08lx\n", XdaDevelopers::NbfUtils::crc32(substr($enchdrdata,0,0x3fc), 0), XdaDevelopers::NbfUtils::crc32(substr($enchdrdata,0,0x3fc).substr($enchdrdata,0x400,0x3fc), 0), XdaDevelopers::NbfUtils::crc32(substr($enchdrdata,0,0x7fc), 0), XdaDevelopers::NbfUtils::crc32(substr($hdrdata,0,0x3fc), 0), XdaDevelopers::NbfUtils::crc32(substr($hdrdata,0,0x3fc).substr($hdrdata,0x400,0x3fc), 0), XdaDevelopers::NbfUtils::crc32(substr($hdrdata,0,0x7fc), 0)); } if ($hdrsize==0x3fc) { printf("figuring out the 0x400 sized nbf header crc's:\n"); printf("stored: enc: %08lx, dec: %08lx\n", unpack("V*", substr($enchdrdata, 0x3fc, 4).substr($hdrdata, 0x3fc, 4))); printf("calced: enc: %08lx, dec: %08lx\n", XdaDevelopers::NbfUtils::crc32(substr($enchdrdata,0,0x3fc), 0), XdaDevelopers::NbfUtils::crc32(substr($hdrdata,0,0x3fc), 0)); } return $hdr; } else { die sprintf("unknown header format:\nencoded: %s\ndecoded: %s\n", unpack("H*", substr($enchdrdata, 0, 16)), unpack("H*", substr($hdrdata, 0, 16))); } } # in : 508 bytes encoded header # out: 508 bytes packed header sub decodeheader { my $data= shift; my @xor_bytes= ( 0x27, 0xBC, 0x9A, 0xD5, 0x0A, 0x93, 0x6D, 0x02, 0x2C, 0x61, 0x0E, 0x6E, 0xBA, 0x51, 0x09, 0x99, 0x29, 0x57, 0xDE, 0x54, 0x94, 0x2B, 0x6F, 0x2A, 0x35, 0xA5, 0x63, 0x59, 0x12, 0x0B, 0x63, 0xE3, 0xDB, 0x77, 0x6E, 0x3E, 0xA4, 0xB8, 0xDC, 0x79, 0x1E, 0xE9, 0xD5, 0x80, 0xD6, 0xC9, 0xBB, 0xDB, 0x93, 0x06, 0xD7, 0xCD, 0x49, 0x2D, 0xDA, 0x15, 0x07, 0x2D, 0xB8, 0xB7, 0x99, 0x95, 0xBA, 0xCF, ); my @bytes = unpack("C*", $data); my $decoded= pack("C*", map { (($bytes[$_]-$_)&0xff) ^ $xor_bytes[$_%@xor_bytes] } 0..$#bytes); return $decoded; } # in : 508 bytes packed header # out: 512 bytes encoded header, with crc sub encodeheader { my $data= shift; my @xor_bytes= ( 0x27, 0xBC, 0x9A, 0xD5, 0x0A, 0x93, 0x6D, 0x02, 0x2C, 0x61, 0x0E, 0x6E, 0xBA, 0x51, 0x09, 0x99, 0x29, 0x57, 0xDE, 0x54, 0x94, 0x2B, 0x6F, 0x2A, 0x35, 0xA5, 0x63, 0x59, 0x12, 0x0B, 0x63, 0xE3, 0xDB, 0x77, 0x6E, 0x3E, 0xA4, 0xB8, 0xDC, 0x79, 0x1E, 0xE9, 0xD5, 0x80, 0xD6, 0xC9, 0xBB, 0xDB, 0x93, 0x06, 0xD7, 0xCD, 0x49, 0x2D, 0xDA, 0x15, 0x07, 0x2D, 0xB8, 0xB7, 0x99, 0x95, 0xBA, 0xCF, ); my @bytes = unpack("C*", $data); my $encoded= pack("C*", map { (($bytes[$_]^$xor_bytes[$_%@xor_bytes])+$_)&0xff } 0..$#bytes); return $encoded; } # typhoon/wizard header: # 00000000: "HTC" : magic # 00000010: "SP3i" : devicename # 00000020: "2.3.33.21" : version # 00000030: "English" : language # 00000040: "2.5.33.105" : bootloader version # # sectionname offset length checksum # +00 +10 +14 +18 # 00000050: "SPL" 91000000 000c0000 9df75da4 # 0000006c: "Splash Screen" 92000000 00020000 03bb7528 # 00000088: "OS" 82040000 01b00000 96541d10 # 000000a4: "IPL" 90000000 00000800 b4d5d4b6 # 000000c0: "GSM" 96000000 00290000 033cd072 # 000000dc: # # 000000f8: 00000005 : section count # 000000fc: 00000011 : flags # 00000100: "CDL___02" : operator # 000001fc: ???????? : header checksum # # # tornado / charmer header: # 00000000: 'HTC' : magic # 00000010: 'PM200' : devicename # 00000020: '1.12.2.23' : version # 00000030: 'WWE' : language # 00000040: '1.12' : bootloader version # # 000000e4: 00000002 # 000000f0: 00000000 # 000000f4: 00000001 # 000000f8: 0000000c : header version .. 0x03 undecoded # 000000fc: 00000011 : flags # 00000100: 'CHRTMO04' # # 00000200: 00000007 : section count # sectionname offset length checksum # +00 +10 +14 +18 # 00000204: 'IPL' 90000000 00000800 e15a2c56 # 00000220: 'SPL' 91000000 000c0000 630025ca # 0000023c: 'GSM' 96000000 00280000 b046b792 # 00000258: 'OS' 80040000 03900000 94c807c1 # 00000274: 'Splash Screen' 92000000 00030000 50a894fa # 00000290: 'Extension ROM' 9b000000 00a00000 9b4d2e2a # 000002ac: 'HTC Logo' 9d000000 00010000 093cf0a5 # 000003fc: e1a9b01d : header checksum # # prodigy roms: # # 00000000: "HTC" : magic # 00000010: "Prodigy" : devicename # 00000020: "2.16.9.101" : version # 00000030: "WWE" : language # 00000040: "2.16" : bootloader version # # 000000e4: 00000002 # 000000f0: 00000000 # 000000f4: 00000000 # 000000f8: 0000000d : header version .. 0x02 undecoded # 000000fc: 00000011 : flags # 00000100: "WIZCDL01" # 00000108: 00000000 # 0000010c: 00000400 # 00000110: 00000003 # # 000003f0: 000000000010740479de4d12ffdae785 # 00000400: 00000009 : section count # # sectionname offset length checksum # +00 +10 +14 +18 +1c +20 # 00000404: "SPL" 91000000 000c0000 9045fa6b 00000800 00000001 # 00000428: "IPL" 90000000 00000800 a82409ed 000c0800 00000001 # 0000044c: "SPL" 91000000 000c0000 42fea745 000c1000 00000002 # 00000470: "IPL" 90000000 00000800 daebde07 00181000 00000002 # 00000494: "GSM" 96000000 00280000 a98a36ad 00181800 00000000 # 000004b8: "OS" 80040000 03900000 1c1b62d4 00401800 00000000 # 000004dc: "Splash Screen" 92000000 00030000 653e635d 03d01800 00000000 # 00000500: "Extension ROM" 9b000000 00a00000 af99467b 03d31800 00000000 # 00000524: "HTC Logo" 9d000000 00010000 5c96dd41 04731800 00000000 # # 000007fc: 8af57e15 # NOTE: empty space is filled with zeroes. # # -- wizard rom -- # 00000000: "HTC" # 00000010: "WIZA100" # 00000020: "2.25.11.102" # 00000030: "WWE" # 00000040: "2.25" # # 000000ec: 00000002 # # 000000f8: 0000000d # 000000fc: 00000011 # 00000100: "WIZCNG01" # 00000108: 00000000 # 0000010c: 00000400 # 00000110: 00000003 # # 000003f0: 00000000 04c41000 092d84ff a9622737 # 00000400: 0000000a # # +00 +10 +14 +18 +1c +20 # 00000404: "SPL" 91000000 000c0000 aeadef04 00000800 00000001 # 00000428: "IPL" 90000000 00000800 ef71cb5c 000c0800 00000001 # 0000044c: "SPL" 91000000 000c0000 fab92414 000c1000 00000002 # 00000470: "IPL" 90000000 00000800 56a219b2 00181000 00000002 # 00000494: "GSM" 96000000 00280000 0d9ff8b2 00181800 00000000 # 000004b8: "OS" 80040000 03900000 16e108c7 00401800 00000000 # 000004dc: "Splash Screen" 92000000 00030000 8c533191 03d01800 00000000 # 00000500: "Extension ROM" 9b000000 00a00000 7aabe7ae 03d31800 00000001 # 00000524: "Extension ROM" 9b000000 00500000 9ab321db 04731800 00000002 # 00000548: "HTC Logo" 9d000000 00010000 093cf0a5 04c31800 00000000 # # # 000007fc: 785af598 sub unpacknbfheader { my $hdrdata=shift; my %hdr; my $list; my $rest; my $rest2=""; ( $hdr{magic}, # a16 $hdr{device}, # a16 $hdr{version}, # a16 $hdr{language}, # a16 $hdr{blversion}, # a16 $list, # a168 -> contains 2 flags as well $hdr{entrycount}, # V -> flags2 $hdr{flags}, # V $hdr{operator}, # A8 $rest, # a244 ) = unpack("A16A16A16A16A16a168VVA8a244", $hdrdata); # large 'A' : remove trailing NULs if ($g_tornado) { # tornado/charmer header $rest2= $list; $hdr{flags2}= $hdr{entrycount}; ( $hdr{entrycount}, $list )= unpack("Va*", substr($hdrdata, 0x200)); } elsif ($g_prodigy) { # prodigy header $rest2= $list; $hdr{flags2}= $hdr{entrycount}; ( $hdr{entrycount}, $list )= unpack("Va*", substr($hdrdata, 0x400)); } if ($hdr{entrycount}>=0 && $hdr{entrycount}<=$g_maxentries) { for (my $i=0 ; $i<$hdr{entrycount} ; $i++) { push @{$hdr{entries}}, parsenbfhdrentry(substr($list,$i*$g_entrysize,$g_entrysize)); } if ($hdr{entrycount}<$g_maxentries && substr($list, $hdr{entrycount}*$g_entrysize) !~ /^\x00*$/) { $hdr{extralist}= substr($list, $hdr{entrycount}*$g_entrysize); } } else { warn "possibly invalid nbf header - entrycount invalid\n"; } if ($rest !~ /^\x00+$/) { $hdr{rest}= $rest; } if ($rest2 !~ /^\x00+$/) { $hdr{rest2}= $rest2; } return \%hdr; } sub parsenbfhdrentry { my $data= shift; my %entry; if (length($data)==36) { ( $entry{name}, $entry{offset}, $entry{length}, $entry{checksum}, $entry{flags}, $entry{id}, ) = unpack("A16V5", $data); $entry{name} .= "_".$entry{id}; } else { ( $entry{name}, $entry{offset}, $entry{length}, $entry{checksum} ) = unpack("A16VVV", $data); } return \%entry; } sub PackNbfHeader { my $hdr= shift; # small 'a' : pad with NULs if ($g_tornado) { return pack("a16a16a16a16a16a168VVa16a236Va508", $hdr->{magic}, # a16 $hdr->{device}, # a16 $hdr->{version}, # a16 $hdr->{language}, # a16 $hdr->{blversion},# a16 $hdr->{rest2}, # a168 $hdr->{flags2}, # V $hdr->{flags}, # V $hdr->{operator}, # a16 "", # a236 $hdr->{entrycount}, # V create_nbf_header_list($hdr) # a508 ); } elsif ($g_prodigy) { return pack("a16a16a16a16a16a168VVa8VVVa736VVVVa1016", $hdr->{magic}, # 00: a16 $hdr->{device}, # 10: a16 $hdr->{version}, # 20: a16 $hdr->{language}, # 30: a16 $hdr->{blversion}, # 40: a16 $hdr->{rest2}, # 50: a168 $hdr->{flags2}, # f8: V $hdr->{flags}, # fc: V $hdr->{operator}, #100: a8 "", #108: V 0x400, #10c: V 3, #110: V "", #114: a736 0x04c41000, #3f4: V 0x092d84ff, #3f8: V 0xa9622737, #3fc: V $hdr->{entrycount},#400: V create_nbf_header_list($hdr) #404: a1016 ); } else { return pack("a16a16a16a16a16a168VVa16a236", $hdr->{magic}, $hdr->{device}, $hdr->{version}, $hdr->{language}, $hdr->{blversion}, create_nbf_header_list($hdr), $hdr->{entrycount}, $hdr->{flags}, $hdr->{operator}, "" ); } } sub create_nbf_header_list { my $hdr= shift; my $data; for (@{$hdr->{entries}}) { $data .= create_nbf_header_entry($_); } return $data; } sub create_nbf_header_entry { my $entry= shift; if ($g_prodigy) { return pack("a16V5", $entryinfo{$entry->{name}}{nbfname} || $entry->{name}, $entry->{offset}, $entry->{length}, $entry->{checksum}, $entry->{flags}, $entry->{id}); } else { return pack("a16VVV", $entryinfo{$entry->{name}}{nbfname} || $entry->{name}, $entry->{offset}, $entry->{length}, $entry->{checksum}); } } # 00000000: "HTC$TYPH-666" : magic # disk on chip id # 0000000c: 00 00 00 00 83 37 02 01 21 0d 0e dc 1b 0b 04 98 # # offset length checksum # +00 +04 +08 # 0000001c: 80000000 00000800 bc8aade6 # 00000028: 80000800 000c0000 e9560778 # 00000034: 800c0800 00020000 b922180d # 00000040: 800e0800 00200000 94077bae # 0000004c: 04e3d4c0 01b00000 40d2bfdc # 00000058: b730c46b 22a13b32 9d9122f6 <-- junk # # 00000064: 00000005 : entry count # 00000068: "1.01.0091" : bootloader version # 00000078: ccc9eab2 <-- junk # # NOTE: empty space is usually filled with random junk. # sub unpacksdheader { my ($hdr)= @_; my %hdr; my $entries; ( $hdr{magic}, $hdr{docuniqueid}, $entries, $hdr{entrycount}, $hdr{blversion}, $hdr{unknown}, )= unpack("A12A16a72VA16V", $hdr); if ($hdr{entrycount}>=0 && $hdr{entrycount}<=6) { for (my $i=0 ; $i<$hdr{entrycount} ; $i++) { push @{$hdr{entries}}, parsesdhdrentry(substr($entries, $i*12, 12)); } } else { warn "possibly invalid sd header - entrycount invalid\n"; } return \%hdr; } sub parsesdhdrentry { my ($ent)= @_; my %ent; ( $ent{sdoffset}, $ent{length}, $ent{checksum}, )= unpack("VVV", $ent); $ent{name}= namefromsdoffset($ent{sdoffset}); return \%ent; } sub namefromsdoffset { my ($ofs)= @_; for (keys %entryinfo) { if ($entryinfo{$_}{sdofs}<=$ofs && $ofs<$entryinfo{$_}{sdofs}+$entryinfo{$_}{length}) { return $entryinfo{$_}{nbfname} || $_; } } return sprintf("unknown_%08lx", $ofs); } sub PackSdHeader { my $hdr= shift; # small 'a' : pad with NULs return pack("a12a16a72Va16V", $hdr->{magic}, $hdr->{docuniqueid}, create_sd_header_list($hdr), $hdr->{entrycount}, $hdr->{blversion}, 0, # unknown value always 0 ); } sub create_sd_header_list { my $hdr= shift; my $data= ""; for (@{$hdr->{entries}}) { $data .= create_sd_header_entry($_); } return $data; } sub create_sd_header_entry { my $entry= shift; return pack("VVV", $entry->{sdoffset}, $entry->{length}, $entry->{checksum}); } ################################################################# # image validation functions sub validate_os { my ($data)= @_; if (substr($$data, 0x40, 4) ne "ECEC") { warn "no ECEC at 0x40 in OS image\n"; return 0; } if (substr($$data, 0x1040040, 4) ne "ECEC") { warn "no ECEC at 0x1040040 in OS image\n"; return 0; } for (0..5) { if (substr($$data, 0x11be048+$_*0x290, 4) ne "RSA1") { warn sprintf("no RSA1 at %08lx in OS image\n", 0x11be048+$_*0x290); return 0; } } return 1; } sub validate_ipl { my ($data)= @_; if (substr($$data, 4, 4) ne "IPL ") { warn "no IPL at 4 in IPL image\n"; return 0; } if (substr($$data, 0xc, 4) ne "1.00") { warn "no 1.00 at 0xc in IPL image\n"; return 0; } if (substr($$data, 0x614, 4) ne "BIPO") { warn "no BIPO at 0x614 in IPL image\n"; return 0; } return 1; } sub validate_spl { my ($data)= @_; if (substr($$data, 0x0040, 4) ne "ECEC") { warn "no ECEC at 0x0040 in SPL image\n"; return 0; } if (substr($$data, 0x0240, 4) ne "ECEC") { warn "no ECEC at 0x0240 in SPL image\n"; return 0; } if (substr($$data, 0x2004, 8) ne "BOOTLOAD") { warn "no BOOTLOAD at 0x2004 in SPL image\n"; return 0; } if (substr($$data, 0x2014, 9) !~ /^\d+\.\d+\.\d+\x00*$/) { warn "no version at 0x2014 in SPL image\n"; return 0; } if (substr($$data, 0x2204, 8) ne "BOOTLOAD") { warn "no BOOTLOAD at 0x2204 in SPL image\n"; return 0; } if (substr($$data, 0x2214, 9) !~ /^\d+\.\d+\.\d+\x00*$/) { warn "no version at 0x2214 in SPL image\n"; return 0; } return 1; } sub validate_splash { my ($data)= @_; if (substr($$data, 0, 0x1d) ne "This is smartphone signature.") { warn "no sm signature at 0 in Splash\n"; return 0; } return 1; } sub validate_gsm { my ($data)= @_; if (substr($$data, 4, 4*7) ne "\x05\x00\x00\xea" x 7) { warn "no 7 * jump +5 at start of gsm\n"; return 0; } return 1; } sub validate_gsmdata { my ($data)= @_; if (substr($$data, 0x14, 6) ne "afcdac") { warn "no afcdac at 0x14 in gsmdata\n"; return 0; } return 1; }