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