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\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 %016b c1:%08b c2:%08b c3:%08b\n"
148 , unpack('n',(substr($card,$trailer_pos+7,2)))
149 , $trailer_pos, $c1, $c2, $c3
153 foreach my $j ( 0 .. $blocks - 1 ) {
154 my $offset = $pos + $j * 0x10;
155 my $block = substr($card, $offset, 0x10);
159 $j % 5 == 0 ? $j / 5 :
160 undef; # display condition only once for block group
162 if ( defined $acl_block ) {
163 my $mask = 1 << $acl_block;
165 = ( ( $c1 & $mask ) * 4 )
166 + ( ( $c2 & $mask ) * 2 )
167 + ( ( $c3 & $mask ) * 1 )
169 $cond >>= $acl_block;
170 $cond = sprintf ' %03b %s'
172 , $j == ( $blocks - 1 )
173 ? $access_condition_trailer->{$cond}
174 : $access_condition_data->{$cond}
180 my $hex = unpack('H*',$block);
181 $hex =~ s/(....)/$1 /g;
184 my $hex_sw = unpack('h*',$block);
185 $hex_sw =~ s/(....)/$1 /g;
186 $hex .= " | $hex_sw";
189 printf "%x %03x %s%s\n", $j, $offset, $hex, $cond;
192 printf "KEY A:%s | %s GDP: %s | B:%s %s\n"
193 ,unpack('H*',substr($card,$trailer_pos ,6))
194 ,unpack('H*',substr($card,$trailer_pos+6 ,3))
195 ,unpack('H*',substr($card,$trailer_pos+9 ,1))
196 ,unpack('H*',substr($card,$trailer_pos+10,6))
197 ,$life_cycle->{substr($card,$trailer_pos+6,3)} || ''
202 $pos += $blocks * 0x10;
206 00 card administration
207 01-07 miscellaneous applications
215 21 multi modal transit
219 40 city card services
220 47-48 access control & security
222 4A Ministry of Defence, Netherlands
223 4B Bosch Telecom, Germany
224 4A Ministry of Defence, Netherlands
225 4C European Union Institutions
227 51-54 access control & security
235 80 administration services
246 C0 entertainment & sports
255 F8-FF miscellaneous applications
258 0001 sector is defect, e.g. access keys are destroyed or unknown
259 0002 sector is reserved
260 0003 sector contains additional directory info (useful only for future cards)
261 0004 sector contains card holder information in ASCII format.
262 0005 sector not applicable (above memory size)
264 0015 - card administration MIKROELEKTRONIKA spol.s.v.MIKROELEKTRONIKA spol.s.v.o. worldwide 1 01.02.2007 Card publisher info
265 0016 - card administration Mikroelektronika spol.s.r.o., Kpt.Mikroelektronika spol.s.r.o., Kpt. PoEurope 1 10.10.2007 Issuer information
267 071C - miscellaneous applications MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o., Europe 1 01.12.2008 Customer profile
268 071D - miscellaneous applications ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o. EUROPE,Croatia 1 01.04.2009 Customer profile
269 071E - miscellaneous applications ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o. EUROPE,Croatia 1 01.04.2009 Bonus counter
271 1835 - city traffic KORID LK, spol.s.r.o. KORID LK, spol.s.r.o. Europe 2 08.09.2008 Eticket
272 1836 - city traffic MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o., Europe 1 01.12.2008 Prepaid coupon 1S
273 1837 - city traffic ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o. EUROPE,Croatia 1 01.04.2009 Prepaid coupon
274 1838 - city traffic MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o. Europe 1 01.05.2009 Prepaid coupon
275 1839 - city traffic Mikroelektronika spol.s r.o Mikroelektronika spol.s r.o EUROPE,Czech R 1 01.08.2009 Prepaid coupon
276 183B - city traffic UNICARD S.A. UNICARD S.A. Poland 15 01.01.2010 city traffic services
278 2061 - bus services Mikroelektronika spol.s r.o. Mikroelektronika spol.s r.o. Europe 1 01.08.2008 Electronic ticket
279 2062 - bus services ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o EUROPE,Croatia 1 01.04.2009 Electronic tiicket
280 2063 - bus services MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o. Europe 3 01.05.2009 electronic ticket
282 887B - electronic purse ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o. EUROPE,Croatia 4 01.04.2009 Electronic purse
283 887C - electronic purse MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o. Europe 4 01.05.2009 electronic purse
284 887D - electronic purse Mikroelektronika spol.s r.o Mikroelektronika spol.s r.o EUROPE,Czech R 4 01.08.2009 Electronic purse