cleanup GBP and indicate public MAD key
[perl-Mifare-MAD.git] / mifare-mad.pl
1 #!/usr/bin/perl
2
3 use warnings;
4 use strict;
5
6 # based on AN10787
7 # MIFARE Application Directory (MAD)
8 # http://www.nxp.com/acrobat_download2/other/identification/MAD_overview.pdf
9
10 use Data::Dump qw(dump);
11
12 my $debug = $ENV{DEBUG} || 0;
13
14 my $function_clusters;
15 my $mad_id;
16
17 while(<DATA>) {
18         chomp;
19         next if m/^#?\s*$/;
20         my ( $code, $function ) = split(/\s+/,$_,2);
21         my $h = '[0-9A-F]';
22         if ( $code =~ m/^($h{2})-($h{2})$/ ) {
23                 foreach my $c ( hex($1) .. hex($2) ) {
24                         $function_clusters->{ unpack('HH',$c) } = $function;
25                 }
26         } elsif ( $code =~ m/^($h{2})$/ ) {
27                 $function_clusters->{ lc $code } = $function;
28         } elsif ( $code =~ m/^($h{4})$/ ) {
29                 $mad_id->{ lc $1 } = $function;
30         } else {
31                 die "can't parse __DATA__ line $.\n$_\n";
32         }
33 }
34
35 my $access_condition_data = {
36 0b000 => 'R:AB W:AB I:AB DTR:AB transport conf',
37 0b010 => 'R:AB W:-- I:-- DTR:-- r/w block',
38 0b100 => 'R:AB W:-B I:-- DTR:-- r/w block', 
39 0b110 => 'R:AB W:-B I:-B DTR:AB r/w block',
40 0b001 => 'R:AB W:-- I:-- DTR:AB value block',
41 0b011 => 'R:-B W:-B I:-- DTR:-- value block',
42 0b101 => 'R:-B W:-- I:-- DTR:-- r/w block',
43 0b111 => 'R:-- W:-- I:-- DTR:-- r/w block',
44 };
45
46 my $access_condition_trailer = {
47 0b000 => 'R/W: KEYA:-/A ACCESS:A-/- DATB:A/A ?',
48 0b010 => 'R/W: KEYA:-/- ACCESS:A-/- DATB:A/- ?'.
49 0b100 => 'R/W: KEYA:-/B ACCESS:AB/- KEYB:-/B',
50 0b110 => 'R/W: KEYA:-/- ACCESS:AB/- KEYB:-/-',
51 0b001 => 'R/W: KEYA:-/A ACCESS:A-/A DATB:A/A ? transport conf',
52 0b011 => 'R/W: KEYA:-/B ACCESS:AB/B KEYB:-/B',
53 0b101 => 'R/W: KEYA:-/- ACCESS:AB/B KEYB:-/-',
54 0b111 => 'R/W: KEYA:-/- ACCESS:AB/- KEYB:-/-',
55 };
56
57
58 if ( $debug ) {
59         warn "# function_clusters ",dump($function_clusters);
60         warn "# mad_id ", dump($mad_id);
61 }
62
63 local $/ = undef;
64 my $card = <>;
65
66 die "expected 4096 bytes, got ",length($card), " bytes\n"
67         unless length $card == 4096;
68
69 my ( $ADV, $MA, $DA );
70
71 foreach my $i ( 0 .. 15 ) {
72
73         my $pos = 0x40 * $i;
74         # General purpose byte (GPB)
75         my $GBP = ord(substr($card,0x39,1));
76
77         if ( $i == 0 ) {
78                 printf "manufacturer block\nSerial number: %s\nCB: %s\nmanufacturer data: %s\n"
79                         , unpack('H*',substr($card,0,4))
80                         , unpack('H*',substr($card,4,1))
81                         , unpack('H*',substr($card,5,11))
82                         ;
83
84                 # MAD
85                 $ADV = $GBP & 0b00000011;
86                 $MA  = $GBP & 0b01000000;
87                 $DA  = $GBP & 0b10000000;
88                 printf "ADV (MAD version code): %d %s\n", $ADV,
89                 printf "MA (multiapplication): %s\n", $MA ? 'yes' : 'monoaplication';
90                 printf "DA (MAD available): %s%s\n",  $DA ? 'yes' : 'no',
91                         substr($card,$pos+0x30,6) eq "\xA0\xA1\xA2\xA3\xA4\xA5" ? ' public' : '';
92
93                 printf "Info byte (publisher sector): %x\n", ord(substr($card,0x11,1));
94         } elsif ( $DA ) {
95                 my $mad_offset = 0x10 + ( $i * 2 );
96                 my $v = unpack('v',(substr($card, $mad_offset, 2)));
97                 my $cluster_id = unpack('HH', (( $v & 0xff00 ) >> 8) );
98                 my $full_id = sprintf "%04x",$v;
99                 printf "MAD sector %-2d@%x %04x [%s]\n%s\n", $i, $mad_offset, $v
100                         , $function_clusters->{ $cluster_id }
101                         , $mad_id->{$full_id} || "FIXME: add $full_id from MAD_overview.pdf to __DATA__ at end of $0"
102                         ;
103
104                 if ( $v == 0x0004 ) {
105                         # RLE encoded card holder information
106                         my $data = substr( $card, $pos, 0x30);
107                         my $o = 0;
108                         my $types = {
109                                 0b00 => 'surname',
110                                 0b01 => 'given name',
111                                 0b10 => 'sex',
112                                 0b11 => 'any',
113                         };
114                         while ( substr($data,$o,1) ne "\x00" ) {
115                                 my $len = ord(substr($data,$o,1));
116                                 my $type = ( $len & 0b11000000 ) >> 6;
117                                 $len     =   $len & 0b00111111;
118                                 my $dump = substr($data,$o+1,$len-1);
119                                 $dump = '0x' . unpack('H*', $dump) if $type == 0b11; # any
120                                 printf "%-10s %2d %s\n", $types->{$type}, $len, $dump;
121                                 $o += $len + 1;
122                         }
123                 } elsif ( $v == 0x0015 ) {
124                         printf "Card number: %s\n", unpack('h*',substr($card,$pos + 0x04,6));
125                 }
126
127         } else {
128                 printf "# sector %-2d\n", $i;
129         }
130
131         my $c1 = ( ord(substr($card,$pos+0x37,1)) & 0xf0 ) >> 4;
132         my $c2 = ( ord(substr($card,$pos+0x38,1)) & 0x0f );
133         my $c3 = ( ord(substr($card,$pos+0x38,1)) & 0xf0 ) >> 4;
134
135         foreach my $j ( 0 .. 3 ) {
136                 my $offset = $pos + $j * 0x10;
137                 my $block = substr($card, $offset, 0x10);
138                 my $mask = 1 << $j;
139                 my $cond
140                         = ( ( $c1 & $mask ) * 4 )
141                         + ( ( $c2 & $mask ) * 2 )
142                         + ( ( $c3 & $mask ) * 1 )
143                         ;
144                 $cond >>= $j;
145
146                 my $hex = unpack('H*',$block);
147                 $hex =~ s/(....)/$1 /g;
148
149                 if ( $ENV{SWAP} && $j < 3 ) {
150                         my $hex_sw = unpack('h*',$block);
151                         $hex_sw =~ s/(....)/$1 /g;
152                         $hex .= " | $hex_sw";
153                 }
154
155                 printf "%04x  %s %03b %s\n", $offset, $hex
156                         , $cond
157                         , $j < 3 ? $access_condition_data->{$cond} : $access_condition_trailer->{$cond}
158                         ;
159         }
160
161
162         printf "KEY A:%s | %s GDP: %s | B:%s\n"
163                 ,unpack('H*',substr($card,$pos+0x30   ,6))
164                 ,unpack('H*',substr($card,$pos+0x30+6 ,3))
165                 ,unpack('H*',substr($card,$pos+0x30+9 ,1))
166                 ,unpack('H*',substr($card,$pos+0x30+10,6))
167                 ;
168
169         print "\n";
170
171 }
172
173 __DATA__
174 00    card administration
175 01-07 miscellaneous applications
176 08    airlines
177 09    ferry trafic
178 10    railway services
179 12    transport
180 18    city traffic
181 19    Czech Railways
182 20    bus services
183 21    multi modal transit
184 28    taxi
185 30    road toll
186 38    company services
187 40    city card services
188 47-48 access control & security
189 49    VIGIK
190 4A    Ministry of Defence, Netherlands
191 4B    Bosch Telecom, Germany
192 4A    Ministry of Defence, Netherlands
193 4C    European Union Institutions
194 50    ski ticketing
195 51-54 access control & security
196 58    academic services
197 60    food
198 68    non food trade
199 70    hotel
200 75    airport services
201 78    car rental
202 79    Dutch government
203 80    administration services
204 88    electronic purse
205 90    television
206 91    cruise ship
207 95    IOPTA
208 97    Metering
209 98    telephone
210 A0    health services
211 A8    warehouse
212 B0    electronic trade
213 B8    banking
214 C0    entertainment & sports
215 C8    car parking
216 C9    Fleet Management
217 D0    fuel, gasoline
218 D8    info services
219 E0    press
220 E1    NFC Forum
221 E8    computer
222 F0    mail
223 F8-FF miscellaneous applications
224
225 0000    sector is free
226 0001    sector is defect, e.g. access keys are destroyed or unknown
227 0002    sector is reserved
228 0003    sector contains additional directory info (useful only for future cards)
229 0004    sector contains card holder information in ASCII format.
230 0005    sector not applicable (above memory size)
231
232 0015 - card administration MIKROELEKTRONIKA spol.s.v.MIKROELEKTRONIKA spol.s.v.o. worldwide 1 01.02.2007 Card publisher info
233 0016 - card administration Mikroelektronika spol.s.r.o., Kpt.Mikroelektronika spol.s.r.o., Kpt. PoEurope    1 10.10.2007 Issuer information
234
235 071C - miscellaneous applications MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o., Europe       1 01.12.2008 Customer profile
236 071D - miscellaneous applications ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o. EUROPE,Croatia 1 01.04.2009 Customer profile
237 071E - miscellaneous applications ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o. EUROPE,Croatia 1 01.04.2009 Bonus counter
238
239 1835 - city traffic KORID LK, spol.s.r.o.       KORID LK, spol.s.r.o.        Europe         2 08.09.2008 Eticket
240 1836 - city traffic MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o., Europe         1 01.12.2008 Prepaid coupon 1S
241 1837 - city traffic ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o.   EUROPE,Croatia 1 01.04.2009 Prepaid coupon
242 1838 - city traffic MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o.  Europe         1 01.05.2009 Prepaid coupon
243 1839 - city traffic Mikroelektronika spol.s r.o Mikroelektronika spol.s r.o  EUROPE,Czech R 1 01.08.2009 Prepaid coupon
244 183B - city traffic UNICARD S.A.                UNICARD S.A.                 Poland        15 01.01.2010 city traffic services
245
246 2061 - bus services Mikroelektronika spol.s r.o. Mikroelektronika spol.s r.o. Europe     1 01.08.2008 Electronic ticket
247 2062 - bus services ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o EUROPE,Croatia 1 01.04.2009 Electronic tiicket
248 2063 - bus services MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o. Europe       3 01.05.2009 electronic ticket
249
250 887B - electronic purse ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o. EUROPE,Croatia  4 01.04.2009 Electronic purse
251 887C - electronic purse MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o. Europe         4 01.05.2009 electronic purse
252 887D - electronic purse Mikroelektronika spol.s r.o Mikroelektronika spol.s r.o EUROPE,Czech R 4 01.08.2009 Electronic purse
253