correctly handle last used keys and save just programmed dump
[perl-Mifare-MAD.git] / nfc-card-dumper.pl
1 #!/usr/bin/perl
2 use warnings;
3 use strict;
4
5 use RFID::Libnfc::Reader;
6 use RFID::Libnfc::Constants;
7 use File::Slurp;
8 use Digest::MD5 qw(md5_hex);
9 use Getopt::Long::Descriptive;
10
11 use Data::Dump qw(dump);
12
13 my ($opt,$usage) = describe_options(
14         '%c %c [dump_with_keys]',
15         [ 'write=s',    'write dump to card' ],
16         [ 'debug|d',    'show debug dumps' ],
17         [ 'help|h',             'usage' ],
18 );
19 print $usage->text, exit if $opt->help;
20
21 my $debug = $ENV{DEBUG} || 0;
22 our $keyfile = shift @ARGV;
23 our ( $tag, $uid, $card_key_file );
24
25 sub write_card_dump;
26
27 my $r = RFID::Libnfc::Reader->new(debug => $debug);
28 if ($r->init()) {
29     warn "reader: %s\n", $r->name;
30     my $tag = $r->connect(IM_ISO14443A_106);
31
32     if ($tag) {
33         $tag->dump_info;
34     } else {
35         warn "No TAG";
36         exit -1;
37     }
38
39         $uid = sprintf "%02x%02x%02x%02x", @{ $tag->uid };
40
41         $card_key_file = "cards/$uid.key";
42         $keyfile ||= $card_key_file;
43
44         if ( -e $keyfile ) {
45                 warn "# loading keys from $keyfile";
46             $tag->load_keys($keyfile);
47                 warn "## _keys = ", dump($tag->{_keys}) if $debug;
48         }
49
50     $tag->select if ($tag->can("select")); 
51
52         my $card;
53
54         print STDERR "reading $uid blocks ";
55     for (my $i = 0; $i < $tag->blocks; $i++) {
56         if (my $data = $tag->read_block($i)) {
57             # if we are dumping an ultralight token, 
58             # we receive 16 bytes (while a block is 4bytes long)
59             # so we can skip next 3 blocks
60             $i += 3 if ($tag->type eq "ULTRA");
61                         $card .= $data;
62                         print STDERR "$i ";
63                 } elsif ( $tag->error =~ m/auth/ ) {
64                         warn $tag->error,"\n";
65
66                         # disconnect from reader so we can run mfoc
67                         RFID::Libnfc::nfc_disconnect($r->{_pdi});
68
69                         print "Dump this card with mfoc? [y] ";
70                         my $yes = <STDIN>; chomp $yes;
71                         exit unless $yes =~ m/y/i || $yes eq '';
72
73                         my $file = "cards/$uid.key";
74                         unlink $file;
75                         warn "# finding keys for card $uid with: mfoc -O $file\n";
76                         exec "mfoc -O $file" || die $!;
77         } else {
78             die $tag->error."\n";
79         }
80     }
81         print STDERR "done\n";
82
83         my $out_file = write_card_dump $tag => $card;
84
85         if ( $opt->write ) {
86                 read_file $opt->write;
87                 print STDERR "writing $uid block ";
88                 foreach my $block ( 0 .. $tag->blocks ) {
89                         my $offset = 0x10 * $block;
90                         $tag->write_block( $block, substr($card,$offset,0x10) );
91                         print STDERR "$block ";
92                 }
93                 print STDERR "done\n";
94                 unlink $card_key_file;
95                 $out_file = write_card_dump $tag => $card;
96         } else {
97                 # view dump
98                 my $txt_file = $out_file;
99                 $txt_file =~ s/\.mfd/.txt/ || die "can't change extension of $out_file to txt";
100                 system "./mifare-mad.pl $out_file > $txt_file";
101                 $ENV{MAD} && system "vi $txt_file";
102         }
103 }
104
105 sub write_card_dump {
106         my ( $tag, $card ) = @_;
107
108         # re-insert keys into dump
109         my $keys = $tag->{_keys} || die "can't find _keys";
110         foreach my $i ( 0 .. $#$keys ) {
111                 my $o = $i * 0x40 + 0x30;
112                 last if $o > length($card);
113                 $card
114                         = substr($card, 0,   $o) . $keys->[$i]->[0]
115                         . substr($card, $o+6, 4) . $keys->[$i]->[1]
116                         . substr($card, $o+16)
117                         ;
118                 warn "# sector $i keys re-inserted at $o\n" if $debug;
119         }
120
121         if ( my $padding = 4096 - length($card) ) {
122                 warn "# add $padding bytes up to 4k dump (needed for keys loading)\n" if $debug;
123                 $card .= "\x00" x $padding;
124         }
125
126         my $md5 = md5_hex($card);
127         my $out_file = "cards/$uid.$md5.mfd";
128         if ( -e $out_file ) {
129                 warn "$out_file allready exists, not overwriting\n";
130         } else {
131                 write_file $out_file, $card;
132                 warn "$out_file ", -s $out_file, " bytes key: $card_key_file\n";
133         }
134
135         if ( ! -e $card_key_file ) {
136                 my $source = $out_file;
137                 $source =~ s{^cards/}{} || die "can't strip directory from out_file";
138                 symlink $source, $card_key_file || die "$card_key_file: $!";
139                 warn "$card_key_file symlink created as default key for $uid\n";
140         }
141
142         return $out_file;
143 }