Merge branch 'master' into master

Signed-off-by: Iceman <iceman@iuse.se>
This commit is contained in:
Iceman
2024-10-23 17:59:52 +02:00
committed by GitHub
10 changed files with 344 additions and 248 deletions
+2
View File
@@ -6,6 +6,8 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac
- Changed `hf iclass info` - now checks for cards silicon version (@antiklesys)
- Changed `hf iclass legrec` - updated script implementation to ensure functionality (@antiklesys)
- Added recovered iclass custom key to dictionary (@antiklesys)
- Added support for all Hitag S response protocol mode (@douniwan5788)
- Fixed 'hf_young.c' - flags declaration was missing a semicolon (@jakkpotts)
- Changed `hf mf sim` - add option to allow key b to be used even if readable (@doegox)
- Changed `data num` - outputed binary strings are now properly zero padded (@iceman1001)
- Changed `hf iclass info` - now tries default keys and decode if legacy (@iceman1001)
+1 -1
View File
@@ -236,7 +236,7 @@ void RunMod(void) {
int button_pressed = BUTTON_HELD(1000);
if (button_pressed == BUTTON_NO_CLICK) { // No button action, proceed with sim
uint16_t flags = 0
uint16_t flags = 0;
FLAG_SET_UID_IN_DATA(flags, 4);
uint8_t data[PM3_CMD_DATA_SIZE] = {0}; // in case there is a read command received we shouldn't break
+168 -190
View File
File diff suppressed because it is too large Load Diff
+7 -1
View File
@@ -1534,7 +1534,7 @@ void MifareStaticNested(uint8_t blockNo, uint8_t keyType, uint8_t targetBlockNo,
continue;
};
if (mifare_classic_authex(pcs, cuid, blockNo, keyType, ui64Key, AUTH_NESTED, NULL, NULL)) {
if (mifare_classic_authex(pcs, cuid, blockNo, keyType, ui64Key, AUTH_NESTED, &nt2, NULL)) {
continue;
};
@@ -1544,6 +1544,12 @@ void MifareStaticNested(uint8_t blockNo, uint8_t keyType, uint8_t targetBlockNo,
};
nt3 = bytes_to_num(receivedAnswer, 4);
// fix for cards with distance 0
if (nt1 == nt2) {
target_nt[0] = nt1;
target_nt[1] = nt1;
target_ks[0] = nt3 ^ target_nt[0];
}
target_ks[1] = nt3 ^ target_nt[1];
isOK = PM3_SUCCESS;
+2 -2
View File
@@ -191,7 +191,7 @@ local function read_config()
if magicconfig == nil then lib14a.disconnect(); return nil, "can't read configuration, "..err_lock end
if #magicconfig ~= 64 and #magicconfig ~= 68 then lib14a.disconnect(); return nil, "partial read of configuration, "..err_lock end
if gtumode == '00' then gtustr = 'Pre-write/Shadow Mode'
elseif gtumode == '01' then gtustr = 'Restore Mode'
elseif gtumode == '01' or gtumode == '04' then gtustr = 'Restore Mode'
elseif gtumode == '02' then gtustr = 'Disabled'
elseif gtumode == '03' then gtustr = 'Disabled, high speed R/W mode for Ultralight'
end
@@ -553,7 +553,7 @@ local function write_gtu(gtu)
if gtu == '00' then
print('Enabling GTU Pre-Write')
send('CF'.._key..'32'..gtu)
elseif gtu == '01' then
elseif gtu == '01' or gtu == '04' then
print('Enabling GTU Restore Mode')
send('CF'.._key..'32'..gtu)
elseif gtu == '02' then
+51 -7
View File
@@ -76,6 +76,10 @@ parser.add_argument('-x', '--init-check', action='store_true', help='Run an init
parser.add_argument('-y', '--final-check', action='store_true', help='Run a final fchk with the found keys')
parser.add_argument('-k', '--keep', action='store_true', help='Keep generated dictionaries after processing')
parser.add_argument('-d', '--debug', action='store_true', help='Enable debug mode')
parser.add_argument('-s', '--supply-chain', action='store_true', help='Enable supply-chain mode. Look for hf-mf-XXXXXXXX-default_nonces.json')
# Such json can be produced from the json saved by
# "hf mf isen --collect_fm11rf08s --key A396EFA4E24F" on a wiped card, then processed with
# jq '{Created: .Created, FileType: "fm11rf08s_default_nonces", nt: .nt | del(.["32"]) | map_values(.a)}'
args = parser.parse_args()
start_time = time.time()
@@ -191,6 +195,22 @@ if os.path.isfile(DICT_DEF_PATH):
else:
print(f"Warning, {DICT_DEF} not found.")
dict_dnwd = None
def_nt = ["" for _ in range(NUM_SECTORS)]
if args.supply_chain:
try:
default_nonces = f'{save_path}hf-mf-{uid:04X}-default_nonces.json'
with open(default_nonces, 'r') as file:
# Load and parse the JSON data
dict_dnwd = json.load(file)
for sec in range(NUM_SECTORS):
def_nt[sec] = dict_dnwd["nt"][f"{sec}"].lower()
print(f"Loaded default nonces from {default_nonces}.")
except FileNotFoundError:
pass
except json.decoder.JSONDecodeError:
print(f"Error parsing {default_nonces}, skipping.")
print("Running staticnested_1nt & 2x1nt when doable...")
keys = [[set(), set()] for _ in range(NUM_SECTORS + NUM_EXTRA_SECTORS)]
all_keys = set()
@@ -225,9 +245,21 @@ for sec in range(NUM_SECTORS + NUM_EXTRA_SECTORS):
keys[sec][key_type] = keys_set.copy()
duplicates.update(all_keys.intersection(keys_set))
all_keys.update(keys_set)
# Prioritize default keys
keys_def_set = DEFAULT_KEYS.intersection(keys_set)
keys_set.difference_update(DEFAULT_KEYS)
if dict_dnwd is not None and sec < NUM_SECTORS:
# Prioritize keys from supply-chain attack
cmd = [tools["staticnested_2x1nt1key"], def_nt[sec], "FFFFFFFFFFFF", f"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type]}_filtered.dic"]
if args.debug:
print(' '.join(cmd))
result = subprocess.run(cmd, capture_output=True, text=True).stdout
keys_def_set = set()
for line in result.split('\n'):
if "MATCH:" in line:
keys_def_set.add(line[12:])
keys_set.difference_update(keys_def_set)
else:
# Prioritize default keys
keys_def_set = DEFAULT_KEYS.intersection(keys_set)
keys_set.difference_update(keys_def_set)
# Prioritize sector 32 keyB starting with 0000
if real_sec == 32:
keyb32cands = set(x for x in keys_set if x.startswith("0000"))
@@ -257,9 +289,21 @@ for sec in range(NUM_SECTORS + NUM_EXTRA_SECTORS):
keys[sec][key_type] = keys_set.copy()
duplicates.update(all_keys.intersection(keys_set))
all_keys.update(keys_set)
# Prioritize default keys
keys_def_set = DEFAULT_KEYS.intersection(keys_set)
keys_set.difference_update(DEFAULT_KEYS)
if dict_dnwd is not None and sec < NUM_SECTORS:
# Prioritize keys from supply-chain attack
cmd = [tools["staticnested_2x1nt1key"], def_nt[sec], "FFFFFFFFFFFF", f"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type]}.dic"]
if args.debug:
print(' '.join(cmd))
result = subprocess.run(cmd, capture_output=True, text=True).stdout
keys_def_set = set()
for line in result.split('\n'):
if "MATCH:" in line:
keys_def_set.add(line[12:])
keys_set.difference_update(keys_def_set)
else:
# Prioritize default keys
keys_def_set = DEFAULT_KEYS.intersection(keys_set)
keys_set.difference_update(keys_def_set)
if len(keys_def_set) > 0:
found_default[sec][key_type] = True
with (open(f"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type]}.dic", "w")) as f:
@@ -484,6 +528,7 @@ if abort:
print("Brute-forcing phase aborted via keyboard!")
args.final_check = False
plus = "[" + color("+", fg="green") + "] "
if args.final_check:
print("Letting fchk do a final dump, just for confirmation and display...")
keys_set = set([i for sl in found_keys for i in sl if i != ""])
@@ -495,7 +540,6 @@ if args.final_check:
print(cmd)
p.console(cmd, passthru=True)
else:
plus = "[" + color("+", fg="green") + "] "
print()
print(plus + color("found keys:", fg="green"))
print()
+47
View File
@@ -1311,6 +1311,52 @@ static int CmdGallagherDecode(const char *cmd) {
return PM3_SUCCESS;
}
static int CmdGallagherEncode (const char *cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf gallagher encode",
"Encode a Gallagher credential block\n"
"Credential block can be specified with or without the bitwise inverse.",
"hf gallagher encode --rc 1 --fc 22153 --cn 1253518 --il 1"
);
void *argtable[] = {
arg_param_begin,
arg_u64_1("r", "rc", "<dec>", "Region code. 4 bits max"),
arg_u64_1("f", "fc", "<dec>", "Facility code. 2 bytes max"),
arg_u64_1("c", "cn", "<dec>", "Card number. 3 bytes max"),
arg_u64_1("i", "il", "<dec>", "Issue level. 4 bits max"),
arg_param_end
};
CLIExecWithReturn(ctx, cmd, argtable, false);
uint64_t region_code = arg_get_u64(ctx, 1); // uint4, input will be validated later
uint64_t facility_code = arg_get_u64(ctx, 2); // uint16
uint64_t card_number = arg_get_u64(ctx, 3); // uint24
uint64_t issue_level = arg_get_u64(ctx, 4); // uint4
CLIParserFree(ctx);
GallagherCredentials_t creds = {
.region_code = region_code,
.facility_code = facility_code,
.card_number = card_number,
.issue_level = issue_level,
};
uint8_t contents[16] = {0};
gallagher_encode_creds(contents, &creds);
for (int i = 0; i < 8; i++) {
contents[i + 8] = contents[i] ^ 0xFF;
}
PrintAndLogEx(SUCCESS, "Raw: " _YELLOW_("%s"), sprint_hex_inrow(contents, ARRAYLEN(contents)/2));
PrintAndLogEx(SUCCESS, "Bitwise: " _YELLOW_("%s"), sprint_hex_inrow(contents, ARRAYLEN(contents)));
return PM3_SUCCESS;
}
static command_t CommandTable[] = {
{"help", CmdHelp, AlwaysAvailable, "This help"},
@@ -1319,6 +1365,7 @@ static command_t CommandTable[] = {
{"delete", CmdGallagherDelete, IfPm3Iso14443, "Delete Gallagher credentials from a DESFire card"},
{"diversifykey", CmdGallagherDiversify, AlwaysAvailable, "Diversify Gallagher key"},
{"decode", CmdGallagherDecode, AlwaysAvailable, "Decode Gallagher credential block"},
{"encode", CmdGallagherEncode, AlwaysAvailable, "Encode Gallagher credential block"},
{NULL, NULL, NULL, NULL}
};
+2 -5
View File
@@ -4131,15 +4131,12 @@ static int CmdHF14AMfSim(const char *Cmd) {
uint8_t uid[10] = {0};
CLIGetHexWithReturn(ctx, 1, uid, &uidlen);
char uidsize[9] = {0};
if (uidlen > 0) {
FLAG_SET_UID_IN_DATA(flags, uidlen);
if (IS_FLAG_UID_IN_EMUL(flags)) {
PrintAndLogEx(WARNING, "Invalid parameter for UID");
CLIParserFree(ctx);
return PM3_EINVARG;
} else {
snprintf(uidsize, sizeof(uidsize), "%i bytes", uidlen);
}
}
@@ -4247,9 +4244,9 @@ static int CmdHF14AMfSim(const char *Cmd) {
}
}
PrintAndLogEx(INFO, _YELLOW_("MIFARE %s") " | %s UID " _YELLOW_("%s") ""
PrintAndLogEx(INFO, _YELLOW_("MIFARE %s") " | %i bytes UID " _YELLOW_("%s") ""
, csize
, uidsize
, uidlen
, (uidlen == 0) ? "n/a" : sprint_hex(uid, uidlen)
);
+37 -8
View File
@@ -147,6 +147,13 @@ static int process_hitags_common_args(CLIParserContext *ctx, lf_hitag_data_t *co
return PM3_EINVARG;
}
uint8_t mode = arg_get_int_def(ctx, 5, 3);
if (mode > 3) {
PrintAndLogEx(WARNING, "Wrong response protocol mode, expected 0, 1, 2 or 3, got %d", mode);
return PM3_EINVARG;
}
// complete options
switch (key_len) {
case HITAG_PASSWORD_SIZE:
@@ -194,6 +201,21 @@ static int process_hitags_common_args(CLIParserContext *ctx, lf_hitag_data_t *co
PrintAndLogEx(INFO, "Authenticating to " _YELLOW_("Hitag S") " in Crypto mode");
}
switch (mode) {
case 0:
packet->mode = HITAGS_UID_REQ_STD;
break;
case 1:
packet->mode = HITAGS_UID_REQ_ADV1;
break;
case 2:
packet->mode = HITAGS_UID_REQ_ADV2;
break;
default:
packet->mode = HITAGS_UID_REQ_FADV;
break;
}
return PM3_SUCCESS;
}
@@ -226,6 +248,9 @@ static void print_error(int8_t reason) {
case -10:
PrintAndLogEx(FAILED, "Write to page failed!");
break;
case -11:
PrintAndLogEx(FAILED, "Read page failed!");
break;
default:
// PM3_REASON_UNKNOWN
PrintAndLogEx(DEBUG, "DEBUG: Error - Hitag S failed");
@@ -254,8 +279,9 @@ static int CmdLFHitagSRead(const char *Cmd) {
arg_str0(NULL, "nrar", "<hex>", "nonce / answer writer, 8 hex bytes"),
arg_lit0(NULL, "crypto", "crypto mode"),
arg_str0("k", "key", "<hex>", "pwd or key, 4 or 6 hex bytes"),
arg_int0("m", "mode", "<dec>", "response protocol mode. 0 (Standard 00110), 1 (Advanced 11000), 2 (Advanced 11001), 3 (Fast Advanced 11010) (def: 3)"),
arg_int0("p", "page", "<dec>", "page address to read from"),
arg_int0("c", "count", "<dec>", "how many pages to read. '0' reads all pages up to the end page (default: 1)"),
arg_int0("c", "count", "<dec>", "how many pages to read. '0' reads all pages up to the end page (def: 1)"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
@@ -264,14 +290,14 @@ static int CmdLFHitagSRead(const char *Cmd) {
if (process_hitags_common_args(ctx, &packet) < 0) return PM3_EINVARG;
uint32_t page = arg_get_int_def(ctx, 5, 0);
uint32_t page = arg_get_int_def(ctx, 6, 0);
if (page > 255) {
PrintAndLogEx(WARNING, "Page address Invalid.");
return PM3_EINVARG;
}
uint32_t count = arg_get_int_def(ctx, 6, 1);
uint32_t count = arg_get_int_def(ctx, 7, 1);
if (count > HITAGS_MAX_PAGES) {
PrintAndLogEx(WARNING, "No more than 64 pages can be read at once.");
@@ -404,8 +430,10 @@ static int CmdLFHitagSRead(const char *Cmd) {
PrintAndLogEx(NORMAL, "Key");
} else
PrintAndLogEx(NORMAL, "Data");
} else
PrintAndLogEx(INFO, "%02u | -- -- -- -- | read failed reason: " _YELLOW_("%d"), page_addr, card->pages_reason[i]);
} else {
PrintAndLogEx(INFO, "% 3u | -- -- -- -- | .... | N/A | " NOLF, page_addr);
print_error(card->pages_reason[i]);
}
}
PrintAndLogEx(INFO, "----+-------------+-------+------+------");
@@ -438,6 +466,7 @@ static int CmdLFHitagSWrite(const char *Cmd) {
arg_str0(NULL, "nrar", "<hex>", "nonce / answer writer, 8 hex bytes"),
arg_lit0(NULL, "crypto", "crypto mode"),
arg_str0("k", "key", "<hex>", "pwd or key, 4 or 6 hex bytes"),
arg_int0("m", "mode", "<dec>", "response protocol mode. 0 (Standard 00110), 1 (Advanced 11000), 2 (Advanced 11001), 3 (Fast Advanced 11010) (def: 3)"),
arg_int1("p", "page", "<dec>", "page address to write to"),
arg_str1("d", "data", "<hex>", "data, 4 hex bytes"),
arg_param_end
@@ -448,12 +477,12 @@ static int CmdLFHitagSWrite(const char *Cmd) {
if (process_hitags_common_args(ctx, &packet) < 0) return PM3_EINVARG;
int page = arg_get_int_def(ctx, 5, 0);
int page = arg_get_int_def(ctx, 6, 0);
uint8_t data[HITAGS_PAGE_SIZE];
int data_len = 0;
int res = CLIParamHexToBuf(arg_get_str(ctx, 6), data, HITAGS_PAGE_SIZE, &data_len);
int res = CLIParamHexToBuf(arg_get_str(ctx, 7), data, HITAGS_PAGE_SIZE, &data_len);
if (res != 0) {
CLIParserFree(ctx);
return PM3_EINVARG;
@@ -538,7 +567,7 @@ static int CmdLFHitagSSim(const char *Cmd) {
CLIParserFree(ctx);
clearCommandBuffer();
SendCommandNG(CMD_LF_HITAGS_SIMULATE, NULL, 0);
SendCommandMIX(CMD_LF_HITAGS_SIMULATE, false, 0, 0, NULL, 0);
return PM3_SUCCESS;
}
+27 -34
View File
@@ -65,30 +65,6 @@ typedef enum {
HT2_LAST_CMD = HT2F_UID_ONLY,
} PACKED hitag_function;
typedef struct {
hitag_function cmd;
uint8_t page;
uint8_t page_count;
uint8_t data[HITAGS_PAGE_SIZE];
uint8_t NrAr[HITAG_NRAR_SIZE];
// unaligned access to key as uint64_t will abort.
// todo: Why does the compiler without -munaligned-access generate unaligned-access code in the first place?
uint8_t key[HITAG_CRYPTOKEY_SIZE] __attribute__((aligned(4)));
uint8_t pwd[HITAG_PASSWORD_SIZE];
// Hitag 1 section.
// will reuse pwd or key field.
uint8_t key_no;
uint8_t logdata_0[4];
uint8_t logdata_1[4];
uint8_t nonce[4];
} PACKED lf_hitag_data_t;
typedef struct {
int status;
uint8_t data[256];
} PACKED lf_hitag_crack_response_t;
//---------------------------------------------------------
// Hitag S
//---------------------------------------------------------
@@ -111,15 +87,6 @@ typedef enum TAG_STATE {
HT_WRITING_BLOCK_DATA
} TSATE;
//number of start-of-frame bits
typedef enum SOF_TYPE {
HT_STANDARD = 0,
HT_ADVANCED,
HT_FAST_ADVANCED,
HT_ONE,
HT_NO_BITS
} stype;
typedef struct {
// con0
uint8_t MEMT : 2;
@@ -156,7 +123,6 @@ struct hitagS_tag {
TSATE tstate; // tag-state
int max_page;
stype mode;
union {
uint8_t pages[64][4];
@@ -177,6 +143,33 @@ struct hitagS_tag {
} PACKED;
typedef struct {
hitag_function cmd;
uint8_t page;
uint8_t page_count;
uint8_t data[HITAGS_PAGE_SIZE];
uint8_t NrAr[HITAG_NRAR_SIZE];
// unaligned access to key as uint64_t will abort.
// todo: Why does the compiler without -munaligned-access generate unaligned-access code in the first place?
uint8_t key[HITAG_CRYPTOKEY_SIZE] __attribute__((aligned(4)));
uint8_t pwd[HITAG_PASSWORD_SIZE];
// Hitag 1 section.
// will reuse pwd or key field.
uint8_t key_no;
uint8_t logdata_0[4];
uint8_t logdata_1[4];
uint8_t nonce[4];
//Hitag s section
uint8_t mode;
} PACKED lf_hitag_data_t;
typedef struct {
int status;
uint8_t data[256];
} PACKED lf_hitag_crack_response_t;
typedef struct {
union {
uint8_t asBytes[HITAGS_PAGE_SIZE];