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