7 # MIFARE Application Directory (MAD)
8 # http://www.nxp.com/acrobat_download2/other/identification/MAD_overview.pdf
10 use Data::Dump qw(dump);
12 my $debug = $ENV{DEBUG} || 0;
14 my $function_clusters;
20 my ( $code, $function ) = split(/\s+/,$_,2);
22 if ( $code =~ m/^($h{2})-($h{2})$/ ) {
23 foreach my $c ( hex($1) .. hex($2) ) {
24 $function_clusters->{ unpack('HH',$c) } = $function;
26 } elsif ( $code =~ m/^($h{2})$/ ) {
27 $function_clusters->{ lc $code } = $function;
28 } elsif ( $code =~ m/^($h{4})$/ ) {
29 $mad_id->{ lc $1 } = $function;
31 die "can't parse __DATA__ line $.\n$_\n";
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',
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:-/-',
58 "\x78\x77\x88" => 'MAD INIT,RW',
59 "\x7F\x07\x88" => 'NFC INIT,RW',
60 "\x07\x8F\x0F" => 'READ ONLY',
65 warn "# function_clusters ",dump($function_clusters);
66 warn "# mad_id ", dump($mad_id);
72 die "expected 4096 bytes, got ",length($card), " bytes\n"
73 unless length $card == 4096;
75 my ( $ADV, $MA, $DA );
79 foreach my $sector ( 0 .. 39 ) {
81 my $blocks = $sector < 32 ? 4 : 16;
83 next if substr($card,$pos,$blocks * 0x10) eq "\x00" x ($blocks * 0x10);
85 # General purpose byte (GPB)
86 my $GBP = ord(substr($card,0x39,1));
89 printf "manufacturer block\nSerial number: %s\nCB: %s\nmanufacturer data: %s\n"
90 , unpack('H*',substr($card,0,4))
91 , unpack('H*',substr($card,4,1))
92 , unpack('H*',substr($card,5,11))
96 $ADV = $GBP & 0b00000011;
97 $MA = $GBP & 0b01000000;
98 $DA = $GBP & 0b10000000;
99 printf "ADV (MAD version code): %d %s\n", $ADV;
100 printf "MA (multiapplication): %s\n", $MA ? 'yes' : 'monoaplication';
101 printf "DA (MAD available): %s%s\n", $DA ? 'yes' : 'no',
102 substr($card,$pos+0x30,6) eq "\xA0\xA1\xA2\xA3\xA4\xA5" ? ' public' : '';
104 printf "Info byte (publisher sector): %x\n", ord(substr($card,0x11,1));
106 my $mad_offset = 0x10 + ( $sector * 2 );
107 my $v = unpack('v',(substr($card, $mad_offset, 2)));
108 my $cluster_id = unpack('HH', (( $v & 0xff00 ) >> 8) );
109 my $full_id = sprintf "%04x",$v;
110 printf "MAD sector %-2d@%x %04x [%s]\n%s\n", $sector, $mad_offset, $v
111 , $function_clusters->{ $cluster_id }
112 , $mad_id->{$full_id} || "FIXME: add $full_id from MAD_overview.pdf to __DATA__ at end of $0"
115 if ( $v == 0x0004 ) {
116 # RLE encoded card holder information
117 my $data = substr( $card, $pos, 0x30);
121 0b01 => 'given name',
125 while ( substr($data,$o,1) ne "\x00" ) {
126 my $len = ord(substr($data,$o,1));
127 my $type = ( $len & 0b11000000 ) >> 6;
128 $len = $len & 0b00111111;
129 my $dump = substr($data,$o+1,$len-1);
130 $dump = '0x' . unpack('H*', $dump) if $type == 0b11; # any
131 printf "%-10s %2d %s\n", $types->{$type}, $len, $dump;
134 } elsif ( $v == 0x0015 ) {
135 printf "Card number: %s\n", unpack('h*',substr($card,$pos + 0x04,6));
139 printf "# sector %-2d with %d blocks\n", $sector, $blocks;
142 my $trailer_pos = $pos + $blocks * 0x10 - 0x10;
143 my $c1 = ( ord(substr($card,$trailer_pos+7,1)) & 0xf0 ) >> 4;
144 my $c2 = ( ord(substr($card,$trailer_pos+8,1)) & 0x0f );
145 my $c3 = ( ord(substr($card,$trailer_pos+8,1)) & 0xf0 ) >> 4;
147 printf "# trailer @%x c1:%d c2:%d c3:%d [%016b]\n"
148 , $trailer_pos, $c1, $c2, $c3
149 , unpack('n',(substr($card,$trailer_pos+7,2)))
152 foreach my $j ( 0 .. $blocks - 1 ) {
153 my $offset = $pos + $j * 0x10;
154 my $block = substr($card, $offset, 0x10);
157 = ( ( $c1 & $mask ) * 4 )
158 + ( ( $c2 & $mask ) * 2 )
159 + ( ( $c3 & $mask ) * 1 )
163 my $hex = unpack('H*',$block);
164 $hex =~ s/(....)/$1 /g;
167 my $hex_sw = unpack('h*',$block);
168 $hex_sw =~ s/(....)/$1 /g;
169 $hex .= " | $hex_sw";
172 printf "%04x %s %03b %s\n", $offset, $hex
174 , $j < 3 ? $access_condition_data->{$cond} : $access_condition_trailer->{$cond}
178 printf "KEY A:%s | %s GDP: %s | B:%s %s\n"
179 ,unpack('H*',substr($card,$trailer_pos ,6))
180 ,unpack('H*',substr($card,$trailer_pos+6 ,3))
181 ,unpack('H*',substr($card,$trailer_pos+9 ,1))
182 ,unpack('H*',substr($card,$trailer_pos+10,6))
183 ,$life_cycle->{substr($card,$trailer_pos+6,3)} || ''
188 $pos += $blocks * 0x10;
192 00 card administration
193 01-07 miscellaneous applications
201 21 multi modal transit
205 40 city card services
206 47-48 access control & security
208 4A Ministry of Defence, Netherlands
209 4B Bosch Telecom, Germany
210 4A Ministry of Defence, Netherlands
211 4C European Union Institutions
213 51-54 access control & security
221 80 administration services
232 C0 entertainment & sports
241 F8-FF miscellaneous applications
244 0001 sector is defect, e.g. access keys are destroyed or unknown
245 0002 sector is reserved
246 0003 sector contains additional directory info (useful only for future cards)
247 0004 sector contains card holder information in ASCII format.
248 0005 sector not applicable (above memory size)
250 0015 - card administration MIKROELEKTRONIKA spol.s.v.MIKROELEKTRONIKA spol.s.v.o. worldwide 1 01.02.2007 Card publisher info
251 0016 - card administration Mikroelektronika spol.s.r.o., Kpt.Mikroelektronika spol.s.r.o., Kpt. PoEurope 1 10.10.2007 Issuer information
253 071C - miscellaneous applications MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o., Europe 1 01.12.2008 Customer profile
254 071D - miscellaneous applications ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o. EUROPE,Croatia 1 01.04.2009 Customer profile
255 071E - miscellaneous applications ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o. EUROPE,Croatia 1 01.04.2009 Bonus counter
257 1835 - city traffic KORID LK, spol.s.r.o. KORID LK, spol.s.r.o. Europe 2 08.09.2008 Eticket
258 1836 - city traffic MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o., Europe 1 01.12.2008 Prepaid coupon 1S
259 1837 - city traffic ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o. EUROPE,Croatia 1 01.04.2009 Prepaid coupon
260 1838 - city traffic MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o. Europe 1 01.05.2009 Prepaid coupon
261 1839 - city traffic Mikroelektronika spol.s r.o Mikroelektronika spol.s r.o EUROPE,Czech R 1 01.08.2009 Prepaid coupon
262 183B - city traffic UNICARD S.A. UNICARD S.A. Poland 15 01.01.2010 city traffic services
264 2061 - bus services Mikroelektronika spol.s r.o. Mikroelektronika spol.s r.o. Europe 1 01.08.2008 Electronic ticket
265 2062 - bus services ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o EUROPE,Croatia 1 01.04.2009 Electronic tiicket
266 2063 - bus services MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o. Europe 3 01.05.2009 electronic ticket
268 887B - electronic purse ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o. EUROPE,Croatia 4 01.04.2009 Electronic purse
269 887C - electronic purse MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o. Europe 4 01.05.2009 electronic purse
270 887D - electronic purse Mikroelektronika spol.s r.o Mikroelektronika spol.s r.o EUROPE,Czech R 4 01.08.2009 Electronic purse