show dumps only if not using --write
[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 my $keyfile = shift @ARGV;
23
24 my $r = RFID::Libnfc::Reader->new(debug => $debug);
25 if ($r->init()) {
26     warn "reader: %s\n", $r->name;
27     my $tag = $r->connect(IM_ISO14443A_106);
28
29     if ($tag) {
30         $tag->dump_info;
31     } else {
32         warn "No TAG";
33         exit -1;
34     }
35
36         my $uid = sprintf "%02x%02x%02x%02x", @{ $tag->uid };
37
38         my $card_key_file = "cards/$uid.key";
39         $keyfile ||= $card_key_file;
40
41         if ( -e $keyfile ) {
42                 warn "# loading keys from $keyfile";
43             $tag->load_keys($keyfile);
44                 warn "## _keys = ", dump($tag->{_keys}) if $debug;
45         }
46
47     $tag->select if ($tag->can("select")); 
48
49         my $card;
50
51         print STDERR "reading $uid blocks ";
52     for (my $i = 0; $i < $tag->blocks; $i++) {
53         if (my $data = $tag->read_block($i)) {
54             # if we are dumping an ultralight token, 
55             # we receive 16 bytes (while a block is 4bytes long)
56             # so we can skip next 3 blocks
57             $i += 3 if ($tag->type eq "ULTRA");
58                         $card .= $data;
59                         print STDERR "$i ";
60                 } elsif ( $tag->error =~ m/auth/ ) {
61                         warn $tag->error,"\n";
62
63                         # disconnect from reader so we can run mfoc
64                         RFID::Libnfc::nfc_disconnect($r->{_pdi});
65
66                         print "Dump this card with mfoc? [y] ";
67                         my $yes = <STDIN>; chomp $yes;
68                         exit unless $yes =~ m/y/i || $yes eq '';
69
70                         my $file = "cards/$uid.keys";
71                         unlink $file;
72                         warn "# finding keys for card $uid with: mfoc -O $file\n";
73                         exec "mfoc -O $file" || die $!;
74         } else {
75             die $tag->error."\n";
76         }
77     }
78         print STDERR "done\n";
79
80         # re-insert keys into dump
81         my $keys = $tag->{_keys} || die "can't find _keys";
82         foreach my $i ( 0 .. $#$keys ) {
83                 my $o = $i * 0x40 + 0x30;
84                 last if $o > length($card);
85                 $card
86                         = substr($card, 0,   $o) . $keys->[$i]->[0]
87                         . substr($card, $o+6, 4) . $keys->[$i]->[1]
88                         . substr($card, $o+16)
89                         ;
90                 warn "# sector $i keys re-inserted at $o\n" if $debug;
91         }
92
93         if ( my $padding = 4096 - length($card) ) {
94                 warn "# add $padding bytes up to 4k dump (needed for keys loading)\n" if $debug;
95                 $card .= "\x00" x $padding;
96         }
97
98         my $md5 = md5_hex($card);
99         my $out_file = "cards/$uid.$md5.mfd";
100         if ( -e $out_file ) {
101                 warn "$out_file allready exists, not overwriting\n";
102         } else {
103                 write_file $out_file, $card;
104                 warn "$out_file ", -s $out_file, " bytes key: $card_key_file\n";
105                 if ( ! -e $card_key_file ) {
106                         $out_file =~ s{^cards/}{} || die "can't strip directory from out_file";
107                         symlink $out_file, $card_key_file || die "$card_key_file: $!";
108                         warn "$card_key_file symlink created as default key for $uid\n";
109                 }
110         }
111
112         if ( $opt->write ) {
113                 my $card = read_file $opt->write;
114                 print STDERR "writing $uid block ";
115                 foreach my $block ( 0 .. $tag->blocks ) {
116                         my $offset = 0x10 * $block;
117                         $tag->write_block( $block, substr($card,$offset,0x10) );
118                         print STDERR "$block ";
119                 }
120                 print STDERR "done\n";
121         } else {
122                 # view dump
123                 my $txt_file = $out_file;
124                 $txt_file =~ s/\.mfd/.txt/ || die "can't change extension to txt";
125                 system "./mifare-mad.pl $out_file > $txt_file";
126                 $ENV{MAD} && system "vi $txt_file";
127         }
128 }
129