Skip to content

Commit 7b65dfb

Browse files
authored
Merge pull request #3021 from Foxushka/hf-mf-keygen
Replaced hf mf bambukeys with hf mf keygen with multiple KDFs support + Snapmaker U1 filament spool KDF
2 parents 3d53e85 + b380132 commit 7b65dfb

File tree

7 files changed

+172
-40
lines changed

7 files changed

+172
-40
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file.
33
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...
44

55
## [unreleased][unreleased]
6+
- Added Snapmaker U1 filament spool KDF in `hf mf keygen` (@Foxushka)
7+
- Replaced `hf mf bambukeys` with `hf mf keygen` with multiple KDFs support (@Foxushka)
68
- Fix `hf seos adf/pacs` handling of cards with different diversifier lengths and different ADF OIDs (@nvx)
79
- Added `data qrcode` - to generate QR codes from inside the pm3 client (@iceman1001)
810
- Fix unicode on mingw/proxspace (@nvx)

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ The Proxmark3 is the swiss-army tool of RFID, allowing for interactions with the
2222
- [Generic Proxmark3 platforms](#generic-proxmark3-platforms)
2323
- [What has changed?](#what-has-changed)
2424
- [Development](#development)
25-
- [Supported operative systems](#supported-operative-systems)
25+
- [Supported operating systems](#supported-operating-systems)
2626
- [Precompiled binaries](#precompiled-binaries)
2727
- [Proxmark3 GUI](#proxmark3-gui)
2828
- [Official channels](#official-channels)
@@ -183,7 +183,7 @@ We usually merge your contributions fast since we do like the idea of getting a
183183
The [public roadmap](https://github.com/RfidResearchGroup/proxmark3/wiki/Public-Roadmap) is an excellent start to read if you are interesting in contributing.
184184

185185

186-
## Supported operating systems
186+
## Supported operating systems
187187

188188
This repo compiles nicely on
189189
- WSL1 on Windows 10

client/src/cmdhfmf.c

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5008,7 +5008,7 @@ void printKeyTableEx(size_t sectorscnt, sector_t *e_sector, uint8_t start_sector
50085008
_YELLOW_("H") ":Hardnested / "
50095009
_YELLOW_("C") ":statiCnested / "
50105010
_YELLOW_("A") ":keyA "
5011-
" )"
5011+
" )"
50125012
);
50135013
if (sectorscnt == 18) {
50145014
PrintAndLogEx(INFO, "( " _MAGENTA_("*") " ) These sectors used for signature. Lays outside of user memory");
@@ -10880,20 +10880,39 @@ static int CmdHF14AMfISEN(const char *Cmd) {
1088010880
return PM3_SUCCESS;
1088110881
}
1088210882

10883-
static int CmdHF14AMfBambuKeys(const char *Cmd) {
10883+
static int CmdHF14AMfKeyGen(const char *Cmd) {
1088410884
CLIParserContext *ctx;
10885-
CLIParserInit(&ctx, "hf mf bambukeys",
10886-
"Generate keys for a Bambu Lab filament tag",
10887-
"hf mf bambukeys -r\n"
10888-
"hf mf bambukeys -r -d\n"
10889-
"hf mf bambukeys -u 11223344\n"
10885+
10886+
char kdf_list[256] = {0};
10887+
snprintf(kdf_list, sizeof(kdf_list), "Available KDFs:");
10888+
10889+
const kdf_t *kdf_table = get_kdf_table();
10890+
size_t kdf_table_size = get_kdf_table_size();
10891+
10892+
for (size_t i = 0; i < kdf_table_size; i++) {
10893+
char tmp[128];
10894+
snprintf(tmp, sizeof(tmp), "\n %zu - %s", i, kdf_table[i].name);
10895+
strncat(kdf_list, tmp, sizeof(kdf_list) - strlen(kdf_list) - 1);
10896+
}
10897+
10898+
char help_text[512] = {0};
10899+
snprintf(help_text, sizeof(help_text),
10900+
"Generate key table for some known KDFs\n%s", kdf_list);
10901+
10902+
CLIParserInit(&ctx, "hf mf keygen",
10903+
help_text,
10904+
"hf mf keygen -r -k 0\n"
10905+
"hf mf keygen -r -d -k 0\n"
10906+
"hf mf keygen -u 11223344 -k 0\n"
10907+
"hf mf keygen -u 11223344 -k 1\n"
1089010908
);
1089110909

1089210910
void *argtable[] = {
1089310911
arg_param_begin,
10894-
arg_str0("u", "uid", "<hex>", "UID (4 hex bytes)"),
10912+
arg_str0("u", "uid", "<hex>", "UID 4/7 hex bytes"),
1089510913
arg_lit0("r", NULL, "Read UID from tag"),
1089610914
arg_lit0("d", NULL, "Dump keys to file"),
10915+
arg_int0("k", "kdf", "<dec>", "KDF algorithm"),
1089710916
arg_param_end
1089810917
};
1089910918
CLIExecWithReturn(ctx, Cmd, argtable, true);
@@ -10903,8 +10922,16 @@ static int CmdHF14AMfBambuKeys(const char *Cmd) {
1090310922
CLIGetHexWithReturn(ctx, 1, uid, &u_len);
1090410923
bool use_tag = arg_get_lit(ctx, 2);
1090510924
bool dump_keys = arg_get_lit(ctx, 3);
10925+
int kdf_idx = arg_get_int_def(ctx, 4, -1);
1090610926
CLIParserFree(ctx);
1090710927

10928+
if (kdf_idx < 0 || kdf_idx >= kdf_table_size) {
10929+
PrintAndLogEx(WARNING, "Invalid KDF algorithm index. Must be 0-%zu", kdf_table_size - 1);
10930+
return PM3_EINVARG;
10931+
}
10932+
10933+
kdf_t kdf = kdf_table[kdf_idx];
10934+
1090810935
if (use_tag) {
1090910936
// read uid from tag
1091010937
int res = mf_read_uid(uid, &u_len, NULL);
@@ -10913,28 +10940,51 @@ static int CmdHF14AMfBambuKeys(const char *Cmd) {
1091310940
}
1091410941
}
1091510942

10916-
if (u_len != 4) {
10917-
PrintAndLogEx(WARNING, "Key must be 4 hex bytes");
10943+
if (u_len != kdf.uid_length) {
10944+
PrintAndLogEx(WARNING, "UID with length %d is required for %s",
10945+
kdf.uid_length,
10946+
kdf.name);
1091810947
return PM3_EINVARG;
1091910948
}
1092010949

1092110950
PrintAndLogEx(INFO, "-----------------------------------");
10922-
PrintAndLogEx(INFO, " UID 4b... " _YELLOW_("%s"), sprint_hex(uid, 4));
10951+
PrintAndLogEx(INFO, " KDF...... " _YELLOW_("%s"), kdf.name);
10952+
PrintAndLogEx(INFO, " UID %db... " _YELLOW_("%s"), u_len, sprint_hex(uid, u_len));
1092310953
PrintAndLogEx(INFO, "-----------------------------------");
1092410954

10925-
uint8_t keys[32 * 6];
10926-
mfc_algo_bambu_all(uid, (void *)keys);
10955+
int sector_count = kdf.sector_count;
10956+
uint8_t *keys = calloc(sector_count * 2 * 6, sizeof(uint8_t));
10957+
if (keys == NULL) {
10958+
PrintAndLogEx(WARNING, "Failed to allocate memory");
10959+
return PM3_EMALLOC;
10960+
}
1092710961

10928-
for (int block = 0; block < 32; block++) {
10929-
PrintAndLogEx(INFO, "%d: %012" PRIX64, block, bytes_to_num(keys + (block * 6), 6));
10962+
kdf.kdf_function(uid, keys);
10963+
10964+
sector_t *e_sector = NULL;
10965+
if (initSectorTable(&e_sector, sector_count) != PM3_SUCCESS) {
10966+
free(keys);
10967+
return PM3_EMALLOC;
1093010968
}
1093110969

10970+
// write required info for table
10971+
for (int i = 0; i < sector_count; i++) {
10972+
e_sector[i].foundKey[0] = true;
10973+
e_sector[i].foundKey[1] = true;
10974+
e_sector[i].Key[0] = bytes_to_num(keys + (i * 6), 6);
10975+
e_sector[i].Key[1] = bytes_to_num(keys + ((sector_count + i) * 6), 6);
10976+
}
10977+
10978+
printKeyTable(sector_count, e_sector);
10979+
1093210980
if (dump_keys) {
1093310981
char fn[FILE_PATH_SIZE] = {0};
10934-
snprintf(fn, sizeof(fn), "hf-mf-%s-key", sprint_hex_inrow(uid, 4));
10935-
saveFileEx(fn, ".bin", keys, 32 * 6, spDump);
10982+
snprintf(fn, sizeof(fn), "hf-mf-%s-key", sprint_hex_inrow(uid, u_len));
10983+
saveFileEx(fn, ".bin", keys, sector_count * 2 * 6, spDump);
1093610984
}
1093710985

10986+
free(keys);
10987+
free(e_sector);
1093810988
return PM3_SUCCESS;
1093910989
}
1094010990

@@ -10954,7 +11004,7 @@ static command_t CommandTable[] = {
1095411004
{"fchk", CmdHF14AMfChk_fast, IfPm3Iso14443a, "Check keys fast, targets all keys on card"},
1095511005
{"decrypt", CmdHf14AMfDecryptBytes, AlwaysAvailable, "Decrypt Crypto1 data from sniff or trace"},
1095611006
{"supercard", CmdHf14AMfSuperCard, IfPm3Iso14443a, "Extract info from a `super card`"},
10957-
{"bambukeys", CmdHF14AMfBambuKeys, AlwaysAvailable, "Generate key table for Bambu Lab filament tag"},
11007+
{"keygen", CmdHF14AMfKeyGen, AlwaysAvailable, "Generate key table for some known KDFs"},
1095811008
{"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("operations") " -----------------------"},
1095911009
{"auth4", CmdHF14AMfAuth4, IfPm3Iso14443a, "ISO14443-4 AES authentication"},
1096011010
{"acl", CmdHF14AMfAcl, AlwaysAvailable, "Decode and print MIFARE Classic access rights bytes"},

common/generator.c

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ uint32_t ul_c_otpgenA(const uint8_t *uid) {
280280
// Each algo implementation should offer two key generation functions.
281281
// 1. function that returns all keys
282282
// 2. function that returns one key, target sector | block
283+
// Then add function to KDFTable with allowed UID sizes
283284
//------------------------------------
284285

285286
//------------------------------------
@@ -576,6 +577,68 @@ int mfc_algo_bambu_all(uint8_t *uid, uint8_t *keys) {
576577
return PM3_SUCCESS;
577578
}
578579

580+
const uint8_t snapmaker_salt_a[] = "Snapmaker_qwertyuiop[,.;]";
581+
const uint8_t snapmaker_salt_b[] = "Snapmaker_qwertyuiop[,.;]_1q2w3e";
582+
583+
int mfc_algo_snapmaker_one(uint8_t *uid, uint8_t sector, uint8_t keytype, uint64_t *key) {
584+
if (uid == NULL) return PM3_EINVARG;
585+
if (key == NULL) return PM3_EINVARG;
586+
if (sector >= 16) return PM3_EINVARG;
587+
588+
const uint8_t *salt = (keytype == 0) ? snapmaker_salt_a : snapmaker_salt_b;
589+
size_t salt_len = (keytype == 0) ? sizeof(snapmaker_salt_a) - 1 : sizeof(snapmaker_salt_b) - 1;
590+
char key_char = (keytype == 0) ? 'a' : 'b';
591+
592+
// "key_a_0", "key_b_15"
593+
char info[16];
594+
snprintf(info, sizeof(info), "key_%c_%d", key_char, sector);
595+
596+
uint8_t output[6];
597+
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
598+
mbedtls_hkdf(md_info, salt, salt_len, uid, 4, (const uint8_t *)info, strlen(info), output, 6);
599+
600+
*key = bytes_to_num(output, 6);
601+
return PM3_SUCCESS;
602+
}
603+
604+
int mfc_algo_snapmaker_all(uint8_t *uid, uint8_t *keys) {
605+
if (uid == NULL) return PM3_EINVARG;
606+
if (keys == NULL) return PM3_EINVARG;
607+
608+
for (int i = 0; i < 16; i++) {
609+
uint64_t key = 0;
610+
mfc_algo_snapmaker_one(uid, i, 0, &key);
611+
num_to_bytes(key, 6, keys + (i * 6));
612+
}
613+
614+
for (int i = 0; i < 16; i++) {
615+
uint64_t key = 0;
616+
mfc_algo_snapmaker_one(uid, i, 1, &key);
617+
num_to_bytes(key, 6, keys + ((16 + i) * 6));
618+
}
619+
620+
return PM3_SUCCESS;
621+
}
622+
623+
static kdf_t KDFTable[] = {
624+
{"Saflok / Maid", 16, mfc_algo_saflok_all, 4},
625+
{"MIZIP", 5, mfc_algo_mizip_all, 4},
626+
{"Disney Infinity", 5, mfc_algo_di_all, 7},
627+
{"Skylanders", 16, mfc_algo_sky_all, 4},
628+
{"Bambu Lab Filament Spool", 16, mfc_algo_bambu_all, 4},
629+
{"Snapmaker Filament Spool", 16, mfc_algo_snapmaker_all, 4},
630+
// {"Vinglock", 16, mfc_algo_ving_all, 4}, // not implemented
631+
// {"Yale Doorman", 16, mfc_algo_yale_all, 4}, // not implemented
632+
};
633+
634+
const kdf_t *get_kdf_table(void) {
635+
return KDFTable;
636+
}
637+
638+
size_t get_kdf_table_size(void) {
639+
return ARRAYLEN(KDFTable);
640+
}
641+
579642
// LF T55x7 White gun cloner algo
580643
uint32_t lf_t55xx_white_pwdgen(uint32_t id) {
581644
uint32_t r1 = rotl(id & 0x000000ec, 8);

common/generator.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,17 @@ uint16_t ul_ev1_packgenG(const uint8_t *uid, const uint8_t *mfg);
4040

4141
uint32_t ul_c_otpgenA(const uint8_t *uid);
4242

43+
// Stucture for KDF function list
44+
typedef struct {
45+
const char *name;
46+
int sector_count;
47+
int (*kdf_function)(uint8_t *, uint8_t *);
48+
int uid_length;
49+
} kdf_t;
50+
51+
const kdf_t *get_kdf_table(void);
52+
size_t get_kdf_table_size(void);
53+
4354
int mfc_algo_ving_one(uint8_t *uid, uint8_t sector, uint8_t keytype, uint64_t *key);
4455
int mfc_algo_ving_all(uint8_t *uid, uint8_t *keys);
4556

@@ -64,6 +75,10 @@ int mfc_algo_touch_one(uint8_t *uid, uint8_t sector, uint8_t keytype, uint64_t *
6475

6576
int mfc_algo_bambu_one(uint8_t *uid, uint8_t sector, uint8_t keytype, uint64_t *key);
6677
int mfc_algo_bambu_all(uint8_t *uid, uint8_t *keys);
78+
79+
int mfc_algo_snapmaker_one(uint8_t *uid, uint8_t sector, uint8_t keytype, uint64_t *key);
80+
int mfc_algo_snapmaker_all(uint8_t *uid, uint8_t *keys);
81+
6782
uint32_t lf_t55xx_white_pwdgen(uint32_t id);
6883

6984
int mfdes_kdf_input_gallagher(uint8_t *uid, uint8_t uidLen, uint8_t keyNo, uint32_t aid, uint8_t *kdfInputOut, uint8_t *kdfInputLen);

doc/commands.json

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4474,23 +4474,6 @@
44744474
],
44754475
"usage": "hf mf autopwn [-hablv] [-k <hex>]... [-s <dec>] [-f <fn>] [--suffix <txt>] [--slow] [--mem] [--ns] [--mini] [--1k] [--2k] [--4k] [--in] [--im] [--is] [--ia] [--i2] [--i5]"
44764476
},
4477-
"hf mf bambukeys": {
4478-
"command": "hf mf bambukeys",
4479-
"description": "Generate keys for a Bambu Lab filament tag",
4480-
"notes": [
4481-
"hf mf bambukeys -r",
4482-
"hf mf bambukeys -r -d",
4483-
"hf mf bambukeys -u 11223344"
4484-
],
4485-
"offline": true,
4486-
"options": [
4487-
"-h, --help This help",
4488-
"-u, --uid <hex> UID (4 hex bytes)",
4489-
"-r Read UID from tag",
4490-
"-d Dump keys to file"
4491-
],
4492-
"usage": "hf mf bambukeys [-hrd] [-u <hex>]"
4493-
},
44944477
"hf mf brute": {
44954478
"command": "hf mf brute",
44964479
"description": "This is a smart bruteforce, exploiting common patterns, bugs and bad designs in key generators.",
@@ -5225,7 +5208,7 @@
52255208
},
52265209
"hf mf help": {
52275210
"command": "hf mf help",
5228-
"description": "help This help list List MIFARE history hardnested Nested attack for hardened MIFARE Classic cards decrypt Decrypt Crypto1 data from sniff or trace bambukeys Generate key table for Bambu Lab filament tag acl Decode and print MIFARE Classic access rights bytes mad Checks and prints MAD value Value blocks view Display content from tag dump file ginfo Info about configuration of the card gdmparsecfg Parse config block to card --------------------------------------------------------------------------------------- hf mf list available offline: yes Alias of `trace list -t mf -c` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol",
5211+
"description": "help This help list List MIFARE history hardnested Nested attack for hardened MIFARE Classic cards decrypt Decrypt Crypto1 data from sniff or trace keygen Generate key table for some known KDFs acl Decode and print MIFARE Classic access rights bytes mad Checks and prints MAD value Value blocks view Display content from tag dump file ginfo Info about configuration of the card gdmparsecfg Parse config block to card --------------------------------------------------------------------------------------- hf mf list available offline: yes Alias of `trace list -t mf -c` with selected protocol data to annotate trace buffer You can load a trace from file (see `trace load -h`) or it be downloaded from device by default It accepts all other arguments of `trace list`. Note that some might not be relevant for this specific protocol",
52295212
"notes": [
52305213
"hf mf list --frame -> show frame delay times",
52315214
"hf mf list -1 -> use trace buffer"
@@ -5303,6 +5286,25 @@
53035286
],
53045287
"usage": "hf mf isen [-hab] [--blk <dec>] [-c <dec>] [-k <hex>] [--blk2 <dec>] [--a2] [--b2] [--c2 <dec>] [--key2 <hex>] [-n <dec>] [--reset] [--hardreset] [--addread] [--addauth] [--incblk2] [--corruptnrar] [--corruptnrarparity] FM11RF08S specific options: [--collect_fm11rf08s] [--collect_fm11rf08s_with_data] [--collect_fm11rf08s_without_backdoor] [-f <fn>]"
53055288
},
5289+
"hf mf keygen": {
5290+
"command": "hf mf keygen",
5291+
"description": "Generate key table for some known KDFs Available KDFs: 0 - Saflok / Maid 1 - MIZIP 2 - Disney Infinity 3 - Skylanders 4 - Bambu Lab Filament Spool 5 - Snapmaker Filament Spool",
5292+
"notes": [
5293+
"hf mf keygen -r -k 0",
5294+
"hf mf keygen -r -d -k 0",
5295+
"hf mf keygen -u 11223344 -k 0",
5296+
"hf mf keygen -u 11223344 -k 1"
5297+
],
5298+
"offline": true,
5299+
"options": [
5300+
"-h, --help This help",
5301+
"-u, --uid <hex> UID 4/7 hex bytes",
5302+
"-r Read UID from tag",
5303+
"-d Dump keys to file",
5304+
"-k, --kdf <dec> KDF algorithm"
5305+
],
5306+
"usage": "hf mf keygen [-hrd] [-u <hex>] [-k <dec>]"
5307+
},
53065308
"hf mf mad": {
53075309
"command": "hf mf mad",
53085310
"description": "Checks and prints MIFARE Application Directory (MAD)",
@@ -13699,6 +13701,6 @@
1369913701
"metadata": {
1370013702
"commands_extracted": 785,
1370113703
"extracted_by": "PM3Help2JSON v1.00",
13702-
"extracted_on": "2025-11-10T15:12:49"
13704+
"extracted_on": "2025-11-10T17:48:24"
1370313705
}
1370413706
}

doc/commands.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,7 @@ Check column "offline" for their availability.
530530
|`hf mf fchk `|N |`Check keys fast, targets all keys on card`
531531
|`hf mf decrypt `|Y |`Decrypt Crypto1 data from sniff or trace`
532532
|`hf mf supercard `|N |`Extract info from a `super card``
533-
|`hf mf bambukeys `|Y |`Generate key table for Bambu Lab filament tag`
533+
|`hf mf keygen `|Y |`Generate key table for some known KDFs`
534534
|`hf mf auth4 `|N |`ISO14443-4 AES authentication`
535535
|`hf mf acl `|Y |`Decode and print MIFARE Classic access rights bytes`
536536
|`hf mf dump `|N |`Dump MIFARE Classic tag to binary file`

0 commit comments

Comments
 (0)