/**
* Copyright (C) 2011 Anders Sundman <[email protected]>
*
* This file is part of mfterm.
*
* mfterm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* mfterm is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with mfterm. If not, see <http://www.gnu.org/licenses/>.
*
* Parts of code used in this file are from the GNU readline library file
* fileman.c (GPLv3). Copyright (C) 1987-2009 Free Software Foundation, Inc
*/
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <stdlib.h>
#include "mfterm.h"
#include "tag.h"
#include "term_cmd.h"
#include "mifare_ctrl.h"
#include "dictionary.h"
#include "spec_syntax.h"
#include "util.h"
#include "mac.h"
command_t commands[] = {
{ "help", com_help, 0, 0, "Display this text" },
{ "?", com_help, 0, 0, "Synonym for 'help'" },
{ "quit", com_quit, 0, 1, "Exit the program" },
{ "q", com_quit, 0, 0, "Exit the program" },
{ "exit", com_quit, 0, 0, "Synonym for 'quit'" },
{ "load", com_load_tag, 1, 1, "Load tag data from a file" },
{ "save", com_save_tag, 1, 1, "Save tag data to a file" },
{ "clear", com_clear_tag, 0, 1, "Clear the current tag data" },
{ "read", com_read_tag, 0, 1, "A|B : Read tag data from a physical tag" },
{ "read unlocked", com_read_tag_unlocked, 0, 1, "On pirate cards, read card without keys" },
{ "write", com_write_tag, 0, 1, "A|B : Write tag data to a physical tag" },
{ "write unlocked", com_write_tag_unlocked, 0, 1, "On pirate cards, write 1k tag with block 0" },
{ "print", com_print, 0, 1, "1k|4k : Print tag data" },
{ "p", com_print, 0, 0, "1k|4k : Print tag data" },
{ "print head", com_print_head, 0, 1, "Print first sector" },
{ "print keys", com_print_keys, 0, 1, "1k|4k : Print tag's keys" },
{ "print ac", com_print_ac, 0, 1, "Print access conditions" },
{ "set", com_set, 0, 1, "#block #offset = xx xx xx : Set tag data" },
{ "setuid", com_setuid, 0, 1, "xx xx xx xx: Set tag UID" },
{ "keys load", com_keys_load, 1, 1, "Load keys from a file" },
{ "keys save", com_keys_save, 1, 1, "Save keys to a file" },
{ "keys clear", com_keys_clear, 0, 1, "Clear the keys" },
{ "keys set", com_keys_set, 0, 1, "A|B #S key : Set a key value" },
{ "keys import", com_keys_import, 0, 1, "Import keys from the current tag" },
{ "keys test", com_keys_test, 0, 1, "Try to authenticate with the keys" },
{ "keys", com_keys_print, 0, 1, "1k|4k : Print the keys" },
{ "dict load", com_dict_load, 1, 1, "Load a dictionary key file" },
{ "dict clear", com_dict_clear, 0, 1, "Clear the key dictionary" },
{ "dict attack", com_dict_attack, 0, 1, "Find keys of a physical tag"},
{ "dict", com_dict_print, 0, 1, "Print the key dictionary" },
{ "spec load", com_spec_load, 1, 1, "Load a specification file" },
{ "spec clear", com_spec_clear, 0, 1, "Unload the specification" },
{ "spec", com_spec_print, 0, 1, "Print the specification" },
{ "mac key", com_mac_key_get_set, 0, 1, "<k0..k7> : Get or set MAC key" },
{ "mac compute", com_mac_block_compute, 0, 1, "#block : Compute block MAC" },
{ "mac update", com_mac_block_update, 0, 1, "#block : Compute block MAC" },
{ "mac validate", com_mac_validate, 0, 1, "1k|4k : Validates block MAC of the whole tag" },
{ (char *)NULL, (cmd_func_t)NULL, 0, 0, (char *)NULL }
};
// Parse a Mifare size type argument (1k|4k)
mf_size_t parse_size(const char* str);
// Parse a Mifare size type argument (1k|4k). Return the default
// argument value if the string is NULL.
mf_size_t parse_size_default(const char* str, mf_size_t default_size);
// Parse a Mifare key type argument (A|B)
mf_key_type_t parse_key_type(const char* str);
// Parse a Mifare key type argument (A|B). Return the default
// argument value if the string is NULL.
mf_key_type_t parse_key_type_default(const char* str,
mf_key_type_t default_type);
// Compute the MAC using the current_mac_key. If update is nonzero,
// the mac of the current tag is updated. If not, the MAC is simply
// printed.
int com_mac_block_compute_impl(char* arg, int update);
/* Look up NAME as the name of a command, and return a pointer to that
command. Return a NULL pointer if NAME isn't a command name. */
command_t* find_command(const char *name) {
command_t* cmd = NULL;
size_t cmd_len = 0;
for (size_t i = 0; commands[i].name; ++i) {
size_t l = strlen(commands[i].name);
if (l > cmd_len && strncmp(name, commands[i].name, l) == 0) {
cmd = &commands[i];
cmd_len = l;
}
}
return cmd;
}
/**
* Helper function to print the specified command alligned with the longest
* command name.
*/
void print_help_(size_t cmd) {
// Find longest command (and cache the result)
static size_t cmd_len_max = 0;
if (cmd_len_max == 0) {
for (int i = 0; commands[i].name; ++i) {
size_t cmd_len = strlen(commands[i].name);
cmd_len_max = cmd_len > cmd_len_max ? cmd_len : cmd_len_max;
}
}
// Format: 4x' ' | cmd | ' '-pad-to-longest-cmd | 4x' ' | doc
printf (" %s", commands[cmd].name);
for (int j = (int)(cmd_len_max - strlen(commands[cmd].name)); j >= 0; --j)
printf(" ");
printf (" %s.\n", commands[cmd].doc);
}
int com_help(char* arg) {
// Help request for specific command?
if (arg && *arg != '\0') {
for (size_t i = 0; commands[i].name; ++i) {
if (strcmp(arg, commands[i].name) == 0) {
print_help_(i);
return 0;
}
}
printf ("No commands match '%s'\n", arg);
}
// Help for all commands (with doc flag)
for (size_t i = 0; commands[i].name; i++) {
if (commands[i].document)
print_help_(i);
}
return 0;
}
int com_quit(char *arg) {
stop_input_loop();
return 0;
}
int com_load_tag(char *arg) {
int res = load_tag(arg);
if (res == 0)
printf("Successfully loaded tag from: %s\n", arg);
return 0;
}
int com_save_tag(char* arg) {
int res = save_tag(arg);
if (res == 0)
printf("Successfully wrote tag to: %s\n", arg);
return 0;
}
int com_clear_tag(char* arg) {
clear_tag(¤t_tag);
return 0;
}
int com_read_tag(char* arg) {
// Add option to choose key
char* ab = strtok(arg, " ");
if (ab && strtok(NULL, " ") != (char*)NULL) {
printf("Too many arguments\n");
return -1;
}
if (!ab)
printf("No key argument (A|B) given. Defaulting to A\n");
// Parse key selection
mf_key_type_t key_type = parse_key_type_default(ab, MF_KEY_A);
if (key_type == MF_INVALID_KEY_TYPE) {
printf("Invalid argument (A|B): %s\n", ab);
return -1;
}
// Issue the read request
mf_read_tag(¤t_tag, key_type);
return 0;
}
int com_read_tag_unlocked(char* arg) {
char* ab = strtok(arg, " ");
if (ab) {
printf("This command doesn't take any arguments\n");
return -1;
}
// Issue the read request
mf_read_tag(¤t_tag, MF_KEY_UNLOCKED);
return 0;
}
int com_write_tag(char* arg) {
// Add option to choose key
char* ab = strtok(arg, " ");
if (!ab) {
printf("Too few arguments: (A|B)\n");
return -1;
}
if (strtok(NULL, " ") != (char*)NULL) {
printf("Too many arguments\n");
return -1;
}
// Parse key selection
mf_key_type_t key_type = parse_key_type(ab);
if (key_type == MF_INVALID_KEY_TYPE) {
printf("Invalid argument (A|B): %s\n", ab);
return -1;
}
// Issue the read request
mf_write_tag(¤t_tag, key_type);
return 0;
}
int com_write_tag_unlocked(char* arg) {
char* ab = strtok(arg, " ");
if (ab) {
printf("This command doesn't take any arguments\n");
return -1;
}
// Issue the write request
mf_write_tag(¤t_tag, MF_KEY_UNLOCKED);
return 0;
}
int com_print(char* arg) {
char* a = strtok(arg, " ");
if (a && strtok(NULL, " ") != (char*)NULL) {
printf("Too many arguments\n");
return -1;
}
mf_size_t size = parse_size_default(a, MF_1K);
if (size == MF_INVALID_SIZE) {
printf("Unknown argument: %s\n", a);
return -1;
}
print_tag(size);
return 0;
}
int com_print_head(char* arg) {
print_tag_head();
return 0;
}
int com_set(char* arg) {
char* block_str = strtok(arg, " ");
char* offset_str = strtok(NULL, " ");
char* byte_str = strtok(NULL, " ");
if (!block_str || !offset_str || !byte_str) {
printf("Too few arguments: #block #offset xx xx xx .. xx\n");
return -1;
}
unsigned int block = (unsigned int) strtoul(block_str, &block_str, 16);
if (*block_str != '\0') {
printf("Invalid block character (non hex): %s\n", block_str);
return -1;
}
if (block > 0xff) {
printf("Invalid block [0,ff]: %x\n", block);
return -1;
}
unsigned int offset = (unsigned int) strtoul(offset_str, &offset_str, 16);
if (*offset_str != '\0') {
printf("Invalid offset character (non hex): %s\n", offset_str);
return -1;
}
if (offset > 0x0f) {
printf("Invalid offset [0,f]: %x\n", offset);
return -1;
}
// Consume the byte tokens
do {
long int byte = strtol(byte_str, &byte_str, 16);
if (*byte_str != '\0') {
printf("Invalid byte character (non hex): %s\n", byte_str);
return -1;
}
if (byte < 0 || byte > 0xff) {
printf("Invalid byte value [0,ff]: %lx\n", byte);
return -1;
}
if (offset > 0x0f) {
printf("Too many bytes specified.\n");
return -1;
}
// Write the data
current_tag.amb[block].mbd.abtData[offset++] = (uint8_t)byte;
} while((byte_str = strtok(NULL, " ")) != (char*)NULL);
return 0;
}
int com_setuid(char* arg) {
char* byte_str = strtok(arg, " ");
int block = 0;
/// TODO : Check arg size (display warning if < 4)
if (!byte_str) {
printf("Too few arguments: xx xx xx xx\n");
return -1;
}
// Consume the byte tokens
do {
long int byte = strtol(byte_str, &byte_str, 16);
if (byte < 0 || byte > 0xff) {
printf("Invalid byte value [0,ff]: %lx\n", byte);
return -1;
}
// Write the data
current_tag.amb[0].mbd.abtData[block++] = (uint8_t)byte;
} while(((byte_str = strtok(NULL, " ")) != (char*)NULL) && (block < 4));
// Compute and write BCC
current_tag.amb[0].mbd.abtData[4] = (uint8_t)current_tag.amb[0].mbd.abtData[0] ^
(uint8_t)current_tag.amb[0].mbd.abtData[1] ^
(uint8_t)current_tag.amb[0].mbd.abtData[2] ^
(uint8_t)current_tag.amb[0].mbd.abtData[3];
return 0;
}
int com_print_keys(char* arg) {
char* a = strtok(arg, " ");
if (a && strtok(NULL, " ") != (char*)NULL) {
printf("Too many arguments\n");
return -1;
}
mf_size_t size = parse_size_default(a, MF_1K);
if (size == MF_INVALID_SIZE) {
printf("Unknown argument: %s\n", a);
return -1;
}
print_keys(¤t_tag, size);
return 0;
}
int com_print_ac(char* arg) {
if (strtok(arg, " ") != (char*)NULL) {
printf("Too many arguments\n");
return -1;
}
print_ac(¤t_tag);
return 0;
}
int com_keys_load(char* arg) {
int res = load_auth(arg);
if (res == 0)
printf("Successfully loaded keys from: %s\n", arg);
return 0;
}
int com_keys_save(char* arg) {
int res = save_auth(arg);
if (res == 0)
printf("Successfully wrote keys to: %s\n", arg);
return 0;
}
int com_keys_clear(char* arg) {
clear_tag(¤t_auth);
return 0;
}
int com_keys_set(char* arg) {
// Arg format: A|B #S key
char* ab = strtok(arg, " ");
char* sector_str = strtok(NULL, " ");
char* key_str = strtok(NULL, " ");
if (strtok(NULL, " ") != (char*)NULL) {
printf("Too many arguments\n");
return -1;
}
if (!ab || !sector_str || !key_str) {
printf("Too few arguments: (A|B) #sector key\n");
return -1;
}
// Read sector
long int sector = strtol(sector_str, §or_str, 16);
// Sanity check sector range
if (*sector_str != '\0') {
printf("Invalid sector character (non hex): %s\n", sector_str);
return -1;
}
if (sector < 0 || sector > 0x1b) {
printf("Invalid sector [0,1b]: %lx\n", sector);
return -1;
}
// Sanity check key length
if (strncmp(key_str, "0x", 2) == 0)
key_str += 2;
if (strlen(key_str) != 12) {
printf("Invalid key (6 byte hex): %s\n", key_str);
return -1;
}
// Compute the block that houses the key for the desired sector
size_t block = sector_to_trailer((size_t)sector);
// Parse key selection and point to appropriate key
uint8_t* key;
mf_key_type_t key_type = parse_key_type(ab);
if (key_type == MF_KEY_A)
key = current_auth.amb[block].mbt.abtKeyA;
else if (key_type == MF_KEY_B)
key = current_auth.amb[block].mbt.abtKeyB;
else {
printf("Invalid argument (A|B): %s\n", ab);
return -1;
}
// Parse the key
if (read_key(key, key_str) == NULL) {
printf("Invalid key character (non hex)\n");
return -1;
}
return 0;
}
int com_keys_import(char* arg) {
import_auth();
return 0;
}
int com_keys_test(char* arg) {
// Arg format: 1k|4k A|B
char* s = strtok(arg, " ");
char* ab = strtok(NULL, " ");
if (s && ab && strtok(NULL, " ") != NULL) {
printf("Too many arguments\n");
return -1;
}
if (!s || !ab) {
printf("Too few arguments: (1k|4k) (A|B)\n");
return -1;
}
// Parse arguments
mf_size_t size = parse_size(s);
if (size == MF_INVALID_SIZE) {
printf("Unknown size argument (1k|4k): %s\n", s);
return -1;
}
mf_key_type_t key_type = parse_key_type(ab);
if (key_type == MF_INVALID_KEY_TYPE) {
printf("Unknown key type argument (A|B): %s\n", ab);
return -1;
}
// Run the auth test
mf_test_auth(¤t_auth, size, key_type);
return 0;
}
int com_keys_print(char* arg) {
char* a = strtok(arg, " ");
if (a && strtok(NULL, " ") != (char*)NULL) {
printf("Too many arguments\n");
return -1;
}
mf_size_t size = parse_size_default(a, MF_1K);
if (size == MF_INVALID_SIZE) {
printf("Unknown argument: %s\n", a);
return -1;
}
print_keys(¤t_auth, size);
return 0;
}
int com_dict_load(char* arg) {
FILE* dict_file = fopen(arg, "r");
if (dict_file == NULL) {
printf("Could not open file: %s\n", arg);
return 1;
}
dictionary_import(dict_file);
fclose(dict_file);
return 0;
}
int com_dict_clear(char* arg) {
dictionary_clear();
return 0;
}
int com_dict_attack(char* arg) {
// Not much point if we don't have any keys
if (!dictionary_get()) {
printf("Dictionary is empty!\n");
return -1;
}
mf_dictionary_attack(¤t_auth);
return 0;
}
int com_dict_print(char* arg) {
key_list_t* kl = dictionary_get();
int count = 0;
while(kl) {
printf("%s\n", sprint_key(kl->key));
kl = kl->next;
++count;
}
printf("Dictionary contains: %d keys\n", count);
return 0;
}
int com_spec_print(char* arg) {
print_instance_tree();
return 0;
}
int com_spec_load(char* arg) {
// Start by clearing the current hierarcy
clear_instance_tree();
tt_clear();
// Open the file
FILE* spec_file = fopen(arg, "r");
if (spec_file == NULL) {
printf("Could not open file: %s\n", arg);
return 1;
}
// Parse the specification
spec_import(spec_file);
fclose(spec_file);
return 0;
}
int com_spec_clear(char* arg) {
clear_instance_tree();
tt_clear();
return 0;
}
int com_mac_key_get_set(char* arg) {
char* key_str = strtok(arg, " ");
if (key_str == 0) {
printf("Current MAC key: \n");
print_hex_array_sep(current_mac_key, 8, " ");
printf("\n");
return 0;
}
uint8_t key[8];
int key_ptr = 0;
// Consume the key tokens
do {
long int byte = strtol(key_str, &key_str, 16);
if (*key_str != '\0') {
printf("Invalid key character (non hex): %s\n", key_str);
return -1;
}
if (byte < 0 || byte > 0xff) {
printf("Invalid byte value [0,ff]: %lx\n", byte);
return -1;
}
if (key_ptr > sizeof(key)) {
printf("Too many bytes specified in key (should be 8).\n");
return -1;
}
// Accept the byte and add it to the key
key[key_ptr++] = (uint8_t)byte;
} while((key_str = strtok(NULL, " ")) != (char*)NULL);
if (key_ptr != sizeof(key)) {
printf("Too few bytes specified in key (should be 8).\n");
return -1;
}
// Everything ok, so update the global
memcpy(current_mac_key, key, 8);
return 0;
}
int com_mac_block_compute(char* arg) {
return com_mac_block_compute_impl(arg, 0);
}
int com_mac_block_update(char* arg) {
return com_mac_block_compute_impl(arg, 1);
}
int com_mac_block_compute_impl(char* arg, int update) {
char* block_str = strtok(arg, " ");
if (!block_str) {
printf("Too few arguments: #block\n");
return -1;
}
unsigned int block = (unsigned int) strtoul(block_str, &block_str, 16);
if (*block_str != '\0') {
printf("Invalid block character (non hex): %s\n", block_str);
return -1;
}
if (block > 0xff) {
printf("Invalid block [0,ff]: %x\n", block);
return -1;
}
// Use the key
unsigned char* mac = compute_block_mac(block, current_mac_key, update);
// MAC is null on error, else 8 bytes
if (mac == 0)
return -1;
// Only need 16 MSBs.
printf("Block %2.2x, MAC : ", block);
print_hex_array_sep(mac, 2, " ");
printf("\n");
return 0;
}
int com_mac_validate(char* arg) {
char* a = strtok(arg, " ");
if (a && strtok(NULL, " ") != (char*)NULL) {
printf("Too many arguments\n");
return -1;
}
mf_size_t size = parse_size_default(a, MF_1K);
if (size == MF_INVALID_SIZE) {
printf("Unknown argument: %s\n", a);
return -1;
}
for (unsigned int i = 1; i < block_count(size); ++i) {
if (is_trailer_block(i))
continue;
unsigned char* mac = compute_block_mac(i, current_mac_key, 0);
printf("Block: %2x ", i);
printf("Tag: ");
print_hex_array_sep(¤t_tag.amb[i].mbd.abtData[14], 2, " ");
printf(" Computed: ");
print_hex_array_sep(mac, 2, " ");
printf(" Result: ");
if (memcmp(mac, ¤t_tag.amb[i].mbd.abtData[14], 2) == 0)
printf("VALID");
else
printf("IN-VALID");
printf("\n");
}
return 0;
}
mf_size_t parse_size(const char* str) {
if (str == NULL)
return MF_INVALID_SIZE;
if (strcasecmp(str, "1k") == 0)
return MF_1K;
if (strcasecmp(str, "4k") == 0)
return MF_4K;
return MF_INVALID_SIZE;
}
mf_size_t parse_size_default(const char* str, mf_size_t default_size) {
if (str == NULL)
return default_size;
return parse_size(str);
}
mf_key_type_t parse_key_type(const char* str) {
if (str == NULL)
return MF_INVALID_KEY_TYPE;
if (strcasecmp(str, "a") == 0)
return MF_KEY_A;
if (strcasecmp(str, "b") == 0)
return MF_KEY_B;
return MF_INVALID_KEY_TYPE;
}
mf_key_type_t parse_key_type_default(const char* str,
mf_key_type_t default_type) {
if (str == NULL)
return default_type;
return parse_key_type(str);
}
// Any command starting with '.' - path spec
int exec_path_command(const char *line) {
instance_t* inst = parse_spec_path(line);
if (inst)
print_tag_data_range(inst->offset_bytes, inst->offset_bits,
inst->size_bytes, inst->size_bits);
else
printf("Invalid Path\n");
return 0;
}