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