Merge branch 'master' into feature/iso14443b-dump

Signed-off-by: Iceman <iceman@iuse.se>
This commit is contained in:
Iceman
2026-07-02 16:13:42 +07:00
committed by GitHub
11 changed files with 285 additions and 55 deletions
+3 -3
View File
@@ -53,11 +53,11 @@ jobs:
if [ -f requirements.txt ]; then python3 -m pip install -r requirements.txt; fi
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -75,4 +75,4 @@ jobs:
run: make -j$((`nproc` + 1))
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
+6 -6
View File
@@ -21,9 +21,9 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: '3.12'
@@ -62,9 +62,9 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: '3.12'
@@ -104,9 +104,9 @@ jobs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: '3.12'
+1 -1
View File
@@ -8,7 +8,7 @@ jobs:
steps:
- uses: actions/checkout@master
- name: Changelog Reminder
uses: peterjgrainger/action-changelog-reminder@v1.2.0
uses: peterjgrainger/action-changelog-reminder@v1.3.0
with:
changelog_regex: 'CHANGELOG.md'
customPrMessage: 'You are welcome to add an entry to the CHANGELOG.md as well'
+8 -8
View File
@@ -21,9 +21,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: '3.12'
@@ -51,9 +51,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: '3.12'
@@ -82,9 +82,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: '3.12'
@@ -124,9 +124,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- uses: actions/setup-python@v5
- uses: actions/setup-python@v6
with:
python-version: '3.12'
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
check-unique:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: check unique keys in dic files
shell: bash
run: |
+3 -3
View File
@@ -105,7 +105,7 @@ jobs:
}
exit 1
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: make clean
run: make clean
@@ -155,7 +155,7 @@ jobs:
steps:
- name: WSL setup
uses: Vampire/setup-wsl@v5
uses: Vampire/setup-wsl@v7
with:
distribution: Ubuntu-24.04
update: "true"
@@ -185,7 +185,7 @@ jobs:
git config --global core.autocrlf false
git config --global core.eol lf
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: make clean
run: make clean
+3 -1
View File
@@ -3,7 +3,9 @@ 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]
- Added `hf 14b dump` support for Standard ISO14443-B tags: two-phase READ BINARY sweep (transparent EF, then SFI scan per ISO7816-4 §7.2.3) with a new `print_std_blocks` display (@thisiscamk)
- Added `hf 14b dump` support for Standard ISO14443-B tags (@thisiscamk)
- Added `hf 14b ctrdbl` and `hf 14b ctdump` commands for interacting with ASK CTS tags (@kormax)
- Fixed `hf legic migrate` failing to parse the optional DCF argument as hex (@IdanHo)
- Add support for parsing Finnish Helsinki Regional Transport (HRT) travel cards (@sanduuz)
- Added standalone mode `HF_DOEGOX_COMMIT`: DESFire suspended commit without relay (@doegox)
+1 -1
View File
@@ -1818,7 +1818,7 @@ static int iso14443b_select_cts_card(iso14b_cts_card_select_t *card) {
}
if (card) {
// pc. fc Product code, Facility code
// REQT response payload: Product code, FAB code
card->pc = r[0];
card->fc = r[1];
}
+241 -14
View File
@@ -48,6 +48,9 @@
// for static arrays
#define ST25TB_SR_BLOCK_SIZE 4
#define CTS_BLOCK_SIZE 2
#define CTS256_BLOCK_COUNT 16
#define CTS512_BLOCK_COUNT 32
// SR memory sizes
@@ -757,17 +760,44 @@ static void print_st_general_info(uint8_t *data, uint8_t len) {
PrintAndLogEx(SUCCESS, "Chip: %02X, " _YELLOW_("%s"), chipid, get_st_chip_model(chipid));
}
typedef struct {
uint8_t product_code;
const char *name;
uint8_t block_count;
} cts_product_info_t;
static const cts_product_info_t cts_products[] = {
{ASK_CTS_PRODUCT_CODE_CTS256B, "CTS256B", CTS256_BLOCK_COUNT},
{ASK_CTS_PRODUCT_CODE_CTS512B, "CTS512B", CTS512_BLOCK_COUNT},
};
static const cts_product_info_t cts_unknown_product = {
0x00,
"unknown",
CTS256_BLOCK_COUNT,
};
static const cts_product_info_t *get_cts_product_info(uint8_t product_code) {
for (size_t i = 0; i < ARRAYLEN(cts_products); i++) {
if (cts_products[i].product_code == product_code) {
return &cts_products[i];
}
}
return &cts_unknown_product;
}
// print UID info from ASK CT chips
static void print_ct_general_info(void *vcard) {
iso14b_cts_card_select_t card;
memcpy(&card, (iso14b_cts_card_select_t *)vcard, sizeof(iso14b_cts_card_select_t));
uint32_t uid32 = MemLeToUint4byte(card.uid);
const cts_product_info_t *product_info = get_cts_product_info(card.pc);
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(SUCCESS, "ASK C-Ticket");
PrintAndLogEx(SUCCESS, " UID: " _GREEN_("%s") " ( " _YELLOW_("%010u") " )", sprint_hex(card.uid, sizeof(card.uid)), uid32);
PrintAndLogEx(SUCCESS, " Product Code: %02X", card.pc);
PrintAndLogEx(SUCCESS, " Facility Code: %02X", card.fc);
PrintAndLogEx(INFO, "--- " _CYAN_("ASK CTS / C-ticket") " -----------------------");
PrintAndLogEx(SUCCESS, " UID : " _GREEN_("%s") " ( " _YELLOW_("%010u") " )", sprint_hex(card.uid, sizeof(card.uid)), uid32);
PrintAndLogEx(SUCCESS, " Product Code : %02X ( " _YELLOW_("%s") " )", card.pc, product_info->name);
PrintAndLogEx(SUCCESS, " Fab Code : %02X", card.fc);
PrintAndLogEx(NORMAL, "");
}
@@ -807,26 +837,26 @@ static void print_footer(void) {
PrintAndLogEx(NORMAL, "");
}
/*
static void print_ct_blocks(uint8_t *data, size_t len) {
size_t blocks = len / ST25TB_SR_BLOCK_SIZE;
size_t blocks = len / CTS_BLOCK_SIZE;
print_hdr();
PrintAndLogEx(NORMAL, "");
PrintAndLogEx(INFO, " block# | data | ascii");
PrintAndLogEx(INFO, "---------+-------+------");
for (int i = 0; i <= blocks; i++) {
for (int i = 0; i < blocks; i++) {
PrintAndLogEx(INFO,
"%3d/0x%02X | %s | %s | %s",
"%3d/0x%02X | %s| %s",
i,
i,
sprint_hex(data + (i * 4), 4),
" ",
sprint_ascii(data + (i * 4), 4)
sprint_hex(data + (i * CTS_BLOCK_SIZE), CTS_BLOCK_SIZE),
sprint_ascii(data + (i * CTS_BLOCK_SIZE), CTS_BLOCK_SIZE)
);
}
print_footer();
PrintAndLogEx(INFO, "---------+-------+------");
PrintAndLogEx(NORMAL, "");
}
*/
static void print_sr_blocks(uint8_t *data, size_t len, const uint8_t *uid, bool dense_output) {
@@ -1488,6 +1518,85 @@ static int CmdHF14Binfo(const char *Cmd) {
// #define ISO14443B_READ_BLK 0x08
// #define ISO14443B_WRITE_BLK 0x09
static int select_cts_card_14443b(bool disconnect, iso14b_cts_card_select_t *card, bool verbose) {
iso14b_raw_cmd_t packet = {
.flags = (ISO14B_CONNECT | ISO14B_SELECT_CTS | ISO14B_CLEARTRACE),
.timeout = 0,
.rawlen = 0,
};
if (disconnect) {
packet.flags |= ISO14B_DISCONNECT;
}
clearCommandBuffer();
PacketResponseNG resp;
SendCommandNG(CMD_HF_ISO14443B_COMMAND, (uint8_t *)&packet, sizeof(iso14b_raw_cmd_t));
if (WaitForResponseTimeout(CMD_HF_ISO14443B_COMMAND, &resp, TIMEOUT) == false) {
if (verbose) {
PrintAndLogEx(WARNING, "timeout while waiting for reply");
}
return PM3_ETIMEOUT;
}
if (resp.status != PM3_SUCCESS) {
if (verbose) {
PrintAndLogEx(FAILED, "ISO 14443-b CTS select failed");
}
return resp.status;
}
if (resp.length < sizeof(iso14b_cts_card_select_t)) {
if (verbose) {
PrintAndLogEx(FAILED, "ISO 14443-3 CTS wrong length");
}
return PM3_ELENGTH;
}
if (card) {
memcpy(card, resp.data.asBytes, sizeof(iso14b_cts_card_select_t));
}
return PM3_SUCCESS;
}
static int read_cts_block(uint8_t blockno, uint8_t *out, uint16_t out_len) {
uint8_t psize = sizeof(iso14b_raw_cmd_t) + 1;
iso14b_raw_cmd_t *packet = (iso14b_raw_cmd_t *)calloc(1, psize);
if (packet == NULL) {
PrintAndLogEx(WARNING, "Failed to allocate memory");
return PM3_EMALLOC;
}
packet->flags = (ISO14B_APPEND_CRC | ISO14B_RAW);
packet->timeout = 0;
packet->rawlen = 1;
packet->raw[0] = ASK_READ | (blockno & ASK_BLOCK_ADDRESS_MASK);
clearCommandBuffer();
PacketResponseNG resp;
SendCommandNG(CMD_HF_ISO14443B_COMMAND, (uint8_t *)packet, psize);
free(packet);
if (WaitForResponseTimeout(CMD_HF_ISO14443B_COMMAND, &resp, TIMEOUT) == false) {
return PM3_ETIMEOUT;
}
if (resp.status != PM3_SUCCESS) {
return resp.status;
}
if (resp.length != CTS_BLOCK_SIZE + 2) {
return PM3_ELENGTH;
}
if (check_crc(CRC_14443_B, resp.data.asBytes, resp.length) == false) {
return PM3_ECRC;
}
if (out) {
memcpy(out, resp.data.asBytes, MIN(out_len, CTS_BLOCK_SIZE));
}
return PM3_SUCCESS;
}
static int read_sr_block(uint8_t blockno, uint8_t *out, uint16_t out_len) {
struct {
uint8_t blockno;
@@ -1656,6 +1765,7 @@ static bool HF14B_ask_ct_reader(bool verbose) {
switch (resp.status) {
case PM3_SUCCESS: {
print_ct_general_info(resp.data.asBytes);
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf 14b ct") "`");
return true;
}
case PM3_ELENGTH: {
@@ -1728,6 +1838,7 @@ static bool HF14B_prime_reader(bool verbose) {
return false;
}
print_prime_general_info(&card);
PrintAndLogEx(HINT, "Hint: Try `" _YELLOW_("hf calypso") "`");
return true;
}
@@ -1817,6 +1928,119 @@ static int CmdHF14BReader(const char *Cmd) {
return readHF14B(cm, verbose, read_plot);
}
static int CmdHF14BCtsDump(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf 14b ctdump",
"Read and print ASK CTS / C-ticket memory blocks",
"hf 14b ctdump\n"
"hf 14b ctdump -v"
);
void *argtable[] = {
arg_param_begin,
arg_lit0("v", "verbose", "verbose output"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, true);
bool verbose = arg_get_lit(ctx, 1);
CLIParserFree(ctx);
iso14b_cts_card_select_t card = {0};
int status = select_cts_card_14443b(false, &card, verbose);
if (status != PM3_SUCCESS) {
switch_off_field_14b();
PrintAndLogEx(WARNING, "no ASK CTS / C-ticket tag found");
return status;
}
print_ct_general_info(&card);
const cts_product_info_t *product_info = get_cts_product_info(card.pc);
uint8_t block_count = product_info->block_count;
uint8_t data[CTS512_BLOCK_COUNT * CTS_BLOCK_SIZE] = {0};
PrintAndLogEx(INFO, "Reading %u blocks", block_count);
for (uint8_t blockno = 0; blockno < block_count; blockno++) {
status = PM3_ESOFT;
for (uint8_t retry = 0; retry < 3; retry++) {
status = read_cts_block(blockno, data + (blockno * CTS_BLOCK_SIZE), CTS_BLOCK_SIZE);
if (status == PM3_SUCCESS) {
break;
}
if (verbose) {
PrintAndLogEx(DEBUG, "block %u read failed, status %d, retry %u", blockno, status, retry + 1);
}
}
if (status != PM3_SUCCESS) {
switch_off_field_14b();
PrintAndLogEx(FAILED, "failed to read block %u (status %d)", blockno, status);
return status;
}
}
switch_off_field_14b();
print_ct_blocks(data, block_count * CTS_BLOCK_SIZE);
return PM3_SUCCESS;
}
static int CmdHF14BCtRdBl(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf 14b ctrdbl",
"Read an ASK CTS / C-ticket block",
"hf 14b ctrdbl -b 6\n"
"hf 14b ctrdbl --block 15 -v"
);
void *argtable[] = {
arg_param_begin,
arg_int1("b", "block", "<dec>", "block number"),
arg_lit0("v", "verbose", "verbose output"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
int blockno = arg_get_int_def(ctx, 1, -1);
bool verbose = arg_get_lit(ctx, 2);
CLIParserFree(ctx);
if (blockno < 0 || blockno > ASK_BLOCK_ADDRESS_MASK) {
PrintAndLogEx(FAILED, "block number out of range, max %u (0x%02X), got " _RED_("%d"),
ASK_BLOCK_ADDRESS_MASK, ASK_BLOCK_ADDRESS_MASK, blockno);
return PM3_EINVARG;
}
iso14b_cts_card_select_t card = {0};
int status = select_cts_card_14443b(false, &card, verbose);
if (status != PM3_SUCCESS) {
switch_off_field_14b();
PrintAndLogEx(WARNING, "no ASK CTS / C-ticket tag found");
return status;
}
const cts_product_info_t *product_info = get_cts_product_info(card.pc);
if (blockno >= product_info->block_count) {
switch_off_field_14b();
PrintAndLogEx(FAILED, "block number out of range for %s, max %u (0x%02X), got " _RED_("%d"),
product_info->name, product_info->block_count - 1, product_info->block_count - 1, blockno);
return PM3_EINVARG;
}
if (verbose) {
print_ct_general_info(&card);
}
uint8_t out[CTS_BLOCK_SIZE] = {0};
status = read_cts_block((uint8_t)blockno, out, sizeof(out));
switch_off_field_14b();
if (status == PM3_SUCCESS) {
PrintAndLogEx(SUCCESS, "block %02u... " _GREEN_("%s") " | " _GREEN_("%s"),
blockno, sprint_hex(out, sizeof(out)), sprint_ascii(out, sizeof(out)));
}
return status;
}
// Read SRI512|SRIX4K block
static int CmdHF14BSriRdBl(const char *Cmd) {
@@ -3516,6 +3740,9 @@ static command_t CommandTable[] = {
{"tearoff", CmdHF14BSriTearoff, IfPm3Iso14443b, "Tear-off attack on ST25TB/SRx counter blocks"},
{"view", CmdHF14BView, AlwaysAvailable, "Display content from tag dump file"},
{"valid", CmdSRIX4kValid, AlwaysAvailable, "SRIX4 checksum test"},
{"---------", CmdHelp, IfPm3Iso14443b, "------------------ " _CYAN_("ASK CTS / C-ticket") " ------------------"},
{"ctdump", CmdHF14BCtsDump, IfPm3Iso14443b, "Dump ASK CTS/C-ticket"},
{"ctrdbl", CmdHF14BCtRdBl, IfPm3Iso14443b, "Read ASK CTS/C-ticket block"},
{"---------", CmdHelp, IfPm3Iso14443b, "------------------------- " _CYAN_("Magic") " -----------------------"},
{"setuid", CmdHF14BSetUID, IfPm3Iso14443b, "Set UID for magic card"},
{NULL, NULL, NULL, NULL}
+11 -13
View File
@@ -12,33 +12,31 @@ cov-build --dir "$COVDIR" --initialize
#########################################
# Build Host prerequisites #
#########################################
cov-build --dir "$COVDIR" --c-coverage=gcov --no-network-coverage --no-generate-build-id --force make CC=$HOSTCC CXX=$HOSTCXX LD=$HOSTLD fpga_compress
cov-build --dir "$COVDIR" make CC=$HOSTCC CXX=$HOSTCXX LD=$HOSTLD fpga_compress
#########################################
# Build ARM, no test coverage #
#########################################
cov-build --dir "$COVDIR" --no-generate-build-id --force make recovery
cov-build --dir "$COVDIR" make recovery
#########################################
# Build client #
#########################################
# make sure to do client after ARM because Coverity retains one build info per file
# and we want the client-side of the common/ analysis
cov-build --dir "$COVDIR" --c-coverage=gcov --no-network-coverage --no-generate-build-id --force make CC=$HOSTCC CXX=$HOSTCXX LD=$HOSTLD mfkey
cov-build --dir "$COVDIR" --c-coverage=gcov --no-network-coverage --no-generate-build-id --force make CC=$HOSTCC CXX=$HOSTCXX LD=$HOSTLD nonce2key
cov-build --dir "$COVDIR" --c-coverage=gcov --no-network-coverage --no-generate-build-id --force make CC=$HOSTCC CXX=$HOSTCXX LD=$HOSTLD mf_nonce_brute
cov-build --dir "$COVDIR" --c-coverage=gcov --no-network-coverage --no-generate-build-id --force make CC=$HOSTCC CXX=$HOSTCXX LD=$HOSTLD mfd_aes_brute
cov-build --dir "$COVDIR" --c-coverage=gcov --no-network-coverage --no-generate-build-id --force make CC=$HOSTCC CXX=$HOSTCXX LD=$HOSTLD client
cov-build --dir "$COVDIR" make CC=$HOSTCC CXX=$HOSTCXX LD=$HOSTLD cryptorf
cov-build --dir "$COVDIR" make CC=$HOSTCC CXX=$HOSTCXX LD=$HOSTLD mfc_card_only
cov-build --dir "$COVDIR" make CC=$HOSTCC CXX=$HOSTCXX LD=$HOSTLD mfc_card_reader
cov-build --dir "$COVDIR" make CC=$HOSTCC CXX=$HOSTCXX LD=$HOSTLD mfulc_des_brute
cov-build --dir "$COVDIR" make CC=$HOSTCC CXX=$HOSTCXX LD=$HOSTLD mfd_aes_brute
cov-build --dir "$COVDIR" make CC=$HOSTCC CXX=$HOSTCXX LD=$HOSTLD hitag2crack
cov-build --dir "$COVDIR" make CC=$HOSTCC CXX=$HOSTCXX LD=$HOSTLD client
#########################################
# Run tests #
#########################################
cov-build --dir "$COVDIR" --c-coverage=gcov --no-network-coverage --test-capture tools/pm3_tests.sh --long
#cov-manage-emit --dir "$COVDIR" list-coverage-known
# test-capture step dropped — Test Advisor only, not in the free tarball
#########################################
# Import Git annotations (~ git blame) #
#########################################
cov-import-scm --dir "$COVDIR" --scm git --filename-regex "$PWD" --log ""$COVDIR"/cov-import-scm-log.txt"
cov-import-scm --dir "$COVDIR" --scm git --filename-regex "$PWD" --log "$COVDIR/cov-import-scm-log.txt"
post_build_hook
+7 -4
View File
@@ -414,10 +414,13 @@ ISO 7816-4 Basic interindustry commands. For command APDU's.
#define ASK_REQT 0x10
#define ASK_IDENTIFY 0x0F
#define ASK_SELECT 0x9F
#define ASK_MULTREAD (0x1 << 4) // High nibble
#define ASK_UPDATE (0x3 << 4) // High nibble
#define ASK_WRITE (0x5 << 4) // High nibble
#define ASK_READ (0x6 << 4) // High nibble
#define ASK_CTS_PRODUCT_CODE_CTS256B 0x50
#define ASK_CTS_PRODUCT_CODE_CTS512B 0x60
#define ASK_BLOCK_ADDRESS_MASK 0x1F
#define ASK_MULTREAD (0x1 << 5) // 001 AAAAA
#define ASK_UPDATE (0x3 << 5) // 011 AAAAA
#define ASK_WRITE (0x5 << 5) // 101 AAAAA
#define ASK_READ (0x6 << 5) // 110 AAAAA
#define ASK_DESACTIVATE 0xF0