c74f03f3820ef61682ad09b47f56b040efeb773b
[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 "# sector %-2d MAD@%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 $condition = '';
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 $trailer = $j == ( $blocks - 1 );
165                         my $mask = 1 << $acl_block;
166                         my $cond
167                                 = ( ( $c1 & $mask ) * 4 )
168                                 + ( ( $c2 & $mask ) * 2 )
169                                 + ( ( $c3 & $mask ) * 1 )
170                                 ;
171                         $cond >>= $acl_block;
172                         $condition = sprintf ' %03b %s'
173                                 , $cond
174                                 , $trailer ? $access_condition_trailer->{$cond}
175                                            : $access_condition_data->{$cond}
176                                 ;
177                         if ( ! $trailer && ( $cond == 0b001 || $cond == 0b011 ) ) {
178                                 my $value_block = unpack 'x4Lx8', $block;
179                                 my $positive = $value_block & 0x8000_0000;
180                                 my $value    = $value_block & 0x7fff_ffff;
181                                 $value = -$value if ! $positive;
182                                 #$condition .= sprintf " = %d 0x%x", $value, $value_block;
183                                 $condition .= " = " . $value;
184                         }
185                 } else {
186                         $condition = '';
187                 }
188
189                 my $hex = unpack('H*',$block);
190                 $hex =~ s/(....)/$1 /g;
191
192                 if ( $ENV{SWAP} ) {
193                         my $hex_sw = unpack('h*',$block);
194                         $hex_sw =~ s/(....)/$1 /g;
195                         $hex .= " | $hex_sw";
196                 }
197
198                 printf "%x %03x  %s%s\n", $j, $offset, $hex, $condition;
199         }
200
201         printf "KEY A:%s | %s GDP: %s | B:%s %s\n"
202                 ,unpack('H*',substr($card,$trailer_pos   ,6))
203                 ,unpack('H*',substr($card,$trailer_pos+6 ,3))
204                 ,unpack('H*',substr($card,$trailer_pos+9 ,1))
205                 ,unpack('H*',substr($card,$trailer_pos+10,6))
206                 ,$life_cycle->{substr($card,$trailer_pos+6,3)} || ''
207                 ;
208
209         print "\n";
210
211         $pos += $blocks * 0x10;
212 }
213
214 __DATA__
215 00    card administration
216 01-07 miscellaneous applications
217 08    airlines
218 09    ferry trafic
219 10    railway services
220 12    transport
221 18    city traffic
222 19    Czech Railways
223 20    bus services
224 21    multi modal transit
225 28    taxi
226 30    road toll
227 38    company services
228 40    city card services
229 47-48 access control & security
230 49    VIGIK
231 4A    Ministry of Defence, Netherlands
232 4B    Bosch Telecom, Germany
233 4A    Ministry of Defence, Netherlands
234 4C    European Union Institutions
235 50    ski ticketing
236 51-54 access control & security
237 58    academic services
238 60    food
239 68    non food trade
240 70    hotel
241 75    airport services
242 78    car rental
243 79    Dutch government
244 80    administration services
245 88    electronic purse
246 90    television
247 91    cruise ship
248 95    IOPTA
249 97    Metering
250 98    telephone
251 A0    health services
252 A8    warehouse
253 B0    electronic trade
254 B8    banking
255 C0    entertainment & sports
256 C8    car parking
257 C9    Fleet Management
258 D0    fuel, gasoline
259 D8    info services
260 E0    press
261 E1    NFC Forum
262 E8    computer
263 F0    mail
264 F8-FF miscellaneous applications
265
266 0000    sector is free
267 0001    sector is defect, e.g. access keys are destroyed or unknown
268 0002    sector is reserved
269 0003    sector contains additional directory info (useful only for future cards)
270 0004    sector contains card holder information in ASCII format.
271 0005    sector not applicable (above memory size)
272
273 0015 - card administration MIKROELEKTRONIKA spol.s.v.MIKROELEKTRONIKA spol.s.v.o. worldwide 1 01.02.2007 Card publisher info
274 0016 - card administration Mikroelektronika spol.s.r.o., Kpt.Mikroelektronika spol.s.r.o., Kpt. PoEurope    1 10.10.2007 Issuer information
275
276 071C - miscellaneous applications MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o., Europe       1 01.12.2008 Customer profile
277 071D - miscellaneous applications ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o. EUROPE,Croatia 1 01.04.2009 Customer profile
278 071E - miscellaneous applications ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o. EUROPE,Croatia 1 01.04.2009 Bonus counter
279
280 1835 - city traffic KORID LK, spol.s.r.o.       KORID LK, spol.s.r.o.        Europe         2 08.09.2008 Eticket
281 1836 - city traffic MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o., Europe         1 01.12.2008 Prepaid coupon 1S
282 1837 - city traffic ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o.   EUROPE,Croatia 1 01.04.2009 Prepaid coupon
283 1838 - city traffic MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o.  Europe         1 01.05.2009 Prepaid coupon
284 1839 - city traffic Mikroelektronika spol.s r.o Mikroelektronika spol.s r.o  EUROPE,Czech R 1 01.08.2009 Prepaid coupon
285 183B - city traffic UNICARD S.A.                UNICARD S.A.                 Poland        15 01.01.2010 city traffic services
286
287 2061 - bus services Mikroelektronika spol.s r.o. Mikroelektronika spol.s r.o. Europe     1 01.08.2008 Electronic ticket
288 2062 - bus services ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o EUROPE,Croatia 1 01.04.2009 Electronic tiicket
289 2063 - bus services MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o. Europe       3 01.05.2009 electronic ticket
290
291 887B - electronic purse ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o. EUROPE,Croatia  4 01.04.2009 Electronic purse
292 887C - electronic purse MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o. Europe         4 01.05.2009 electronic purse
293 887D - electronic purse Mikroelektronika spol.s r.o Mikroelektronika spol.s r.o EUROPE,Czech R 4 01.08.2009 Electronic purse
294