From f8f6216958d0efc253d6c2b70da047ccf49bd52c Mon Sep 17 00:00:00 2001 From: team-orangeBlue <63470411+team-orangeBlue@users.noreply.github.com> Date: Sat, 11 Apr 2026 00:11:41 +0300 Subject: [PATCH 1/8] Correct a typo in helptext Mifare Plus ACLs are 4 bytes, not 3... oops Fixed typo in `hf mfp acl` Signed-off-by: team-orangeBlue <63470411+team-orangeBlue@users.noreply.github.com> --- client/src/cmdhfmfp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/cmdhfmfp.c b/client/src/cmdhfmfp.c index cc47acba3..b69bbf45f 100644 --- a/client/src/cmdhfmfp.c +++ b/client/src/cmdhfmfp.c @@ -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, From 394aec40e2d61fecaf82a8a3efdcd48fe05d9033 Mon Sep 17 00:00:00 2001 From: team-orangeBlue <63470411+team-orangeBlue@users.noreply.github.com> Date: Sat, 11 Apr 2026 00:14:36 +0300 Subject: [PATCH 2/8] Add ST+ACL check to `hf mfp wrbl` Sector trailers are now checked for errors. Writes should not go through if for whatever reason ACLs provided to write are invalid. Reminder that ACLs are checked on the 4-byte method of MFP with the encrypted-only exchange byte. Signed-off-by: team-orangeBlue <63470411+team-orangeBlue@users.noreply.github.com> --- client/src/cmdhfmfp.c | 50 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/client/src/cmdhfmfp.c b/client/src/cmdhfmfp.c index b69bbf45f..b21fb2fdf 100644 --- a/client/src/cmdhfmfp.c +++ b/client/src/cmdhfmfp.c @@ -1180,6 +1180,43 @@ 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)); + 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); + 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 +1234,7 @@ static int CmdHFMFPWrbl(const char *Cmd) { arg_lit0(NULL, "nmr", "Do not expect MAC in response"), arg_str1("d", "data", "", "Data, 16 hex bytes"), arg_str0("k", "key", "", "Key, 16 hex bytes"), + arg_lit0(NULL, "force", "Override warnings"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, false); @@ -1214,6 +1252,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 +1279,16 @@ static int CmdHFMFPWrbl(const char *Cmd) { PrintAndLogEx(ERR, " 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); From 2b5ac4ca44c036bdc4b99dd2f18c50e4431f1163 Mon Sep 17 00:00:00 2001 From: team-orangeBlue <63470411+team-orangeBlue@users.noreply.github.com> Date: Sat, 11 Apr 2026 00:16:56 +0300 Subject: [PATCH 3/8] Update CHANGELOG.md Signed-off-by: team-orangeBlue <63470411+team-orangeBlue@users.noreply.github.com> --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8a40a173..7aa41fc8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 mfp wrbl` command to check for Sector Trailer errors that could potentially lock sectors out (@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) From 1cb843a22bbb5c5afc93478f8ab2806792ec6b39 Mon Sep 17 00:00:00 2001 From: team-orangeBlue <63470411+team-orangeBlue@users.noreply.github.com> Date: Sat, 11 Apr 2026 00:38:14 +0300 Subject: [PATCH 4/8] Fix bug in ACL block-RO checker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The code as-is would bug out like below: ``` (input) --blk 255 -d ...f0f0f0... [=] Sector trailer (ST) write detected [!] ⚠️ Strict ReadOnly Access Conditions on block 240 detected [!] ⚠️ Strict ReadOnly Access Conditions on block 241 detected [!] ⚠️ Strict ReadOnly Access Conditions on block 242 detected ``` This is incorrect. in 16-block sectors, ACLs cover chunks of 5 blocks at once. It should (and now does) look like: ``` [=] Sector trailer (ST) write detected [!] ⚠️ Strict ReadOnly Access Conditions on blocks 240-244 detected [!] ⚠️ Strict ReadOnly Access Conditions on blocks 245-249 detected [!] ⚠️ Strict ReadOnly Access Conditions on blocks 250-254 detected ``` Signed-off-by: team-orangeBlue <63470411+team-orangeBlue@users.noreply.github.com> --- client/src/cmdhfmfp.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/client/src/cmdhfmfp.c b/client/src/cmdhfmfp.c index b21fb2fdf..3ca9d495c 100644 --- a/client/src/cmdhfmfp.c +++ b/client/src/cmdhfmfp.c @@ -1195,10 +1195,16 @@ static int mfp_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<127) + 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; } } From 9e52f0701f65933e1dcfcf4ed632cd1f97dbe733 Mon Sep 17 00:00:00 2001 From: team-orangeBlue <63470411+team-orangeBlue@users.noreply.github.com> Date: Sat, 11 Apr 2026 00:41:31 +0300 Subject: [PATCH 5/8] Correct ACL RO check See commit 1cb843a22bbb5c5afc93478f8ab2806792ec6b39 Signed-off-by: team-orangeBlue <63470411+team-orangeBlue@users.noreply.github.com> --- client/src/cmdhfmf.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index c01ad0550..8f70b5b01 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -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<127) + 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; } } From 8e1f2d7a8f3fd99430acc0cc0a14725acea134f3 Mon Sep 17 00:00:00 2001 From: team-orangeBlue <63470411+team-orangeBlue@users.noreply.github.com> Date: Sat, 11 Apr 2026 00:42:33 +0300 Subject: [PATCH 6/8] Update CHANGELOG.md Signed-off-by: team-orangeBlue <63470411+team-orangeBlue@users.noreply.github.com> --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7aa41fc8c..3b63d9ef2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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] +- Fixed ACL RO checks on 16-block sectors when using `hf mf wrbl` and `hf mfp wrbl` (@team-orangeBlue) - Changed `hf mfp wrbl` command to check for Sector Trailer errors that could potentially lock sectors out (@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) From 38af2eb1c52bc93a4a29a8ef63c719c0bbac52a4 Mon Sep 17 00:00:00 2001 From: team-orangeBlue <63470411+team-orangeBlue@users.noreply.github.com> Date: Sat, 11 Apr 2026 00:56:59 +0300 Subject: [PATCH 7/8] bugfix oops Signed-off-by: team-orangeBlue <63470411+team-orangeBlue@users.noreply.github.com> --- client/src/cmdhfmf.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 8f70b5b01..e3aeec83e 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -875,7 +875,7 @@ static int mf_analyse_st_block(uint8_t blockno, uint8_t *block, bool force) { 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<127) + 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); From 877e5c6b701f73a57ceea87362ebbb7e87f32641 Mon Sep 17 00:00:00 2001 From: team-orangeBlue <63470411+team-orangeBlue@users.noreply.github.com> Date: Sat, 11 Apr 2026 00:57:28 +0300 Subject: [PATCH 8/8] bugfix #2 oops #2 Signed-off-by: team-orangeBlue <63470411+team-orangeBlue@users.noreply.github.com> --- client/src/cmdhfmfp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/cmdhfmfp.c b/client/src/cmdhfmfp.c index 3ca9d495c..47718c363 100644 --- a/client/src/cmdhfmfp.c +++ b/client/src/cmdhfmfp.c @@ -1201,7 +1201,7 @@ static int mfp_analyse_st_block(uint8_t blockno, uint8_t *block, bool force) { 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<127) + 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);