hf mfu chk: one dictionary check that detects UL-C or UL-AES

hf mfu cchk and hf mfu aeschk were the same command twice. Ninety lines of
dictionary loading and chunked device calls were duplicated, and the only
real difference was which key slot they targeted: aeschk took --idx, cchk
hardcoded the Ultralight C slot. Both are replaced by hf mfu chk, which
reads the tag type off the card with GetHF14AMfU_Type() and picks the slot
itself. Ultralight AES still honours --idx, 0 DataProtKey, 1 UIDRetrKey,
2 OriginalityKey. Ultralight C holds a single key, so --idx is rejected
there rather than silently ignored. Any other tag is refused with a pointer
to hf mfu info.

Without -f the dictionary is mfulc_default_keys.dic for both tag types. A
segment check keeps needing an explicit -f, because the segment dictionaries
hold four byte keys and the default one does not. aeschk used to advertise
mfulaes_default_keys.dic in its help, which has never been shipped.

Collapsing the two bodies also fixes --retries. firstChunk and lastChunk
were declared outside the retry loop and never reset, so only the first pass
was correct. From the second pass on, every chunk went out with firstchunk
clear and lastchunk set, which on the device side means no select and a
field teardown after each chunk, against a field the previous pass had
already dropped. They are now scoped to the pass.

client/pyscripts/mfulaes_mask_recovery.py called aeschk and now calls chk.
doc/commands.md and doc/commands.json are regenerated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
iceman1001andClaude Opus 5 committed 2026-09-16 12:40:47 +02:00
1 parent 60eca59fee
commit 00aabd7443
5 files changed
+132 -221

No files matched your search

+11 -27
View File
@@ -9185,26 +9185,6 @@
],
"usage": "hf mfu aesauth [-hlkn0] [--key <hex>] [-i <0..2>] [--schann] [-r <n>]"
},
"hf mfu aeschk": {
"command": "hf mfu aeschk",
"description": "It checks MIFARE Ultralight AES tags keys against a dictionary file with keys Key index 0... DataProtKey (default) Key index 1... UIDRetrKey Key index 2... OriginalityKey",
"notes": [
"hf mfu aeschk -f mfulaes_default_keys.dic"
],
"offline": false,
"options": [
"-h, --help This help",
"-f, --file <fn> filename of dictionary",
"-i, --idx <0..2> Key index (def: 0)",
"-s, --segment <0..3> Segment index (full key if not specified)",
"-r, --retries <0..255> Number of retries (def: 0)",
"-k, --key <hex> Starting key, 16 hex bytes (def: zero key), for segment check",
"-x, --xor XOR starting key with segment candidates (def: override)",
"-n, --nocheck Skip checking tag answer correctness",
"-0, --read0 Use fast READ0 (skip anticol)"
],
"usage": "hf mfu aeschk [-hxn0] [-f <fn>] [-i <0..2>] [-s <0..3>] [-r <0..255>] [-k <hex>]"
},
"hf mfu aesgetuid": {
"command": "hf mfu aesgetuid",
"description": "Retreives real UID on Mifare Ultralight AES tags when random ID is enabled. Uses key index 1 (UIDRetrKey). If no key is specified, null key will be tried.",
@@ -9266,16 +9246,20 @@
],
"usage": "hf mfu cauth [-hlkn0] [--key <hex>] [-r <n>] [--noauth] [--reset] [--collect] [--pair <ERndB:ERndARndBp>]..."
},
"hf mfu cchk": {
"command": "hf mfu cchk",
"description": "It checks MIFARE Ultralight C tags keys against a dictionary file with keys",
"hf mfu chk": {
"command": "hf mfu chk",
"description": "Checks MIFARE Ultralight C / Ultralight AES tag keys against a dictionary file. The tag type is read off the card and picks the key slot to attack. UL-C ..... single 3DES key, --idx does not apply UL-AES ... key index 0 DataProtKey (default) key index 1 UIDRetrKey key index 2 OriginalityKey Without -f, mfulc_default_keys.dic is used for both tag types. A segment check (-s) holds 4 byte keys, so it needs its own dictionary via -f.",
"notes": [
"hf mfu cchk -f mfulc_default_keys.dic"
"hf mfu chk",
"hf mfu chk -f mfulc_default_keys.dic",
"hf mfu chk -f mfulaes_hw1.dic --idx 1",
"hf mfu chk -f mfulc_segment_hw1.dic -s 0"
],
"offline": false,
"options": [
"-h, --help This help",
"-f, --file <fn> filename of dictionary",
"-i, --idx <0..2> Key index, Ultralight AES only (def: 0)",
"-s, --segment <0..3> Segment index (full key if not specified)",
"-r, --retries <0..255> Number of retries (def: 0)",
"-k, --key <hex> Starting key, 16 hex bytes (def: zero key), for segment check",
@@ -9283,7 +9267,7 @@
"-n, --nocheck Skip checking tag answer correctness",
"-0, --read0 Use fast READ0 (skip anticol)"
],
"usage": "hf mfu cchk [-hxn0] [-f <fn>] [-s <0..3>] [-r <0..255>] [-k <hex>]"
"usage": "hf mfu chk [-hxn0] [-f <fn>] [-i <0..2>] [-s <0..3>] [-r <0..255>] [-k <hex>]"
},
"hf mfu countertear": {
"command": "hf mfu countertear",
@@ -16606,8 +16590,8 @@
}
},
"metadata": {
"commands_extracted": 924,
"commands_extracted": 923,
"extracted_by": "PM3Help2JSON v1.00",
"extracted_on": "2026-09-16T09:42:55+00:00"
"extracted_on": "2026-09-16T10:14:24+00:00"
}
}
+1 -2
View File
@@ -748,11 +748,10 @@ Check column "offline" for their availability.
|`hf mfu pwdgen `|Y |`Generate pwd from known algos`
|`hf mfu otptear `|N |`Tear-off test on OTP bits`
|`hf mfu countertear `|N |`Tear-off test on Ev1/NTAG Counter bits`
|`hf mfu chk `|N |`Ultralight C/AES - Authentication dictionary check`
|`hf mfu cauth `|N |`Ultralight-C - Authentication`
|`hf mfu cchk `|N |`Ultralight-C - Authentication dictionary check`
|`hf mfu desbrute `|Y |`Ultralight-C - 3DES key segment brute force`
|`hf mfu aesauth `|N |`Ultralight-AES - Authentication`
|`hf mfu aeschk `|N |`Ultralight-AES - Authentication dictionary check`
|`hf mfu aesgetuid `|N |`Ultralight-AES - Get UID when RID in use`
|`hf mfu setkey `|N |`Ultralight C/AES - Set 3DES/AES keys`
|`hf mfu dump `|N |`Dump MIFARE Ultralight family tag to binary file`