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