Merge pull request #3229 from team-orangeBlue/mfp_write_checker

Add ST check to `hf mfp wrbl`, fix ST RO checks for 16-block sectors
This commit is contained in:
Iceman
2026-04-11 14:24:37 +07:00
committed by GitHub
3 changed files with 71 additions and 7 deletions
+6 -4
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 system code name annotation to 'hf felica info' and 'hf felica rqsyscode'
- Fixed `hf mf wrbl` and `hf mfp wrbl` the ACL RO checks on 16-block sectors correct (@team-orangeBlue)
- Changed `hf mfp wrbl` command to check for Sector Trailer errors that could potentially lock sectors out (@team-orangeBlue)
- Changed `hf felica info` and `hf felica rqsyscode` system code name annotation (@team-orangeBlue)
- Added `lf relay` command where it relays between two pm3 devices over internet. Thanks to Moerno for the code! (@iceman1001)
- Changed `hf mf acl` command to have more recognized generic configurations (@team-orangeBlue)
- Added `hf mfp acl` command (@team-orangeBlue)
@@ -14,10 +16,10 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac
- Added `hf mfdes createdelegateapp` command (@kormax)
- Optimized `hf iclass legbrute` throughput: replaced recursive `suc()`/`output()` cipher functions with iterative loops, added 256-entry LUT for the `select()` function eliminating redundant bit arithmetic and halving key lookups per state step, switched successor state to in-place pointer update removing per-call struct copies, added `doMAC_brute()` with byte-wise LSB-first processing and direct output bit packing eliminating all bitstream struct overhead and output reversal calls per key candidate, and replaced per-iteration 64-bit modulo progress check with a countdown counter (@antiklesys)
- Improved `hf iclass legbrute` fixed multithreaded key-range partitioning so threads cover non-overlapping slices of the 40-bit keyspace, added ETA display, keyboard abort with resume hint, `_Atomic` correctness for shared state, `pthread_create` error handling, and thread count capped at available CPUs (@antiklesys)
- Added wildcard support to `hf secc sim` payloads (@antiklesys)
- Changed `hf secc sim` it nows supports wildcard to payloads (@antiklesys)
- Added `hf secc` to build a base for simulating basic function of iclass SE config cards (@antiklesys)
- Improved SIO parsing for `hf iclass view` based on Iceman's "Dismantling the SEOS Protocol" talk (@antiklesys)
- Added live fc/cn update to `hf iclass tagsim` refreshing the csn with each update (@antiklesys)
- Changed `hf iclass view` , improved SIO parsing for based on @Iceman1001's "Dismantling the SEOS Protocol" talk (@antiklesys)
- Changed `hf iclass tagsim` added live fc/cn update to refreshing the csn with each update (@antiklesys)
- Added `--live` option to `hf iclass lookup` command to perform a live recovery of the reader's key by simulating a tag and running the lookup command against both standard and elite dictionaries (@antiklesys)
- Added `hf iclass tagsim` command to quickly simulate an iclass card based on facility code and card number(@antiklesys)
- Added `-f` parameter to `hf iclass sam` command to use the sam to parse a card dump (@antiklesys)
+8 -2
View File
@@ -869,10 +869,16 @@ static int mf_analyse_st_block(uint8_t blockno, uint8_t *block, bool force) {
}
bool ro_detected = false;
uint8_t bar = mfNumBlocksPerSector(mfSectorNum(blockno));
//uint8_t bar = mfNumBlocksPerSector(mfSectorNum(blockno));
uint8_t bar = 4;
for (uint8_t foo = 0; foo < bar; foo++) {
if (mfReadOnlyAccessConditions(foo, &block[6])) {
PrintAndLogEx(WARNING, "Strict ReadOnly Access Conditions on block " _YELLOW_("%u") " detected", blockno - bar + 1 + foo);
// WARNING: Sectors 33+ assume ACLs apply to groups of 4 blocks, not 1 block.
// The code as-is is bugged and actually wastes iterations. If you have 16 blocks, it'll run all 16 but only error out like it's a 4-block sector.
if (blockno<128)
PrintAndLogEx(WARNING, "Strict ReadOnly Access Conditions on block " _YELLOW_("%u") " detected", blockno - bar + 1 + foo);
else
PrintAndLogEx(WARNING, "Strict ReadOnly Access Conditions on blocks " _YELLOW_("%u-%u") " detected", blockno - bar*4 + 1 + foo*5, blockno - bar*4 + 1 + foo*5 + 4);
ro_detected = true;
}
}
+57 -1
View File
@@ -809,7 +809,7 @@ static int CmdHFMFPAcl(const char *Cmd) {
" ACCESS = access bytes inside sector trailer block\n"
" Increment, decrement, transfer, restore is for value blocks",
"hf mf acl\n"
"hf mf acl -d FF0780\n");
"hf mf acl -d 0FFF0780\n");
void *argtable[] = {
arg_param_begin,
@@ -1180,6 +1180,49 @@ static int CmdHFMFPRdsc(const char *Cmd) {
return PM3_SUCCESS;
}
static int mfp_analyse_st_block(uint8_t blockno, uint8_t *block, bool force) {
if (mfIsSectorTrailer(blockno) == false) {
return PM3_SUCCESS;
}
PrintAndLogEx(INFO, "Sector trailer (ST) write detected");
// ensure access right isn't messed up.
if (mfValidateAccessConditions(block + 6) == false || ((block[5] >> 4) != ((~block[5]) & 0xF))) {
PrintAndLogEx(WARNING, "Invalid Access Conditions detected, replacing with default values");
memcpy(block + 5, "\x0F\xFF\x07\x80\x69", 5);
}
bool ro_detected = false;
//uint8_t bar = mfNumBlocksPerSector(mfSectorNum(blockno));
uint8_t bar = 4;
for (uint8_t foo = 0; foo < bar; foo++) {
if (mfReadOnlyAccessConditions(foo, &block[6])) {
// WARNING: Sectors 33+ assume ACLs apply to groups of 4 blocks, not 1 block.
// The code as-is is bugged and actually wastes iterations. If you have 16 blocks, it'll run all 16 but only error out like it's a 4-block sector.
if (blockno<128)
PrintAndLogEx(WARNING, "Strict ReadOnly Access Conditions on block " _YELLOW_("%u") " detected", blockno - bar + 1 + foo);
else
PrintAndLogEx(WARNING, "Strict ReadOnly Access Conditions on blocks " _YELLOW_("%u-%u") " detected", blockno - bar*4 + 1 + foo*5, blockno - bar*4 + 1 + foo*5 + 4);
ro_detected = true;
}
}
if (ro_detected) {
if (force) {
PrintAndLogEx(WARNING, " --force override, continuing...");
} else {
PrintAndLogEx(INFO, "Exiting, please run `" _YELLOW_("hf mf acl -d %s") "` to understand", sprint_hex_inrow(&block[6], 3));
PrintAndLogEx(INFO, "Use `" _YELLOW_("--force") "` to override and write this data");
return PM3_EINVARG;
}
} else {
PrintAndLogEx(SUCCESS, "ST checks ( " _GREEN_("ok") " )");
}
return PM3_SUCCESS;
}
static int CmdHFMFPWrbl(const char *Cmd) {
CLIParserContext *ctx;
CLIParserInit(&ctx, "hf mfp wrbl",
@@ -1197,6 +1240,7 @@ static int CmdHFMFPWrbl(const char *Cmd) {
arg_lit0(NULL, "nmr", "Do not expect MAC in response"),
arg_str1("d", "data", "<hex>", "Data, 16 hex bytes"),
arg_str0("k", "key", "<hex>", "Key, 16 hex bytes"),
arg_lit0(NULL, "force", "Override warnings"),
arg_param_end
};
CLIExecWithReturn(ctx, Cmd, argtable, false);
@@ -1214,6 +1258,8 @@ static int CmdHFMFPWrbl(const char *Cmd) {
uint8_t key[250] = {0};
int keylen = 0;
CLIGetHexWithReturn(ctx, 7, key, &keylen);
bool force = arg_get_lit(ctx, 8);
CLIParserFree(ctx);
uint8_t keyn[2] = {0};
@@ -1239,6 +1285,16 @@ static int CmdHFMFPWrbl(const char *Cmd) {
PrintAndLogEx(ERR, "<data> must be 16 bytes. Got %d", datainlen);
return PM3_EINVARG;
}
// Necessary checks before doing any actual computing + tag interaction
// Block 0 detection
if (blockNum == 0) {
PrintAndLogEx(FAILED, "Cannot write block 0 on Mifare Plus");
return PM3_EINVARG;
}
// ACL validity check
if (mfp_analyse_st_block(blockNum, datain, force) != PM3_SUCCESS) {
return PM3_EINVARG;
}
uint8_t sectorNum = mfSectorNum(blockNum & 0xff);
uint16_t uKeyNum = 0x4000 + sectorNum * 2 + (keyB ? 1 : 0);