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