Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changed hf iclass view/decrypt to detect SIO lengths better and show if legacy credentials are encrypted #2117

Merged
merged 1 commit into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file.
This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log...

## [unreleased][unreleased]
- Changed `hf iclass view/decrypt` to detect SIO lengths better and show if legacy credentials are encrypted (@nvx)
- Changed the json file formats for mfc, 14b, 15, legic, cryptorf, ndef (@iceman1001)
- Depricated the EML file format when saving dump files. (@iceman1001)
- Added `sim014.bin` - new sim module firmware v4.42 with improved ISO7816 Protocol T0 support (@gentilkiwi)
Expand Down
160 changes: 97 additions & 63 deletions client/src/cmdhficlass.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ static uint8_t empty[PICOPASS_BLOCK_SIZE] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
static uint8_t zeros[PICOPASS_BLOCK_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

static int CmdHelp(const char *Cmd);
static void printIclassSIO(uint8_t *iclass_dump);
static void print_iclass_sio(uint8_t *iclass_dump, size_t dump_len);

static uint8_t iClass_Key_Table[ICLASS_KEYS_MAX][PICOPASS_BLOCK_SIZE] = {
{ 0xAE, 0xA6, 0x84, 0xA6, 0xDA, 0xB2, 0x32, 0x78 },
Expand Down Expand Up @@ -1215,7 +1215,7 @@ static int CmdHFiClassEView(const char *Cmd) {
printIclassDumpContents(dump, 1, blocks, bytes, dense_output);

if (verbose) {
printIclassSIO(dump);
print_iclass_sio(dump, bytes);
}

free(dump);
Expand Down Expand Up @@ -1266,7 +1266,8 @@ static int CmdHFiClassESetBlk(const char *Cmd) {
}

static void iclass_decode_credentials(uint8_t *data) {
if (memcmp(data + (5 * PICOPASS_BLOCK_SIZE), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", PICOPASS_BLOCK_SIZE)) {
picopass_hdr_t *hdr = (picopass_hdr_t *)data;
if (memcmp(hdr->app_issuer_area, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", PICOPASS_BLOCK_SIZE)) {
// Not a Legacy or SR card, nothing to do here.
return;
}
Expand Down Expand Up @@ -1502,7 +1503,7 @@ static int CmdHFiClassDecrypt(const char *Cmd) {
printIclassDumpContents(decrypted, 1, (decryptedlen / 8), decryptedlen, dense_output);

if (verbose) {
printIclassSIO(decrypted);
print_iclass_sio(decrypted, decryptedlen);
}

PrintAndLogEx(NORMAL, "");
Expand Down Expand Up @@ -2750,67 +2751,99 @@ static int CmdHFiClass_loclass(const char *Cmd) {
return bruteforceFileNoKeys(filename);
}

static void detect_credential(uint8_t *data, bool *legacy, bool *se, bool *sr) {
*legacy = false;
*sr = false;
*se = false;
static void detect_credential(uint8_t *iclass_dump, size_t dump_len, bool *is_legacy, bool *is_se, bool *is_sr, uint8_t **sio_start_ptr, size_t *sio_length) {
*is_legacy = false;
*is_sr = false;
*is_se = false;
if (sio_start_ptr != NULL) {
*sio_start_ptr = NULL;
}
if (sio_length != NULL) {
*sio_length = 0;
}

if (dump_len < sizeof(picopass_hdr_t)) {
// Can't really do anything with a dump that doesn't include the header
return;
}

picopass_hdr_t *hdr = (picopass_hdr_t *)iclass_dump;

if (!memcmp(hdr->app_issuer_area, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", PICOPASS_BLOCK_SIZE)) {
// Legacy AIA
if (!memcmp(data + (5 * PICOPASS_BLOCK_SIZE), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", PICOPASS_BLOCK_SIZE)) {
*legacy = true;
*is_legacy = true;

if (dump_len < 11 * PICOPASS_BLOCK_SIZE) {
// Can't reliably detect if the card is SR without checking
// blocks 6 and 10
return;
}

// SR bit set in legacy config block
if ((data[6 * PICOPASS_BLOCK_SIZE] & ICLASS_CFG_BLK_SR_BIT) == ICLASS_CFG_BLK_SR_BIT) {
if ((iclass_dump[6 * PICOPASS_BLOCK_SIZE] & ICLASS_CFG_BLK_SR_BIT) == ICLASS_CFG_BLK_SR_BIT) {
// If the card is blank (all FF's) then we'll reach here too, so check for an empty block 10
// to avoid false positivies
if (memcmp(data + (10 * PICOPASS_BLOCK_SIZE), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", PICOPASS_BLOCK_SIZE)) {
*sr = true;
if (memcmp(iclass_dump + (10 * PICOPASS_BLOCK_SIZE), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", PICOPASS_BLOCK_SIZE)) {
*is_sr = true;
if (sio_start_ptr != NULL) {
// SR SIO starts at block 10
*sio_start_ptr = iclass_dump + (10 * PICOPASS_BLOCK_SIZE);
}
}
}
} else if (!memcmp(hdr->app_issuer_area, "\xFF\xFF\xFF\x00\x06\xFF\xFF\xFF", PICOPASS_BLOCK_SIZE)) {
// SE AIA
*is_se = true;

if (sio_start_ptr != NULL) {
// SE SIO starts at block 6
*sio_start_ptr = iclass_dump + (6 * PICOPASS_BLOCK_SIZE);
}
}

if (sio_length == NULL || sio_start_ptr == NULL || *sio_start_ptr == NULL) {
// No need to calculate length
return;
}

// SE AIA
if (!memcmp(data + (5 * PICOPASS_BLOCK_SIZE), "\xFF\xFF\xFF\x00\x06\xFF\xFF\xFF", PICOPASS_BLOCK_SIZE)) {
*se = true;
uint8_t *sio_start = *sio_start_ptr;

if (sio_start[0] != 0x30) {
// SIOs always start with a SEQUENCE(P), if this is missing then bail
return;
}

if (sio_start[1] >= 0x80 || sio_start[1] == 0x00) {
// We only support definite short form lengths
return;
}

// Length of bytes within the SEQUENCE, plus tag and length bytes for the SEQUENCE tag
*sio_length = sio_start[1] + 2;
}

// print ASN1 decoded array in TLV view
static void printIclassSIO(uint8_t *iclass_dump) {
bool isLegacy, isSE, isSR;
detect_credential(iclass_dump, &isLegacy, &isSE, &isSR);

static void print_iclass_sio(uint8_t *iclass_dump, size_t dump_len) {
bool is_legacy, is_se, is_sr;
uint8_t *sio_start;
if (isSE) {
// SE SIO starts at block 6
sio_start = iclass_dump + (6 * PICOPASS_BLOCK_SIZE);
} else if (isSR) {
// SR SIO starts at block 10
sio_start = iclass_dump + (10 * PICOPASS_BLOCK_SIZE);
} else {
// No SIO on Legacy credentials
size_t sio_length;
detect_credential(iclass_dump, dump_len, &is_legacy, &is_se, &is_sr, &sio_start, &sio_length);

if (sio_start == NULL) {
return;
}

// Readers assume the SIO always fits within 7 blocks (they don't read any further blocks)
// Search backwards to find the last 0x05 0x00 seen at the end of the SIO
const uint8_t pattern_sio_end[] = {0x05, 0x00};
int dlen = byte_strrstr(sio_start, 7 * PICOPASS_BLOCK_SIZE, pattern_sio_end, 2);
if (dlen == -1) {
if (dump_len < sio_length + (sio_start - iclass_dump)) {
// SIO length exceeds the size of the dump we have, bail
return;
}

dlen += sizeof(pattern_sio_end);

PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "---------------------------- " _CYAN_("SIO - RAW") " ----------------------------");
print_hex_noascii_break(sio_start, dlen, 32);
print_hex_noascii_break(sio_start, sio_length, 32);
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, "------------------------- " _CYAN_("SIO - ASN1 TLV") " --------------------------");
asn1_print(sio_start, dlen, " ");
asn1_print(sio_start, sio_length, " ");
PrintAndLogEx(NORMAL, "");
}

Expand Down Expand Up @@ -2862,9 +2895,17 @@ void printIclassDumpContents(uint8_t *iclass_dump, uint8_t startblock, uint8_t e
);
*/

bool isLegacy = false, isSE = false, isSR = false;
if (filemaxblock >= 17) {
detect_credential(iclass_dump, &isLegacy, &isSE, &isSR);
bool is_legacy, is_se, is_sr;
uint8_t *sio_start;
size_t sio_length;
detect_credential(iclass_dump, endblock * 8, &is_legacy, &is_se, &is_sr, &sio_start, &sio_length);

bool is_legacy_decrypted = is_legacy && (iclass_dump[(6 * PICOPASS_BLOCK_SIZE) + 7] & 0x03) == 0x00;

int sio_start_block = 0, sio_end_block = 0;
if (sio_start && sio_length > 0) {
sio_start_block = (sio_start - iclass_dump) / PICOPASS_BLOCK_SIZE;
sio_end_block = sio_start_block + (sio_length + PICOPASS_BLOCK_SIZE - 1) / PICOPASS_BLOCK_SIZE - 1;
}

int i = startblock;
Expand Down Expand Up @@ -2937,32 +2978,25 @@ void printIclassDumpContents(uint8_t *iclass_dump, uint8_t startblock, uint8_t e
} else {
const char *info_ks[] = {"CSN", "Config", "E-purse", "Debit", "Credit", "AIA", "User"};

if (i >= 6 && i <= 9 && isLegacy && isSE == false) {
if (i >= 6 && i <= 9 && is_legacy) {
// legacy credential
PrintAndLogEx(INFO, "%3d/0x%02X | " _YELLOW_("%s") "| " _YELLOW_("%s") " | %s | User / Cred "
, i
, i
, sprint_hex(blk, 8)
, sprint_ascii(blk, 8)
, lockstr
);
} else if (i >= 6 && i <= 12 && isSE) {
// SIO credential
PrintAndLogEx(INFO, "%3d/0x%02X | " _CYAN_("%s") "| " _CYAN_("%s") " | %s | User / SIO / SE"
PrintAndLogEx(INFO, "%3d/0x%02X | " _YELLOW_("%s") "| " _YELLOW_("%s") " | %s | User / %s "
, i
, i
, sprint_hex(blk, 8)
, sprint_ascii(blk, 8)
, lockstr
, i == 6 ? "HID CFG" : (is_legacy_decrypted ? "Cred" : "Enc Cred")
);
} else if (i >= 10 && i <= 16 && isSR) {
} else if (sio_start_block != 0 && i >= sio_start_block && i <= sio_end_block) {
// SIO credential
PrintAndLogEx(INFO, "%3d/0x%02X | " _CYAN_("%s") "| " _CYAN_("%s") " | %s | User / SIO / SR"
PrintAndLogEx(INFO, "%3d/0x%02X | " _CYAN_("%s") "| " _CYAN_("%s") " | %s | User / SIO / %s"
, i
, i
, sprint_hex(blk, 8)
, sprint_ascii(blk, 8)
, lockstr
, is_se ? "SE" : "SR"
);
} else {
if (i < 6) {
Expand All @@ -2977,7 +3011,7 @@ void printIclassDumpContents(uint8_t *iclass_dump, uint8_t startblock, uint8_t e

if (regular_print_block) {
// suppress repeating blocks, truncate as such that the first and last block with the same data is shown
// but the blocks in between are replaced with a single line of "*" if dense_output is enabled
// but the blocks in between are replaced with a single line of "......" if dense_output is enabled
if (dense_output && i > 6 && i < (endblock - 1) && !in_repeated_block && !memcmp(blk, blk - 8, 8) &&
!memcmp(blk, blk + 8, 8) && !memcmp(blk, blk + 16, 8)) {
// we're in a user block that isn't the first user block nor last two user blocks,
Expand All @@ -3003,13 +3037,13 @@ void printIclassDumpContents(uint8_t *iclass_dump, uint8_t startblock, uint8_t e
i++;
}
PrintAndLogEx(INFO, "---------+-------------------------+----------+---+----------------");
if (isLegacy)
if (is_legacy)
PrintAndLogEx(HINT, _YELLOW_("yellow") " = legacy credential");

if (isSE)
if (is_se)
PrintAndLogEx(HINT, _CYAN_("cyan") " = SIO / SE credential");

if (isSR)
if (is_sr)
PrintAndLogEx(HINT, _CYAN_("cyan") " = SIO / SR credential");

PrintAndLogEx(NORMAL, "");
Expand Down Expand Up @@ -3067,7 +3101,7 @@ static int CmdHFiClassView(const char *Cmd) {
iclass_decode_credentials(dump);

if (verbose) {
printIclassSIO(dump);
print_iclass_sio(dump, bytes_read);
}

free(dump);
Expand Down Expand Up @@ -4535,13 +4569,13 @@ int info_iclass(bool shallow_mod) {
memcpy(aia, hdr->app_issuer_area, sizeof(aia));
}

// if CSN ends with FF12E0, it's inside HID CSN range.
bool isHidRange = (memcmp(hdr->csn + 5, "\xFF\x12\xE0", 3) == 0);
// if CSN starts with E012FFF (big endian), it's inside HID CSN range.
bool is_hid_range = (hdr->csn[4] & 0xF0) == 0xF0 && (memcmp(hdr->csn + 5, "\xFF\x12\xE0", 3) == 0);

bool legacy = (memcmp(aia, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) == 0);
bool se_enabled = (memcmp(aia, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0);
if (is_hid_range) {
bool legacy = (memcmp(aia, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) == 0);
bool se_enabled = (memcmp(aia, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0);

if (isHidRange) {
PrintAndLogEx(SUCCESS, " CSN.......... " _YELLOW_("HID range"));
if (legacy)
PrintAndLogEx(SUCCESS, " Credential... " _GREEN_("iCLASS legacy"));
Expand Down
Loading