document script invocation examples
[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 my $life_cycle = {
58 "\x78\x77\x88" => 'MAD INIT,RW',
59 "\x7F\x07\x88" => 'NFC INIT,RW',
60 "\x07\x8F\x0F" => 'READ ONLY',
61 };
62
63
64 if ( $debug ) {
65         warn "# function_clusters ",dump($function_clusters);
66         warn "# mad_id ", dump($mad_id);
67 }
68
69 local $/ = undef;
70 my $card = <>;
71
72 die "expected 4096 or 1024 bytes, got ",length($card), " bytes\n"
73         unless length $card == 4096 || length $card == 1024;
74
75 my ( $ADV, $MA, $DA );
76
77 my $pos = 0;
78
79 foreach my $sector ( 0 .. 39 ) {
80
81         my $blocks = $sector < 32 ? 4 : 16;
82
83         last if $pos >= length($card);
84         next if substr($card,$pos,$blocks * 0x10) eq "\x00" x ($blocks * 0x10);
85
86         # General purpose byte (GPB)
87         my $GBP = ord(substr($card,0x39,1));
88
89         if ( $sector == 0 ) {
90                 printf "manufacturer block\nSerial number: %s\nCB: %s\nmanufacturer data: %s\n"
91                         , unpack('H*',substr($card,0,4))
92                         , unpack('H*',substr($card,4,1))
93                         , unpack('H*',substr($card,5,11))
94                         ;
95
96                 # MAD
97                 $ADV = $GBP & 0b00000011;
98                 $MA  = $GBP & 0b01000000;
99                 $DA  = $GBP & 0b10000000;
100                 printf "ADV (MAD version code): %d\n", $ADV;
101                 printf "MA (multiapplication): %s\n", $MA ? 'yes' : 'monoaplication';
102                 printf "DA (MAD available): %s%s\n",  $DA ? 'yes' : 'no',
103                         substr($card,$pos+0x30,6) eq "\xA0\xA1\xA2\xA3\xA4\xA5" ? ' public' : '';
104
105                 printf "Info byte (publisher sector): %x\n", ord(substr($card,0x11,1));
106         } elsif ( $DA ) {
107                 my $mad_offset = 0x10 + ( $sector * 2 );
108                 my $v = unpack('v',(substr($card, $mad_offset, 2)));
109                 my $cluster_id = unpack('HH', (( $v & 0xff00 ) >> 8) );
110                 my $full_id = sprintf "%04x",$v;
111                 printf "MAD sector %-2d@%x %04x [%s]\n%s\n", $sector, $mad_offset, $v
112                         , $function_clusters->{ $cluster_id }
113                         , $mad_id->{$full_id} || "FIXME: add $full_id from MAD_overview.pdf to __DATA__ at end of $0"
114                         ;
115
116                 if ( $v == 0x0004 ) {
117                         # RLE encoded card holder information
118                         my $data = substr( $card, $pos, 0x30);
119                         my $o = 0;
120                         my $types = {
121                                 0b00 => 'surname',
122                                 0b01 => 'given name',
123                                 0b10 => 'sex',
124                                 0b11 => 'any',
125                         };
126                         while ( substr($data,$o,1) ne "\x00" ) {
127                                 my $len = ord(substr($data,$o,1));
128                                 my $type = ( $len & 0b11000000 ) >> 6;
129                                 $len     =   $len & 0b00111111;
130                                 my $dump = substr($data,$o+1,$len-1);
131                                 $dump = '0x' . unpack('H*', $dump) if $type == 0b11; # any
132                                 printf "%-10s %2d %s\n", $types->{$type}, $len, $dump;
133                                 $o += $len + 1;
134                         }
135                 } elsif ( $v == 0x0015 ) {
136                         printf "Card number: %s\n", unpack('h*',substr($card,$pos + 0x04,6));
137                 }
138
139         } else {
140                 printf "# sector %-2d with %d blocks\n", $sector, $blocks;
141         }
142
143         my $trailer_pos = $pos + $blocks * 0x10 - 0x10;
144         my $c1 = ( ord(substr($card,$trailer_pos+7,1)) & 0xf0 ) >> 4;
145         my $c2 = ( ord(substr($card,$trailer_pos+8,1)) & 0x0f );
146         my $c3 = ( ord(substr($card,$trailer_pos+8,1)) & 0xf0 ) >> 4;
147
148         printf "# trailer @%x %016b c1:%08b c2:%08b c3:%08b\n"
149                 , unpack('n',(substr($card,$trailer_pos+7,2)))
150                 , $trailer_pos, $c1, $c2, $c3
151                 ;
152
153         my $cond = '';
154         foreach my $j ( 0 .. $blocks - 1 ) {
155                 my $offset = $pos + $j * 0x10;
156                 my $block = substr($card, $offset, 0x10);
157
158                 my $acl_block =
159                         $sector < 32 ? $j :
160                         $j % 5 == 0  ? $j / 5 :
161                         undef; # display condition only once for block group
162
163                 if ( defined $acl_block ) {
164                         my $mask = 1 << $acl_block;
165                         $cond
166                                 = ( ( $c1 & $mask ) * 4 )
167                                 + ( ( $c2 & $mask ) * 2 )
168                                 + ( ( $c3 & $mask ) * 1 )
169                                 ;
170                         $cond >>= $acl_block;
171                         $cond = sprintf ' %03b %s'
172                                 , $cond
173                                 , $j == ( $blocks - 1 )
174                                         ? $access_condition_trailer->{$cond}
175                                         : $access_condition_data->{$cond}
176                                 ;
177                 } else {
178                         $cond = '';
179                 }
180
181                 my $hex = unpack('H*',$block);
182                 $hex =~ s/(....)/$1 /g;
183
184                 if ( $ENV{SWAP} ) {
185                         my $hex_sw = unpack('h*',$block);
186                         $hex_sw =~ s/(....)/$1 /g;
187                         $hex .= " | $hex_sw";
188                 }
189
190                 printf "%x %03x  %s%s\n", $j, $offset, $hex, $cond;
191         }
192
193         printf "KEY A:%s | %s GDP: %s | B:%s %s\n"
194                 ,unpack('H*',substr($card,$trailer_pos   ,6))
195                 ,unpack('H*',substr($card,$trailer_pos+6 ,3))
196                 ,unpack('H*',substr($card,$trailer_pos+9 ,1))
197                 ,unpack('H*',substr($card,$trailer_pos+10,6))
198                 ,$life_cycle->{substr($card,$trailer_pos+6,3)} || ''
199                 ;
200
201         print "\n";
202
203         $pos += $blocks * 0x10;
204 }
205
206 __DATA__
207 00    card administration
208 01-07 miscellaneous applications
209 08    airlines
210 09    ferry trafic
211 10    railway services
212 12    transport
213 18    city traffic
214 19    Czech Railways
215 20    bus services
216 21    multi modal transit
217 28    taxi
218 30    road toll
219 38    company services
220 40    city card services
221 47-48 access control & security
222 49    VIGIK
223 4A    Ministry of Defence, Netherlands
224 4B    Bosch Telecom, Germany
225 4A    Ministry of Defence, Netherlands
226 4C    European Union Institutions
227 50    ski ticketing
228 51-54 access control & security
229 58    academic services
230 60    food
231 68    non food trade
232 70    hotel
233 75    airport services
234 78    car rental
235 79    Dutch government
236 80    administration services
237 88    electronic purse
238 90    television
239 91    cruise ship
240 95    IOPTA
241 97    Metering
242 98    telephone
243 A0    health services
244 A8    warehouse
245 B0    electronic trade
246 B8    banking
247 C0    entertainment & sports
248 C8    car parking
249 C9    Fleet Management
250 D0    fuel, gasoline
251 D8    info services
252 E0    press
253 E1    NFC Forum
254 E8    computer
255 F0    mail
256 F8-FF miscellaneous applications
257
258 0000    sector is free
259 0001    sector is defect, e.g. access keys are destroyed or unknown
260 0002    sector is reserved
261 0003    sector contains additional directory info (useful only for future cards)
262 0004    sector contains card holder information in ASCII format.
263 0005    sector not applicable (above memory size)
264
265 0015 - card administration MIKROELEKTRONIKA spol.s.v.MIKROELEKTRONIKA spol.s.v.o. worldwide 1 01.02.2007 Card publisher info
266 0016 - card administration Mikroelektronika spol.s.r.o., Kpt.Mikroelektronika spol.s.r.o., Kpt. PoEurope    1 10.10.2007 Issuer information
267
268 071C - miscellaneous applications MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o., Europe       1 01.12.2008 Customer profile
269 071D - miscellaneous applications ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o. EUROPE,Croatia 1 01.04.2009 Customer profile
270 071E - miscellaneous applications ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o. EUROPE,Croatia 1 01.04.2009 Bonus counter
271
272 1835 - city traffic KORID LK, spol.s.r.o.       KORID LK, spol.s.r.o.        Europe         2 08.09.2008 Eticket
273 1836 - city traffic MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o., Europe         1 01.12.2008 Prepaid coupon 1S
274 1837 - city traffic ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o.   EUROPE,Croatia 1 01.04.2009 Prepaid coupon
275 1838 - city traffic MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o.  Europe         1 01.05.2009 Prepaid coupon
276 1839 - city traffic Mikroelektronika spol.s r.o Mikroelektronika spol.s r.o  EUROPE,Czech R 1 01.08.2009 Prepaid coupon
277 183B - city traffic UNICARD S.A.                UNICARD S.A.                 Poland        15 01.01.2010 city traffic services
278
279 2061 - bus services Mikroelektronika spol.s r.o. Mikroelektronika spol.s r.o. Europe     1 01.08.2008 Electronic ticket
280 2062 - bus services ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o EUROPE,Croatia 1 01.04.2009 Electronic tiicket
281 2063 - bus services MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o. Europe       3 01.05.2009 electronic ticket
282
283 887B - electronic purse ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o. EUROPE,Croatia  4 01.04.2009 Electronic purse
284 887C - electronic purse MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o. Europe         4 01.05.2009 electronic purse
285 887D - electronic purse Mikroelektronika spol.s r.o Mikroelektronika spol.s r.o EUROPE,Czech R 4 01.08.2009 Electronic purse
286