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 or 1024 bytes, got ",length($card), " bytes\n"
73 unless length $card == 4096 || length $card == 1024;
75 my ( $ADV, $MA, $DA );
79 foreach my $sector ( 0 .. 39 ) {
81 my $blocks = $sector < 32 ? 4 : 16;
83 last if $pos >= length($card);
84 next if substr($card,$pos,$blocks * 0x10) eq "\x00" x ($blocks * 0x10);
86 # General purpose byte (GPB)
87 my $GBP = ord(substr($card,0x39,1));
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))
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' : '';
105 printf "Info byte (publisher sector): %x\n", ord(substr($card,0x11,1));
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 "MAD sector %-2d@%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"
116 if ( $v == 0x0004 ) {
117 # RLE encoded card holder information
118 my $data = substr( $card, $pos, 0x30);
122 0b01 => 'given name',
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;
135 } elsif ( $v == 0x0015 ) {
136 printf "Card number: %s\n", unpack('h*',substr($card,$pos + 0x04,6));
140 printf "# sector %-2d with %d blocks\n", $sector, $blocks;
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;
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
154 foreach my $j ( 0 .. $blocks - 1 ) {
155 my $offset = $pos + $j * 0x10;
156 my $block = substr($card, $offset, 0x10);
160 $j % 5 == 0 ? $j / 5 :
161 undef; # display condition only once for block group
163 if ( defined $acl_block ) {
164 my $mask = 1 << $acl_block;
166 = ( ( $c1 & $mask ) * 4 )
167 + ( ( $c2 & $mask ) * 2 )
168 + ( ( $c3 & $mask ) * 1 )
170 $cond >>= $acl_block;
171 $cond = sprintf ' %03b %s'
173 , $j == ( $blocks - 1 )
174 ? $access_condition_trailer->{$cond}
175 : $access_condition_data->{$cond}
181 my $hex = unpack('H*',$block);
182 $hex =~ s/(....)/$1 /g;
185 my $hex_sw = unpack('h*',$block);
186 $hex_sw =~ s/(....)/$1 /g;
187 $hex .= " | $hex_sw";
190 printf "%x %03x %s%s\n", $j, $offset, $hex, $cond;
193 printf "KEY A:%s | %s GDP: %s | B:%s %s\n"
194 ,unpack('H*',substr($card,$trailer_pos ,6))
195 ,unpack('H*',substr($card,$trailer_pos+6 ,3))
196 ,unpack('H*',substr($card,$trailer_pos+9 ,1))
197 ,unpack('H*',substr($card,$trailer_pos+10,6))
198 ,$life_cycle->{substr($card,$trailer_pos+6,3)} || ''
203 $pos += $blocks * 0x10;
207 00 card administration
208 01-07 miscellaneous applications
216 21 multi modal transit
220 40 city card services
221 47-48 access control & security
223 4A Ministry of Defence, Netherlands
224 4B Bosch Telecom, Germany
225 4A Ministry of Defence, Netherlands
226 4C European Union Institutions
228 51-54 access control & security
236 80 administration services
247 C0 entertainment & sports
256 F8-FF miscellaneous applications
259 0001 sector is defect, e.g. access keys are destroyed or unknown
260 0002 sector is reserved
261 0003 sector contains additional directory info (useful only for future cards)
262 0004 sector contains card holder information in ASCII format.
263 0005 sector not applicable (above memory size)
265 0015 - card administration MIKROELEKTRONIKA spol.s.v.MIKROELEKTRONIKA spol.s.v.o. worldwide 1 01.02.2007 Card publisher info
266 0016 - card administration Mikroelektronika spol.s.r.o., Kpt.Mikroelektronika spol.s.r.o., Kpt. PoEurope 1 10.10.2007 Issuer information
268 071C - miscellaneous applications MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o., Europe 1 01.12.2008 Customer profile
269 071D - miscellaneous applications ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o. EUROPE,Croatia 1 01.04.2009 Customer profile
270 071E - miscellaneous applications ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o. EUROPE,Croatia 1 01.04.2009 Bonus counter
272 1835 - city traffic KORID LK, spol.s.r.o. KORID LK, spol.s.r.o. Europe 2 08.09.2008 Eticket
273 1836 - city traffic MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o., Europe 1 01.12.2008 Prepaid coupon 1S
274 1837 - city traffic ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o. EUROPE,Croatia 1 01.04.2009 Prepaid coupon
275 1838 - city traffic MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o. Europe 1 01.05.2009 Prepaid coupon
276 1839 - city traffic Mikroelektronika spol.s r.o Mikroelektronika spol.s r.o EUROPE,Czech R 1 01.08.2009 Prepaid coupon
277 183B - city traffic UNICARD S.A. UNICARD S.A. Poland 15 01.01.2010 city traffic services
279 2061 - bus services Mikroelektronika spol.s r.o. Mikroelektronika spol.s r.o. Europe 1 01.08.2008 Electronic ticket
280 2062 - bus services ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o EUROPE,Croatia 1 01.04.2009 Electronic tiicket
281 2063 - bus services MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o. Europe 3 01.05.2009 electronic ticket
283 887B - electronic purse ZAGREBACKI Holding d.o.o. MIKROELEKTRONIKA spol.s.r.o. EUROPE,Croatia 4 01.04.2009 Electronic purse
284 887C - electronic purse MIKROELEKTRONIKA spol.s.r. MIKROELEKTRONIKA spol.s.r.o. Europe 4 01.05.2009 electronic purse
285 887D - electronic purse Mikroelektronika spol.s r.o Mikroelektronika spol.s r.o EUROPE,Czech R 4 01.08.2009 Electronic purse