#!perl -w # (C) 2003-2007 Willem Jan Hengeveld # Web: http://www.xs4all.nl/~itsme/ # http://wiki.xda-developers.com/ # # $Id: alpinenbfdecode.pl 1884 2008-06-26 10:49:25Z itsme $ # # perl script demonstrating how to decrypt the nbf files for the # htc-alpine, and newer htc-magician upgrade files. # # todo: figure out how to correctly calculate the CRC # use strict; use warnings; use IO::File; use Getopt::Long; use XdaDevelopers::NbfUtils; sub usage { return <<__EOF__ Usage: alpinenbfdecode [options] infile outfile\n -d output decoded file -e output encoded file -r output raw file -st devicetype -so operatorname -sl language -sn devicename -sv version __EOF__ } sub FT_DECODED { 1; } sub FT_ENCODED { 2; } sub FT_RAW { 3; } my %typename= ( 1=>"decoded", 2=>"encoded", 3=>"raw", ); my $dsttype=0; my $srctype=0; my $verbose=0; my $do_calculatecrc= 0; my %newvals; GetOptions( "v"=> \$verbose, "c"=> \$do_calculatecrc, "d"=> sub { $dsttype= FT_DECODED; }, "e"=> sub { $dsttype= FT_ENCODED; }, "r"=> sub { $dsttype= FT_RAW; }, "st=s"=> sub { $newvals{devicetype}= $_[1]; }, "so=s"=> sub { $newvals{operatorname}= $_[1]; }, "sl=s"=> sub { $newvals{language}= $_[1]; }, "sn=s"=> sub { $newvals{devicename}= $_[1]; }, "sv=s"=> sub { $newvals{version}= $_[1]; }, ) or die usage(); my $ifn= shift || die usage(); my $ifh= IO::File->new($ifn, "r") or die "$ifn: $!\n"; binmode $ifh; my $ofn= shift; my $nbfstr; $ifh->read($nbfstr, 0xAC) or die "read $ifn: $!\n"; $srctype= HeaderDecoder::isdecoded($nbfstr)?FT_DECODED :HeaderDecoder::isencoded($nbfstr)?FT_ENCODED :FT_RAW; print "srctype=$typename{$srctype}\n"; # decode and split header my $nbfdata; if ($srctype==FT_ENCODED) { print "decoding input header\n" if ($verbose); $nbfdata= HeaderDecoder::decodehdr($nbfstr); } elsif ($srctype==FT_DECODED) { print "plain input header\n" if ($verbose); $nbfdata= substr($nbfstr, 0, 0x80); $ifh->seek(0x80, SEEK_SET); } else { print "no input header\n" if ($verbose); $ifh->seek(0, SEEK_SET); $nbfdata= ""; } my $hdr= HeaderDecoder::splithdr($nbfdata); # print header to screen print(map({ sprintf(" %-15s: %s\n", $_, $hdr->{$_}); } sort keys %$hdr), "\n") if (!$ofn || $verbose); if (!$ofn && !$do_calculatecrc) { # only input name -> exit after listing contents exit(0); } if (!$nbfdata) { $hdr= HeaderDecoder::default_hdr(); if ($ifn =~ /ms\w*\.nb/i) { $hdr->{field50}= 'A2C00000'; } $do_calculatecrc= 1; # only can calculate crc of plaintext if ($srctype==FT_ENCODED) { die "need crc of plaintext file to decode\n"; } } # update header with new values $hdr->{$_}= $newvals{$_} for (keys %newvals); my $ofh; if ($ofn) { # if no output type specified, derive from file extension $dsttype ||= ($ofn =~ /\.nba$/i)?FT_DECODED :($ofn =~ /\.nbf$/i)?FT_ENCODED :FT_RAW; $ofh= IO::File->new($ofn, "w") or die "$ofn: $!\n"; binmode $ofh; # seek to start of data. - write header later if ($dsttype==FT_ENCODED) { $ofh->seek(0xAC, SEEK_SET); } elsif ($dsttype==FT_DECODED) { $ofh->seek(0x80, SEEK_SET); } else { $ofh->seek(0x00, SEEK_SET); } } else { $dsttype ||= FT_RAW; } print "dsttype=$typename{$dsttype}\n"; # write output my $xorkey= hex($hdr->{checksum}); if (($srctype==FT_ENCODED)!=($dsttype==FT_ENCODED)) { printf("xorring in->out with %08lx\n", $xorkey) if ($verbose); } my $crc= 0; my $crc1= 0; my $crc2= 0; while (!$ifh->eof) { my $data; $ifh->read($data, 0x40000) or die "read: $ifn: $!\n"; if ($do_calculatecrc) { $crc= XdaDevelopers::NbfUtils::crc32($data, $crc); } $crc1= XdaDevelopers::NbfUtils::crc32($data, $crc1); if (($srctype==FT_ENCODED)!=($dsttype==FT_ENCODED)) { $xorkey= XdaDevelopers::NbfUtils::alpinexorchunk($data, $xorkey); } $crc2= XdaDevelopers::NbfUtils::crc32($data, $crc2); $ofh->print($data) if ($ofh); } printf("crcs: %08lx %08lx %08lx\n", $crc, $crc1, $crc2); if ($do_calculatecrc) { # !!!! problem: crc is incorrect for radio. printf("do_calculatecrc=%08lx\n", $crc); # update header if new crc was calculated $hdr->{checksum}= sprintf("%08lx", $crc); } if ($ofh) { $ofh->seek(0, SEEK_SET); my $newhdrstr= HeaderDecoder::packhdr($hdr); if ($dsttype==FT_ENCODED) { print "encoding output header\n" if ($verbose); $ofh->print(HeaderDecoder::encodehdr($newhdrstr)); } elsif ($dsttype==FT_DECODED) { print "plain output header\n" if ($verbose); $ofh->print($newhdrstr); } $ofh->seek(0, SEEK_END); $ofh->close(); } $ifh->close(); exit(0); package HeaderDecoder; our @EXPORT= qw(decodehdr encodehdr splithdr packhdr isdecoded isencoded default_hdr); my @fieldnames; my @xlat; my @rxlat; INIT { @fieldnames= qw(devicetype operatorname language version devicename field48 field50 field58 unknown checksum); @xlat= (0x79, 0x7A, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x31, 0x30, 0x2B, 0x2F); $rxlat[$_]= 0x2a for (0..127); $rxlat[$xlat[$_]]= $_ for (0..$#xlat); } sub decodehdr { my $str= shift; my $xlatstr= pack("C*", map { $rxlat[$_] } unpack("C*", $str)); my $bits= unpack("B*", $xlatstr); $bits =~ s/\d\d(\d{6})/$1/g; return pack("B*", $bits); } sub encodehdr { my $str= shift; my $bits= unpack("B*", "$str*"); $bits =~ s/(\d{6})/00$1/g; my $xlatstr= pack("B*", $bits); my $enc= pack("C*", map { $xlat[$_] } unpack("C*", $xlatstr)); substr($enc, -1,1)= "="; return $enc; } sub splithdr { my $nbfdata= shift; my %hdr; my @fieldvalues= unpack("A16A16A8A16A16A8A8A8A24A8", $nbfdata); $hdr{$fieldnames[$_]}= $fieldvalues[$_] for (0..$#fieldnames); return \%hdr; } sub packhdr { my $hdr= shift; return pack("A16A16A8A16A16A8A8A8A24A8", map { $hdr->{$fieldnames[$_]} } 0..$#fieldnames); } sub isdecoded { return (shift =~ /^\w+\s{2,}\S+\s{2,}\w+\s{2,}\S+\s+\w+\s+/); } sub isencoded { return (shift =~ /39yq\w+39yq/); } sub default_hdr { return { language => 'WWE', operatorname => 'CDL__001', version => '1.06.00', devicetype => 'PM10C', devicename => 'Magician', field48 => '0', checksum => '', field58 => '0', unknown => '', field50 => '0', }; }