From c1bc38b39af96a706e088e80ebad93ef1a19ecf1 Mon Sep 17 00:00:00 2001 From: Iceman Date: Fri, 31 Jan 2025 22:24:45 +0100 Subject: [PATCH 001/105] Update Troubleshooting.md Signed-off-by: Iceman --- .../Troubleshooting.md | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/doc/md/Installation_Instructions/Troubleshooting.md b/doc/md/Installation_Instructions/Troubleshooting.md index 14402b68f..079e4bf4a 100644 --- a/doc/md/Installation_Instructions/Troubleshooting.md +++ b/doc/md/Installation_Instructions/Troubleshooting.md @@ -29,6 +29,7 @@ Always use the latest repository commits from *master* branch. There are always - [Qt Session management error](#qt-session-management-error) - [found architecture 'x86\_64' required architecture 'arm64' error](#found-architecture-x86_64-required-architecture-arm64-error) - [wrong permissions on runtime directory /run/user/1000](#wrong-permissions-on-runtime-directory-runuser1000) + - [proxspace `file not found or locked` on Windows 11](#proxspace-file-not-found-or-locked-on-windows-11) ## `pm3` or `pm3-flash*` doesn't see my Proxmark @@ -360,4 +361,33 @@ export XDG_RUNTIME_DIR=/run/user/1000 or export XDG_RUNTIME_DIR=/var/run/user/1000 -``` \ No newline at end of file +``` + +## proxspace 'file not found or locked' on Windows 11 +^[Top](#top) + +if you receive an error "file not found or locked" for any operation that needs to write a file. + +The cause is that Windows locks down many folders as 'read only', and you can't easily change this setting. + +How to fix (use this at your own risk): + +``` + Open your Windows Settings Control Panel + Then select "Privacy and security" + Then select "Windows Security" + Then select "Virus & threat protection" + Then scroll down and select "Manage ransomware protection" + Then select "Allow an app through Controlled folder access" + Answer "Yes" to allow this app to make changes to your system + Then select "Add an allowed app" to select the proper "proxmark3.exe" in the client folder. + +Potentially also do: + Select "Recently blocked apps" + Then select the most recent "proxmark3.exe" by pressing the "+" next to it. + Then select "Close". + +Side note: +You may also be able to choose "Browse all apps" and find your specific proxmark3.exe in the client folder but +be sure to choose the proper location and specific file in case you have more than one stored on your PC somewhere. +``` From eb210c14d3e1d05419e6bf4290f149321c6ffc45 Mon Sep 17 00:00:00 2001 From: team-orangeBlue <63470411+team-orangeBlue@users.noreply.github.com> Date: Sat, 1 Feb 2025 00:25:21 +0300 Subject: [PATCH 002/105] Initial MF4 support Explained MF4 "thinking logic". Also commented on MF3. Signed-off-by: team-orangeBlue <63470411+team-orangeBlue@users.noreply.github.com> --- doc/magic_cards_notes.md | 67 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/doc/magic_cards_notes.md b/doc/magic_cards_notes.md index fb838d4db..b9099927b 100644 --- a/doc/magic_cards_notes.md +++ b/doc/magic_cards_notes.md @@ -29,6 +29,7 @@ Useful docs: * [MIFARE Classic Gen1B](#mifare-classic-gen1b) * [Mifare Classic Direct Write OTP](#mifare-classic-direct-write-otp) * [MIFARE Classic OTP 2.0](#mifare-classic-otp-20) + * [MIFARE Classic MF4](#mifare-classic-mf4) * [MIFARE Classic DirectWrite aka Gen2 aka CUID](#mifare-classic-directwrite-aka-gen2-aka-cuid) * [MIFARE Classic Gen3 aka APDU](#mifare-classic-gen3-aka-apdu) * [MIFARE Classic USCUID](#mifare-classic-uscuid) @@ -642,6 +643,68 @@ hf mf info * Write: `40(7)`, `43`, `A0xx`+crc, `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`+crc +## MIFARE Classic MF4 + +^[Top](#top) + +Similar to OTP 2.0, but now additional configuration is possible. +Were manufactured by iKey LLC as a replacement for MF3. + +### Characteristics + +* Initial UID is 00000000 +* BCC: unknown +* SAK/ATQA: configurable +* ATS: configurable +* PPS: configurable (fake response) +* All bytes are 00 from factory wherever possible. + +### Identify + +^[Top](#top) + +Only possible before personalization. + +``` +hf mf info +... +[=] --- Magic Tag Information +[+] Magic capabilities... Gen 1a + +[=] --- PRNG Information +[+] Prng................. hard + +hf mf cgetblk --blk 3 +hf mf rdbl --blk 3 +[ If the ACLs do not match, this is an MF4 ] +``` + +### Magic commands + +^[Top](#top) + +Warning: changing the UID from 00000000 will disable all of these commands permanently. + +* Read backdoor: `40(7)`, `43`, `30xx`+crc +* Write: `40(7)`, `43`, `A0xx`+crc, `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`+crc + +### Magic configuration + +By accessing the 14th and 15th sector trailers using gen1 mode, it is possible to re-configure the tag. + +The layout for a sector is below: +* block 0: data +* block 1: data +* block 2: data +* block 3[0-5] - key A +* block 3[6] - configuration byte +* block 3[7] - ACL byte, configuration/RFU +* block 3[8] - ACL byte +* block 3[9] - ACL user byte +* block 3[10-15] - key B + +[ W.I.P - INCOMPLETE; DO NOT MERGE; DO NOT PUBLISH ] + ## MIFARE Classic DirectWrite aka Gen2 aka CUID ^[Top](#top) @@ -650,8 +713,8 @@ hf mf info * Other names: * MF-8 (RU) - * MF-3 (RU) - * What's so special about this chip in particular..? + * MF-3 (RU) - not susceptible to "field reset bug", a way to detect [OTP](#mifare-classic-direct-write-otp) chips. + * MF-3.2 (RU) - static nonce `01200145`, helps avoid magic detection. ### Identify From 766d30ecfa9689453e060a025be6538d8a804a46 Mon Sep 17 00:00:00 2001 From: Benjamin DELPY Date: Sun, 2 Feb 2025 22:57:31 +0100 Subject: [PATCH 003/105] Update intertic.py to support new ContractProvider for Strasbourg/CTS Signed-off-by: Benjamin DELPY --- client/pyscripts/intertic.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/pyscripts/intertic.py b/client/pyscripts/intertic.py index 7c940238d..f262040c2 100644 --- a/client/pyscripts/intertic.py +++ b/client/pyscripts/intertic.py @@ -284,6 +284,7 @@ FRA_OrganizationalAuthority_Contract_Provider = { }, 0x091: { 1: InterticHelper('Strasbourg', 'CTS', Describe_Usage_4), # More dump needed, not only tram ! + 5: InterticHelper('Strasbourg', 'CTS / new', Describe_Usage_4), # More dump needed, not only tram ! }, 0x502: { 83: InterticHelper('Annecy', 'Sibra', Describe_Usage_2), From 1ae4cf37d80d24e8ac006e0892e69e31c45bbda2 Mon Sep 17 00:00:00 2001 From: Donny <107092000+Donny-Guo@users.noreply.github.com> Date: Sun, 2 Feb 2025 23:42:19 -0800 Subject: [PATCH 004/105] Fix facility code and card number checking in LF HID Brute --- client/src/cmdlfhid.c | 5 +- client/src/wiegand_formats.c | 157 ++++++++++------------------------- client/src/wiegand_formats.h | 10 ++- 3 files changed, 58 insertions(+), 114 deletions(-) diff --git a/client/src/cmdlfhid.c b/client/src/cmdlfhid.c index 4ad3cbc66..5c97c6683 100644 --- a/client/src/cmdlfhid.c +++ b/client/src/cmdlfhid.c @@ -544,6 +544,7 @@ static int CmdHIDBrute(const char *Cmd) { } wiegand_card_t card_hi, card_low; + cardformatlimit_t limit = get_card_format_limit(format_idx); memset(&card_hi, 0, sizeof(wiegand_card_t)); char field[3] = {0}; @@ -623,13 +624,13 @@ static int CmdHIDBrute(const char *Cmd) { return PM3_ESOFT; } if (strcmp(field, "fc") == 0) { - if (card_hi.FacilityCode < 0xFF) { + if (card_hi.FacilityCode < limit.FacilityCode) { card_hi.FacilityCode++; } else { fin_hi = true; } } else if (strcmp(field, "cn") == 0) { - if (card_hi.CardNumber < 0xFFFF) { + if (card_hi.CardNumber < limit.CardNumber) { card_hi.CardNumber++; } else { fin_hi = true; diff --git a/client/src/wiegand_formats.c b/client/src/wiegand_formats.c index e3e146153..4641ec3c9 100644 --- a/client/src/wiegand_formats.c +++ b/client/src/wiegand_formats.c @@ -1499,46 +1499,46 @@ static void hid_print_card(wiegand_card_t *card, const cardformat_t format) { } static const cardformat_t FormatTable[] = { - {"H10301", Pack_H10301, Unpack_H10301, "HID H10301 26-bit", {1, 1, 0, 0, 1}}, // imported from old pack/unpack - {"ind26", Pack_ind26, Unpack_ind26, "Indala 26-bit", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au - {"ind27", Pack_ind27, Unpack_ind27, "Indala 27-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au - {"indasc27", Pack_indasc27, Unpack_indasc27, "Indala ASC 27-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au - {"Tecom27", Pack_Tecom27, Unpack_Tecom27, "Tecom 27-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au - {"2804W", Pack_2804W, Unpack_2804W, "2804 Wiegand 28-bit", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au - {"ind29", Pack_ind29, Unpack_ind29, "Indala 29-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au - {"ATSW30", Pack_ATSW30, Unpack_ATSW30, "ATS Wiegand 30-bit", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au - {"ADT31", Pack_ADT31, Unpack_ADT31, "HID ADT 31-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au - {"HCP32", Pack_hcp32, Unpack_hcp32, "HID Check Point 32-bit", {1, 0, 0, 0, 0}}, // from cardinfo.barkweb.com.au - {"HPP32", Pack_hpp32, Unpack_hpp32, "HID Hewlett-Packard 32-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au - {"Kastle", Pack_Kastle, Unpack_Kastle, "Kastle 32-bit", {1, 1, 1, 0, 1}}, // from @xilni; PR #23 on RfidResearchGroup/proxmark3 - {"Kantech", Pack_Kantech, Unpack_Kantech, "Indala/Kantech KFS 32-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au - {"WIE32", Pack_wie32, Unpack_wie32, "Wiegand 32-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au - {"D10202", Pack_D10202, Unpack_D10202, "HID D10202 33-bit", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au - {"H10306", Pack_H10306, Unpack_H10306, "HID H10306 34-bit", {1, 1, 0, 0, 1}}, // imported from old pack/unpack - {"N10002", Pack_N10002, Unpack_N10002, "Honeywell/Northern N10002 34-bit", {1, 1, 0, 0, 1}}, // from proxclone.com - {"Optus34", Pack_Optus, Unpack_Optus, "Indala Optus 34-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au - {"SMP34", Pack_Smartpass, Unpack_Smartpass, "Cardkey Smartpass 34-bit", {1, 1, 1, 0, 0}}, // from cardinfo.barkweb.com.au - {"BQT34", Pack_bqt34, Unpack_bqt34, "BQT 34-bit", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au - {"C1k35s", Pack_C1k35s, Unpack_C1k35s, "HID Corporate 1000 35-bit std", {1, 1, 0, 0, 1}}, // imported from old pack/unpack - {"C15001", Pack_C15001, Unpack_C15001, "HID KeyScan 36-bit", {1, 1, 0, 1, 1}}, // from Proxmark forums - {"S12906", Pack_S12906, Unpack_S12906, "HID Simplex 36-bit", {1, 1, 1, 0, 1}}, // from cardinfo.barkweb.com.au - {"Sie36", Pack_Sie36, Unpack_Sie36, "HID 36-bit Siemens", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au - {"H10320", Pack_H10320, Unpack_H10320, "HID H10320 37-bit BCD", {1, 0, 0, 0, 1}}, // from Proxmark forums - {"H10302", Pack_H10302, Unpack_H10302, "HID H10302 37-bit huge ID", {1, 0, 0, 0, 1}}, // from Proxmark forums - {"H10304", Pack_H10304, Unpack_H10304, "HID H10304 37-bit", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au - {"P10004", Pack_P10004, Unpack_P10004, "HID P10004 37-bit PCSC", {1, 1, 0, 0, 0}}, // from @bthedorff; PR #1559 - {"HGen37", Pack_HGeneric37, Unpack_HGeneric37, "HID Generic 37-bit", {1, 0, 0, 0, 1}}, // from cardinfo.barkweb.com.au - {"MDI37", Pack_MDI37, Unpack_MDI37, "PointGuard MDI 37-bit", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au - {"BQT38", Pack_bqt38, Unpack_bqt38, "BQT 38-bit", {1, 1, 1, 0, 1}}, // from cardinfo.barkweb.com.au - {"ISCS", Pack_iscs38, Unpack_iscs38, "ISCS 38-bit", {1, 1, 0, 1, 1}}, // from cardinfo.barkweb.com.au - {"PW39", Pack_pw39, Unpack_pw39, "Pyramid 39-bit wiegand format", {1, 1, 0, 0, 1}}, // from cardinfo.barkweb.com.au - {"P10001", Pack_P10001, Unpack_P10001, "HID P10001 Honeywell 40-bit", {1, 1, 0, 0, 0}}, // from cardinfo.barkweb.com.au - {"Casi40", Pack_CasiRusco40, Unpack_CasiRusco40, "Casi-Rusco 40-bit", {1, 0, 0, 0, 0}}, // from cardinfo.barkweb.com.au - {"C1k48s", Pack_C1k48s, Unpack_C1k48s, "HID Corporate 1000 48-bit std", {1, 1, 0, 0, 1}}, // imported from old pack/unpack - {"BC40", Pack_bc40, Unpack_bc40, "Bundy TimeClock 40-bit", {1, 1, 0, 1, 1}}, // from - {"Avig56", Pack_Avig56, Unpack_Avig56, "Avigilon 56-bit", {1, 1, 0, 0, 1}}, - {"Defcon32", Pack_Defcon32, Unpack_Defcon32, "Custom Defcon RFCTF 42 BIT format", {1, 1, 1, 0, 1}}, // Created by (@micsen) for the CTF - {NULL, NULL, NULL, NULL, {0, 0, 0, 0, 0}} // Must null terminate array + {"H10301", Pack_H10301, Unpack_H10301, "HID H10301 26-bit", {1, 1, 0, 0, 1}, {0xFF, 0xFFFF, 0, 0}}, // imported from old pack/unpack + {"ind26", Pack_ind26, Unpack_ind26, "Indala 26-bit", {1, 1, 0, 0, 1}, {0xFFF, 0xFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"ind27", Pack_ind27, Unpack_ind27, "Indala 27-bit", {1, 1, 0, 0, 0}, {0x1FFF, 0x3FFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"indasc27", Pack_indasc27, Unpack_indasc27, "Indala ASC 27-bit", {1, 1, 0, 0, 0}, {0x1FFF, 0x3FFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"Tecom27", Pack_Tecom27, Unpack_Tecom27, "Tecom 27-bit", {1, 1, 0, 0, 0}, {0x7FF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"2804W", Pack_2804W, Unpack_2804W, "2804 Wiegand 28-bit", {1, 1, 0, 0, 1}, {0xFF, 0x7FFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"ind29", Pack_ind29, Unpack_ind29, "Indala 29-bit", {1, 1, 0, 0, 0}, {0x1FFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"ATSW30", Pack_ATSW30, Unpack_ATSW30, "ATS Wiegand 30-bit", {1, 1, 0, 0, 1}, {0xFFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"ADT31", Pack_ADT31, Unpack_ADT31, "HID ADT 31-bit", {1, 1, 0, 0, 0}, {0xF, 0x7FFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"HCP32", Pack_hcp32, Unpack_hcp32, "HID Check Point 32-bit", {1, 0, 0, 0, 0}, {0, 0x3FFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"HPP32", Pack_hpp32, Unpack_hpp32, "HID Hewlett-Packard 32-bit", {1, 1, 0, 0, 0}, {0xFFF, 0x1FFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"Kastle", Pack_Kastle, Unpack_Kastle, "Kastle 32-bit", {1, 1, 1, 0, 1}, {0xFF, 0xFFFF, 0x1F, 0}}, // from @xilni; PR #23 on RfidResearchGroup/proxmark3 + {"Kantech", Pack_Kantech, Unpack_Kantech, "Indala/Kantech KFS 32-bit", {1, 1, 0, 0, 0}, {0xFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"WIE32", Pack_wie32, Unpack_wie32, "Wiegand 32-bit", {1, 1, 0, 0, 0}, {0xFFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"D10202", Pack_D10202, Unpack_D10202, "HID D10202 33-bit", {1, 1, 0, 0, 1}, {0x7F, 0xFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"H10306", Pack_H10306, Unpack_H10306, "HID H10306 34-bit", {1, 1, 0, 0, 1}, {0xFFFF, 0xFFFF, 0, 0}}, // imported from old pack/unpack + {"N10002", Pack_N10002, Unpack_N10002, "Honeywell/Northern N10002 34-bit", {1, 1, 0, 0, 1}, {0xFFFF, 0xFFFF, 0, 0}}, // from proxclone.com + {"Optus34", Pack_Optus, Unpack_Optus, "Indala Optus 34-bit", {1, 1, 0, 0, 0}, {0x3FF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"SMP34", Pack_Smartpass, Unpack_Smartpass, "Cardkey Smartpass 34-bit", {1, 1, 1, 0, 0}, {0x3FF, 0xFFFF, 0x7, 0}}, // from cardinfo.barkweb.com.au + {"BQT34", Pack_bqt34, Unpack_bqt34, "BQT 34-bit", {1, 1, 0, 0, 1}, {0xFF, 0xFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"C1k35s", Pack_C1k35s, Unpack_C1k35s, "HID Corporate 1000 35-bit std", {1, 1, 0, 0, 1}, {0xFFF, 0xFFFFF, 0, 0}}, // imported from old pack/unpack + {"C15001", Pack_C15001, Unpack_C15001, "HID KeyScan 36-bit", {1, 1, 0, 1, 1}, {0xFF, 0xFFFF, 0, 0x3FF}}, // from Proxmark forums + {"S12906", Pack_S12906, Unpack_S12906, "HID Simplex 36-bit", {1, 1, 1, 0, 1}, {0xFF, 0x3, 0xFFFFFF, 0}}, // from cardinfo.barkweb.com.au + {"Sie36", Pack_Sie36, Unpack_Sie36, "HID 36-bit Siemens", {1, 1, 0, 0, 1}, {0x3FFFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"H10320", Pack_H10320, Unpack_H10320, "HID H10320 37-bit BCD", {1, 0, 0, 0, 1}, {0, 99999999, 0, 0}}, // from Proxmark forums + {"H10302", Pack_H10302, Unpack_H10302, "HID H10302 37-bit huge ID", {1, 0, 0, 0, 1}, {0, 0x7FFFFFFFF, 0, 0}}, // from Proxmark forums + {"H10304", Pack_H10304, Unpack_H10304, "HID H10304 37-bit", {1, 1, 0, 0, 1}, {0xFFFF, 0x7FFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"P10004", Pack_P10004, Unpack_P10004, "HID P10004 37-bit PCSC", {1, 1, 0, 0, 0}, {0x1FFF, 0x3FFFF, 0, 0}}, // from @bthedorff; PR #1559 + {"HGen37", Pack_HGeneric37, Unpack_HGeneric37, "HID Generic 37-bit", {1, 0, 0, 0, 1}, {0, 0x7FFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"MDI37", Pack_MDI37, Unpack_MDI37, "PointGuard MDI 37-bit", {1, 1, 0, 0, 1}, {0xF, 0x1FFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"BQT38", Pack_bqt38, Unpack_bqt38, "BQT 38-bit", {1, 1, 1, 0, 1}, {0xFFF, 0x3FFFF, 0x7, 0}}, // from cardinfo.barkweb.com.au + {"ISCS", Pack_iscs38, Unpack_iscs38, "ISCS 38-bit", {1, 1, 0, 1, 1}, {0x3FF, 0xFFFFFF, 0, 0x7}}, // from cardinfo.barkweb.com.au + {"PW39", Pack_pw39, Unpack_pw39, "Pyramid 39-bit wiegand format", {1, 1, 0, 0, 1}, {0xFFFF, 0xFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"P10001", Pack_P10001, Unpack_P10001, "HID P10001 Honeywell 40-bit", {1, 1, 0, 0, 0}, {0xFFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"Casi40", Pack_CasiRusco40, Unpack_CasiRusco40, "Casi-Rusco 40-bit", {1, 0, 0, 0, 0}, {0, 0xFFFFFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"C1k48s", Pack_C1k48s, Unpack_C1k48s, "HID Corporate 1000 48-bit std", {1, 1, 0, 0, 1}, {0x003FFFFF, 0x007FFFFF, 0, 0}}, // imported from old pack/unpack + {"BC40", Pack_bc40, Unpack_bc40, "Bundy TimeClock 40-bit", {1, 1, 0, 1, 1}, {0xFFF, 0xFFFFF, 0, 0x7F}}, // from + {"Avig56", Pack_Avig56, Unpack_Avig56, "Avigilon 56-bit", {1, 1, 0, 0, 1}, {0xFFFFF, 0x3FFFFFFFF, 0, 0}}, + {"Defcon32", Pack_Defcon32, Unpack_Defcon32, "Custom Defcon RFCTF 42 BIT format", {1, 1, 1, 0, 1}, {0xFFFF, 0xFFFFF, 0xF, 0}}, // Created by (@micsen) for the CTF + {NULL, NULL, NULL, NULL, {0, 0, 0, 0, 0}, {0, 0, 0, 0}} // Must null terminate array }; void HIDListFormats(void) { @@ -1664,74 +1664,9 @@ void HIDUnpack(int idx, wiegand_message_t *packed) { } } -int HIDDumpPACSBits(const uint8_t *const data, const uint8_t length, bool verbose) { - uint8_t n = length - 1; - uint8_t pad = data[0]; - char *binstr = (char *)calloc((length * 8) + 1, sizeof(uint8_t)); - if (binstr == NULL) { - return PM3_EMALLOC; - } - - bytes_2_binstr(binstr, data + 1, n); - - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(SUCCESS, "PACS......... " _GREEN_("%s"), sprint_hex_inrow(data, length)); - PrintAndLogEx(SUCCESS, "padded bin... " _GREEN_("%s") " ( %zu )", binstr, strlen(binstr)); - - binstr[strlen(binstr) - pad] = '\0'; - PrintAndLogEx(SUCCESS, "bin.......... " _GREEN_("%s") " ( %zu )", binstr, strlen(binstr)); - - size_t hexlen = 0; - uint8_t hex[16] = {0}; - binstr_2_bytes(hex, &hexlen, binstr); - PrintAndLogEx(SUCCESS, "hex.......... " _GREEN_("%s"), sprint_hex_inrow(hex, hexlen)); - - uint32_t top = 0, mid = 0, bot = 0; - if (binstring_to_u96(&top, &mid, &bot, binstr) != strlen(binstr)) { - PrintAndLogEx(ERR, "Binary string contains none <0|1> chars"); - free(binstr); - return PM3_EINVARG; - } - - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(INFO, "Wiegand decode"); - wiegand_message_t packed = initialize_message_object(top, mid, bot, strlen(binstr)); - HIDTryUnpack(&packed); - - PrintAndLogEx(NORMAL, ""); - - if (strlen(binstr) >= 26 && verbose) { - - // iCLASS Legacy - PrintAndLogEx(INFO, "Clone to " _YELLOW_("iCLASS Legacy")); - PrintAndLogEx(SUCCESS, " hf iclass encode --ki 0 --bin %s", binstr); - PrintAndLogEx(NORMAL, ""); - - // HID Prox II - PrintAndLogEx(INFO, "Downgrade to " _YELLOW_("HID Prox II")); - PrintAndLogEx(SUCCESS, " lf hid clone -w H10301 --bin %s", binstr); - PrintAndLogEx(NORMAL, ""); - - // MIFARE Classic - char mfcbin[28] = {0}; - mfcbin[0] = '1'; - memcpy(mfcbin + 1, binstr, strlen(binstr)); - binstr_2_bytes(hex, &hexlen, mfcbin); - - PrintAndLogEx(INFO, "Downgrade to " _YELLOW_("MIFARE Classic") " (Pm3 simulation)"); - PrintAndLogEx(SUCCESS, " hf mf eclr;"); - PrintAndLogEx(SUCCESS, " hf mf esetblk --blk 0 -d 049DBA42A23E80884400C82000000000;"); - PrintAndLogEx(SUCCESS, " hf mf esetblk --blk 1 -d 1B014D48000000000000000000000000;"); - PrintAndLogEx(SUCCESS, " hf mf esetblk --blk 3 -d A0A1A2A3A4A5787788C189ECA97F8C2A;"); - PrintAndLogEx(SUCCESS, " hf mf esetblk --blk 5 -d 020000000000000000000000%s;", sprint_hex_inrow(hex, hexlen)); - PrintAndLogEx(SUCCESS, " hf mf esetblk --blk 7 -d 484944204953787788AA204752454154;"); - PrintAndLogEx(SUCCESS, " hf mf sim --1k -i;"); - PrintAndLogEx(NORMAL, ""); - - PrintAndLogEx(INFO, "Downgrade to " _YELLOW_("MIFARE Classic 1K")); - PrintAndLogEx(SUCCESS, " hf mf encodehid --bin %s", binstr); - PrintAndLogEx(NORMAL, ""); - } - free(binstr); - return PM3_SUCCESS; -} +cardformatlimit_t get_card_format_limit(int format_idx){ + if ((format_idx < 0) || (format_idx > ARRAYLEN(FormatTable) - 2)) + return FormatTable[ARRAYLEN(FormatTable) - 1].FieldLimits; + else + return FormatTable[format_idx].FieldLimits; +} \ No newline at end of file diff --git a/client/src/wiegand_formats.h b/client/src/wiegand_formats.h index 630d9cbb4..51606b763 100644 --- a/client/src/wiegand_formats.h +++ b/client/src/wiegand_formats.h @@ -38,6 +38,13 @@ typedef struct { bool hasParity; } cardformatdescriptor_t; +typedef struct { + uint32_t FacilityCode; + uint64_t CardNumber; + uint32_t IssueLevel; + uint32_t OEM; +} cardformatlimit_t; + // Structure for defined Wiegand card formats available for packing/unpacking typedef struct { const char *Name; @@ -45,6 +52,7 @@ typedef struct { bool (*Unpack)(wiegand_message_t *packed, wiegand_card_t *card); const char *Descrp; cardformatdescriptor_t Fields; + cardformatlimit_t FieldLimits; } cardformat_t; void HIDListFormats(void); @@ -54,7 +62,7 @@ bool HIDPack(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bo bool HIDTryUnpack(wiegand_message_t *packed); void HIDPackTryAll(wiegand_card_t *card, bool preamble); void HIDUnpack(int idx, wiegand_message_t *packed); -int HIDDumpPACSBits(const uint8_t *const data, const uint8_t length, bool verbose); void print_wiegand_code(wiegand_message_t *packed); void print_desc_wiegand(cardformat_t *fmt, wiegand_message_t *packed); +cardformatlimit_t get_card_format_limit(int format_idx); #endif From 80942c8badaef5a6d151ba18ac1246b809674ad8 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Mon, 3 Feb 2025 10:10:55 +0100 Subject: [PATCH 005/105] Fix ARM GCC14 warning error: '%02X' directive output may be truncated writing between 2 and 4 bytes into a region of size 3 [-Werror=format-truncation=] --- client/src/cmdhfseos.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/src/cmdhfseos.c b/client/src/cmdhfseos.c index 65a00000d..479906aff 100644 --- a/client/src/cmdhfseos.c +++ b/client/src/cmdhfseos.c @@ -999,7 +999,7 @@ static int seos_pacs_adf_select(char *oid, int oid_len, uint8_t *get_data, int g snprintf(selectedOID, sizeof(selectedOID), "%s", oid); uint16_t selectedOIDLen = strlen(selectedOID); - char selectedOIDLenHex[3]; + char selectedOIDLenHex[5]; snprintf(selectedOIDLenHex, sizeof(selectedOIDLenHex), "%02X", (selectedOIDLen) / 2); char selectedADF[strlen(ADFprefix) + strlen(selectedOIDLenHex) + selectedOIDLen + 1]; @@ -1112,9 +1112,8 @@ static int seos_adf_select(char *oid, int oid_len, int key_index) { const char *ADFprefix = "06"; char selectedOID[100]; snprintf(selectedOID, sizeof(selectedOID), "%s", oid); - uint16_t selectedOIDLen = strlen(selectedOID); - char selectedOIDLenHex[3]; + char selectedOIDLenHex[5]; snprintf(selectedOIDLenHex, sizeof(selectedOIDLenHex), "%02X", (selectedOIDLen) / 2); char selectedADF[strlen(ADFprefix) + strlen(selectedOIDLenHex) + selectedOIDLen + 1]; From 272286f56581d7da059148b0d17f656464600adf Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Mon, 3 Feb 2025 10:11:28 +0100 Subject: [PATCH 006/105] Fix Opensuse-leap docker: use ARM GCC14 --- docker/opensuse-leap/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/opensuse-leap/Dockerfile b/docker/opensuse-leap/Dockerfile index f381bfd9e..701ac8505 100644 --- a/docker/opensuse-leap/Dockerfile +++ b/docker/opensuse-leap/Dockerfile @@ -6,7 +6,7 @@ RUN zypper --non-interactive install --no-recommends shadow sudo git patterns-de RUN zypper addrepo https://download.opensuse.org/repositories/home:wkazubski/15.6/home:wkazubski.repo && \ zypper --gpg-auto-import-keys refresh && \ - zypper --non-interactive install cross-arm-none-eabi-gcc13 cross-arm-none-eabi-newlib + zypper --non-interactive install cross-arm-none-eabi-gcc14 cross-arm-none-eabi-newlib RUN zypper --non-interactive install cmake python3 python3-pip && \ python3 -m pip install ansicolors sslcrypto From f0830ce6b0a03fe5198ef8cfd4cafd6d1aa9b7be Mon Sep 17 00:00:00 2001 From: Donny <107092000+Donny-Guo@users.noreply.github.com> Date: Mon, 3 Feb 2025 01:58:08 -0800 Subject: [PATCH 007/105] Remove new struct and recover missing code section --- client/src/cmdlfhid.c | 6 +- client/src/wiegand_formats.c | 157 +++++++++++++++++++++++++---------- client/src/wiegand_formats.h | 14 ++-- 3 files changed, 119 insertions(+), 58 deletions(-) diff --git a/client/src/cmdlfhid.c b/client/src/cmdlfhid.c index 5c97c6683..baed95b29 100644 --- a/client/src/cmdlfhid.c +++ b/client/src/cmdlfhid.c @@ -544,7 +544,7 @@ static int CmdHIDBrute(const char *Cmd) { } wiegand_card_t card_hi, card_low; - cardformatlimit_t limit = get_card_format_limit(format_idx); + cardformatdescriptor_t card_descriptor = HIDGetCardFormat(format_idx).Fields; memset(&card_hi, 0, sizeof(wiegand_card_t)); char field[3] = {0}; @@ -624,13 +624,13 @@ static int CmdHIDBrute(const char *Cmd) { return PM3_ESOFT; } if (strcmp(field, "fc") == 0) { - if (card_hi.FacilityCode < limit.FacilityCode) { + if (card_hi.FacilityCode < card_descriptor.MaxFC) { card_hi.FacilityCode++; } else { fin_hi = true; } } else if (strcmp(field, "cn") == 0) { - if (card_hi.CardNumber < limit.CardNumber) { + if (card_hi.CardNumber < card_descriptor.MaxCN) { card_hi.CardNumber++; } else { fin_hi = true; diff --git a/client/src/wiegand_formats.c b/client/src/wiegand_formats.c index 4641ec3c9..34d4bdb51 100644 --- a/client/src/wiegand_formats.c +++ b/client/src/wiegand_formats.c @@ -1499,46 +1499,46 @@ static void hid_print_card(wiegand_card_t *card, const cardformat_t format) { } static const cardformat_t FormatTable[] = { - {"H10301", Pack_H10301, Unpack_H10301, "HID H10301 26-bit", {1, 1, 0, 0, 1}, {0xFF, 0xFFFF, 0, 0}}, // imported from old pack/unpack - {"ind26", Pack_ind26, Unpack_ind26, "Indala 26-bit", {1, 1, 0, 0, 1}, {0xFFF, 0xFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"ind27", Pack_ind27, Unpack_ind27, "Indala 27-bit", {1, 1, 0, 0, 0}, {0x1FFF, 0x3FFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"indasc27", Pack_indasc27, Unpack_indasc27, "Indala ASC 27-bit", {1, 1, 0, 0, 0}, {0x1FFF, 0x3FFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"Tecom27", Pack_Tecom27, Unpack_Tecom27, "Tecom 27-bit", {1, 1, 0, 0, 0}, {0x7FF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"2804W", Pack_2804W, Unpack_2804W, "2804 Wiegand 28-bit", {1, 1, 0, 0, 1}, {0xFF, 0x7FFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"ind29", Pack_ind29, Unpack_ind29, "Indala 29-bit", {1, 1, 0, 0, 0}, {0x1FFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"ATSW30", Pack_ATSW30, Unpack_ATSW30, "ATS Wiegand 30-bit", {1, 1, 0, 0, 1}, {0xFFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"ADT31", Pack_ADT31, Unpack_ADT31, "HID ADT 31-bit", {1, 1, 0, 0, 0}, {0xF, 0x7FFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"HCP32", Pack_hcp32, Unpack_hcp32, "HID Check Point 32-bit", {1, 0, 0, 0, 0}, {0, 0x3FFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"HPP32", Pack_hpp32, Unpack_hpp32, "HID Hewlett-Packard 32-bit", {1, 1, 0, 0, 0}, {0xFFF, 0x1FFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"Kastle", Pack_Kastle, Unpack_Kastle, "Kastle 32-bit", {1, 1, 1, 0, 1}, {0xFF, 0xFFFF, 0x1F, 0}}, // from @xilni; PR #23 on RfidResearchGroup/proxmark3 - {"Kantech", Pack_Kantech, Unpack_Kantech, "Indala/Kantech KFS 32-bit", {1, 1, 0, 0, 0}, {0xFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"WIE32", Pack_wie32, Unpack_wie32, "Wiegand 32-bit", {1, 1, 0, 0, 0}, {0xFFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"D10202", Pack_D10202, Unpack_D10202, "HID D10202 33-bit", {1, 1, 0, 0, 1}, {0x7F, 0xFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"H10306", Pack_H10306, Unpack_H10306, "HID H10306 34-bit", {1, 1, 0, 0, 1}, {0xFFFF, 0xFFFF, 0, 0}}, // imported from old pack/unpack - {"N10002", Pack_N10002, Unpack_N10002, "Honeywell/Northern N10002 34-bit", {1, 1, 0, 0, 1}, {0xFFFF, 0xFFFF, 0, 0}}, // from proxclone.com - {"Optus34", Pack_Optus, Unpack_Optus, "Indala Optus 34-bit", {1, 1, 0, 0, 0}, {0x3FF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"SMP34", Pack_Smartpass, Unpack_Smartpass, "Cardkey Smartpass 34-bit", {1, 1, 1, 0, 0}, {0x3FF, 0xFFFF, 0x7, 0}}, // from cardinfo.barkweb.com.au - {"BQT34", Pack_bqt34, Unpack_bqt34, "BQT 34-bit", {1, 1, 0, 0, 1}, {0xFF, 0xFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"C1k35s", Pack_C1k35s, Unpack_C1k35s, "HID Corporate 1000 35-bit std", {1, 1, 0, 0, 1}, {0xFFF, 0xFFFFF, 0, 0}}, // imported from old pack/unpack - {"C15001", Pack_C15001, Unpack_C15001, "HID KeyScan 36-bit", {1, 1, 0, 1, 1}, {0xFF, 0xFFFF, 0, 0x3FF}}, // from Proxmark forums - {"S12906", Pack_S12906, Unpack_S12906, "HID Simplex 36-bit", {1, 1, 1, 0, 1}, {0xFF, 0x3, 0xFFFFFF, 0}}, // from cardinfo.barkweb.com.au - {"Sie36", Pack_Sie36, Unpack_Sie36, "HID 36-bit Siemens", {1, 1, 0, 0, 1}, {0x3FFFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"H10320", Pack_H10320, Unpack_H10320, "HID H10320 37-bit BCD", {1, 0, 0, 0, 1}, {0, 99999999, 0, 0}}, // from Proxmark forums - {"H10302", Pack_H10302, Unpack_H10302, "HID H10302 37-bit huge ID", {1, 0, 0, 0, 1}, {0, 0x7FFFFFFFF, 0, 0}}, // from Proxmark forums - {"H10304", Pack_H10304, Unpack_H10304, "HID H10304 37-bit", {1, 1, 0, 0, 1}, {0xFFFF, 0x7FFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"P10004", Pack_P10004, Unpack_P10004, "HID P10004 37-bit PCSC", {1, 1, 0, 0, 0}, {0x1FFF, 0x3FFFF, 0, 0}}, // from @bthedorff; PR #1559 - {"HGen37", Pack_HGeneric37, Unpack_HGeneric37, "HID Generic 37-bit", {1, 0, 0, 0, 1}, {0, 0x7FFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"MDI37", Pack_MDI37, Unpack_MDI37, "PointGuard MDI 37-bit", {1, 1, 0, 0, 1}, {0xF, 0x1FFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"BQT38", Pack_bqt38, Unpack_bqt38, "BQT 38-bit", {1, 1, 1, 0, 1}, {0xFFF, 0x3FFFF, 0x7, 0}}, // from cardinfo.barkweb.com.au - {"ISCS", Pack_iscs38, Unpack_iscs38, "ISCS 38-bit", {1, 1, 0, 1, 1}, {0x3FF, 0xFFFFFF, 0, 0x7}}, // from cardinfo.barkweb.com.au - {"PW39", Pack_pw39, Unpack_pw39, "Pyramid 39-bit wiegand format", {1, 1, 0, 0, 1}, {0xFFFF, 0xFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"P10001", Pack_P10001, Unpack_P10001, "HID P10001 Honeywell 40-bit", {1, 1, 0, 0, 0}, {0xFFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"Casi40", Pack_CasiRusco40, Unpack_CasiRusco40, "Casi-Rusco 40-bit", {1, 0, 0, 0, 0}, {0, 0xFFFFFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"C1k48s", Pack_C1k48s, Unpack_C1k48s, "HID Corporate 1000 48-bit std", {1, 1, 0, 0, 1}, {0x003FFFFF, 0x007FFFFF, 0, 0}}, // imported from old pack/unpack - {"BC40", Pack_bc40, Unpack_bc40, "Bundy TimeClock 40-bit", {1, 1, 0, 1, 1}, {0xFFF, 0xFFFFF, 0, 0x7F}}, // from - {"Avig56", Pack_Avig56, Unpack_Avig56, "Avigilon 56-bit", {1, 1, 0, 0, 1}, {0xFFFFF, 0x3FFFFFFFF, 0, 0}}, - {"Defcon32", Pack_Defcon32, Unpack_Defcon32, "Custom Defcon RFCTF 42 BIT format", {1, 1, 1, 0, 1}, {0xFFFF, 0xFFFFF, 0xF, 0}}, // Created by (@micsen) for the CTF - {NULL, NULL, NULL, NULL, {0, 0, 0, 0, 0}, {0, 0, 0, 0}} // Must null terminate array + {"H10301", Pack_H10301, Unpack_H10301, "HID H10301 26-bit", {1, 1, 0, 0, 1, 0xFF, 0xFFFF, 0, 0}}, // imported from old pack/unpack + {"ind26", Pack_ind26, Unpack_ind26, "Indala 26-bit", {1, 1, 0, 0, 1, 0xFFF, 0xFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"ind27", Pack_ind27, Unpack_ind27, "Indala 27-bit", {1, 1, 0, 0, 0, 0x1FFF, 0x3FFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"indasc27", Pack_indasc27, Unpack_indasc27, "Indala ASC 27-bit", {1, 1, 0, 0, 0, 0x1FFF, 0x3FFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"Tecom27", Pack_Tecom27, Unpack_Tecom27, "Tecom 27-bit", {1, 1, 0, 0, 0, 0x7FF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"2804W", Pack_2804W, Unpack_2804W, "2804 Wiegand 28-bit", {1, 1, 0, 0, 1, 0xFF, 0x7FFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"ind29", Pack_ind29, Unpack_ind29, "Indala 29-bit", {1, 1, 0, 0, 0, 0x1FFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"ATSW30", Pack_ATSW30, Unpack_ATSW30, "ATS Wiegand 30-bit", {1, 1, 0, 0, 1, 0xFFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"ADT31", Pack_ADT31, Unpack_ADT31, "HID ADT 31-bit", {1, 1, 0, 0, 0, 0xF, 0x7FFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"HCP32", Pack_hcp32, Unpack_hcp32, "HID Check Point 32-bit", {1, 0, 0, 0, 0, 0, 0x3FFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"HPP32", Pack_hpp32, Unpack_hpp32, "HID Hewlett-Packard 32-bit", {1, 1, 0, 0, 0, 0xFFF, 0x1FFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"Kastle", Pack_Kastle, Unpack_Kastle, "Kastle 32-bit", {1, 1, 1, 0, 1, 0xFF, 0xFFFF, 0x1F, 0}}, // from @xilni; PR #23 on RfidResearchGroup/proxmark3 + {"Kantech", Pack_Kantech, Unpack_Kantech, "Indala/Kantech KFS 32-bit", {1, 1, 0, 0, 0, 0xFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"WIE32", Pack_wie32, Unpack_wie32, "Wiegand 32-bit", {1, 1, 0, 0, 0, 0xFFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"D10202", Pack_D10202, Unpack_D10202, "HID D10202 33-bit", {1, 1, 0, 0, 1, 0x7F, 0xFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"H10306", Pack_H10306, Unpack_H10306, "HID H10306 34-bit", {1, 1, 0, 0, 1, 0xFFFF, 0xFFFF, 0, 0}}, // imported from old pack/unpack + {"N10002", Pack_N10002, Unpack_N10002, "Honeywell/Northern N10002 34-bit", {1, 1, 0, 0, 1, 0xFFFF, 0xFFFF, 0, 0}}, // from proxclone.com + {"Optus34", Pack_Optus, Unpack_Optus, "Indala Optus 34-bit", {1, 1, 0, 0, 0, 0x3FF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"SMP34", Pack_Smartpass, Unpack_Smartpass, "Cardkey Smartpass 34-bit", {1, 1, 1, 0, 0, 0x3FF, 0xFFFF, 0x7, 0}}, // from cardinfo.barkweb.com.au + {"BQT34", Pack_bqt34, Unpack_bqt34, "BQT 34-bit", {1, 1, 0, 0, 1, 0xFF, 0xFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"C1k35s", Pack_C1k35s, Unpack_C1k35s, "HID Corporate 1000 35-bit std", {1, 1, 0, 0, 1, 0xFFF, 0xFFFFF, 0, 0}}, // imported from old pack/unpack + {"C15001", Pack_C15001, Unpack_C15001, "HID KeyScan 36-bit", {1, 1, 0, 1, 1, 0xFF, 0xFFFF, 0, 0x3FF}}, // from Proxmark forums + {"S12906", Pack_S12906, Unpack_S12906, "HID Simplex 36-bit", {1, 1, 1, 0, 1, 0xFF, 0x3, 0xFFFFFF, 0}}, // from cardinfo.barkweb.com.au + {"Sie36", Pack_Sie36, Unpack_Sie36, "HID 36-bit Siemens", {1, 1, 0, 0, 1, 0x3FFFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"H10320", Pack_H10320, Unpack_H10320, "HID H10320 37-bit BCD", {1, 0, 0, 0, 1, 0, 99999999, 0, 0}}, // from Proxmark forums + {"H10302", Pack_H10302, Unpack_H10302, "HID H10302 37-bit huge ID", {1, 0, 0, 0, 1, 0, 0x7FFFFFFFF, 0, 0}}, // from Proxmark forums + {"H10304", Pack_H10304, Unpack_H10304, "HID H10304 37-bit", {1, 1, 0, 0, 1, 0xFFFF, 0x7FFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"P10004", Pack_P10004, Unpack_P10004, "HID P10004 37-bit PCSC", {1, 1, 0, 0, 0, 0x1FFF, 0x3FFFF, 0, 0}}, // from @bthedorff; PR #1559 + {"HGen37", Pack_HGeneric37, Unpack_HGeneric37, "HID Generic 37-bit", {1, 0, 0, 0, 1, 0, 0x7FFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"MDI37", Pack_MDI37, Unpack_MDI37, "PointGuard MDI 37-bit", {1, 1, 0, 0, 1, 0xF, 0x1FFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"BQT38", Pack_bqt38, Unpack_bqt38, "BQT 38-bit", {1, 1, 1, 0, 1, 0xFFF, 0x3FFFF, 0x7, 0}}, // from cardinfo.barkweb.com.au + {"ISCS", Pack_iscs38, Unpack_iscs38, "ISCS 38-bit", {1, 1, 0, 1, 1, 0x3FF, 0xFFFFFF, 0, 0x7}}, // from cardinfo.barkweb.com.au + {"PW39", Pack_pw39, Unpack_pw39, "Pyramid 39-bit wiegand format", {1, 1, 0, 0, 1, 0xFFFF, 0xFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"P10001", Pack_P10001, Unpack_P10001, "HID P10001 Honeywell 40-bit", {1, 1, 0, 0, 0, 0xFFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"Casi40", Pack_CasiRusco40, Unpack_CasiRusco40, "Casi-Rusco 40-bit", {1, 0, 0, 0, 0, 0, 0xFFFFFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"C1k48s", Pack_C1k48s, Unpack_C1k48s, "HID Corporate 1000 48-bit std", {1, 1, 0, 0, 1, 0x003FFFFF, 0x007FFFFF, 0, 0}}, // imported from old pack/unpack + {"BC40", Pack_bc40, Unpack_bc40, "Bundy TimeClock 40-bit", {1, 1, 0, 1, 1, 0xFFF, 0xFFFFF, 0, 0x7F}}, // from + {"Avig56", Pack_Avig56, Unpack_Avig56, "Avigilon 56-bit", {1, 1, 0, 0, 1, 0xFFFFF, 0x3FFFFFFFF, 0, 0}}, + {"Defcon32", Pack_Defcon32, Unpack_Defcon32, "Custom Defcon RFCTF 42 BIT format", {1, 1, 1, 0, 1, 0xFFFF, 0xFFFFF, 0xF, 0}}, // Created by (@micsen) for the CTF + {NULL, NULL, NULL, NULL, {0, 0, 0, 0, 0, 0, 0, 0, 0}} // Must null terminate array }; void HIDListFormats(void) { @@ -1664,9 +1664,74 @@ void HIDUnpack(int idx, wiegand_message_t *packed) { } } -cardformatlimit_t get_card_format_limit(int format_idx){ - if ((format_idx < 0) || (format_idx > ARRAYLEN(FormatTable) - 2)) - return FormatTable[ARRAYLEN(FormatTable) - 1].FieldLimits; - else - return FormatTable[format_idx].FieldLimits; -} \ No newline at end of file +int HIDDumpPACSBits(const uint8_t *const data, const uint8_t length, bool verbose) { + uint8_t n = length - 1; + uint8_t pad = data[0]; + char *binstr = (char *)calloc((length * 8) + 1, sizeof(uint8_t)); + if (binstr == NULL) { + return PM3_EMALLOC; + } + + bytes_2_binstr(binstr, data + 1, n); + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(SUCCESS, "PACS......... " _GREEN_("%s"), sprint_hex_inrow(data, length)); + PrintAndLogEx(SUCCESS, "padded bin... " _GREEN_("%s") " ( %zu )", binstr, strlen(binstr)); + + binstr[strlen(binstr) - pad] = '\0'; + PrintAndLogEx(SUCCESS, "bin.......... " _GREEN_("%s") " ( %zu )", binstr, strlen(binstr)); + + size_t hexlen = 0; + uint8_t hex[16] = {0}; + binstr_2_bytes(hex, &hexlen, binstr); + PrintAndLogEx(SUCCESS, "hex.......... " _GREEN_("%s"), sprint_hex_inrow(hex, hexlen)); + + uint32_t top = 0, mid = 0, bot = 0; + if (binstring_to_u96(&top, &mid, &bot, binstr) != strlen(binstr)) { + PrintAndLogEx(ERR, "Binary string contains none <0|1> chars"); + free(binstr); + return PM3_EINVARG; + } + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "Wiegand decode"); + wiegand_message_t packed = initialize_message_object(top, mid, bot, strlen(binstr)); + HIDTryUnpack(&packed); + + PrintAndLogEx(NORMAL, ""); + + if (strlen(binstr) >= 26 && verbose) { + + // iCLASS Legacy + PrintAndLogEx(INFO, "Clone to " _YELLOW_("iCLASS Legacy")); + PrintAndLogEx(SUCCESS, " hf iclass encode --ki 0 --bin %s", binstr); + PrintAndLogEx(NORMAL, ""); + + // HID Prox II + PrintAndLogEx(INFO, "Downgrade to " _YELLOW_("HID Prox II")); + PrintAndLogEx(SUCCESS, " lf hid clone -w H10301 --bin %s", binstr); + PrintAndLogEx(NORMAL, ""); + + // MIFARE Classic + char mfcbin[28] = {0}; + mfcbin[0] = '1'; + memcpy(mfcbin + 1, binstr, strlen(binstr)); + binstr_2_bytes(hex, &hexlen, mfcbin); + + PrintAndLogEx(INFO, "Downgrade to " _YELLOW_("MIFARE Classic") " (Pm3 simulation)"); + PrintAndLogEx(SUCCESS, " hf mf eclr;"); + PrintAndLogEx(SUCCESS, " hf mf esetblk --blk 0 -d 049DBA42A23E80884400C82000000000;"); + PrintAndLogEx(SUCCESS, " hf mf esetblk --blk 1 -d 1B014D48000000000000000000000000;"); + PrintAndLogEx(SUCCESS, " hf mf esetblk --blk 3 -d A0A1A2A3A4A5787788C189ECA97F8C2A;"); + PrintAndLogEx(SUCCESS, " hf mf esetblk --blk 5 -d 020000000000000000000000%s;", sprint_hex_inrow(hex, hexlen)); + PrintAndLogEx(SUCCESS, " hf mf esetblk --blk 7 -d 484944204953787788AA204752454154;"); + PrintAndLogEx(SUCCESS, " hf mf sim --1k -i;"); + PrintAndLogEx(NORMAL, ""); + + PrintAndLogEx(INFO, "Downgrade to " _YELLOW_("MIFARE Classic 1K")); + PrintAndLogEx(SUCCESS, " hf mf encodehid --bin %s", binstr); + PrintAndLogEx(NORMAL, ""); + } + free(binstr); + return PM3_SUCCESS; +} diff --git a/client/src/wiegand_formats.h b/client/src/wiegand_formats.h index 51606b763..1063c2859 100644 --- a/client/src/wiegand_formats.h +++ b/client/src/wiegand_formats.h @@ -36,15 +36,12 @@ typedef struct { bool hasIssueLevel; bool hasOEMCode; bool hasParity; + uint32_t MaxFC; // max Facility Code + uint64_t MaxCN; // max CardNumber + uint32_t MaxIL; // max IssueLevel + uint32_t MaxOEM;// max OEM } cardformatdescriptor_t; -typedef struct { - uint32_t FacilityCode; - uint64_t CardNumber; - uint32_t IssueLevel; - uint32_t OEM; -} cardformatlimit_t; - // Structure for defined Wiegand card formats available for packing/unpacking typedef struct { const char *Name; @@ -52,7 +49,6 @@ typedef struct { bool (*Unpack)(wiegand_message_t *packed, wiegand_card_t *card); const char *Descrp; cardformatdescriptor_t Fields; - cardformatlimit_t FieldLimits; } cardformat_t; void HIDListFormats(void); @@ -62,7 +58,7 @@ bool HIDPack(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bo bool HIDTryUnpack(wiegand_message_t *packed); void HIDPackTryAll(wiegand_card_t *card, bool preamble); void HIDUnpack(int idx, wiegand_message_t *packed); +int HIDDumpPACSBits(const uint8_t *const data, const uint8_t length, bool verbose); void print_wiegand_code(wiegand_message_t *packed); void print_desc_wiegand(cardformat_t *fmt, wiegand_message_t *packed); -cardformatlimit_t get_card_format_limit(int format_idx); #endif From 53a1d5be015012a1912052588c058955140bc351 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Mon, 3 Feb 2025 16:14:28 +0100 Subject: [PATCH 008/105] better fix, thanks @iceman --- client/src/cmdhfseos.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/cmdhfseos.c b/client/src/cmdhfseos.c index 479906aff..3160c1c84 100644 --- a/client/src/cmdhfseos.c +++ b/client/src/cmdhfseos.c @@ -999,8 +999,8 @@ static int seos_pacs_adf_select(char *oid, int oid_len, uint8_t *get_data, int g snprintf(selectedOID, sizeof(selectedOID), "%s", oid); uint16_t selectedOIDLen = strlen(selectedOID); - char selectedOIDLenHex[5]; - snprintf(selectedOIDLenHex, sizeof(selectedOIDLenHex), "%02X", (selectedOIDLen) / 2); + char selectedOIDLenHex[3]; + snprintf(selectedOIDLenHex, sizeof(selectedOIDLenHex), "%02X", (selectedOIDLen >> 1) & 0xFF); char selectedADF[strlen(ADFprefix) + strlen(selectedOIDLenHex) + selectedOIDLen + 1]; snprintf(selectedADF, sizeof(selectedADF), "%s%s%s", ADFprefix, selectedOIDLenHex, selectedOID); @@ -1113,8 +1113,8 @@ static int seos_adf_select(char *oid, int oid_len, int key_index) { char selectedOID[100]; snprintf(selectedOID, sizeof(selectedOID), "%s", oid); uint16_t selectedOIDLen = strlen(selectedOID); - char selectedOIDLenHex[5]; - snprintf(selectedOIDLenHex, sizeof(selectedOIDLenHex), "%02X", (selectedOIDLen) / 2); + char selectedOIDLenHex[3]; + snprintf(selectedOIDLenHex, sizeof(selectedOIDLenHex), "%02X", (selectedOIDLen >> 1) & 0xFF); char selectedADF[strlen(ADFprefix) + strlen(selectedOIDLenHex) + selectedOIDLen + 1]; snprintf(selectedADF, sizeof(selectedADF), "%s%s%s", ADFprefix, selectedOIDLenHex, selectedOID); From be6dc2538c5507b67e56ea25cdb090c94cfb52f5 Mon Sep 17 00:00:00 2001 From: team-orangeBlue <63470411+team-orangeBlue@users.noreply.github.com> Date: Mon, 3 Feb 2025 22:38:20 +0300 Subject: [PATCH 009/105] Finish MF4 documentation Sufficient for configuration of an MF4 tag A-Z as needed. I hope you figure it out! Signed-off-by: team-orangeBlue <63470411+team-orangeBlue@users.noreply.github.com> --- doc/magic_cards_notes.md | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/doc/magic_cards_notes.md b/doc/magic_cards_notes.md index b9099927b..e103b1188 100644 --- a/doc/magic_cards_notes.md +++ b/doc/magic_cards_notes.md @@ -690,7 +690,9 @@ Warning: changing the UID from 00000000 will disable all of these commands perma ### Magic configuration -By accessing the 14th and 15th sector trailers using gen1 mode, it is possible to re-configure the tag. +^[Top](#top) + +By accessing trailers of sectors 11-15 using gen1 mode, it is possible to re-configure the tag. The layout for a sector is below: * block 0: data @@ -698,12 +700,34 @@ The layout for a sector is below: * block 2: data * block 3[0-5] - key A * block 3[6] - configuration byte -* block 3[7] - ACL byte, configuration/RFU +* block 3[7] - ACL byte [bits 7-4], configuration[3-0]/RFU * block 3[8] - ACL byte * block 3[9] - ACL user byte * block 3[10-15] - key B -[ W.I.P - INCOMPLETE; DO NOT MERGE; DO NOT PUBLISH ] +Any data set in one mode will be mirrored to the other, as such be careful when configuring from gen1 mode to avoid unintentionally changing access conditions, keys or configurations. + +Here is how the IC can be configured: +* ATS + * Maximum length is 16 bytes inclduing TL + * Stored in trailers of sectors 0-10 (bytes 0-10: byte 6 of the matching sector; bytes 11-15: byte 7 lower half of sectors `(byte num.-11) (is lower half? +1 if yes)`) + * To avoid issues, please set unused bytes to 00 + * **Example** - to make the 15th byte `AF` you should set block 31 to `FFFFFFFFFFFF 00 0 A 8000 FFFFFFFFFFFF` and block 35 to `FFFFFFFFFFFF 00 0 F 8000 FFFFFFFFFFFF` +* ATQA/SAK + * If the values are changed from defaults, the custom values will be used during anticollision. + * SAK (CL2/final select, default 0x08): sector 11 trailer, byte 6 + * SAK (7b intermediate, default 0x04): sector 12 trailer, byte 6 + * ATQA (higher half (transmission), default 0x44): sector 13 trailer, byte 6 + * ATQA (lower half (transmission), default 0x00): sector 14 trailer, byte 6 + * **Example** - to make the SAK `28`, you should set block 47 to `FFFFFFFFFFFF 28 0 0 8000 FFFFFFFFFFFF` +* Anticollision behavior + * PPS support: sector 14 trailer, byte 7, bit 2 (from least significant); 0: off, 1: on + * RATS support: sector 14 trailer, byte 7, bit 0 (from least significant); 0: off; 1: on + * CL2 (7 byte UID) support: sector 15 trailer, byte 7, bit 3 (from least significant); 0: 4 bytes, 1: 7 bytes + * **Example** - to enable 7 byte UIDs, you should set block 63 to `FFFFFFFFFFFF 00 0 8 8000 FFFFFFFFFFFF` +* Locking the IC, i.e. removing magic wakeup + * In block 63, set byte 7 bits 2 and 0 to `0b1`, resulting in byte 7 containing at least `05`. + * Write your UID. ## MIFARE Classic DirectWrite aka Gen2 aka CUID From 27140128b31a5236799dd732d40f907f771d2ea5 Mon Sep 17 00:00:00 2001 From: team-orangeBlue <63470411+team-orangeBlue@users.noreply.github.com> Date: Mon, 3 Feb 2025 22:39:54 +0300 Subject: [PATCH 010/105] 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 aa93e1207..aa0fd1938 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 `doc/magic_cards_notes.md` - now contains documentation for iKey LLC's MF4 tag (@team-orangeBlue) - Changed `hf mf cload` - now accepts MFC Ev1 sized dumps (@iceman1001) - Changed `hf mfu info` - now properly identify ULEv1 AES 50pF (@iceman1001) - Changed `hf mf info` - now differentiates between full USCUID and cut down ZUID chips (@nvx) From ebd85fbc780dbc30ae7854f2da4ced4f5ebdcd6e Mon Sep 17 00:00:00 2001 From: Donny <107092000+Donny-Guo@users.noreply.github.com> Date: Mon, 3 Feb 2025 14:40:05 -0800 Subject: [PATCH 011/105] Update range checkings in all pack functions for LF HID --- client/src/wiegand_formats.c | 287 +++++++++++------------------------ client/src/wiegand_formats.h | 2 +- 2 files changed, 93 insertions(+), 196 deletions(-) diff --git a/client/src/wiegand_formats.c b/client/src/wiegand_formats.c index 34d4bdb51..0b600f804 100644 --- a/client/src/wiegand_formats.c +++ b/client/src/wiegand_formats.c @@ -19,13 +19,12 @@ #include #include "commonutil.h" +static bool validate_card_limit(int format_idx, wiegand_card_t *card); -static bool Pack_Defcon32(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_Defcon32(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0x00FFFF) return false; // Can't encode FC. - if (card->CardNumber > 0x0fffff) return false; // Can't encode CN. - if (card->IssueLevel > 0x00000F) return false; // Can't encode Issue - if (card->OEM > 0) return false; // Not used in this format + + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 42; /* @@ -83,13 +82,10 @@ static bool Unpack_Defcon32(wiegand_message_t *packed, wiegand_card_t *card) { } -static bool Pack_H10301(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_H10301(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0xFF) return false; // Can't encode FC. - if (card->CardNumber > 0xFFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 26; // Set number of bits packed->Bot |= (card->CardNumber & 0xFFFF) << 1; @@ -113,14 +109,11 @@ static bool Unpack_H10301(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_ind26(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_ind26(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0xFFF) return false; // 12 bits - if (card->CardNumber > 0xFFF) return false; // 12 bits - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 26; // Set number of bits @@ -153,13 +146,10 @@ static bool Unpack_ind26(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_Tecom27(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_Tecom27(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0x7FF) return false; // Can't encode FC. - if (card->CardNumber > 0xFFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 27; set_nonlinear_field(packed, card->FacilityCode, 11, (uint8_t[]) {15, 19, 24, 23, 22, 18, 6, 10, 14, 3, 2}); @@ -179,14 +169,11 @@ static bool Unpack_Tecom27(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_ind27(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_ind27(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0x1FFF) return false; // 13 bits - if (card->CardNumber > 0x3FFF) return false; // 14 bits - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // 4 bit + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 27; // Set number of bits @@ -208,13 +195,10 @@ static bool Unpack_ind27(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_indasc27(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_indasc27(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0x1FFF) return false; // 13 bits - if (card->CardNumber > 0x3FFF) return false; // 14 bits - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 27; set_nonlinear_field(packed, card->FacilityCode, 13, (uint8_t[]) {9, 4, 6, 5, 0, 7, 19, 8, 10, 16, 24, 12, 22}); @@ -234,13 +218,10 @@ static bool Unpack_indasc27(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_2804W(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_2804W(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0x0FF) return false; // Can't encode FC. - if (card->CardNumber > 0x7FFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 28; set_linear_field(packed, card->FacilityCode, 4, 8); @@ -273,14 +254,11 @@ static bool Unpack_2804W(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_ind29(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_ind29(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0x1FFF) return false; // 13 bits - if (card->CardNumber > 0xFFFF) return false; // 16 bits - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // 4 bit + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 29; // Set number of bits @@ -302,13 +280,10 @@ static bool Unpack_ind29(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_ATSW30(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_ATSW30(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0xFFF) return false; // Can't encode FC. - if (card->CardNumber > 0xFFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 30; set_linear_field(packed, card->FacilityCode, 1, 12); @@ -337,13 +312,10 @@ static bool Unpack_ATSW30(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_ADT31(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_ADT31(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0x0F) return false; // Can't encode FC. - if (card->CardNumber > 0x7FFFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 31; set_linear_field(packed, card->FacilityCode, 1, 4); @@ -363,14 +335,11 @@ static bool Unpack_ADT31(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_hcp32(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_hcp32(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0) return false; // Not used - if (card->CardNumber > 0x3FFF) return false; // 24 bits - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 32; // Set number of bits @@ -390,14 +359,11 @@ static bool Unpack_hcp32(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_hpp32(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_hpp32(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0xFFF) return false; // 12 bits - if (card->CardNumber > 0x1FFFFFFF) return false; // 29 bits - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 32; // Set number of bits @@ -419,14 +385,11 @@ static bool Unpack_hpp32(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_wie32(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_wie32(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0xFFF) return false; // 12 bits - if (card->CardNumber > 0xFFFF) return false; // 16 bits - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 32; // Set number of bits @@ -448,13 +411,10 @@ static bool Unpack_wie32(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_Kastle(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_Kastle(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0x00FF) return false; // Can't encode FC. - if (card->CardNumber > 0x0000FFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0x001F) return false; // IL is only 5 bits. - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 32; // Set number of bits set_bit_by_position(packed, 1, 1); // Always 1 @@ -483,13 +443,10 @@ static bool Unpack_Kastle(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_Kantech(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_Kantech(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0xFF) return false; // Can't encode FC. - if (card->CardNumber > 0xFFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 32; set_linear_field(packed, card->FacilityCode, 7, 8); @@ -508,13 +465,10 @@ static bool Unpack_Kantech(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_D10202(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_D10202(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0x007F) return false; // Can't encode FC. - if (card->CardNumber > 0x00FFFFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 33; // Set number of bits set_linear_field(packed, card->FacilityCode, 1, 7); @@ -539,13 +493,10 @@ static bool Unpack_D10202(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_H10306(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_H10306(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0xFFFF) return false; // Can't encode FC. - if (card->CardNumber > 0xFFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 34; // Set number of bits packed->Bot |= (card->CardNumber & 0xFFFF) << 1; @@ -573,13 +524,10 @@ static bool Unpack_H10306(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_N10002(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_N10002(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0xFFFF) return false; // Can't encode FC. - if (card->CardNumber > 0xFFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 34; // Set number of bits set_linear_field(packed, card->FacilityCode, 1, 16); @@ -612,13 +560,10 @@ static bool Unpack_N10002(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_C1k35s(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_C1k35s(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0xFFF) return false; // Can't encode FC. - if (card->CardNumber > 0xFFFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 35; // Set number of bits packed->Bot |= (card->CardNumber & 0x000FFFFF) << 1; @@ -646,13 +591,10 @@ static bool Unpack_C1k35s(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_H10320(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_H10320(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0) return false; // Can't encode FC. (none in this format) - if (card->CardNumber > 99999999) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 37; // Set number of bits @@ -707,13 +649,10 @@ static bool Unpack_H10320(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_S12906(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_S12906(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0xFF) return false; // Can't encode FC. - if (card->IssueLevel > 0x03) return false; // Can't encode IL. - if (card->CardNumber > 0x00FFFFFF) return false; // Can't encode CN. - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 36; // Set number of bits set_linear_field(packed, card->FacilityCode, 1, 8); @@ -740,13 +679,10 @@ static bool Unpack_S12906(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_Sie36(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_Sie36(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0x0003FFFF) return false; // Can't encode FC. - if (card->CardNumber > 0x0000FFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 36; // Set number of bits set_linear_field(packed, card->FacilityCode, 1, 18); @@ -775,13 +711,10 @@ static bool Unpack_Sie36(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_C15001(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_C15001(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0x000000FF) return false; // Can't encode FC. - if (card->CardNumber > 0x0000FFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0x000003FF) return false; // Can't encode OEM. + if (!validate_card_limit(format_idx, card)) return false; if (card->OEM == 0) card->OEM = 900; @@ -813,13 +746,10 @@ static bool Unpack_C15001(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_H10302(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_H10302(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0) return false; // Can't encode FC. (none in this format) - if (card->CardNumber > 0x00000007FFFFFFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 37; // Set number of bits set_linear_field(packed, card->CardNumber, 1, 35); @@ -842,13 +772,10 @@ static bool Unpack_H10302(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_P10004(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_P10004(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0x00001FFF) return false; // Can't encode FC. - if (card->CardNumber > 0x0003FFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 37; // Set number of bits @@ -871,13 +798,10 @@ static bool Unpack_P10004(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_H10304(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_H10304(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0x0000FFFF) return false; // Can't encode FC. - if (card->CardNumber > 0x0007FFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 37; // Set number of bits @@ -904,13 +828,10 @@ static bool Unpack_H10304(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_HGeneric37(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_HGeneric37(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0) return false; // Not used in this format - if (card->CardNumber > 0x0007FFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 37; // Set number of bits @@ -956,13 +877,10 @@ static bool Unpack_HGeneric37(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_MDI37(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_MDI37(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0x0000F) return false; // Can't encode FC. - if (card->CardNumber > 0x1FFFFFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 37; // Set number of bits @@ -991,14 +909,11 @@ static bool Unpack_MDI37(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_P10001(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_P10001(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0xFFF) return false; // Can't encode FC. - if (card->CardNumber > 0xFFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 40; // Set number of bits set_linear_field(packed, 0xF, 0, 4); @@ -1032,14 +947,11 @@ static bool Unpack_P10001(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_C1k48s(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_C1k48s(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0x003FFFFF) return false; // Can't encode FC. - if (card->CardNumber > 0x007FFFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 48; // Set number of bits packed->Bot |= (card->CardNumber & 0x007FFFFF) << 1; @@ -1069,14 +981,11 @@ static bool Unpack_C1k48s(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_CasiRusco40(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_CasiRusco40(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0) return false; // Can't encode FC. - if (card->CardNumber > 0xFFFFFFFFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 40; // Set number of bits set_linear_field(packed, card->CardNumber, 1, 38); @@ -1095,14 +1004,11 @@ static bool Unpack_CasiRusco40(wiegand_message_t *packed, wiegand_card_t *card) return true; } -static bool Pack_Optus(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_Optus(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0x3FF) return false; // Can't encode FC. - if (card->CardNumber > 0xFFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 34; // Set number of bits set_linear_field(packed, card->CardNumber, 1, 16); @@ -1123,14 +1029,11 @@ static bool Unpack_Optus(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_Smartpass(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_Smartpass(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0x3FF) return false; // Can't encode FC. - if (card->CardNumber > 0xFFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0x7) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 34; // Set number of bits @@ -1153,14 +1056,11 @@ static bool Unpack_Smartpass(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_bqt34(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_bqt34(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0xFF) return false; // Can't encode FC. - if (card->CardNumber > 0xFFFFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 34; // Set number of bits @@ -1193,14 +1093,11 @@ static bool Unpack_bqt34(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_bqt38(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_bqt38(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0xFFF) return false; // 12 bits - if (card->CardNumber > 0x3FFFF) return false; // 19 bits - if (card->IssueLevel > 0x7) return false; // 4 bit - if (card->OEM > 0) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 38; // Set number of bits @@ -1235,14 +1132,11 @@ static bool Unpack_bqt38(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_iscs38(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_iscs38(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0x3FF) return false; // 12 bits - if (card->CardNumber > 0xFFFFFF) return false; // 19 bits - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0x7) return false; // 4 bit + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 38; // Set number of bits @@ -1277,14 +1171,11 @@ static bool Unpack_iscs38(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool Pack_pw39(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_pw39(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0xFFFF) return false; // 12 bits - if (card->CardNumber > 0xFFFFF) return false; // 19 bits - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0) return false; // 4 bit + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 39; // Set number of bits @@ -1318,14 +1209,11 @@ static bool Unpack_pw39(wiegand_message_t *packed, wiegand_card_t *card) { } -static bool Pack_bc40(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_bc40(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); - if (card->FacilityCode > 0xFFF) return false; // Can't encode FC. - if (card->CardNumber > 0xFFFFF) return false; // Can't encode CN. - if (card->IssueLevel > 0) return false; // Not used in this format - if (card->OEM > 0x7F) return false; // Not used in this format + if (!validate_card_limit(format_idx, card)) return false; packed->Length = 40; // Set number of bits @@ -1372,12 +1260,11 @@ static bool step_parity_check(wiegand_message_t *packed, int start, int length, return parity; } -static bool Pack_Avig56(wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { +static bool Pack_Avig56(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); packed->Length = 56; - if (card->FacilityCode > 0xFFFFF) return false; // Can't encode FC. - if (card->CardNumber > 0x3FFFFFFFF) return false; // Can't encode CN. + if (!validate_card_limit(format_idx, card)) return false; set_linear_field(packed, card->FacilityCode, 1, 20); set_linear_field(packed, card->CardNumber, 21, 34); @@ -1594,13 +1481,23 @@ int HIDFindCardFormat(const char *format) { return -1; } +// validate if the card's FC, CN, IL, OEM are within the limit of its format +// return true if the card is valid +static bool validate_card_limit(int format_idx, wiegand_card_t *card) { + cardformatdescriptor_t card_descriptor = FormatTable[format_idx].Fields; + return !((card->FacilityCode > card_descriptor.MaxFC) || + (card->CardNumber > card_descriptor.MaxCN)|| + (card->IssueLevel > card_descriptor.MaxIL) || + (card->OEM > card_descriptor.MaxOEM)); +} + bool HIDPack(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); if ((format_idx < 0) || (format_idx > ARRAYLEN(FormatTable) - 2)) return false; - return FormatTable[format_idx].Pack(card, packed, preamble); + return FormatTable[format_idx].Pack(format_idx, card, packed, preamble); } void HIDPackTryAll(wiegand_card_t *card, bool preamble) { @@ -1613,7 +1510,7 @@ void HIDPackTryAll(wiegand_card_t *card, bool preamble) { int i = 0; while (FormatTable[i].Name) { memset(&packed, 0, sizeof(wiegand_message_t)); - bool res = FormatTable[i].Pack(card, &packed, preamble); + bool res = FormatTable[i].Pack(i, card, &packed, preamble); if (res) { cardformat_t fmt = HIDGetCardFormat(i); print_desc_wiegand(&fmt, &packed); diff --git a/client/src/wiegand_formats.h b/client/src/wiegand_formats.h index 1063c2859..ff231f25f 100644 --- a/client/src/wiegand_formats.h +++ b/client/src/wiegand_formats.h @@ -45,7 +45,7 @@ typedef struct { // Structure for defined Wiegand card formats available for packing/unpacking typedef struct { const char *Name; - bool (*Pack)(wiegand_card_t *card, wiegand_message_t *packed, bool preamble); + bool (*Pack)(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble); bool (*Unpack)(wiegand_message_t *packed, wiegand_card_t *card); const char *Descrp; cardformatdescriptor_t Fields; From 24f474feaab365852ef49b9fc36d0bc34e198d92 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Wed, 5 Feb 2025 23:07:08 +0100 Subject: [PATCH 012/105] hf 14a sim/simaid: Fix emulated RATS for -t 11 --- armsrc/iso14443a.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index 0611256e7..7d045d1f8 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -1185,7 +1185,7 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, rATQA[1] = 0x03; sak = 0x20; memcpy(rRATS, "\x06\x75\x77\x81\x02\x80\x00\x00", 8); - rRATS_len = 8; + rRATS_len = 8; // including CRC break; } case 4: { // ISO/IEC 14443-4 - javacard (JCOP) @@ -1254,8 +1254,8 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, } case 11: { // ISO/IEC 14443-4 - javacard (JCOP) / EMV - memcpy(rRATS, "\x13\x78\x80\x72\x02\x80\x31\x80\x66\xb1\x84\x0c\x01\x6e\x01\x83\x00\x90\x00", 19); - rRATS_len = 19; + memcpy(rRATS, "\x13\x78\x80\x72\x02\x80\x31\x80\x66\xb1\x84\x0c\x01\x6e\x01\x83\x00\x90\x00\x00\x00", 21); + rRATS_len = 21; // including CRC rATQA[0] = 0x04; sak = 0x20; break; From 99330345667b6fab48f65e4c7f5a516f62eed656 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Wed, 5 Feb 2025 23:25:21 +0100 Subject: [PATCH 013/105] hf 14a simaid: support reader requests without CID --- armsrc/iso14443a.c | 53 ++++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index 7d045d1f8..3617900be 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -4055,15 +4055,20 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *data, dynamic_response_info.modulation_n = 0; // Check for ISO 14443A-4 compliant commands, look at left nibble + uint8_t offset = 0; switch (receivedCmd[0]) { - case 0x0B: - case 0x0A: { // IBlock (command CID) + case 0x0B: // IBlock with CID + case 0x0A: + offset = 1; + case 0x02: // IBlock without CID + case 0x03: { dynamic_response_info.response[0] = receivedCmd[0]; dynamic_response_info.response[1] = 0x00; - switch (receivedCmd[3]) { // APDU Class Byte - // receivedCmd in this case is expecting to structured with a CID, then the APDU command for SelectFile - // | IBlock (CID) | CID | APDU Command | CRC | + switch (receivedCmd[2+offset]) { // APDU Class Byte + // receivedCmd in this case is expecting to structured with possibly a CID, then the APDU command for SelectFile + // | IBlock (CID) | CID | APDU Command | CRC | + // or | IBlock (noCID) | APDU Command | CRC | case 0xA4: { // SELECT FILE // Select File AID uses the following format for GlobalPlatform @@ -4072,8 +4077,8 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *data, // xx in this case is len of the AID value in hex // aid len is found as a hex value in receivedCmd[6] (Index Starts at 0) - int aid_len = receivedCmd[6]; - uint8_t *received_aid = &receivedCmd[7]; + int aid_len = receivedCmd[5+offset]; + uint8_t *received_aid = &receivedCmd[6+offset]; // aid enumeration flag if (enumerate == true) { @@ -4083,29 +4088,29 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *data, if (memcmp(aidFilter, received_aid, aid_len) == 0) { // Evaluate the AID sent by the Reader to the AID supplied // AID Response will be parsed here - memcpy(dynamic_response_info.response + 2, aidResponse, respondLen + 2); + memcpy(dynamic_response_info.response + 1 + offset, aidResponse, respondLen + 1 + offset); dynamic_response_info.response_n = respondLen + 2; } else { // Any other SELECT FILE command will return with a Not Found - dynamic_response_info.response[2] = 0x6A; - dynamic_response_info.response[3] = 0x82; - dynamic_response_info.response_n = 4; + dynamic_response_info.response[1 + offset] = 0x6A; + dynamic_response_info.response[2 + offset] = 0x82; + dynamic_response_info.response_n = 3 + offset; } } break; case 0xDA: { // PUT DATA // Just send them a 90 00 response - dynamic_response_info.response[2] = 0x90; - dynamic_response_info.response[3] = 0x00; - dynamic_response_info.response_n = 4; + dynamic_response_info.response[1 + offset] = 0x90; + dynamic_response_info.response[2 + offset] = 0x00; + dynamic_response_info.response_n = 3 + offset; } break; case 0xCA: { // GET DATA if (sentCount == 0) { // APDU Command will just be parsed here - memcpy(dynamic_response_info.response + 2, apduCommand, apduLen + 2); - dynamic_response_info.response_n = respondLen + 2; + memcpy(dynamic_response_info.response + 1 + offset, apduCommand, apduLen + 2); + dynamic_response_info.response_n = respondLen + 1 + offset; } else { finished = true; break; @@ -4116,18 +4121,18 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *data, default : { // Any other non-listed command // Respond Not Found - dynamic_response_info.response[2] = 0x6A; - dynamic_response_info.response[3] = 0x82; - dynamic_response_info.response_n = 4; + dynamic_response_info.response[1 + offset] = 0x6A; + dynamic_response_info.response[2 + offset] = 0x82; + dynamic_response_info.response_n = 3 + offset; } } break; } break; - case 0xCA: - case 0xC2: { // Readers sends deselect command - dynamic_response_info.response[0] = 0xCA; + case 0xCA: // S-Block Deselect with CID + case 0xC2: { // S-Block Deselect without CID + dynamic_response_info.response[0] = receivedCmd[0]; dynamic_response_info.response[1] = 0x00; dynamic_response_info.response_n = 2; finished = true; @@ -4149,7 +4154,9 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *data, if (dynamic_response_info.response_n > 0) { // Copy the CID from the reader query - dynamic_response_info.response[1] = receivedCmd[1]; + if (offset > 0) { + dynamic_response_info.response[1] = receivedCmd[1]; + } // Add CRC bytes, always used in ISO 14443A-4 compliant cards AddCrc14A(dynamic_response_info.response, dynamic_response_info.response_n); From f8abfc07312e8bddfe6e82a90d83b4968265978d Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Wed, 5 Feb 2025 23:31:07 +0100 Subject: [PATCH 014/105] hf 14a simaid: ignore premature HALT until RATS is reached --- armsrc/iso14443a.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index 3617900be..619842c12 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -4009,6 +4009,7 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *data, // main loop bool finished = false; + bool got_rats = false; while (finished == false) { // BUTTON_PRESS check done in GetIso14443aCommandFromReader WDT_HIT(); @@ -4046,9 +4047,12 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *data, } else if (receivedCmd[0] == ISO14443A_CMD_HALT && len == 4) { // Received a HALT LogTrace(receivedCmd, Uart.len, Uart.startTime * 16 - DELAY_AIR2ARM_AS_TAG, Uart.endTime * 16 - DELAY_AIR2ARM_AS_TAG, Uart.parity, true); p_response = NULL; - finished = true; + if (got_rats) { + finished = true; + } } else if (receivedCmd[0] == ISO14443A_CMD_RATS && len == 4) { // Received a RATS request p_response = &responses[RESP_INDEX_RATS]; + got_rats = true; } else { // clear old dynamic responses dynamic_response_info.response_n = 0; From d2c198a2ec20a331bbcb60f1f973bcf57156e2b7 Mon Sep 17 00:00:00 2001 From: Lucifer Voeltner Date: Fri, 7 Feb 2025 20:48:12 +0700 Subject: [PATCH 015/105] Stylise 'Dorma Kaba' -> 'dormakaba' and 'SAFLOK' -> 'Saflok' --- client/src/cmdhfmf.c | 2 +- client/src/cmdhfmfu.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 043ce9655..b80fb138e 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -9818,7 +9818,7 @@ static int CmdHF14AMfInfo(const char *Cmd) { } if (e_sector[1].foundKey[MF_KEY_A] && (e_sector[1].Key[MF_KEY_A] == 0x2A2C13CC242A)) { - PrintAndLogEx(SUCCESS, "Dorma Kaba SAFLOK detected"); + PrintAndLogEx(SUCCESS, "dormakaba Saflok detected"); } } else { diff --git a/client/src/cmdhfmfu.c b/client/src/cmdhfmfu.c index 1fe6a5cd0..e74b926e2 100644 --- a/client/src/cmdhfmfu.c +++ b/client/src/cmdhfmfu.c @@ -68,7 +68,7 @@ static uint8_t default_aes_keys[][16] = { { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, // all FF { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }, // 11 22 33 { 0x47, 0x45, 0x4D, 0x58, 0x50, 0x52, 0x45, 0x53, 0x53, 0x4F, 0x53, 0x41, 0x4D, 0x50, 0x4C, 0x45 }, // gemalto - { 0x56, 0x4c, 0x67, 0x56, 0x99, 0x69, 0x64, 0x9f, 0x17, 0xC6, 0xC6, 0x16, 0x01, 0x10, 0x4D, 0xCA } // Virtual Dorma Kaba + { 0x56, 0x4c, 0x67, 0x56, 0x99, 0x69, 0x64, 0x9f, 0x17, 0xC6, 0xC6, 0x16, 0x01, 0x10, 0x4D, 0xCA } // Virtual dormakaba }; static uint8_t default_3des_keys[][16] = { From 6465f247ea0048306a8da2a230478aeb4a084e7f Mon Sep 17 00:00:00 2001 From: n-hutton Date: Fri, 7 Feb 2025 16:27:08 +0000 Subject: [PATCH 016/105] force bitfiles to be identical for same source code --- fpga/Makefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/fpga/Makefile b/fpga/Makefile index 821ac0ec7..b31c000ab 100644 --- a/fpga/Makefile +++ b/fpga/Makefile @@ -44,6 +44,9 @@ XST_OPTS_AREA += -opt_level 2 XST_OPTS_AREA += -fsm_style bram XST_OPTS_AREA += -fsm_encoding compact +# par specific option (set determistic seed) +PAR_OPTIONS = -t 1 + # Types of selective module compilation: # WITH_LF Enables selection of LF modules (and disables all HF) @@ -179,12 +182,14 @@ work: %.ncd: %_map.ncd $(Q)$(RM) $@ $(info [-] PAR $@) - $(Q)$(XILINX_TOOLS_PREFIX)par $(VERBOSITY) -w $< $@ + $(Q)$(XILINX_TOOLS_PREFIX)par $(PAR_OPTIONS) $(VERBOSITY) -w $< $@ %.bit: %.ncd $(Q)$(RM) $@ $*.drc $*.rbt $(info [=] BITGEN $@) $(Q)$(XILINX_TOOLS_PREFIX)bitgen $(VERBOSITY) -w $* $@ + #$(shell printf '\xff%.0s' {1..22} | dd of=fpga_pm3_hf.bit bs=1 seek=48 conv=notrunc) + echo "FFFFFFFFFFFFFFFFFFFFFF" | dd of=fpga_pm3_hf.bit bs=1 seek=48 conv=notrunc $(Q)$(CP) $@ .. # Build all targets From bb4e14bb4c242992397d3c174c023317986d0a6c Mon Sep 17 00:00:00 2001 From: n-hutton Date: Fri, 7 Feb 2025 16:36:01 +0000 Subject: [PATCH 017/105] remove comment before PR open --- fpga/Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/fpga/Makefile b/fpga/Makefile index b31c000ab..963fd1e28 100644 --- a/fpga/Makefile +++ b/fpga/Makefile @@ -188,7 +188,6 @@ work: $(Q)$(RM) $@ $*.drc $*.rbt $(info [=] BITGEN $@) $(Q)$(XILINX_TOOLS_PREFIX)bitgen $(VERBOSITY) -w $* $@ - #$(shell printf '\xff%.0s' {1..22} | dd of=fpga_pm3_hf.bit bs=1 seek=48 conv=notrunc) echo "FFFFFFFFFFFFFFFFFFFFFF" | dd of=fpga_pm3_hf.bit bs=1 seek=48 conv=notrunc $(Q)$(CP) $@ .. From 0560d74b0176e54c4796dbfdf4f9490b8e5000d2 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Sun, 9 Feb 2025 21:28:43 +0100 Subject: [PATCH 018/105] Modified makefile.platform.sample in order to make it more clear and simpler when configuring the compilation to generate correct arm image for their specific hardware. We have RDV4, PM3GENERIC (which most Pm3 Easy, Rdv1, Rdv2 etc) ICOPYX, PM3 Max --- CHANGELOG.md | 1 + Makefile.platform.sample | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aa0fd1938..3b8f34610 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 `Makefile.platform.sample` file - now have clear instructions for generating images for other proxmark3 hardware (@iceman1001) - Changed `doc/magic_cards_notes.md` - now contains documentation for iKey LLC's MF4 tag (@team-orangeBlue) - Changed `hf mf cload` - now accepts MFC Ev1 sized dumps (@iceman1001) - Changed `hf mfu info` - now properly identify ULEv1 AES 50pF (@iceman1001) diff --git a/Makefile.platform.sample b/Makefile.platform.sample index 2661a0720..26cb08a0f 100644 --- a/Makefile.platform.sample +++ b/Makefile.platform.sample @@ -1,8 +1,20 @@ # If you want to use it, copy this file as Makefile.platform and adjust it to your needs # Run 'make PLATFORM=' to get an exhaustive list of possible parameters for this file. +# By default PM3 RDV4 image is generated. +# Comment the line below and uncomment further down according to which device you have PLATFORM=PM3RDV4 + +# For PM3 Easy: +# uncomment the line below #PLATFORM=PM3GENERIC + +# For ICOPY-X and PM3 Max: +# uncomment the two lines below +#PLATFORM=PM3ICOPY3 +#PLATFORM_EXTRAS=FLASH + + # If you want more than one PLATFORM_EXTRAS option, separate them by spaces: #PLATFORM_EXTRAS=BTADDON #PLATFORM_EXTRAS=FLASH @@ -10,6 +22,7 @@ PLATFORM=PM3RDV4 #PLATFORM_EXTRAS=BTADDON FLASH #STANDALONE=LF_SAMYRUN + # Uncomment the line below to set the correct LED order on board Proxmark3 Easy # Only available with PLATFORM=PM3GENERIC #LED_ORDER=PM3EASY From 1acc030fd4166029d36d465b11193b4592972aef Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Wed, 12 Feb 2025 08:32:13 +0100 Subject: [PATCH 019/105] rework simaid & rename few vars --- armsrc/Standalone/hf_msdsal.c | 2 +- armsrc/Standalone/hf_reblay.c | 2 +- armsrc/Standalone/hf_tcprst.c | 4 +- armsrc/appmain.c | 21 +++--- armsrc/iso14443a.c | 98 +++++++++++++--------------- armsrc/iso14443a.h | 9 +-- client/src/cmdhf14a.c | 119 ++++++++++++++++++++-------------- include/pm3_cmd.h | 3 +- 8 files changed, 137 insertions(+), 121 deletions(-) diff --git a/armsrc/Standalone/hf_msdsal.c b/armsrc/Standalone/hf_msdsal.c index 4ea27b082..711e653a4 100644 --- a/armsrc/Standalone/hf_msdsal.c +++ b/armsrc/Standalone/hf_msdsal.c @@ -445,7 +445,7 @@ void RunMod(void) { // received a RATS request } else if (receivedCmd[0] == ISO14443A_CMD_RATS && len == 4) { prevCmd = 0; - p_response = &responses[RESP_INDEX_RATS]; + p_response = &responses[RESP_INDEX_ATS]; } else { if (g_dbglevel == DBG_DEBUG) { diff --git a/armsrc/Standalone/hf_reblay.c b/armsrc/Standalone/hf_reblay.c index 51a30f64e..1b84eb3f7 100644 --- a/armsrc/Standalone/hf_reblay.c +++ b/armsrc/Standalone/hf_reblay.c @@ -338,7 +338,7 @@ void RunMod() { } else if (receivedCmd[1] == 0x70 && receivedCmd[0] == ISO14443A_CMD_ANTICOLL_OR_SELECT && len == 9) { // Received a SELECT (cascade 1) p_response = &responses[RESP_INDEX_SAKC1]; } else if (receivedCmd[0] == ISO14443A_CMD_RATS && len == 4) { // Received a RATS request - p_response = &responses[RESP_INDEX_RATS]; + p_response = &responses[RESP_INDEX_ATS]; resp = 1; } else if (receivedCmd[0] == 0xf2 && len == 4) { // ACKed - Time extension DbpString(_YELLOW_("!!") " Reader accepted time extension!"); diff --git a/armsrc/Standalone/hf_tcprst.c b/armsrc/Standalone/hf_tcprst.c index 9b42d0fd0..e6f75bc75 100644 --- a/armsrc/Standalone/hf_tcprst.c +++ b/armsrc/Standalone/hf_tcprst.c @@ -247,7 +247,7 @@ void RunMod(void) { } else if (receivedCmd[1] == 0x70 && receivedCmd[0] == ISO14443A_CMD_ANTICOLL_OR_SELECT_2 && len == 9) { // Received a SELECT (cascade 2) p_response = &responses[RESP_INDEX_SAKC2]; } else if (receivedCmd[0] == ISO14443A_CMD_RATS && len == 4) { // Received a RATS request - p_response = &responses[RESP_INDEX_RATS]; + p_response = &responses[RESP_INDEX_ATS]; } else if (receivedCmd[0] == ISO14443A_CMD_PPS) { p_response = &responses[RESP_INDEX_PPS]; } else { @@ -425,7 +425,7 @@ void RunMod(void) { } else if (receivedCmd[1] == 0x70 && receivedCmd[0] == ISO14443A_CMD_ANTICOLL_OR_SELECT_2 && len == 9) { // Received a SELECT (cascade 2) p_response = &responses[RESP_INDEX_SAKC2]; } else if (receivedCmd[0] == ISO14443A_CMD_RATS && len == 4) { // Received a RATS request - p_response = &responses[RESP_INDEX_RATS]; + p_response = &responses[RESP_INDEX_ATS]; } else if (receivedCmd[0] == ISO14443A_CMD_PPS) { p_response = &responses[RESP_INDEX_PPS]; } else { diff --git a/armsrc/appmain.c b/armsrc/appmain.c index e9c1fc48d..aeff70df2 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -1696,20 +1696,21 @@ static void PacketReceived(PacketCommandNG *packet) { uint8_t tagtype; uint16_t flags; uint8_t uid[10]; - uint8_t rats[20]; + uint8_t ats[20]; uint8_t aid[30]; - uint8_t response[100]; - uint8_t apdu[100]; - int aid_len; - int respond_len; - int apdu_len; - bool enumerate; + uint8_t selectaid_response[100]; + uint8_t getdata_response[100]; + uint32_t ats_len; + uint32_t aid_len; + uint32_t selectaid_response_len; + uint32_t getdata_response_len; } PACKED; struct p *payload = (struct p *) packet->data.asBytes; + // ## Simulate iso14443a tag - pass tag type, UID, ATS, AID, responses SimulateIso14443aTagAID(payload->tagtype, payload->flags, payload->uid, - payload->rats, sizeof(payload->rats), payload->aid, payload->response, - payload->apdu, payload->aid_len, payload->respond_len, - payload->apdu_len, payload->enumerate); // ## Simulate iso14443a tag - pass tag type, UID, rats, aid, resp, apdu + payload->ats, payload->ats_len, payload->aid, payload->aid_len, + payload->selectaid_response, payload->selectaid_response_len, + payload->getdata_response, payload->getdata_response_len); break; } case CMD_HF_ISO14443A_ANTIFUZZ: { diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index 619842c12..41b4aa546 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -1108,7 +1108,7 @@ bool prepare_allocated_tag_modulation(tag_response_info_t *response_info, uint8_ } bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, - uint8_t *iRATs, size_t irats_len, tag_response_info_t **responses, + uint8_t *ats, size_t ats_len, tag_response_info_t **responses, uint32_t *cuid, uint32_t counters[3], uint8_t tearings[3], uint8_t *pages) { uint8_t sak = 0; // The first response contains the ATQA (note: bytes are transmitted in reverse order). @@ -1130,9 +1130,9 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, // TA(1) = 0x80: different divisors not supported, DR = 1, DS = 1 // TB(1) = not present. Defaults: FWI = 4 (FWT = 256 * 16 * 2^4 * 1/fc = 4833us), SFGI = 0 (SFG = 256 * 16 * 2^0 * 1/fc = 302us) // TC(1) = 0x02: CID supported, NAD not supported -// static uint8_t rRATS[] = { 0x04, 0x58, 0x80, 0x02, 0x00, 0x00 }; - static uint8_t rRATS[40] = { 0x05, 0x75, 0x80, 0x60, 0x02, 0x00, 0x00, 0x00 }; - uint8_t rRATS_len = 8; +// static uint8_t rATS[] = { 0x04, 0x58, 0x80, 0x02, 0x00, 0x00 }; + static uint8_t rATS[40] = { 0x05, 0x75, 0x80, 0x60, 0x02, 0x00, 0x00, 0x00 }; + uint8_t rATS_len = 8; // GET_VERSION response for EV1/NTAG static uint8_t rVERSION[10] = { 0x00 }; @@ -1184,8 +1184,8 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, rATQA[0] = 0x44; rATQA[1] = 0x03; sak = 0x20; - memcpy(rRATS, "\x06\x75\x77\x81\x02\x80\x00\x00", 8); - rRATS_len = 8; // including CRC + memcpy(rATS, "\x06\x75\x77\x81\x02\x80\x00\x00", 8); + rATS_len = 8; // including CRC break; } case 4: { // ISO/IEC 14443-4 - javacard (JCOP) @@ -1254,8 +1254,8 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, } case 11: { // ISO/IEC 14443-4 - javacard (JCOP) / EMV - memcpy(rRATS, "\x13\x78\x80\x72\x02\x80\x31\x80\x66\xb1\x84\x0c\x01\x6e\x01\x83\x00\x90\x00\x00\x00", 21); - rRATS_len = 21; // including CRC + memcpy(rATS, "\x13\x78\x80\x72\x02\x80\x31\x80\x66\xb1\x84\x0c\x01\x6e\x01\x83\x00\x90\x00\x00\x00", 21); + rATS_len = 21; // including CRC rATQA[0] = 0x04; sak = 0x20; break; @@ -1271,17 +1271,17 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, } } - // copy the iRATs if supplied. - // iRATs is a pointer to 20 byte array - // rRATS is a 40 byte array - if ((flags & FLAG_RATS_IN_DATA) == FLAG_RATS_IN_DATA) { - memcpy(rRATS, iRATs, irats_len); + // copy the ats if supplied. + // ats is a pointer to 20 byte array + // rATS is a 40 byte array + if ((flags & FLAG_ATS_IN_DATA) == FLAG_ATS_IN_DATA) { + memcpy(rATS, ats, ats_len); // rats len is dictated by the first char of the string, add 2 crc bytes - rRATS_len = (iRATs[0] + 2); + rATS_len = (ats[0] + 2); // Since its Varible length we can send value > 40 and overflow our array. // Even if RATS protocol defined as max 40 bytes doesn't mean people try stuff - if (rRATS_len > sizeof(rRATS)) { - if (g_dbglevel >= DBG_ERROR) Dbprintf("[-] ERROR: iRATS overflow. Max %zu, got %zu", sizeof(rRATS), rRATS_len); + if (rATS_len > sizeof(rATS)) { + if (g_dbglevel >= DBG_ERROR) Dbprintf("[-] ERROR: ATS overflow. Max %zu, got %zu", sizeof(rATS), rATS_len); return false; } } @@ -1379,7 +1379,7 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, return false; } - AddCrc14A(rRATS, rRATS_len - 2); + AddCrc14A(rATS, rATS_len - 2); AddCrc14A(rPPS, sizeof(rPPS) - 2); @@ -1407,7 +1407,7 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, { .response = rSAKc1, .response_n = sizeof(rSAKc1) }, // Acknowledge select - cascade 1 { .response = rSAKc2, .response_n = sizeof(rSAKc2) }, // Acknowledge select - cascade 2 { .response = rSAKc3, .response_n = sizeof(rSAKc3) }, // Acknowledge select - cascade 3 - { .response = rRATS, .response_n = sizeof(rRATS) }, // dummy ATS (pseudo-ATR), answer to RATS + { .response = rATS, .response_n = sizeof(rATS) }, // dummy ATS (pseudo-ATR), answer to RATS { .response = rVERSION, .response_n = sizeof(rVERSION) }, // EV1/NTAG GET_VERSION response { .response = rSIGN, .response_n = sizeof(rSIGN) }, // EV1/NTAG READ_SIG response { .response = rPPS, .response_n = sizeof(rPPS) }, // PPS response @@ -1415,7 +1415,7 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, }; // since rats len is variable now. - responses_init[RESP_INDEX_RATS].response_n = rRATS_len; + responses_init[RESP_INDEX_ATS].response_n = rATS_len; // "precompiled" responses. // These exist for speed reasons. There are no time in the anti collision phase to calculate responses. @@ -1428,7 +1428,7 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, // 85 bytes normally (rats = 8 bytes) // 77 bytes + ratslen, -#define ALLOCATED_TAG_MODULATION_BUFFER_SIZE ( ((77 + rRATS_len) * 8) + 77 + rRATS_len + 12 + 12 + 12) +#define ALLOCATED_TAG_MODULATION_BUFFER_SIZE ( ((77 + rATS_len) * 8) + 77 + rATS_len + 12 + 12 + 12) uint8_t *free_buffer = BigBuf_calloc(ALLOCATED_TAG_MODULATION_BUFFER_SIZE); // modulation buffer pointer and current buffer free space size @@ -1455,7 +1455,7 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, // 'hf 14a sim' //----------------------------------------------------------------------------- void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *data, uint8_t exitAfterNReads, - uint8_t *iRATs, size_t irats_len) { + uint8_t *ats, size_t ats_len) { #define ATTACK_KEY_COUNT 16 @@ -1497,7 +1497,7 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *data, uint8_ .modulation_n = 0 }; - if (SimulateIso14443aInit(tagType, flags, data, iRATs, irats_len, &responses, &cuid, counters, tearings, &pages) == false) { + if (SimulateIso14443aInit(tagType, flags, data, ats, ats_len, &responses, &cuid, counters, tearings, &pages) == false) { BigBuf_free_keep_EM(); reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0); return; @@ -1820,7 +1820,7 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *data, uint8_ EmSend4bit(CARD_NACK_NA); p_response = NULL; } else { - p_response = &responses[RESP_INDEX_RATS]; + p_response = &responses[RESP_INDEX_ATS]; } } else if (receivedCmd[0] == MIFARE_ULC_AUTH_1) { // ULC authentication, or Desfire Authentication LogTrace(receivedCmd, Uart.len, Uart.startTime * 16 - DELAY_AIR2ARM_AS_TAG, Uart.endTime * 16 - DELAY_AIR2ARM_AS_TAG, Uart.parity, true); @@ -3941,9 +3941,10 @@ It can also continue after the AID has been selected, and respond to other reque This was forked from the original function to allow for more flexibility in the future, and to increase the processing speed of the original function. /// */ -void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *data, - uint8_t *iRATs, size_t irats_len, uint8_t *aid, uint8_t *resp, - uint8_t *apdu, int aidLen, int respondLen, int apduLen, bool enumerate) { +void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid, + uint8_t *ats, size_t ats_len, uint8_t *aid, size_t aid_len, + uint8_t *selectaid_response, size_t selectaid_response_len, + uint8_t *getdata_response, size_t getdata_response_len) { tag_response_info_t *responses; uint32_t cuid = 0; uint32_t counters[3] = { 0x00, 0x00, 0x00 }; @@ -3954,6 +3955,12 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *data, uint8_t receivedCmd[MAX_FRAME_SIZE] = { 0x00 }; uint8_t receivedCmdPar[MAX_PARITY_SIZE] = { 0x00 }; + // Buffers must be provided by the caller, even if lengths are 0 + // Copy the AID, AID Response, and the GetData APDU response into our variables + if ((aid == NULL) || (selectaid_response == NULL) || (getdata_response == NULL)) { + reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINVARG, NULL, 0); + } + // free eventually allocated BigBuf memory but keep Emulator Memory BigBuf_free_keep_EM(); @@ -3970,7 +3977,7 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *data, .modulation_n = 0 }; - if (SimulateIso14443aInit(tagType, flags, data, iRATs, irats_len, &responses, &cuid, counters, tearings, &pages) == false) { + if (SimulateIso14443aInit(tagType, flags, uid, ats, ats_len, &responses, &cuid, counters, tearings, &pages) == false) { BigBuf_free_keep_EM(); reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0); return; @@ -3990,23 +3997,6 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *data, set_tracing(true); LED_A_ON(); - // Filters for when this comes through - static uint8_t aidFilter[30] = { 0x00 }; // Default AID Value - static uint8_t aidResponse[100] = { 0x00 }; // Default AID Response - static uint8_t apduCommand [100] = { 0x00 }; // Default APDU GetData Response - - // Copy the AID, AID Response, and the GetData APDU response into our variables - if (aid != 0) { - memcpy(aidFilter, aid, aidLen); - } - if (resp != 0) { - memcpy(aidResponse, resp, respondLen); - } - if (apdu != 0) { - memcpy(apduCommand, apdu, apduLen); - } - - // main loop bool finished = false; bool got_rats = false; @@ -4051,7 +4041,7 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *data, finished = true; } } else if (receivedCmd[0] == ISO14443A_CMD_RATS && len == 4) { // Received a RATS request - p_response = &responses[RESP_INDEX_RATS]; + p_response = &responses[RESP_INDEX_ATS]; got_rats = true; } else { // clear old dynamic responses @@ -4081,19 +4071,19 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *data, // xx in this case is len of the AID value in hex // aid len is found as a hex value in receivedCmd[6] (Index Starts at 0) - int aid_len = receivedCmd[5+offset]; + int received_aid_len = receivedCmd[5+offset]; uint8_t *received_aid = &receivedCmd[6+offset]; // aid enumeration flag - if (enumerate == true) { - Dbprintf("Received AID (%d):", aid_len); - Dbhexdump(aid_len, received_aid, false); + if ((flags & FLAG_ENUMERATE_AID) == FLAG_ENUMERATE_AID) { + Dbprintf("Received AID (%d):", received_aid_len); + Dbhexdump(received_aid_len, received_aid, false); } - if (memcmp(aidFilter, received_aid, aid_len) == 0) { // Evaluate the AID sent by the Reader to the AID supplied + if ((received_aid_len == aid_len) && (memcmp(aid, received_aid, aid_len) == 0)) { // Evaluate the AID sent by the Reader to the AID supplied // AID Response will be parsed here - memcpy(dynamic_response_info.response + 1 + offset, aidResponse, respondLen + 1 + offset); - dynamic_response_info.response_n = respondLen + 2; + memcpy(dynamic_response_info.response + 1 + offset, selectaid_response, selectaid_response_len + 1 + offset); + dynamic_response_info.response_n = selectaid_response_len + 2; } else { // Any other SELECT FILE command will return with a Not Found dynamic_response_info.response[1 + offset] = 0x6A; dynamic_response_info.response[2 + offset] = 0x82; @@ -4113,8 +4103,8 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *data, case 0xCA: { // GET DATA if (sentCount == 0) { // APDU Command will just be parsed here - memcpy(dynamic_response_info.response + 1 + offset, apduCommand, apduLen + 2); - dynamic_response_info.response_n = respondLen + 1 + offset; + memcpy(dynamic_response_info.response + 1 + offset, getdata_response, getdata_response_len + 2); + dynamic_response_info.response_n = selectaid_response_len + 1 + offset; } else { finished = true; break; diff --git a/armsrc/iso14443a.h b/armsrc/iso14443a.h index 24b388252..34a94dbb5 100644 --- a/armsrc/iso14443a.h +++ b/armsrc/iso14443a.h @@ -105,7 +105,7 @@ typedef enum { RESP_INDEX_SAKC1, RESP_INDEX_SAKC2, RESP_INDEX_SAKC3, - RESP_INDEX_RATS, + RESP_INDEX_ATS, RESP_INDEX_VERSION, RESP_INDEX_SIGNATURE, RESP_INDEX_PPS, @@ -145,9 +145,10 @@ void RAMFUNC SniffIso14443a(uint8_t param); void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *data, uint8_t exitAfterNReads, uint8_t *iRATs, size_t irats_len); -void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *data, - uint8_t *iRATs, size_t irats_len, uint8_t *aid, uint8_t *resp, - uint8_t *apdu, int aid_len, int respond_len, int apdu_len, bool enumerate); +void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid, + uint8_t *ats, size_t ats_len, uint8_t *aid, size_t aid_len, + uint8_t *selectaid_response, size_t selectaid_response_len, + uint8_t *getdata_response, size_t getdata_response_len); bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, uint8_t *iRATs, size_t irats_len, tag_response_info_t **responses, diff --git a/client/src/cmdhf14a.c b/client/src/cmdhf14a.c index c627f4504..6f9032017 100644 --- a/client/src/cmdhf14a.c +++ b/client/src/cmdhf14a.c @@ -3715,58 +3715,61 @@ int CmdHF14AAIDSim(const char *Cmd) { CLIParserInit(&ctx, "hf 14a simaid", "Simulate ISO/IEC 14443 type A tag with 4,7 or 10 byte UID, and filter for AID Values\n" "These AID Values can be responded to and include extra APDU commands on GetData after response\n", - "hf 14a simaid -t 3 -> MIFARE Desfire\n" - "hf 14a simaid -t 4 -> ISO/IEC 14443-4\n" - "hf 14a simaid -t 11 -> Javacard (JCOP)\n" - "hf 14a simaid -t 3 --aid a000000000000000000000 --response 9000 --apdu 9000 -> AID, Response and APDU\n" - "hf 14a simaid -t 3 --rats 05788172220101 --response 01009000 --apdu 86009000 -> Custom RATS Added\n" - "hf 14a simaid -t 3 --rats 05788172220101 -x -> Enumerate AID Values\n" + "hf 14a simaid -t 3 -> MIFARE Desfire\n" + "hf 14a simaid -t 4 -> ISO/IEC 14443-4\n" + "hf 14a simaid -t 11 -> Javacard (JCOP)\n" + "hf 14a simaid -t 3 --aid a000000000000000000000 --selectaid_response 9000 --getdata_response 9000 -> Custom AID and responses\n" + "hf 14a simaid -t 3 --ats 05788172220101 --selectaid_response 01009000 --getdata_response 86009000 -> Custom ATS and responses\n" + "hf 14a simaid -t 3 --ats 05788172220101 -x -> Enumerate AID Values\n" ); void *argtable[] = { arg_param_begin, arg_int1("t", "type", "<1-12> ", "Simulation type to use"), arg_str0("u", "uid", "", "<4|7|10> hex bytes UID"), - arg_str0("r", "rats", "", "<0-20> hex bytes RATS"), - arg_str0("a", "aid", "", "<0-100> hex bytes for AID to respond to (Default: A000000000000000000000)"), - arg_str0("e", "response", "", "<0-100> hex bytes for APDU Response to AID Select (Default: 9000)"), - arg_str0("p", "apdu", "", "<0-100> hex bytes for APDU Response to Get Data request after AID (Default: 9000)"), + arg_str0("r", "ats", "", "<0-20> hex bytes ATS"), + arg_str0("a", "aid", "", "<0-30> hex bytes for AID to respond to (Default: A000000000000000000000)"), + arg_str0("e", "selectaid_response", "", "<0-100> hex bytes for APDU Response to AID Select (Default: 9000)"), + arg_str0("p", "getdata_response", "", "<0-100> hex bytes for APDU Response to Get Data request after AID (Default: 9000)"), arg_lit0("x", "enumerate", "Enumerate all AID values via returning Not Found and print them to console "), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, false); - int tagtype = arg_get_int_def(ctx, 1, 1); - - bool enumerate = arg_get_lit(ctx, 7); - int uid_len = 0; - int rats_len = 0; + int ats_len = 0; int aid_len = 0; - int respond_len = 0; - int apdu_len = 0; + int selectaid_response_len = 0; + int getdata_response_len = 0; uint8_t uid[10] = {0}; - uint8_t rats[20] = {0}; - uint8_t aid[30] = {0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - uint8_t response[100] = {0x90, 0x00}; - uint8_t apdu[100] = {0x90, 0x00}; + uint8_t ats[20] = {0}; + uint8_t aid[30] = {0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t default_aid_len = 11; + uint8_t selectaid_response[100] = {0x90, 0x00}; + uint8_t default_selectaid_response_len = 2; + uint8_t getdata_response[100] = {0x90, 0x00}; + uint8_t default_getdata_response_len = 2; + int tagtype = arg_get_int_def(ctx, 1, 1); CLIGetHexWithReturn(ctx, 2, uid, &uid_len); - CLIGetHexWithReturn(ctx, 3, rats, &rats_len); + CLIGetHexWithReturn(ctx, 3, ats, &ats_len); CLIGetHexWithReturn(ctx, 4, aid, &aid_len); - CLIGetHexWithReturn(ctx, 5, response, &respond_len); - CLIGetHexWithReturn(ctx, 6, apdu, &apdu_len); + CLIGetHexWithReturn(ctx, 5, selectaid_response, &selectaid_response_len); + CLIGetHexWithReturn(ctx, 6, getdata_response, &getdata_response_len); + bool enumerate = arg_get_lit(ctx, 7); + CLIParserFree(ctx); - // default value fill for the AID, response, and apdu + // default value fill for the AID, selectaid_response, and getdata_response if (aid_len == 0) { - aid_len = 11; + aid_len = default_aid_len; } - if (respond_len == 0) { - respond_len = 2; + + if (selectaid_response_len == 0) { + selectaid_response_len = default_selectaid_response_len; } - if (apdu_len == 0) { - apdu_len = 2; + if (getdata_response_len == 0) { + getdata_response_len = default_getdata_response_len; } uint16_t flags = 0; @@ -3776,19 +3779,39 @@ int CmdHF14AAIDSim(const char *Cmd) { FLAG_SET_UID_IN_DATA(flags, uid_len); if (IS_FLAG_UID_IN_EMUL(flags)) { PrintAndLogEx(ERR, "Please specify a 4, 7, or 10 byte UID"); - CLIParserFree(ctx); return PM3_EINVARG; } PrintAndLogEx(SUCCESS, "Emulating " _YELLOW_("ISO/IEC 14443 type A tag")" with " _GREEN_("%d byte UID (%s)"), uid_len, sprint_hex(uid, uid_len)); useUIDfromEML = false; } - if (rats_len > 0) { - flags |= FLAG_RATS_IN_DATA; + if (ats_len > sizeof(ats)) { + PrintAndLogEx(ERR, "Provided ATS too long"); + return PM3_EINVARG; } + if (aid_len > sizeof(aid)) { + PrintAndLogEx(ERR, "Provided AID too long"); + return PM3_EINVARG; + } - CLIParserFree(ctx); + if (selectaid_response_len > sizeof(selectaid_response)) { + PrintAndLogEx(ERR, "Provided SelectAID response too long"); + return PM3_EINVARG; + } + + if (getdata_response_len > sizeof(getdata_response)) { + PrintAndLogEx(ERR, "Provided GetData response too long"); + return PM3_EINVARG; + } + + if (ats_len > 0) { + flags |= FLAG_ATS_IN_DATA; + } + + if (enumerate) { + flags |= FLAG_ENUMERATE_AID; + } if (tagtype > 12) { PrintAndLogEx(ERR, "Undefined tag %d", tagtype); @@ -3803,31 +3826,31 @@ int CmdHF14AAIDSim(const char *Cmd) { uint8_t tagtype; uint16_t flags; uint8_t uid[10]; - uint8_t rats[20]; + uint8_t ats[20]; uint8_t aid[30]; - uint8_t response[100]; - uint8_t apdu[100]; - int aid_len; - int respond_len; - int apdu_len; - bool enumerate; + uint8_t selectaid_response[100]; + uint8_t getdata_response[100]; + uint32_t ats_len; + uint32_t aid_len; + uint32_t selectaid_response_len; + uint32_t getdata_response_len; } PACKED payload; payload.tagtype = tagtype; payload.flags = flags; - payload.enumerate = enumerate; // Copy data to payload memcpy(payload.uid, uid, uid_len); - memcpy(payload.rats, rats, rats_len); + memcpy(payload.ats, ats, ats_len); memcpy(payload.aid, aid, aid_len); - memcpy(payload.response, response, respond_len); - memcpy(payload.apdu, apdu, apdu_len); + memcpy(payload.selectaid_response, selectaid_response, selectaid_response_len); + memcpy(payload.getdata_response, getdata_response, getdata_response_len); // copy the lengths data to the payload - memcpy(&payload.aid_len, &aid_len, sizeof(aid_len)); - memcpy(&payload.respond_len, &respond_len, sizeof(respond_len)); - memcpy(&payload.apdu_len, &apdu_len, sizeof(apdu_len)); + payload.ats_len = ats_len; + payload.aid_len = aid_len; + payload.selectaid_response_len = selectaid_response_len; + payload.getdata_response_len = getdata_response_len; clearCommandBuffer(); SendCommandNG(CMD_HF_ISO14443A_SIM_AID, (uint8_t *)&payload, sizeof(payload)); diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index 996b04079..9128dbb90 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -772,7 +772,8 @@ typedef struct { #define FLAG_INTERACTIVE 0x0001 #define FLAG_ATQA_IN_DATA 0x0002 #define FLAG_SAK_IN_DATA 0x0004 -#define FLAG_RATS_IN_DATA 0x0008 +#define FLAG_ATS_IN_DATA 0x0008 +#define FLAG_ENUMERATE_AID 0x0010 // internal constants, use the function macros instead #define FLAG_MASK_UID 0x0030 From fcd6de8b7b4fcb27bf16ddf33c0e592de8860369 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Wed, 12 Feb 2025 09:06:04 +0100 Subject: [PATCH 020/105] 4A emulation: avoid overflow and don't rely on TL --- armsrc/iso14443a.c | 18 ++++++++++-------- client/src/cmdhf14a.c | 12 ++++++------ 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index 41b4aa546..ad21732bc 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -1131,7 +1131,7 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, // TB(1) = not present. Defaults: FWI = 4 (FWT = 256 * 16 * 2^4 * 1/fc = 4833us), SFGI = 0 (SFG = 256 * 16 * 2^0 * 1/fc = 302us) // TC(1) = 0x02: CID supported, NAD not supported // static uint8_t rATS[] = { 0x04, 0x58, 0x80, 0x02, 0x00, 0x00 }; - static uint8_t rATS[40] = { 0x05, 0x75, 0x80, 0x60, 0x02, 0x00, 0x00, 0x00 }; + static uint8_t rATS[40] = { 0x06, 0x75, 0x80, 0x60, 0x02, 0x00, 0x00, 0x00 }; uint8_t rATS_len = 8; // GET_VERSION response for EV1/NTAG @@ -1275,15 +1275,17 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, // ats is a pointer to 20 byte array // rATS is a 40 byte array if ((flags & FLAG_ATS_IN_DATA) == FLAG_ATS_IN_DATA) { - memcpy(rATS, ats, ats_len); - // rats len is dictated by the first char of the string, add 2 crc bytes - rATS_len = (ats[0] + 2); - // Since its Varible length we can send value > 40 and overflow our array. - // Even if RATS protocol defined as max 40 bytes doesn't mean people try stuff - if (rATS_len > sizeof(rATS)) { - if (g_dbglevel >= DBG_ERROR) Dbprintf("[-] ERROR: ATS overflow. Max %zu, got %zu", sizeof(rATS), rATS_len); + // Even if RATS protocol defined as max 40 bytes doesn't mean people try stuff. Check for overflow before copy + if (ats_len + 2 > sizeof(rATS)) { + if (g_dbglevel >= DBG_ERROR) Dbprintf("[-] ERROR: ATS overflow. Max %zu, got %zu", sizeof(rATS) - 2, ats_len); return false; } + memcpy(rATS, ats, ats_len); + rATS_len = ats_len + 2; + // ATS length (without CRC) is supposed to match its first byte TL + if (ats_len != ats[0]) { + if (g_dbglevel >= DBG_INFO) Dbprintf("[-] WARNING: actual ATS length (%zu) differs from its TL value (%u).", ats_len, ats[0]); + } } // if uid not supplied then get from emulator memory diff --git a/client/src/cmdhf14a.c b/client/src/cmdhf14a.c index 6f9032017..ac99ea229 100644 --- a/client/src/cmdhf14a.c +++ b/client/src/cmdhf14a.c @@ -3715,12 +3715,12 @@ int CmdHF14AAIDSim(const char *Cmd) { CLIParserInit(&ctx, "hf 14a simaid", "Simulate ISO/IEC 14443 type A tag with 4,7 or 10 byte UID, and filter for AID Values\n" "These AID Values can be responded to and include extra APDU commands on GetData after response\n", - "hf 14a simaid -t 3 -> MIFARE Desfire\n" - "hf 14a simaid -t 4 -> ISO/IEC 14443-4\n" - "hf 14a simaid -t 11 -> Javacard (JCOP)\n" - "hf 14a simaid -t 3 --aid a000000000000000000000 --selectaid_response 9000 --getdata_response 9000 -> Custom AID and responses\n" - "hf 14a simaid -t 3 --ats 05788172220101 --selectaid_response 01009000 --getdata_response 86009000 -> Custom ATS and responses\n" - "hf 14a simaid -t 3 --ats 05788172220101 -x -> Enumerate AID Values\n" + "hf 14a simaid -t 3 -> MIFARE Desfire\n" + "hf 14a simaid -t 4 -> ISO/IEC 14443-4\n" + "hf 14a simaid -t 11 -> Javacard (JCOP)\n" + "hf 14a simaid -t 3 --aid a000000000000000000000 --selectaid_response 9000 --getdata_response 9000 -> Custom AID and responses\n" + "hf 14a simaid -t 3 --ats 0578817222 --selectaid_response 01009000 --getdata_response 86009000 -> Custom ATS and responses\n" + "hf 14a simaid -t 3 --ats 0578817222 -x -> Enumerate AID Values\n" ); void *argtable[] = { From 2efa4909f6e684554a4d7ee0d4e0f3c4c0692c70 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Wed, 12 Feb 2025 09:17:32 +0100 Subject: [PATCH 021/105] show FSD in RATS trace --- client/src/cmdhflist.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/cmdhflist.c b/client/src/cmdhflist.c index 911b12b8d..c7b8279a0 100644 --- a/client/src/cmdhflist.c +++ b/client/src/cmdhflist.c @@ -274,7 +274,8 @@ int applyIso14443a(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize, bool i MifareAuthState = masNone; break; case ISO14443A_CMD_RATS: - snprintf(exp, size, "RATS - FSDI=%x, CID=%x", (cmd[1] & 0xF0) >> 4, (cmd[1] & 0x0F)); + uint16_t fsdi2fsd[] = {16, 24, 32, 40, 48, 64, 96, 128, 256, 512, 1024, 2048, 4096, 4096, 4096, 4096}; + snprintf(exp, size, "RATS - FSDI=%x (FSD=%u), CID=%x", (cmd[1] & 0xF0) >> 4, fsdi2fsd[(cmd[1] & 0xF0) >> 4], (cmd[1] & 0x0F)); break; /* Actually, PPSS is Dx case ISO14443A_CMD_PPS: From 4e5a8b98481f02e0830aa17843bb10c1ab7a87e5 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Wed, 12 Feb 2025 15:46:12 +0100 Subject: [PATCH 022/105] 4A emulation: warn if chaining command received --- armsrc/iso14443a.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index ad21732bc..f7153fa94 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -4050,7 +4050,7 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid, dynamic_response_info.response_n = 0; dynamic_response_info.modulation_n = 0; - // Check for ISO 14443A-4 compliant commands, look at left nibble + // Check for ISO 14443A-4 compliant commands, look at left byte (PCB) uint8_t offset = 0; switch (receivedCmd[0]) { case 0x0B: // IBlock with CID @@ -4136,12 +4136,15 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid, break; default: { - // Never seen this command before + // Never seen this PCB before LogTrace(receivedCmd, Uart.len, Uart.startTime * 16 - DELAY_AIR2ARM_AS_TAG, Uart.endTime * 16 - DELAY_AIR2ARM_AS_TAG, Uart.parity, true); if (g_dbglevel >= DBG_DEBUG) { Dbprintf("Received unknown command (len=%d):", len); Dbhexdump(len, receivedCmd, false); } + if ((receivedCmd[0] & 0x10) == 0x10) { + Dbprintf("Warning, reader sent a chained command but we lack support for it. Ignoring command."); + } // Do not respond dynamic_response_info.response_n = 0; } From 0ce8ef130b2882b9ea586d89acc49cf8d8d31dd3 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Wed, 12 Feb 2025 16:39:00 +0100 Subject: [PATCH 023/105] Fix clang error label followed by a declaration is a C23 extension --- client/src/cmdhflist.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/cmdhflist.c b/client/src/cmdhflist.c index c7b8279a0..fdd189568 100644 --- a/client/src/cmdhflist.c +++ b/client/src/cmdhflist.c @@ -273,10 +273,11 @@ int applyIso14443a(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize, bool i snprintf(exp, size, "HALT"); MifareAuthState = masNone; break; - case ISO14443A_CMD_RATS: + case ISO14443A_CMD_RATS: { uint16_t fsdi2fsd[] = {16, 24, 32, 40, 48, 64, 96, 128, 256, 512, 1024, 2048, 4096, 4096, 4096, 4096}; snprintf(exp, size, "RATS - FSDI=%x (FSD=%u), CID=%x", (cmd[1] & 0xF0) >> 4, fsdi2fsd[(cmd[1] & 0xF0) >> 4], (cmd[1] & 0x0F)); break; + } /* Actually, PPSS is Dx case ISO14443A_CMD_PPS: snprintf(exp, size, "PPS"); From 430ef1f273a4ff432d8e9ea974af595158d33bf9 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Wed, 12 Feb 2025 22:01:09 +0100 Subject: [PATCH 024/105] 4A sim: add malloc checks and fix buf size macro --- armsrc/iso14443a.c | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index f7153fa94..1a7905527 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -1077,6 +1077,7 @@ bool prepare_tag_modulation(tag_response_info_t *response_info, size_t max_buffe if (ts->max > max_buffer_size) { Dbprintf("ToSend buffer, Out-of-bound, when modulating bits for tag answer:"); Dbhexdump(response_info->response_n, response_info->response, false); + Dbprintf("Need %i, got %i", ts->max, max_buffer_size); return false; } @@ -1491,7 +1492,17 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *data, uint8_ #define DYNAMIC_MODULATION_BUFFER_SIZE 512 uint8_t *dynamic_response_buffer = BigBuf_calloc(DYNAMIC_RESPONSE_BUFFER_SIZE); + if (dynamic_response_buffer == NULL) { + BigBuf_free_keep_EM(); + reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EMALLOC, NULL, 0); + return; + } uint8_t *dynamic_modulation_buffer = BigBuf_calloc(DYNAMIC_MODULATION_BUFFER_SIZE); + if (dynamic_modulation_buffer == NULL) { + BigBuf_free_keep_EM(); + reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EMALLOC, NULL, 0); + return; + } tag_response_info_t dynamic_response_info = { .response = dynamic_response_buffer, .response_n = 0, @@ -3971,7 +3982,17 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid, #define DYNAMIC_MODULATION_BUFFER2_SIZE 1536 uint8_t *dynamic_response_buffer2 = BigBuf_calloc(DYNAMIC_RESPONSE_BUFFER2_SIZE); + if (dynamic_response_buffer2 == NULL) { + BigBuf_free_keep_EM(); + reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EMALLOC, NULL, 0); + return; + } uint8_t *dynamic_modulation_buffer2 = BigBuf_calloc(DYNAMIC_MODULATION_BUFFER2_SIZE); + if (dynamic_modulation_buffer2 == NULL) { + BigBuf_free_keep_EM(); + reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EMALLOC, NULL, 0); + return; + } tag_response_info_t dynamic_response_info = { .response = dynamic_response_buffer2, .response_n = 0, @@ -4161,7 +4182,7 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid, AddCrc14A(dynamic_response_info.response, dynamic_response_info.response_n); dynamic_response_info.response_n += 2; - if (prepare_tag_modulation(&dynamic_response_info, DYNAMIC_MODULATION_BUFFER_SIZE) == false) { + if (prepare_tag_modulation(&dynamic_response_info, DYNAMIC_MODULATION_BUFFER2_SIZE) == false) { if (g_dbglevel >= DBG_DEBUG) DbpString("Error preparing tag response"); LogTrace(receivedCmd, Uart.len, Uart.startTime * 16 - DELAY_AIR2ARM_AS_TAG, Uart.endTime * 16 - DELAY_AIR2ARM_AS_TAG, Uart.parity, true); break; From bc7d1200ba4cdc4cfb0bcfb247c453c1f2093cc9 Mon Sep 17 00:00:00 2001 From: leommxj Date: Fri, 14 Feb 2025 23:06:03 +0800 Subject: [PATCH 025/105] update cmdhfemrtd.c to support JPEG2000 code stream photo --- client/src/cmdhfemrtd.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/client/src/cmdhfemrtd.c b/client/src/cmdhfemrtd.c index af480c5f0..9b2bab31d 100644 --- a/client/src/cmdhfemrtd.c +++ b/client/src/cmdhfemrtd.c @@ -767,23 +767,35 @@ static bool emrtd_select_and_read(uint8_t *dataout, size_t *dataoutlen, uint16_t static const uint8_t jpeg_header[4] = { 0xFF, 0xD8, 0xFF, 0xE0 }; static const uint8_t jpeg2k_header[6] = { 0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50 }; +static const uint8_t jpeg2k_cs_header[4] = { 0xFF, 0x4F, 0xFF, 0x51 }; static int emrtd_dump_ef_dg2(uint8_t *file_contents, size_t file_length, const char *path) { size_t offset; int datalen = 0; + char suffix[5] = { '\0' }; // This is a hacky impl that just looks for the image header. I'll improve it eventually. // based on mrpkey.py // Note: Doing file_length - 6 to account for the longest data we're checking. // Checks first byte before the rest to reduce overhead for (offset = 0; offset < file_length - 6; offset++) { - if ((file_contents[offset] == 0xFF && memcmp(jpeg_header, file_contents + offset, 4) == 0) || - (file_contents[offset] == 0x00 && memcmp(jpeg2k_header, file_contents + offset, 6) == 0)) { + if (file_contents[offset] == 0xFF) { + if (memcmp(jpeg_header, file_contents + offset, 4) == 0) { + datalen = file_length - offset; + strcpy(suffix, ".jpg"); + break; + } else if (memcmp(jpeg2k_cs_header, file_contents + offset, 4) == 0) { + datalen = file_length - offset; + // no standardized extension for codestream data, using .jpc + strcpy(suffix, ".jpc"); + break; + } + } else if (file_contents[offset] == 0x00 && memcmp(jpeg2k_header, file_contents + offset, 6) == 0) { + strcpy(suffix, ".jp2"); datalen = file_length - offset; break; } } - // If we didn't get any data, return false. if (datalen == 0) { return PM3_ESOFT; @@ -797,7 +809,7 @@ static int emrtd_dump_ef_dg2(uint8_t *file_contents, size_t file_length, const c strncat(filepath, PATHSEP, 2); strcat(filepath, dg_table[EF_DG2].filename); - saveFile(filepath, file_contents[offset] == 0xFF ? ".jpg" : ".jp2", file_contents + offset, datalen); + saveFile(filepath, suffix, file_contents + offset, datalen); free(filepath); return PM3_SUCCESS; From 1a4256e819426cdd96724d4dd2ff0ab2ea78da08 Mon Sep 17 00:00:00 2001 From: ry4000 <154689120+ry4000@users.noreply.github.com> Date: Sat, 15 Feb 2025 12:38:46 +1100 Subject: [PATCH 026/105] R&Y: AID Updates to `aid_desfire.json` **Additions** - F484D1 (HID: Unknown DESFire EV1) **Amendments** - Umo Mobility (Now listed as US/CA) - NORTIC (Updated Names) **Deletions** - Duplicate DEL Delhi Metro Travel Card (App 5) Signed-off-by: ry4000 <154689120+ry4000@users.noreply.github.com> --- client/resources/aid_desfire.json | 38 +++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/client/resources/aid_desfire.json b/client/resources/aid_desfire.json index 144a3e237..680d7909a 100644 --- a/client/resources/aid_desfire.json +++ b/client/resources/aid_desfire.json @@ -335,6 +335,14 @@ "Description": "Card Application Directory (CAD)", "Type": "pacs" }, + { + "AID": "F484D1", + "Vendor": "HID", + "Country": "US", + "Name": "Unknown DESFire EV1", + "Description": "Access Control", + "Type": "pacs" + }, { "AID": "F484E3", "Vendor": "HID", @@ -1034,7 +1042,7 @@ { "AID": "087522", "Vendor": "Umo Mobility via Cubic Transportation Systems", - "Country": "US", + "Country": "US / CA", "Name": "Umo Mobility Card", "Description": "Umo Mobility Card", "Type": "transport" @@ -1159,14 +1167,6 @@ "Description": "DEL Delhi Metro (App 5)", "Type": "transport" }, - { - "AID": "444D05", - "Vendor": "Delhi Metro Rail Corporation Limited", - "Country": "IN", - "Name": "Delhi Metro Travel Card (DEL)", - "Description": "DEL Delhi Metro (App 5)", - "Type": "transport" - }, { "AID": "444D06", "Vendor": "Delhi Metro Rail Corporation Limited", @@ -1219,7 +1219,7 @@ "AID": "578000", "Vendor": "Norwegian Public Roads Administration (NPRA)", "Country": "NO", - "Name": "NORTIC (Norway Public Transport Card)", + "Name": "NORTIC (Norway Public Transport Card) (Card Issuer App)", "Description": "Norwegian Ticketing Interoperable Concept // FID 0C: Card Issuer Header", "Type": "transport" }, @@ -1227,7 +1227,7 @@ "AID": "578001", "Vendor": "Norwegian Public Roads Administration (NPRA)", "Country": "NO", - "Name": "NORTIC (Norway Public Transport Card)", + "Name": "NORTIC (Norway Public Transport Card) (Transport App)", "Description": "FIDs 01: Product Retailer; 02: Service Provider; 03: Special Event; 04: Stored Value; 05: General Event Log; 06: SV Reload Log; 0A: Environment; 0C: Card Holder", "Type": "transport" }, @@ -1242,7 +1242,7 @@ { "AID": "677F8E", "Vendor": "Umo Mobility via Cubic Transportation Systems", - "Country": "US", + "Country": "US / CA", "Name": "Umo Mobility Card", "Description": "Umo Mobility Card", "Type": "transport" @@ -1258,7 +1258,7 @@ { "AID": "992CB5", "Vendor": "Umo Mobility via Cubic Transportation Systems", - "Country": "US", + "Country": "US / CA", "Name": "Umo Mobility Card", "Description": "Umo Mobility Card", "Type": "transport" @@ -1274,7 +1274,7 @@ { "AID": "A4237D", "Vendor": "Umo Mobility via Cubic Transportation Systems", - "Country": "US", + "Country": "US / CA", "Name": "Umo Mobility Card", "Description": "Umo Mobility Card", "Type": "transport" @@ -1282,7 +1282,7 @@ { "AID": "C65B80", "Vendor": "Umo Mobility via Cubic Transportation Systems", - "Country": "US", + "Country": "US / CA", "Name": "Umo Mobility Card", "Description": "Umo Mobility Card", "Type": "transport" @@ -1353,10 +1353,10 @@ }, { "AID": "F21050", - "Vendor": "Metro Christchurch via INIT", - "Country": "NZ", - "Name": "Metrocard (CHC)", - "Description": "FIDs: 00: Backup Data; 01/02: Trip History; 03: Card Balance", + "Vendor": "Metro Christchurch via INIT / Arc", + "Country": "NZ / CA", + "Name": "Metrocard (CHC) / Arc (YEG)", + "Description": "CHC FIDs: 00: Backup Data; 01/02: Trip History; 03: Card Balance", "Type": "transport" }, { From 07122e78a6c7ceddf538349ac4d1238a468037a0 Mon Sep 17 00:00:00 2001 From: ry4000 <154689120+ry4000@users.noreply.github.com> Date: Sat, 15 Feb 2025 13:46:46 +1100 Subject: [PATCH 027/105] R&Y: Added Super Street Fighter NESYS Keys to `mfc_default_keys.dic` **Additions** - Super Street Fighter 4 NESYS Keys **Amendments** - Added `TEKKEN 6 Namco Data Card` to the BNP list *since they share the same keys* Signed-off-by: ry4000 <154689120+ry4000@users.noreply.github.com> --- client/dictionaries/mfc_default_keys.dic | 37 +++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/client/dictionaries/mfc_default_keys.dic b/client/dictionaries/mfc_default_keys.dic index 48b40a3f6..e5af9c766 100644 --- a/client/dictionaries/mfc_default_keys.dic +++ b/client/dictionaries/mfc_default_keys.dic @@ -2175,6 +2175,7 @@ D144BD193063 # Brazil transport Sec 8 / A 50d4c54fcdf5 # +# TEKKEN 6 Namco Data Card # Bandai Namco Passport [fka Banapassport] / Sega Aime Card # Dumped on the Flipper Devices Discord Server 6090D00632F5 @@ -2211,6 +2212,40 @@ C8382A233993 7B304F2A12A6 FC9418BF788B # +# Super Street Fighter 4 Capcom NESYS Card +4B6F74696174 +6F746961744B +4176696E7520 +76696E752041 +576C61737265 +6C6173726557 +416962616320 +696261632041 +42622074656E +622074656E42 +416174363030 +617436303041 +5475206F7469 +75206F746954 +41726576696E +726576696E41 +4B63206C6173 +63206C61734B +41656E696261 +656E69626141 +5A3030622074 +30306220745A +557469617436 +746961743655 +48696E75206F +696E75206F48 +496173726576 +617372657649 +52626163206C +626163206C52 +4F2074656E69 +2074656E694F +# # Guest Cashless Prepaid Arcade Payment Cards 168168168168 198407157610 @@ -3072,4 +3107,4 @@ AB921CF0752C 206F7C4C4F36 265A5F32DE73 567D734C403C -2426217B3B3B \ No newline at end of file +2426217B3B3B From f6b5281a5c76280a0f286139c0d7f1abc21cd055 Mon Sep 17 00:00:00 2001 From: Lucifer Voeltner Date: Sun, 16 Feb 2025 11:45:34 +0700 Subject: [PATCH 028/105] Switch around some logic in ul_auth_select to make the client print the UID properly in fully read-protected cards when 'hf mfu info' is called with a key --- client/src/cmdhfmfu.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/client/src/cmdhfmfu.c b/client/src/cmdhfmfu.c index e74b926e2..0a46e4cd9 100644 --- a/client/src/cmdhfmfu.c +++ b/client/src/cmdhfmfu.c @@ -669,6 +669,9 @@ static int try_default_aes_keys(bool override) { } static int ul_auth_select(iso14a_card_select_t *card, uint64_t tagtype, bool hasAuthKey, uint8_t *authkey, uint8_t *pack, uint8_t packSize) { + if (ul_select(card) == false) { + return PM3_ESOFT; + } if (hasAuthKey && (tagtype & MFU_TT_UL_C)) { //will select card automatically and close connection on error @@ -676,12 +679,7 @@ static int ul_auth_select(iso14a_card_select_t *card, uint64_t tagtype, bool has PrintAndLogEx(WARNING, "Authentication Failed UL-C"); return PM3_ESOFT; } - } else { - if (ul_select(card) == false) { - return PM3_ESOFT; - } - if (hasAuthKey) { if (ulev1_requestAuthentication(authkey, pack, packSize) == PM3_EWRONGANSWER) { DropField(); From 3f794818f081faaf50eddbe16be342d9484eecd1 Mon Sep 17 00:00:00 2001 From: Donny <107092000+Donny-Guo@users.noreply.github.com> Date: Sat, 15 Feb 2025 21:56:19 -0800 Subject: [PATCH 029/105] fix incorrect HID bitlen calculations and wiegand format display --- armsrc/lfops.c | 4 --- client/src/cmdhficlass.c | 9 ++---- client/src/cmdhfmf.c | 3 +- client/src/cmdlfhid.c | 7 ++--- client/src/cmdwiegand.c | 3 +- client/src/wiegand_formats.c | 35 ++++++++++++++++++++---- client/src/wiegand_formats.h | 1 + client/src/wiegand_formatutils.c | 47 ++++++++++++++++++++------------ 8 files changed, 69 insertions(+), 40 deletions(-) diff --git a/armsrc/lfops.c b/armsrc/lfops.c index 0716efc60..e81d5570c 100644 --- a/armsrc/lfops.c +++ b/armsrc/lfops.c @@ -1316,10 +1316,6 @@ int lf_hid_watch(int findone, uint32_t *high, uint32_t *low, bool ledcontrol) { cardnum = (lo >> 1) & 0xFFFF; fac = (lo >> 17) & 0xFF; } - if (bitlen == 37) { - cardnum = (lo >> 1) & 0x7FFFF; - fac = ((hi & 0xF) << 12) | (lo >> 20); - } if (bitlen == 34) { cardnum = (lo >> 1) & 0xFFFF; fac = ((hi & 1) << 15) | (lo >> 17); diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index 22b90834e..492403344 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -1451,8 +1451,7 @@ static int iclass_decode_credentials_new_pacs(uint8_t *d) { PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "Wiegand decode"); - wiegand_message_t packed = initialize_message_object(top, mid, bot, 0); - HIDTryUnpack(&packed); + decode_wiegand(top, mid, bot, 0); return PM3_SUCCESS; } @@ -1492,8 +1491,7 @@ static void iclass_decode_credentials(uint8_t *data) { PrintAndLogEx(SUCCESS, "Binary..................... " _GREEN_("%s"), pbin); PrintAndLogEx(INFO, "Wiegand decode"); - wiegand_message_t packed = initialize_message_object(top, mid, bot, 0); - HIDTryUnpack(&packed); + decode_wiegand(top, mid, bot, 0); } } else { @@ -2916,8 +2914,7 @@ static int CmdHFiClass_ReadBlock(const char *Cmd) { PrintAndLogEx(SUCCESS, " bin : %s", pbin); PrintAndLogEx(INFO, ""); PrintAndLogEx(INFO, "------------------------------ " _CYAN_("Wiegand") " -------------------------------"); - wiegand_message_t packed = initialize_message_object(top, mid, bot, 0); - HIDTryUnpack(&packed); + decode_wiegand(top, mid, bot, 0); } } else { PrintAndLogEx(INFO, "no credential found"); diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index b80fb138e..b17e9caf8 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -6293,8 +6293,7 @@ static int CmdHF14AMfMAD(const char *Cmd) { PrintAndLogEx(SUCCESS, "Binary... " _GREEN_("%s"), pbin); PrintAndLogEx(INFO, "Wiegand decode"); - wiegand_message_t packed = initialize_message_object(top, mid, bot, 0); - HIDTryUnpack(&packed); + decode_wiegand(top, mid, bot, 0); } } diff --git a/client/src/cmdlfhid.c b/client/src/cmdlfhid.c index baed95b29..fb1eeb56e 100644 --- a/client/src/cmdlfhid.c +++ b/client/src/cmdlfhid.c @@ -160,8 +160,7 @@ int demodHID(bool verbose) { return PM3_ESOFT; } - wiegand_message_t packed = initialize_message_object(hi2, hi, lo, 0); - if (HIDTryUnpack(&packed) == false) { + if (!decode_wiegand(hi2, hi, lo, 0)) { // if failed to unpack wiegand printDemodBuff(0, false, false, true); } PrintAndLogEx(INFO, "raw: " _GREEN_("%08x%08x%08x"), hi2, hi, lo); @@ -214,8 +213,8 @@ static int CmdHIDReader(const char *Cmd) { } do { - lf_read(false, 16000); - demodHID(!cm); + lf_read(false, 16000); // get data of 16000 samples from proxmark device + demodHID(!cm); // demod data and print results if found } while (cm && !kbd_enter_pressed()); return PM3_SUCCESS; diff --git a/client/src/cmdwiegand.c b/client/src/cmdwiegand.c index 9fc06b6f9..7853b71cd 100644 --- a/client/src/cmdwiegand.c +++ b/client/src/cmdwiegand.c @@ -160,8 +160,7 @@ int CmdWiegandDecode(const char *Cmd) { return PM3_EINVARG; } - wiegand_message_t packed = initialize_message_object(top, mid, bot, blen); - HIDTryUnpack(&packed); + decode_wiegand(top, mid, bot, blen); return PM3_SUCCESS; } diff --git a/client/src/wiegand_formats.c b/client/src/wiegand_formats.c index 0b600f804..b64aa0e74 100644 --- a/client/src/wiegand_formats.c +++ b/client/src/wiegand_formats.c @@ -1534,18 +1534,18 @@ bool HIDTryUnpack(wiegand_message_t *packed) { found_cnt++; hid_print_card(&card, FormatTable[i]); - - if (FormatTable[i].Fields.hasParity || card.ParityValid == false) + // if fields has parity AND card parity is false + if (FormatTable[i].Fields.hasParity && (card.ParityValid == false)) found_invalid_par++; } ++i; } if (found_cnt) { - PrintAndLogEx(INFO, "found %u matching format%c", found_cnt, (found_cnt > 1) ? 's' : ' '); + PrintAndLogEx(INFO, "found %u matching format%c with bit len %d", found_cnt, (found_cnt > 1) ? 's' : ' ', packed->Length); } - - if (packed->Length && found_invalid_par == 0) { + + if (packed->Length && ((found_cnt - found_invalid_par) == 0)) { // if length > 0 and no valid parity matches PrintAndLogEx(WARNING, "Wiegand unknown bit len %d", packed->Length); PrintAndLogEx(HINT, "Try 0xFFFF's http://cardinfo.barkweb.com.au/"); } @@ -1561,6 +1561,31 @@ void HIDUnpack(int idx, wiegand_message_t *packed) { } } +// decode wiegand format using HIDTryUnpack +// return true if at least one valid matching formats found +bool decode_wiegand(uint32_t top, uint32_t mid, uint32_t bot, int n) { + bool decode_result; + + if (top == 0 && mid == 0 && bot == 0) { + decode_result = false; + } else if ((n > 0) || ((mid & 0xFFFFFFC0) > 0)) { // if n > 0 or there's more than 38 bits + wiegand_message_t packed = initialize_message_object(top, mid, bot, n); + decode_result = HIDTryUnpack(&packed); + } else { // n <= 0 and 39-64 bits are all 0, try two possible bitlens + wiegand_message_t packed1 = initialize_message_object(top, mid, bot, n); // 26-37 bits + wiegand_message_t packed2 = initialize_message_object(top, mid, bot, 38); // 38 bits + bool packed1_result = HIDTryUnpack(&packed1); + bool packed2_result = HIDTryUnpack(&packed2); + decode_result = (packed1_result || packed2_result); + } + + if (decode_result == false) { + PrintAndLogEx(DEBUG, "DEBUG: Error - " _RED_("HID no values found")); + } + + return decode_result; +} + int HIDDumpPACSBits(const uint8_t *const data, const uint8_t length, bool verbose) { uint8_t n = length - 1; uint8_t pad = data[0]; diff --git a/client/src/wiegand_formats.h b/client/src/wiegand_formats.h index ff231f25f..503e85daf 100644 --- a/client/src/wiegand_formats.h +++ b/client/src/wiegand_formats.h @@ -58,6 +58,7 @@ bool HIDPack(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bo bool HIDTryUnpack(wiegand_message_t *packed); void HIDPackTryAll(wiegand_card_t *card, bool preamble); void HIDUnpack(int idx, wiegand_message_t *packed); +bool decode_wiegand(uint32_t top, uint32_t mid, uint32_t bot, int n); int HIDDumpPACSBits(const uint8_t *const data, const uint8_t length, bool verbose); void print_wiegand_code(wiegand_message_t *packed); void print_desc_wiegand(cardformat_t *fmt, wiegand_message_t *packed); diff --git a/client/src/wiegand_formatutils.c b/client/src/wiegand_formatutils.c index f77ab5bfe..fed31d9fb 100644 --- a/client/src/wiegand_formatutils.c +++ b/client/src/wiegand_formatutils.c @@ -132,7 +132,23 @@ static uint8_t get_length_from_header(wiegand_message_t *data) { /** * detect if message has "preamble" / "sentinel bit" * Right now we just calculate the highest bit set - * 37 bit formats is hard to detect since it doesnt have a sentinel bit + * 38 bits format is handled by directly setting n=38 in initialize_message_object() + * since it's hard to distinguish 38 bits with formats with preamble bit (26-36 bits) + * + * (from http://www.proxmark.org/forum/viewtopic.php?pid=5368#p5368) + * 0000 0010 0000 0000 01xx xxxx xxxx xxxx xxxx xxxx xxxx 26-bit + * 0000 0010 0000 0000 1xxx xxxx xxxx xxxx xxxx xxxx xxxx 27-bit + * 0000 0010 0000 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx 28-bit + * 0000 0010 0000 001x xxxx xxxx xxxx xxxx xxxx xxxx xxxx 29-bit + * 0000 0010 0000 01xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx 30-bit + * 0000 0010 0000 1xxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx 31-bit + * 0000 0010 0001 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx 32-bit + * 0000 0010 001x xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx 33-bit + * 0000 0010 01xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx 34-bit + * 0000 0010 1xxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx 35-bit + * 0000 0011 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx 36-bit + * 0000 000x xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx 37-bit + * 0000 00xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx 38-bit */ uint8_t len = 0; uint32_t hfmt = 0; // for calculating card length @@ -140,26 +156,23 @@ static uint8_t get_length_from_header(wiegand_message_t *data) { if ((data->Top & 0x000FFFFF) > 0) { // > 64 bits hfmt = data->Top & 0x000FFFFF; len = 64; - } else if (data->Mid > 0) { // < 63-32 bits - + } else if (data->Mid > 0) { // detect HID format b38 set - if (data->Mid & 0xFFFFFFC0) { + if (data->Mid & 0xFFFFFFC0) { // 39-64 bits hfmt = data->Mid; - len = 32; - } else { - + len = 31; // remove leading 1 (preamble) in 39-64 bits format + } else { // detect card format 26-37 bits using "preamble" / "sentinel bit" PrintAndLogEx(DEBUG, "hid preamble detected"); - len = 32; - - if ((data->Mid ^ 0x20) == 0) { hfmt = data->Bot; len = 0; } - else if ((data->Mid & 0x10) == 0) { hfmt = data->Mid & 0x1F; } - else if ((data->Mid & 0x08) == 0) { hfmt = data->Mid & 0x0F; } - else if ((data->Mid & 0x04) == 0) { hfmt = data->Mid & 0x07; } - else if ((data->Mid & 0x02) == 0) { hfmt = data->Mid & 0x03; } - else if ((data->Mid & 0x01) == 0) { hfmt = data->Mid & 0x01; } - else { hfmt = data->Mid & 0x3F;} - } + // if bit 38 is set: => 26-36 bits + if (((data->Mid >> 5) & 1) == 1) { + hfmt = (((data->Mid & 31) << 12) | (data->Bot >> 26)); //get bits 27-37 to check for format len bit + len = 19; + } else { // if bit 38 is not set => 37 bits + hfmt = 0; + len = 37; + } + } } else { hfmt = data->Bot; len = 0; From 831a05b193e0b59f1dd4d2a31c74baac6026f494 Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Mon, 17 Feb 2025 19:36:37 +0200 Subject: [PATCH 030/105] Add new AID entries to `aid_desfire.json` --- client/resources/aid_desfire.json | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/client/resources/aid_desfire.json b/client/resources/aid_desfire.json index 680d7909a..a03f7312f 100644 --- a/client/resources/aid_desfire.json +++ b/client/resources/aid_desfire.json @@ -519,6 +519,46 @@ "Description": "Multiservice Student Card", "Type": "student" }, + { + "AID": "764970", + "Vendor": "Ubiquiti Inc", + "Country": "N/A", + "Name": "UniFi Access VID App", + "Description": "Contains issuer-signed static credential information used for KDF & other authentication operations", + "Type": "pacs" + }, + { + "AID": "84D3FC", + "Vendor": "Ubiquiti Inc", + "Country": "N/A", + "Name": "UniFi Access FCD App", + "Description": "Contains static credential information used for KDF & other authentication operations", + "Type": "pacs" + }, + { + "AID": "416343", + "Vendor": "Ubiquiti Inc", + "Country": "N/A", + "Name": "UniFi Access ACC App", + "Description": "Application created after enrollment into the system, containins unique authentication info", + "Type": "pacs" + }, + { + "AID": "454955", + "Vendor": "Ubiquiti Inc", + "Country": "N/A", + "Name": "UniFi Access Touch Pass Apple Wallet Express", + "Description": "AID value is 'UIE' (UniFi Express) reversed. This app is selectable with or without auth", + "Type": "pacs" + }, + { + "AID": "534955", + "Vendor": "Ubiquiti Inc", + "Country": "N/A", + "Name": "UniFi Access Touch Pass Apple Wallet Secure", + "Description": "AID value is 'UIS' (UniFi Secure) reversed. This app is selectable only after manual auth", + "Type": "pacs" + }, { "AID": "535501", "Vendor": "TU Delft", @@ -1399,6 +1439,14 @@ "Description": "FIDs 02: Card Balance; 04: Refill History; 08: Card Information; 0E: Trip History", "Type": "transport" }, + { + "AID": "F21191", + "Vendor": "Metropolitan Transportation Commission via Cubic", + "Country": "US", + "Name": "Clipper Card (Mobile)", + "Description": "", + "Type": "transport" + }, { "AID": "F21201", "Vendor": "Green Bay Metro Transit via Genfare", From 23dda089629f01a37046e3ede803f6549a3fa8e6 Mon Sep 17 00:00:00 2001 From: kormax <3392860+kormax@users.noreply.github.com> Date: Mon, 17 Feb 2025 19:52:07 +0200 Subject: [PATCH 031/105] Fixes to DESFire product type recognition --- client/src/cmdhfmfdes.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client/src/cmdhfmfdes.c b/client/src/cmdhfmfdes.c index 6483cf9c9..3a62c7751 100644 --- a/client/src/cmdhfmfdes.c +++ b/client/src/cmdhfmfdes.c @@ -355,6 +355,10 @@ static nxp_cardtype_t getCardType(uint8_t type, uint8_t major, uint8_t minor) { if (type == 0x81 && major == 0x42 && minor == 0x00) return DESFIRE_EV2; + // Apple Wallet DESFire Applet + if (type == 0x91 && major == 0x62 && minor == 0x01) + return DESFIRE_EV2; + // Plus EV1 if (type == 0x02 && major == 0x11 && minor == 0x00) return PLUS_EV1; @@ -377,7 +381,7 @@ static nxp_cardtype_t getCardType(uint8_t type, uint8_t major, uint8_t minor) { // ref: https://www.nxp.com/docs/en/application-note/AN12343.pdf p7 static nxp_producttype_t getProductType(const uint8_t *versionhw) { - uint8_t product = versionhw[2]; + uint8_t product = versionhw[1]; if (product == 0x01) return DESFIRE_PHYSICAL; @@ -394,7 +398,7 @@ static nxp_producttype_t getProductType(const uint8_t *versionhw) { static const char *getProductTypeStr(const uint8_t *versionhw) { - uint8_t product = versionhw[2]; + uint8_t product = versionhw[1]; if (product == 0x01) return "MIFARE DESFire native IC (physical card)"; From 778ede25e7588bf506c8a40fdce12dbba8db2d3b Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Mon, 17 Feb 2025 21:24:09 +0100 Subject: [PATCH 032/105] renamed lua scripts. minor adaptations --- ...t55_chk_date.lua => lf_t55xx_chk_date.lua} | 0 .../{t55_fix.lua => lf_t55xx_fix.lua} | 0 client/src/cmdhfemrtd.c | 3 +- client/src/cmdhfmfu.c | 2 + client/src/crypto/asn1utils.c | 31 ++++ client/src/crypto/asn1utils.h | 2 + client/src/fileutils.c | 14 +- client/src/fileutils.h | 23 +++ client/t55_chk.lua | 133 ------------------ 9 files changed, 68 insertions(+), 140 deletions(-) rename client/luascripts/{t55_chk_date.lua => lf_t55xx_chk_date.lua} (100%) rename client/luascripts/{t55_fix.lua => lf_t55xx_fix.lua} (100%) delete mode 100644 client/t55_chk.lua diff --git a/client/luascripts/t55_chk_date.lua b/client/luascripts/lf_t55xx_chk_date.lua similarity index 100% rename from client/luascripts/t55_chk_date.lua rename to client/luascripts/lf_t55xx_chk_date.lua diff --git a/client/luascripts/t55_fix.lua b/client/luascripts/lf_t55xx_fix.lua similarity index 100% rename from client/luascripts/t55_fix.lua rename to client/luascripts/lf_t55xx_fix.lua diff --git a/client/src/cmdhfemrtd.c b/client/src/cmdhfemrtd.c index 9b2bab31d..2160c9b06 100644 --- a/client/src/cmdhfemrtd.c +++ b/client/src/cmdhfemrtd.c @@ -1877,14 +1877,13 @@ static int emrtd_print_ef_sod_info(uint8_t *dg_hashes_calc, uint8_t *dg_hashes_s PrintAndLogEx(INFO, "------------------------ " _CYAN_("EF_SOD") " ------------------------"); PrintAndLogEx(INFO, "Document Security Object"); PrintAndLogEx(INFO, "contains the digital signatures of the passport data"); + PrintAndLogEx(INFO, ""); if (hash_algo == -1) { PrintAndLogEx(SUCCESS, "Hash algorithm... " _YELLOW_("Unknown")); - PrintAndLogEx(INFO, ""); } else { PrintAndLogEx(SUCCESS, "Hash algorithm... " _YELLOW_("%s"), hashalg_table[hash_algo].name); - PrintAndLogEx(INFO, ""); uint8_t all_zeroes[64] = { 0x00 }; diff --git a/client/src/cmdhfmfu.c b/client/src/cmdhfmfu.c index 0a46e4cd9..e84e55169 100644 --- a/client/src/cmdhfmfu.c +++ b/client/src/cmdhfmfu.c @@ -2067,6 +2067,7 @@ uint64_t GetHF14AMfU_Type(void) { uint8_t version[10] = {0x00}; int len = ulev1_getVersion(version, sizeof(version)); DropField(); + switch (len) { case 0x0A: { /* @@ -2158,6 +2159,7 @@ uint64_t GetHF14AMfU_Type(void) { tagtype = MFU_TT_UNKNOWN; break; } + // This is a test from cards that doesn't answer to GET_VERSION command // UL vs UL-C vs NTAG203 vs FUDAN FM11NT021 (which is NTAG213 compatiable) if (tagtype & (MFU_TT_UL | MFU_TT_UL_C | MFU_TT_NTAG_203)) { diff --git a/client/src/crypto/asn1utils.c b/client/src/crypto/asn1utils.c index 1fa839a7f..67392f534 100644 --- a/client/src/crypto/asn1utils.c +++ b/client/src/crypto/asn1utils.c @@ -107,6 +107,37 @@ int asn1_print(uint8_t *asn1buf, size_t asn1buflen, const char *indent) { return PM3_SUCCESS; } +int asn1_get_tag_length(const uint8_t *data, size_t *n, size_t *offset, size_t total_length) { + + if (*offset >= total_length) { + return -1; + } + + if (data[*offset] & 0x80) { + + // Long form: number of length bytes is indicated by the lower 7 bits + size_t len_bytes = data[*offset] & 0x7F; + + *offset += 1; + + if (*offset + len_bytes > total_length) { + return -1; + } + + *n = 0; + for (size_t i = 0; i < len_bytes; i++) { + *n = (*n << 8) | data[*offset]; + *offset += 1; + } + } else { + // Short form: length is directly represented + *n = data[*offset]; + *offset += 1; + } + + return 0; +} + typedef struct { const char *hex; diff --git a/client/src/crypto/asn1utils.h b/client/src/crypto/asn1utils.h index e55a2926f..57d443e6a 100644 --- a/client/src/crypto/asn1utils.h +++ b/client/src/crypto/asn1utils.h @@ -25,6 +25,8 @@ int asn1_print(uint8_t *asn1buf, size_t asn1buflen, const char *indent); int ecdsa_asn1_get_signature(uint8_t *signature, size_t signaturelen, uint8_t *rval, uint8_t *sval); + +int asn1_get_tag_length(const uint8_t *data, size_t *n, size_t *offset, size_t total_length); int asn1_selftest(void); #endif /* asn1utils.h */ diff --git a/client/src/fileutils.c b/client/src/fileutils.c index db151f3a1..86a3d06b5 100644 --- a/client/src/fileutils.c +++ b/client/src/fileutils.c @@ -1105,7 +1105,9 @@ int loadFileEML_safe(const char *preferredName, void **pdata, size_t *datalen) { int loadFileNFC_safe(const char *preferredName, void *data, size_t maxdatalen, size_t *datalen, nfc_df_e ft) { - if (data == NULL) return PM3_EINVARG; + if (data == NULL) { + return PM3_EINVARG; + } *datalen = 0; int retval = PM3_SUCCESS; @@ -1137,16 +1139,17 @@ int loadFileNFC_safe(const char *preferredName, void *data, size_t maxdatalen, s memset(line, 0, sizeof(line)); if (fgets(line, sizeof(line), f) == NULL) { - if (feof(f)) + if (feof(f)) { break; - + } fclose(f); PrintAndLogEx(FAILED, "file reading error"); return PM3_EFILE; } - if (line[0] == '#') + if (line[0] == '#') { continue; + } str_cleanrn(line, sizeof(line)); str_lower(line); @@ -2626,6 +2629,7 @@ int detect_nfc_dump_format(const char *preferredName, nfc_df_e *dump_type, bool fclose(f); if (verbose) { + switch (*dump_type) { case NFC_DF_MFU: PrintAndLogEx(INFO, "Detected MIFARE Ultralight / NTAG based dump format"); @@ -3107,7 +3111,7 @@ int pm3_load_dump(const char *fn, void **pdump, size_t *dumplen, size_t maxdumpl break; } case FLIPPER: { - nfc_df_e dumptype; + nfc_df_e dumptype = NFC_DF_UNKNOWN; res = detect_nfc_dump_format(fn, &dumptype, true); if (res != PM3_SUCCESS) { break; diff --git a/client/src/fileutils.h b/client/src/fileutils.h index ae04b8265..6fc450dd1 100644 --- a/client/src/fileutils.h +++ b/client/src/fileutils.h @@ -282,8 +282,31 @@ int loadFileDICTIONARYEx(const char *preferredName, void *data, size_t maxdatale */ int loadFileDICTIONARY_safe(const char *preferredName, void **pdata, uint8_t keylen, uint32_t *keycnt); +/** + * @brief Utility function to load data safely from a DICTIONARY textfile. This method takes a preferred name. + * E.g. mfc_default_keys.dic + * + * @param preferredName + * @param suffix + * @param pdata A pointer to a pointer (for reverencing the loaded dictionary) + * @param keylen the number of bytes a key per row is + * @param verbose print messages if true + * @return 0 for ok, 1 for failz +*/ int loadFileDICTIONARY_safe_ex(const char *preferredName, const char *suffix, void **pdata, uint8_t keylen, uint32_t *keycnt, bool verbose); +/** + * @brief Utility function to load data from a XML textfile. This method takes a preferred name. + * E.g. dumpdata-15.xml + * + * @param preferredName + * @param data The data array to store the loaded bytes from file + * @param maxdatalen maximum size of data array in bytes + * @param datalen the number of bytes loaded from file + * @return 0 for ok, 1 for failz +*/ +int loadFileXML_safe(const char *preferredName, const char *suffix, void **pdata, size_t *datalen); + int loadFileBinaryKey(const char *preferredName, const char *suffix, void **keya, void **keyb, size_t *alen, size_t *blen); /** diff --git a/client/t55_chk.lua b/client/t55_chk.lua deleted file mode 100644 index 8f88cdf3c..000000000 --- a/client/t55_chk.lua +++ /dev/null @@ -1,133 +0,0 @@ -local os = require("os") -local ac = require('ansicolors') -local getopt = require('getopt') -local dir = os.getenv('HOME') .. '/proxmark3/client/dictionaries/' -local dictionary_path = dir .. 'T5577date.dic' -local cyan = ac.cyan -local res = ac.reset - -author = ' Author: jareckib - created 02.02.2025' -version = ' version v1.01' -desc = [[ - A simple script for searching the password for T5577. The script creates a - dictionary starting from the entered starting year to the entered ending year. - There are two search methods - DDMMYYYY or YYYYMMDD. Checking the entire year - takes about 1 minute and 50 seconds. Date from 1900 to 2100. The script may be - useful if the password is, for example, a date of birth. -]] - -usage = [[ - script run t55_chk [-s start_year] [-e end_year] [-d | -y] -]] -options = [[ - -h Show this help message - -s Starting year (required) - -e Ending year (default: current year) - -d Search method: DDMMYYYY - -y Search method: YYYYMMDD -]] -examples = [[ - script run t55_chk -s 1999 -d - start from 1999, end year is current year, method 01011999 - script run t55_chk -s 1999 -y - start from 1999, end year is current year, method 19990101 - script run t55_chk -s 1999 -e 2001 -y - start from 1999, end year 2001, method 19990101 - script run t55_chk -s 1999 -e 2001 -d - start from 1999, end year 2001, method 01011999 -]] - -local function help() - print(ac.green..author..res) - print(version) - print(desc) - print(cyan..' Usage:'..res) - print(usage) - print(cyan..' Options:'..res) - print(options) - print(cyan..' Examples:'..res) - print(examples) -end - -local days_in_month = { - [1] = 31, [2] = 28, [3] = 31, [4] = 30, [5] = 31, [6] = 30, - [7] = 31, [8] = 31, [9] = 30, [10] = 31, [11] = 30, [12] = 31 -} - -local function generate_dictionary(start_year, end_year, mode) - local file = io.open(dictionary_path, "w") - if not file then - print(ac.yellow .. ' ERROR: ' .. ac.reset .. 'Cannot create T5577date.dic') - return false - end - - for year = start_year, end_year do - for month = 1, 12 do - local days_in_current_month = days_in_month[month] - if month == 2 and ((year % 4 == 0 and year % 100 ~= 0) or (year % 400 == 0)) then - days_in_current_month = 29 - end - - for day = 1, days_in_current_month do - local month_str = string.format("%02d", month) - local day_str = string.format("%02d", day) - local year_str = tostring(year) - local entry = (mode == "1") and (year_str .. month_str .. day_str) or (day_str .. month_str .. year_str) - file:write(entry .. "\n") - end - end - end - - file:close() - return true -end - -local function oops(err) - core.console('clear') - print( string.rep('--',39) ) - print( string.rep('--',39) ) - print(ac.red..' ERROR:'..res.. err) - print( string.rep('--',39) ) - print( string.rep('--',39) ) - return nil, err -end - -local function main(args) - if #args == 0 then return help() end - - local start_year, end_year, mode = nil, nil, nil - local current_year = tonumber(os.date("%Y")) - - for o, a in getopt.getopt(args, 'hs:e:dy') do - if o == 'h' then return help() end - if o == 's' then - start_year = tonumber(a) - if not start_year then return oops('Invalid start year') end - end - if o == 'e' then - end_year = tonumber(a) - if not end_year then return oops('Invalid end year (-e)') end - end - if o == 'd' then mode = "d" end - if o == 'y' then mode = "y" end - end - - if not start_year then return oops('Starting year is required') end - if start_year < 1900 or start_year > 2100 then - return oops('Start year must be between 1900 and 2100') - end - if args[#args] == "-e" then return oops('Ending year cannot be empty') end - if not end_year then end_year = current_year end - if end_year < 1900 or end_year > 2100 then - return oops('End year must be between 1900 and 2100') - end - - if end_year < start_year then return oops('End year cannot be earlier than start year') end - if not mode then return oops('You must select searching method'..cyan..' -d'..res.. ' or '..cyan.. '-y'..res) end - - if generate_dictionary(start_year, end_year, mode) then - print(ac.green .. " File created: " .. dictionary_path .. res) - print(cyan .. " Starting password testing on T5577..." .. res) - core.console('lf t55 chk -f ' .. dictionary_path) - else - return oops('Problem saving the file') - end -end - -main(args) \ No newline at end of file From 4d4ab58c63e024815b85067ff7db5e1ce2f13d0e Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Mon, 17 Feb 2025 21:26:53 +0100 Subject: [PATCH 033/105] text --- client/luascripts/{t55_chk.lua => lf_t55xx_chk.lua} | 12 ++++++------ client/luascripts/lf_t55xx_chk_date.lua | 4 ++-- client/luascripts/lf_t55xx_fix.lua | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) rename client/luascripts/{t55_chk.lua => lf_t55xx_chk.lua} (90%) diff --git a/client/luascripts/t55_chk.lua b/client/luascripts/lf_t55xx_chk.lua similarity index 90% rename from client/luascripts/t55_chk.lua rename to client/luascripts/lf_t55xx_chk.lua index 3133e3a81..9343b0cde 100644 --- a/client/luascripts/t55_chk.lua +++ b/client/luascripts/lf_t55xx_chk.lua @@ -19,7 +19,7 @@ desc = [[ ]] usage = [[ - script run t55_chk [-s start_year] [-e end_year] [-d | -y] + script run lf_t55xx_chk [-s start_year] [-e end_year] [-d | -y] ]] options = [[ -h this help @@ -29,10 +29,10 @@ options = [[ -y search method: YYYYMMDD ]] examples = [[ - script run t55_chk -s 1999 -d -> start 1999, end is current year, method 01011999 - script run t55_chk -s 1999 -y -> start 1999, end is current year, method 19990101 - script run t55_chk -s 1999 -e 2001 -y -> start 1999, end year 2001, method 19990101 - script run t55_chk -s 1999 -e 2001 -d -> start 1999, end year 2001, method 01011999 + script run lf_t55xx_chk -s 1999 -d -> start 1999, end is current year, method 01011999 + script run lf_t55xx_chk -s 1999 -y -> start 1999, end is current year, method 19990101 + script run lf_t55xx_chk -s 1999 -e 2001 -y -> start 1999, end year 2001, method 19990101 + script run lf_t55xx_chk -s 1999 -e 2001 -d -> start 1999, end year 2001, method 01011999 ]] local function help() @@ -135,4 +135,4 @@ local function main(args) return oops('Problem saving the file') end end - main(args) \ No newline at end of file + main(args) diff --git a/client/luascripts/lf_t55xx_chk_date.lua b/client/luascripts/lf_t55xx_chk_date.lua index a6b217832..dcd5124d2 100644 --- a/client/luascripts/lf_t55xx_chk_date.lua +++ b/client/luascripts/lf_t55xx_chk_date.lua @@ -14,10 +14,10 @@ desc = [[ useful if the password is, for example, a date of birth. ]] usage = [[ - script run t55_chk_date + script run lf_t55xx_chk_date ]] arguments = [[ - script run t55_chk_date -h : this help + script run lf_t55xx_chk_date -h : this help ]] local DEBUG = true diff --git a/client/luascripts/lf_t55xx_fix.lua b/client/luascripts/lf_t55xx_fix.lua index 354cf8dfc..bed267eac 100644 --- a/client/luascripts/lf_t55xx_fix.lua +++ b/client/luascripts/lf_t55xx_fix.lua @@ -17,10 +17,10 @@ desc = [[ only performs the reanimation procedure. The script revives 99% of blocked tags. ]] usage = [[ - script run t55_fix + script run lf_t55xx_fix ]] arguments = [[ - script run t55_fix -h : this help + script run lf_t55xx_fix -h : this help ]] local function help() From 3e9de0130389c21d6fba805ab4b6f18f114fcdd6 Mon Sep 17 00:00:00 2001 From: n-hutton Date: Tue, 18 Feb 2025 15:49:33 +0000 Subject: [PATCH 034/105] PR feedback --- tools/fpga_compress/fpga_compress.c | 38 ++++++++++++----------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/tools/fpga_compress/fpga_compress.c b/tools/fpga_compress/fpga_compress.c index 97ba90024..a623b0b32 100644 --- a/tools/fpga_compress/fpga_compress.c +++ b/tools/fpga_compress/fpga_compress.c @@ -18,6 +18,8 @@ #include #include #include +#include +#include "time.h" #include "fpga.h" #include "lz4hc.h" @@ -380,30 +382,22 @@ static int FpgaGatherVersion(FILE *infile, char *infile_name, char *dst, int len strncat(dst, tempstr, len - strlen(dst) - 1); } - strncat(dst, " ", len - strlen(dst) - 1); - if (bitparse_find_section(infile, 'c', &fpga_info_len)) { - for (uint32_t i = 0; i < fpga_info_len; i++) { - char c = (char)fgetc(infile); - if (i < sizeof(tempstr)) { - if (c == '/') c = '-'; - if (c == ' ') c = '0'; - tempstr[i] = c; - } - } - strncat(dst, tempstr, len - strlen(dst) - 1); + // Get file statistics to extract date and time via file timestamp + int fd = fileno(infile); + struct stat fileStat; + + if (fstat(fd, &fileStat) == 0) { + struct tm *modTime = localtime(&fileStat.st_mtime); + + + char timeBuf[64]; + snprintf(timeBuf, sizeof(timeBuf), " %02d-%02d-%04d %02d:%02d:%02d", + modTime->tm_mday, modTime->tm_mon + 1, modTime->tm_year + 1900, + modTime->tm_hour, modTime->tm_min, modTime->tm_sec); + + strncat(dst, timeBuf, len - strlen(dst) - 1); } - if (bitparse_find_section(infile, 'd', &fpga_info_len)) { - strncat(dst, " ", len - strlen(dst) - 1); - for (uint32_t i = 0; i < fpga_info_len; i++) { - char c = (char)fgetc(infile); - if (i < sizeof(tempstr)) { - if (c == ' ') c = '0'; - tempstr[i] = c; - } - } - strncat(dst, tempstr, len - strlen(dst) - 1); - } return 0; } From 7c37231b2cbc605e2be29585b98dee2d46ec4a3b Mon Sep 17 00:00:00 2001 From: n-hutton Date: Tue, 18 Feb 2025 16:09:16 +0000 Subject: [PATCH 035/105] add helpful note for macos users --- .../macOS-Homebrew-Installation-Instructions.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/md/Installation_Instructions/macOS-Homebrew-Installation-Instructions.md b/doc/md/Installation_Instructions/macOS-Homebrew-Installation-Instructions.md index 68fe1e899..9102de117 100644 --- a/doc/md/Installation_Instructions/macOS-Homebrew-Installation-Instructions.md +++ b/doc/md/Installation_Instructions/macOS-Homebrew-Installation-Instructions.md @@ -58,6 +58,20 @@ The fastest option is to run the brew command with the `arch -arm64` prefix i.e. Visual Studio Code still runs under Rosetta 2 and if you're developing for proxmark3 on an Apple Silicon Mac you might want to consider running the Insiders build which has support for running natively on Apple Silicon. +If you see an error when linking: +``` +ld: warning: ignoring file /usr/local/Cellar/.../libpython3.9.dylib, building for macOS-arm64 but attempting to link with file built for macOS-x86_64 +Undefined symbols for architecture arm64: + "_PyArg_UnpackTuple", referenced from: + _SwigPyObject_own in pm3_pywrap.o + ... +``` +your build environment has tried to link python across architectures. You can find or install python via homebrew (arm64) and inform the linker to use this +``` +brew install python@ +export LDFLAGS="-L/opt/homebrew/Cellar/python@/./Frameworks/Python.framework/Versions//lib/" && make +``` + ## Install Proxmark3 tools ^[Top](#top) From 72122f090fadd8308bc7f78c6916e09d129a8ae2 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Tue, 18 Feb 2025 18:37:31 +0100 Subject: [PATCH 036/105] less verbose device side printing --- armsrc/hitagS.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/armsrc/hitagS.c b/armsrc/hitagS.c index 18039f431..5e7420566 100644 --- a/armsrc/hitagS.c +++ b/armsrc/hitagS.c @@ -1029,13 +1029,14 @@ static int hts_send_receive(const uint8_t *tx, size_t txlen, uint8_t *rx, size_t response_bit[i] = (rx[i / 8] >> (7 - (i % 8))) & 1; } - Dbprintf("htS: rxlen...... %zu", *rxlen); - Dbprintf("htS: sizeofrx... %zu", sizeofrx); - DbpString("htS: response_bit:"); - Dbhexdump(*rxlen, response_bit, false); - Dbprintf("htS: skipping %d bit SOF", sof_bits); - if ((rx[0] >> (8 - sof_bits)) != ((1 << sof_bits) - 1)) { - DbpString("htS: Warning, not all bits of SOF are 1"); + DBG Dbprintf("htS: rxlen...... %zu", *rxlen); + DBG Dbprintf("htS: sizeofrx... %zu", sizeofrx); + DBG DbpString("htS: response_bit:"); + DBG Dbhexdump(*rxlen, response_bit, false); + DBG Dbprintf("htS: skipping %d bit SOF", sof_bits); + + if ((rx[0] >> (8 - sof_bits)) != ((1 << sof_bits) - 1)) { + DBG DbpString("htS: Warning, not all bits of SOF are 1"); } } From 50b9067173c3c8daa48ba1ef5f310ada3e7950b9 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Tue, 18 Feb 2025 18:38:16 +0100 Subject: [PATCH 037/105] style --- armsrc/iso14443a.c | 2 -- armsrc/mifaresim.c | 11 +++++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index 1a7905527..ba66eb063 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -690,8 +690,6 @@ static RAMFUNC int ManchesterDecoding_Thinfilm(uint8_t bit) { if (Demod.bitCount) { // there are some remaining data bits Demod.shiftReg <<= (8 - Demod.bitCount); // left align the decoded bits Demod.output[Demod.len++] = Demod.shiftReg & 0xFF; // and add them to the output - -// Dbprintf("A | len... %u - %u == 0x%02x", Demod.len, Demod.bitCount, Demod.output[0]); return true; } diff --git a/armsrc/mifaresim.c b/armsrc/mifaresim.c index 65f18bdac..4ee88ec34 100644 --- a/armsrc/mifaresim.c +++ b/armsrc/mifaresim.c @@ -1271,18 +1271,21 @@ void Mifare1ksim(uint16_t flags, uint8_t exitAfterNReads, uint8_t *uid, uint16_t mf_crypto1_decryptEx(pcs, receivedCmd, receivedCmd_len, receivedCmd_dec); if (CheckCrc14A(receivedCmd_dec, receivedCmd_len)) { if (IsSectorTrailer(cardWRBL)) { + emlGetMem(response, cardWRBL, 1); - if (!IsAccessAllowed(cardWRBL, cardAUTHKEY, AC_KEYA_WRITE)) { + + if (IsAccessAllowed(cardWRBL, cardAUTHKEY, AC_KEYA_WRITE) == false) { memcpy(receivedCmd_dec, response, 6); // don't change KeyA } - if (!IsAccessAllowed(cardWRBL, cardAUTHKEY, AC_KEYB_WRITE)) { + if (IsAccessAllowed(cardWRBL, cardAUTHKEY, AC_KEYB_WRITE) == false) { memcpy(receivedCmd_dec + 10, response + 10, 6); // don't change KeyA } - if (!IsAccessAllowed(cardWRBL, cardAUTHKEY, AC_AC_WRITE)) { + if (IsAccessAllowed(cardWRBL, cardAUTHKEY, AC_AC_WRITE) == false) { memcpy(receivedCmd_dec + 6, response + 6, 4); // don't change AC bits } + } else { - if (!IsAccessAllowed(cardWRBL, cardAUTHKEY, AC_DATA_WRITE)) { + if (IsAccessAllowed(cardWRBL, cardAUTHKEY, AC_DATA_WRITE) == false) { memcpy(receivedCmd_dec, response, 16); // don't change anything } } From d99ec776c8e3e71f231f47e6d791952cf1784976 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Tue, 18 Feb 2025 18:41:09 +0100 Subject: [PATCH 038/105] style --- client/src/cmdhf14a.c | 11 ++++------- client/src/cmdhficlass.c | 18 ++++++++++-------- client/src/cmdhficlass.h | 1 + 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/client/src/cmdhf14a.c b/client/src/cmdhf14a.c index ac99ea229..33ce1a28d 100644 --- a/client/src/cmdhf14a.c +++ b/client/src/cmdhf14a.c @@ -2358,7 +2358,7 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { tc1 = (card.ats[1] & 0x40) == 0x40; int16_t fsci = card.ats[1] & 0x0f; - PrintAndLogEx(INFO, " " _YELLOW_("%02X") "............ T0 TA1 is%s present, TB1 is%s present, " + PrintAndLogEx(INFO, " ..." _YELLOW_("%02X") "............ T0 TA1 is%s present, TB1 is%s present, " "TC1 is%s present, FSCI is %d (FSC = %d)", card.ats[1], (ta1 ? "" : _RED_(" NOT")), @@ -2380,7 +2380,7 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { if (card.ats[pos] & 0x04) strcat(dr, "8, "); if (strlen(ds) != 0) ds[strlen(ds) - 2] = '\0'; if (strlen(dr) != 0) dr[strlen(dr) - 2] = '\0'; - PrintAndLogEx(INFO, " " _YELLOW_("%02X") "......... TA1 different divisors are%s supported, " + PrintAndLogEx(INFO, " ......" _YELLOW_("%02X") "......... TA1 different divisors are%s supported, " "DR: [%s], DS: [%s]", card.ats[pos], ((card.ats[pos] & 0x80) ? _RED_(" NOT") : ""), @@ -2395,7 +2395,7 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { uint32_t sfgi = card.ats[pos] & 0x0F; uint32_t fwi = card.ats[pos] >> 4; - PrintAndLogEx(INFO, " " _YELLOW_("%02X") "...... TB1 SFGI = %d (SFGT = %s%d/fc), FWI = " _YELLOW_("%d") " (FWT = %d/fc)", + PrintAndLogEx(INFO, " ........." _YELLOW_("%02X") "...... TB1 SFGI = %d (SFGT = %s%d/fc), FWI = " _YELLOW_("%d") " (FWT = %d/fc)", card.ats[pos], (sfgi), sfgi ? "" : "(not needed) ", @@ -2407,7 +2407,7 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { } if (tc1 && (card.ats_len > pos + 2)) { - PrintAndLogEx(INFO, " " _YELLOW_("%02X") "... TC1 NAD is%s supported, CID is%s supported", + PrintAndLogEx(INFO, " ............" _YELLOW_("%02X") "... TC1 NAD is%s supported, CID is%s supported", card.ats[pos], (card.ats[pos] & 0x01) ? "" : _RED_(" NOT"), (card.ats[pos] & 0x02) ? "" : _RED_(" NOT") @@ -2546,11 +2546,8 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { , sprint_ascii(card.ats + pos, calen) ); } - - PrintAndLogEx(NORMAL, ""); } } - } if (do_aid_search) { diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index 492403344..b08b7ba8c 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -66,8 +66,6 @@ static uint8_t empty[PICOPASS_BLOCK_SIZE] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, static uint8_t zeros[PICOPASS_BLOCK_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; static int CmdHelp(const char *Cmd); -static void print_iclass_sio(uint8_t *iclass_dump, size_t dump_len); - static uint8_t iClass_Key_Table[ICLASS_KEYS_MAX][PICOPASS_BLOCK_SIZE] = { { 0xAE, 0xA6, 0x84, 0xA6, 0xDA, 0xB2, 0x32, 0x78 }, { 0xFD, 0xCB, 0x5A, 0x52, 0xEA, 0x8F, 0x30, 0x90 }, @@ -3184,7 +3182,7 @@ static void detect_credential(uint8_t *iclass_dump, size_t dump_len, bool *is_le picopass_hdr_t *hdr = (picopass_hdr_t *)iclass_dump; - if (!memcmp(hdr->app_issuer_area, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", PICOPASS_BLOCK_SIZE)) { + if (memcmp(hdr->app_issuer_area, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", PICOPASS_BLOCK_SIZE) == 0) { // Legacy AIA *is_legacy = true; @@ -3206,7 +3204,7 @@ static void detect_credential(uint8_t *iclass_dump, size_t dump_len, bool *is_le } } } - } else if (!memcmp(hdr->app_issuer_area, "\xFF\xFF\xFF\x00\x06\xFF\xFF\xFF", PICOPASS_BLOCK_SIZE)) { + } else if (memcmp(hdr->app_issuer_area, "\xFF\xFF\xFF\x00\x06\xFF\xFF\xFF", PICOPASS_BLOCK_SIZE) == 0) { // SE AIA *is_se = true; @@ -3238,7 +3236,8 @@ static void detect_credential(uint8_t *iclass_dump, size_t dump_len, bool *is_le } // print ASN1 decoded array in TLV view -static void print_iclass_sio(uint8_t *iclass_dump, size_t dump_len) { +void print_iclass_sio(uint8_t *iclass_dump, size_t dump_len, bool verbose) { + bool is_legacy, is_se, is_sr; uint8_t *sio_start; size_t sio_length; @@ -3249,7 +3248,7 @@ static void print_iclass_sio(uint8_t *iclass_dump, size_t dump_len) { } if (dump_len < sio_length + (sio_start - iclass_dump)) { - // SIO length exceeds the size of the dump we have, bail + // SIO length exceeds the size of the dump return; } @@ -3257,9 +3256,11 @@ static void print_iclass_sio(uint8_t *iclass_dump, size_t dump_len) { PrintAndLogEx(INFO, "---------------------------- " _CYAN_("SIO - RAW") " ----------------------------"); print_hex_noascii_break(sio_start, sio_length, 32); PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(INFO, "------------------------- " _CYAN_("SIO - ASN1 TLV") " --------------------------"); + if (verbose) { + PrintAndLogEx(INFO, "----------------------- " _CYAN_("SIO - ASN1 TLV") " ---------------------------"); asn1_print(sio_start, sio_length, " "); PrintAndLogEx(NORMAL, ""); + } } void printIclassDumpContents(uint8_t *iclass_dump, uint8_t startblock, uint8_t endblock, size_t filesize, bool dense_output) { @@ -3457,8 +3458,9 @@ void printIclassDumpContents(uint8_t *iclass_dump, uint8_t startblock, uint8_t e if (is_legacy) PrintAndLogEx(HINT, _YELLOW_("yellow") " = legacy credential"); - if (is_se) + if (is_se) { PrintAndLogEx(HINT, _CYAN_("cyan") " = SIO / SE credential"); + } if (is_sr) PrintAndLogEx(HINT, _CYAN_("cyan") " = SIO / SR credential"); diff --git a/client/src/cmdhficlass.h b/client/src/cmdhficlass.h index 6fac5cee5..b4fcd0524 100644 --- a/client/src/cmdhficlass.h +++ b/client/src/cmdhficlass.h @@ -43,4 +43,5 @@ uint32_t picopass_elite_rng(void); uint32_t picopass_elite_lcg(void); uint8_t picopass_elite_nextByte(void); void generate_key_block_inverted(const uint8_t *startingKey, uint64_t index, uint8_t *keyBlock); +void print_iclass_sio(uint8_t *iclass_dump, size_t dump_len, bool verbose); #endif From e5293f13899593734f71b71b0010e43dc904d9aa Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Tue, 18 Feb 2025 18:42:21 +0100 Subject: [PATCH 039/105] style --- client/src/cmdhfmf.c | 11 ++++++----- client/src/cmdhfmfdes.c | 23 +++++++++++++---------- client/src/cmdhfmfp.c | 8 ++++---- client/src/cmdhfmfu.c | 20 ++++++++++---------- 4 files changed, 33 insertions(+), 29 deletions(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index b17e9caf8..096d7c7e3 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -2542,8 +2542,8 @@ static int CmdHF14AMfAutoPWN(const char *Cmd) { CLIParamStrToBuf(arg_get_str(ctx, 5), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); int outfnlen = 0; - char outfilename[127] = {0}; - CLIParamStrToBuf(arg_get_str(ctx, 6), (uint8_t *)outfilename, 127, &outfnlen); + char outfilename[FILE_PATH_SIZE] = {0}; + CLIParamStrToBuf(arg_get_str(ctx, 6), (uint8_t *)outfilename, FILE_PATH_SIZE, &outfnlen); bool slow = arg_get_lit(ctx, 7); @@ -4263,8 +4263,9 @@ static int CmdHF14AMfSim(const char *Cmd) { , (uidlen == 0) ? "n/a" : sprint_hex(uid, uidlen) ); - PrintAndLogEx(INFO, "Options [ numreads: %d, flags: 0x%04x ]" + PrintAndLogEx(INFO, "Options [ numreads: %d, flags: %d (0x%04x) ]" , exitAfterNReads + , flags , flags); struct { @@ -4299,7 +4300,7 @@ static int CmdHF14AMfSim(const char *Cmd) { bool keypress = kbd_enter_pressed(); while (keypress == false) { - if (WaitForResponseTimeout(CMD_HF_MIFARE_SIMULATE, &resp, 1500) == 0) { + if (WaitForResponseTimeout(CMD_HF_MIFARE_SIMULATE, &resp, 1500) == false) { keypress = kbd_enter_pressed(); continue; } @@ -9840,7 +9841,7 @@ static int CmdHF14AMfInfo(const char *Cmd) { res = detect_classic_static_nonce(); if (res == NONCE_STATIC) { - PrintAndLogEx(SUCCESS, "Static nonce......... " _YELLOW_("yes")); + PrintAndLogEx(SUCCESS, "Static nonce... " _YELLOW_("yes")); } diff --git a/client/src/cmdhfmfdes.c b/client/src/cmdhfmfdes.c index 3a62c7751..25544ffbe 100644 --- a/client/src/cmdhfmfdes.c +++ b/client/src/cmdhfmfdes.c @@ -756,7 +756,7 @@ static int CmdHF14ADesInfo(const char *Cmd) { if (major == 2 && minor == 2) PrintAndLogEx(INFO, "\t2.2 - DESFire Ev2 XL, Originality check, proximity check, EAL5"); if (major == 3 && minor == 0) - PrintAndLogEx(INFO, "\t3.0 - DESFire Ev3, Originality check, proximity check, badass EAL6 ?"); + PrintAndLogEx(INFO, "\t3.0 - DESFire Ev3, Originality check, proximity check, badass EAL6"); if (major == 0xA0 && minor == 0) PrintAndLogEx(INFO, "\tx.x - DUOX, Originality check, proximity check, EAL6++"); @@ -805,12 +805,16 @@ static int CmdHF14ADesInfo(const char *Cmd) { } if (aidbuflen > 2) { + + uint8_t j = aidbuflen / 3; PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(SUCCESS, "--- " _CYAN_("AID list")); - PrintAndLogEx(SUCCESS, "AIDs: " NOLF); - for (int i = 0; i < aidbuflen; i += 3) - PrintAndLogEx(NORMAL, "%s %06x" NOLF, (i == 0) ? "" : ",", DesfireAIDByteToUint(&aidbuf[i])); - PrintAndLogEx(NORMAL, "\n"); + PrintAndLogEx(SUCCESS, "--- " _CYAN_("AID list") " ( " _YELLOW_("%u") " found )", j); + + j = 0; + for (int i = 0; i < aidbuflen; i += 3, j++) { + uint32_t aid = DesfireAIDByteToUint(&aidbuf[i]); + PrintAndLogEx(SUCCESS, _YELLOW_("%06X") ", %s", aid, getAidCommentStr(aid)); + } } DesfireFillPICCInfo(&dctx, &PICCInfo, true); @@ -821,7 +825,7 @@ static int CmdHF14ADesInfo(const char *Cmd) { PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "--- " _CYAN_("Free memory")); if (PICCInfo.freemem != 0xffffffff) { - PrintAndLogEx(SUCCESS, " Available free memory on card : " _GREEN_("%d bytes"), PICCInfo.freemem); + PrintAndLogEx(SUCCESS, " Available free memory on card... " _GREEN_("%d") " bytes", PICCInfo.freemem); } else { PrintAndLogEx(SUCCESS, " Card doesn't support 'free mem' cmd"); } @@ -1809,7 +1813,7 @@ static int CmdHF14aDesMAD(const char *Cmd) { AppListS AppList = {{0}}; DesfireFillAppList(&dctx, &PICCInfo, AppList, false, false, false); // no deep scan, no scan files - PrintAndLogEx(SUCCESS, "# Applications... " _GREEN_("%zu"), PICCInfo.appCount); + PrintAndLogEx(SUCCESS, "# Applications.... " _GREEN_("%zu"), PICCInfo.appCount); if (PICCInfo.freemem == 0xffffffff) { PrintAndLogEx(SUCCESS, "Free memory...... " _YELLOW_("n/a")); } else { @@ -5594,7 +5598,7 @@ static int CmdHF14ADesLsApp(const char *Cmd) { SetAPDULogging(APDULogging); CLIParserFree(ctx); - PrintAndLogEx(INPLACE, _YELLOW_("It may take up to 15 seconds. Processing....")); + PrintAndLogEx(INFO, "It may take up to " _YELLOW_("15") " seconds. Processing..."); res = DesfireSelectAndAuthenticateEx(&dctx, securechann, 0x000000, noauth, verbose); if (res != PM3_SUCCESS) { @@ -5606,7 +5610,6 @@ static int CmdHF14ADesLsApp(const char *Cmd) { AppListS AppList = {{0}}; DesfireFillAppList(&dctx, &PICCInfo, AppList, !nodeep, scanfiles, true); - printf("\33[2K\r"); // clear current line before printing PrintAndLogEx(NORMAL, ""); // print zone diff --git a/client/src/cmdhfmfp.c b/client/src/cmdhfmfp.c index 218b87504..70bc28e09 100644 --- a/client/src/cmdhfmfp.c +++ b/client/src/cmdhfmfp.c @@ -1990,9 +1990,9 @@ int CmdHFMFPNDEFRead(const char *Cmd) { memcpy(ndefkey, key, 16); } - uint8_t sector0[16 * 4] = {0}; - uint8_t sector10[16 * 4] = {0}; - uint8_t data[4096] = {0}; + uint8_t sector0[MIFARE_1K_MAXBLOCK] = {0}; + uint8_t sector10[MIFARE_1K_MAXBLOCK] = {0}; + uint8_t data[MIFARE_4K_MAX_BYTES] = {0}; int datalen = 0; if (verbose) @@ -2034,7 +2034,7 @@ int CmdHFMFPNDEFRead(const char *Cmd) { PrintAndLogEx(INFO, "reading data from tag"); for (int i = 0; i < madlen; i++) { if (ndefAID == mad[i]) { - uint8_t vsector[16 * 4] = {0}; + uint8_t vsector[MIFARE_1K_MAXBLOCK] = {0}; if (mfpReadSector(i + 1, keyB ? MF_KEY_B : MF_KEY_A, ndefkey, vsector, false)) { PrintAndLogEx(ERR, "error, reading sector %d", i + 1); return PM3_ESOFT; diff --git a/client/src/cmdhfmfu.c b/client/src/cmdhfmfu.c index e84e55169..ed8ddaf01 100644 --- a/client/src/cmdhfmfu.c +++ b/client/src/cmdhfmfu.c @@ -4071,8 +4071,8 @@ static int CmdHF14AMfUCSetUid(const char *Cmd) { PacketResponseNG resp; clearCommandBuffer(); SendCommandMIX(CMD_HF_MIFAREU_READBL, 2, 0, 0, NULL, 0); - if (!WaitForResponseTimeout(CMD_ACK, &resp, 1500)) { - PrintAndLogEx(WARNING, "command execution time out"); + if (WaitForResponseTimeout(CMD_ACK, &resp, 1500) == false) { + PrintAndLogEx(WARNING, "Command execute timeout"); return PM3_ETIMEOUT; } @@ -4084,8 +4084,8 @@ static int CmdHF14AMfUCSetUid(const char *Cmd) { // block 1 write and block2 write hf14a_config config; SendCommandNG(CMD_HF_ISO14443A_GET_CONFIG, NULL, 0); - if (!WaitForResponseTimeout(CMD_HF_ISO14443A_GET_CONFIG, &resp, 2000)) { - PrintAndLogEx(WARNING, "command execution time out"); + if (WaitForResponseTimeout(CMD_HF_ISO14443A_GET_CONFIG, &resp, 2000) == false) { + PrintAndLogEx(WARNING, "command execute timeout"); return PM3_ETIMEOUT; } memcpy(&config, resp.data.asBytes, sizeof(hf14a_config)); @@ -4103,8 +4103,8 @@ static int CmdHF14AMfUCSetUid(const char *Cmd) { data[3] = 0x88 ^ uid[0] ^ uid[1] ^ uid[2]; clearCommandBuffer(); SendCommandMIX(CMD_HF_MIFAREU_WRITEBL, 0, 0, 0, data, sizeof(data)); - if (!WaitForResponseTimeout(CMD_ACK, &resp, 1500)) { - PrintAndLogEx(WARNING, "command execution time out"); + if (WaitForResponseTimeout(CMD_ACK, &resp, 1500) == false) { + PrintAndLogEx(WARNING, "Command execute timeout"); return PM3_ETIMEOUT; } @@ -4115,8 +4115,8 @@ static int CmdHF14AMfUCSetUid(const char *Cmd) { data[3] = uid[6]; clearCommandBuffer(); SendCommandMIX(CMD_HF_MIFAREU_WRITEBL, 1, 0, 0, data, sizeof(data)); - if (!WaitForResponseTimeout(CMD_ACK, &resp, 1500)) { - PrintAndLogEx(WARNING, "command execution time out"); + if (WaitForResponseTimeout(CMD_ACK, &resp, 1500) == false) { + PrintAndLogEx(WARNING, "Command execute timeout"); return PM3_ETIMEOUT; } @@ -4127,8 +4127,8 @@ static int CmdHF14AMfUCSetUid(const char *Cmd) { data[3] = oldblock2[3]; clearCommandBuffer(); SendCommandMIX(CMD_HF_MIFAREU_WRITEBL, 2, 0, 0, data, sizeof(data)); - if (!WaitForResponseTimeout(CMD_ACK, &resp, 1500)) { - PrintAndLogEx(WARNING, "command execution time out"); + if (WaitForResponseTimeout(CMD_ACK, &resp, 1500) == false) { + PrintAndLogEx(WARNING, "Command execute timeout"); return PM3_ETIMEOUT; } From af6fdd09e209302f2d339c8d57e759a9a5deaf2f Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Tue, 18 Feb 2025 18:43:10 +0100 Subject: [PATCH 040/105] make sure variable are set before being used --- client/src/cmdlfem410x.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/client/src/cmdlfem410x.c b/client/src/cmdlfem410x.c index ecb252ffc..145392eb7 100644 --- a/client/src/cmdlfem410x.c +++ b/client/src/cmdlfem410x.c @@ -108,6 +108,8 @@ static void em410x_construct_emul_graph(uint8_t *uid, uint8_t clock, uint8_t gap // print 64 bit EM410x ID in multiple formats void printEM410x(uint32_t hi, uint64_t id, bool verbose, int type) { + if (!id && !hi) return; + if (verbose == false) { if (type & 0x1) { // Short ID PrintAndLogEx(SUCCESS, "EM 410x ID "_GREEN_("%010" PRIX64), id); @@ -252,6 +254,11 @@ static int ask_em410x_binary_decode(bool verbose, uint32_t *hi, uint64_t *lo, ui return PM3_ESOFT; } + if (!lo && !hi) { + PrintAndLogEx(DEBUG, "DEBUG: Error - Em410x decoded to all zeros"); + return PM3_ESOFT; + } + PrintAndLogEx(DEBUG, "DEBUG: Em410x idx: %zu, Len: %zu, Printing DemodBuffer:", *idx, *size); if (g_debugMode) { printDemodBuff(0, false, false, true); From 2f56bdcf1096848308266ba8a531f7f403b4b2d8 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Tue, 18 Feb 2025 18:44:24 +0100 Subject: [PATCH 041/105] text and style --- client/src/cmdlfem4x50.c | 10 +++++----- client/src/cmdlfguard.c | 8 ++++---- client/src/cmdlfhid.c | 4 ++-- client/src/cmdtrace.c | 19 ++++++++++++------- 4 files changed, 23 insertions(+), 18 deletions(-) diff --git a/client/src/cmdlfem4x50.c b/client/src/cmdlfem4x50.c index e1ee8181f..bda13661f 100644 --- a/client/src/cmdlfem4x50.c +++ b/client/src/cmdlfem4x50.c @@ -30,12 +30,10 @@ static int CmdHelp(const char *Cmd); -// Each record is 4 bytes long ... a single line in the dump output -// Reads each record from `data`, reverses the four bytes, and writes to `words` -static void em4x50_prepare_result(const uint8_t *data, int first_record_inclusive, int last_record_inclusive, em4x50_word_t *words) { +static void em4x50_prepare_result(const uint8_t *data, int fwr, int lwr, em4x50_word_t *words) { // restructure received result in "em4x50_word_t" structure - for (int i = first_record_inclusive; i <= last_record_inclusive; i++) { + for (int i = fwr; i <= lwr; i++) { for (int j = 0; j < 4; j++) { words[i].byte[j] = data[i * 4 + (3 - j)]; } @@ -641,8 +639,10 @@ int em4x50_read(em4x50_data_t *etd, em4x50_word_t *out) { return PM3_ESOFT; } + em4x50_read_data_response_t *o = (em4x50_read_data_response_t *)resp.data.asBytes; + em4x50_word_t words[EM4X50_NO_WORDS] = {0}; - em4x50_prepare_result(resp.data.asBytes, etd->addresses & 0xFF, (etd->addresses >> 8) & 0xFF, words); + em4x50_prepare_result((uint8_t *)o->words, etd->addresses & 0xFF, (etd->addresses >> 8) & 0xFF, words); if (out != NULL) { memcpy(out, &words, sizeof(em4x50_word_t) * EM4X50_NO_WORDS); diff --git a/client/src/cmdlfguard.c b/client/src/cmdlfguard.c index ea567499a..f8e3a5112 100644 --- a/client/src/cmdlfguard.c +++ b/client/src/cmdlfguard.c @@ -98,7 +98,7 @@ static int demod_guard_raw(uint8_t *raw, uint8_t rlen) { // but will leave the g_GraphBuffer intact. // if successful it will push askraw data back to g_DemodBuffer ready for emulation int demodGuard(bool verbose) { - (void) verbose; // unused so far + (void) verbose; //Differential Biphase //get binary from ask wave if (ASKbiphaseDemod(0, 64, 0, 0, false) != PM3_SUCCESS) { @@ -285,7 +285,7 @@ static int CmdGuardClone(const char *Cmd) { return PM3_EINVARG; } - fmtlen &= 0x7f; + fmtlen &= 0x7F; uint32_t facilitycode = (fc & 0x000000FF); uint32_t cardnumber = (cn & 0x00FFFFFF); @@ -317,7 +317,7 @@ static int CmdGuardClone(const char *Cmd) { free(bs); - PrintAndLogEx(INFO, "Preparing to clone Guardall to " _YELLOW_("%s") " with Facility Code: " _GREEN_("%u") " Card Number: " _GREEN_("%u") " xorKey: " _GREEN_("%u") + PrintAndLogEx(INFO, "Preparing to clone Guardall to " _YELLOW_("%s") " with fc: " _GREEN_("%u") " cn: " _GREEN_("%u") " xor: " _GREEN_("%u") , cardtype , facilitycode , cardnumber @@ -375,7 +375,7 @@ static int CmdGuardSim(const char *Cmd) { return PM3_ESOFT; } - PrintAndLogEx(SUCCESS, "Simulating Guardall Prox - xorKey: " _YELLOW_("%u") " Facility Code: " _YELLOW_("%u") " CardNumber: " _YELLOW_("%u") + PrintAndLogEx(SUCCESS, "Simulating Guardall Prox - xorKey: " _YELLOW_("%u") " fc: " _YELLOW_("%u") " cn: " _YELLOW_("%u") , xorval , facilitycode , cardnumber diff --git a/client/src/cmdlfhid.c b/client/src/cmdlfhid.c index fb1eeb56e..bb1946414 100644 --- a/client/src/cmdlfhid.c +++ b/client/src/cmdlfhid.c @@ -213,8 +213,8 @@ static int CmdHIDReader(const char *Cmd) { } do { - lf_read(false, 16000); // get data of 16000 samples from proxmark device - demodHID(!cm); // demod data and print results if found + lf_read(false, 16000); + demodHID(!cm); } while (cm && !kbd_enter_pressed()); return PM3_SUCCESS; diff --git a/client/src/cmdtrace.c b/client/src/cmdtrace.c index c91f6a31b..113c89f7f 100644 --- a/client/src/cmdtrace.c +++ b/client/src/cmdtrace.c @@ -164,6 +164,7 @@ static uint16_t extractChallenges(uint16_t tracepos, uint16_t traceLen, uint8_t } // extract MFC + /* switch (frame[0]) { case MIFARE_AUTH_KEYA: { if (data_len > 3) { @@ -176,9 +177,11 @@ static uint16_t extractChallenges(uint16_t tracepos, uint16_t traceLen, uint8_t break; } } + */ - // extract MFU-C + // extract MFU-C KEY when written. switch (frame[0]) { + case MIFARE_ULC_AUTH_1: { if (data_len != 4) { break; @@ -195,7 +198,7 @@ static uint16_t extractChallenges(uint16_t tracepos, uint16_t traceLen, uint8_t break; } - PrintAndLogEx(INFO, "MFU-C AUTH"); + PrintAndLogEx(INFO, "Found a MFU-C authententication attempt"); PrintAndLogEx(INFO, "3DES %s " NOLF, sprint_hex_inrow(next_hdr->frame + 1, 8)); next_hdr = (tracelog_hdr_t *)(trace + tracepos); @@ -203,6 +206,8 @@ static uint16_t extractChallenges(uint16_t tracepos, uint16_t traceLen, uint8_t if (next_hdr->frame[0] == MIFARE_ULC_AUTH_2 && next_hdr->data_len == 19) { PrintAndLogEx(NORMAL, "%s", sprint_hex_inrow(next_hdr->frame + 1, 16)); + } else { + PrintAndLogEx(NORMAL, "( " _RED_("partial") " )"); } return tracepos; @@ -323,7 +328,7 @@ static uint16_t extractChallenges(uint16_t tracepos, uint16_t traceLen, uint8_t case MFDES_AUTHENTICATE: { // Assume wrapped or unwrapped - PrintAndLogEx(INFO, "AUTH NATIVE (keyNo %d)", frame[pos + long_jmp]); + PrintAndLogEx(INFO, "Found a MFDES Auth NATIVE (keyNo %d)", frame[pos + long_jmp]); if (next_record_is_response(tracepos, trace) == false) { break; } @@ -348,7 +353,7 @@ static uint16_t extractChallenges(uint16_t tracepos, uint16_t traceLen, uint8_t } case MFDES_AUTHENTICATE_ISO: { // Assume wrapped or unwrapped - PrintAndLogEx(INFO, "AUTH ISO (keyNo %d)", frame[pos + long_jmp]); + PrintAndLogEx(INFO, "Found a MFDES Auth ISO (keyNo %d)", frame[pos + long_jmp]); if (next_record_is_response(tracepos, trace) == false) { break; } @@ -379,7 +384,7 @@ static uint16_t extractChallenges(uint16_t tracepos, uint16_t traceLen, uint8_t } case MFDES_AUTHENTICATE_AES: { // Assume wrapped or unwrapped - PrintAndLogEx(INFO, "AUTH AES (keyNo %d)", frame[pos + long_jmp]); + PrintAndLogEx(INFO, "Found a MFDES Auth AES (keyNo %d)", frame[pos + long_jmp]); if (next_record_is_response(tracepos, trace)) { break; } @@ -403,7 +408,7 @@ static uint16_t extractChallenges(uint16_t tracepos, uint16_t traceLen, uint8_t return tracepos; } case MFDES_AUTHENTICATE_EV2F: { - PrintAndLogEx(INFO, "AUTH EV2 First"); + PrintAndLogEx(INFO, "Found a MFDES Auth EV2 First"); uint16_t tmp = extractChall_ev2(tracepos, trace, pos, long_jmp); if (tmp == 0) break; @@ -412,7 +417,7 @@ static uint16_t extractChallenges(uint16_t tracepos, uint16_t traceLen, uint8_t } case MFDES_AUTHENTICATE_EV2NF: { - PrintAndLogEx(INFO, "AUTH EV2 Non First"); + PrintAndLogEx(INFO, "Found a MFDES Auth EV2 Non First"); uint16_t tmp = extractChall_ev2(tracepos, trace, pos, long_jmp); if (tmp == 0) break; From f5650a53afec108f8c3f3dd3a30128ba36faa46a Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Tue, 18 Feb 2025 18:48:33 +0100 Subject: [PATCH 042/105] text and style --- client/src/cmdhficlass.c | 16 +----- client/src/emv/cmdemv.c | 10 ++-- client/src/fileutils.c | 2 +- client/src/mifare/desfirecore.c | 91 ++++++++++++++++++------------- client/src/mifare/desfirecrypto.c | 8 +-- client/src/mifare/mad.c | 14 +++-- client/src/mifare/mifarehost.c | 11 +++- 7 files changed, 86 insertions(+), 66 deletions(-) diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index b08b7ba8c..d99cbe986 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -1344,10 +1344,7 @@ static int CmdHFiClassEView(const char *Cmd) { PrintAndLogEx(NORMAL, ""); printIclassDumpContents(dump, 1, blocks, bytes, dense_output); - - if (verbose) { - print_iclass_sio(dump, bytes); - } + print_iclass_sio(dump, bytes, verbose); free(dump); return PM3_SUCCESS; @@ -1708,11 +1705,7 @@ static int CmdHFiClassDecrypt(const char *Cmd) { } printIclassDumpContents(decrypted, 1, (decryptedlen / 8), decryptedlen, dense_output); - - if (verbose) { - print_iclass_sio(decrypted, decryptedlen); - } - + print_iclass_sio(decrypted, decryptedlen, verbose); PrintAndLogEx(NORMAL, ""); // decode block 6 @@ -3518,10 +3511,7 @@ static int CmdHFiClassView(const char *Cmd) { print_picopass_info((picopass_hdr_t *) dump); printIclassDumpContents(dump, startblock, endblock, bytes_read, dense_output); iclass_decode_credentials(dump); - - if (verbose) { - print_iclass_sio(dump, bytes_read); - } + print_iclass_sio(dump, bytes_read, verbose); free(dump); return PM3_SUCCESS; diff --git a/client/src/emv/cmdemv.c b/client/src/emv/cmdemv.c index 4d7daec9e..4c38dae53 100644 --- a/client/src/emv/cmdemv.c +++ b/client/src/emv/cmdemv.c @@ -2122,9 +2122,9 @@ static int CmdEMVScan(const char *Cmd) { uint8_t psenum = (channel == CC_CONTACT) ? 1 : 2; - char filename[FILE_PATH_SIZE] = {0}; - int fnlen = 0; - CLIParamStrToBuf(arg_get_str(ctx, 12), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); + uint8_t filename[FILE_PATH_SIZE] = {0}; + int filenamelen = sizeof(filename) - 1; // CLIGetStrWithReturn does not guarantee string to be null-terminated + CLIGetStrWithReturn(ctx, 12, filename, &filenamelen); CLIParserFree(ctx); @@ -2507,7 +2507,7 @@ static int CmdEMVRoca(const char *Cmd) { void *argtable[] = { arg_param_begin, - arg_lit0("t", "selftest", "Self test"), + arg_lit0(NULL, "test", "Perform self tests"), arg_lit0("a", "apdu", "Show APDU requests and responses"), arg_lit0("w", "wired", "Send data via contact (iso7816) interface. (def: Contactless interface)"), arg_param_end @@ -2981,7 +2981,7 @@ static command_t CommandTable[] = { {"-----------", CmdHelp, AlwaysAvailable, "----------------------- " _CYAN_("General") " -----------------------"}, {"help", CmdHelp, AlwaysAvailable, "This help"}, {"list", CmdEMVList, AlwaysAvailable, "List ISO7816 history"}, - {"test", CmdEMVTest, AlwaysAvailable, "Crypto logic selftest"}, + {"test", CmdEMVTest, AlwaysAvailable, "Perform crypto logic self tests"}, {"-----------", CmdHelp, IfPm3Iso14443a, "---------------------- " _CYAN_("Operations") " ---------------------"}, {"challenge", CmdEMVGenerateChallenge, IfPm3Iso14443, "Generate challenge"}, {"exec", CmdEMVExec, IfPm3Iso14443, "Executes EMV contactless transaction"}, diff --git a/client/src/fileutils.c b/client/src/fileutils.c index 86a3d06b5..73b992fd2 100644 --- a/client/src/fileutils.c +++ b/client/src/fileutils.c @@ -809,7 +809,7 @@ int saveFileJSONrootEx(const char *preferredName, const void *root, size_t flags if (res == 0) { if (verbose) { - PrintAndLogEx(SUCCESS, "Saved to json file `" _YELLOW_("%s") "`", filename); + PrintAndLogEx(SUCCESS, "Saved to json file " _YELLOW_("%s"), filename); } free(filename); return PM3_SUCCESS; diff --git a/client/src/mifare/desfirecore.c b/client/src/mifare/desfirecore.c index ac44508f2..07297d05a 100644 --- a/client/src/mifare/desfirecore.c +++ b/client/src/mifare/desfirecore.c @@ -1008,7 +1008,7 @@ void DesfirePrintAIDFunctions(uint32_t appid) { if ((aid[2] >> 4) == 0xF) { uint16_t short_aid = ((aid[2] & 0xF) << 12) | (aid[1] << 4) | (aid[0] >> 4); PrintAndLogEx(SUCCESS, " AID mapped to MIFARE Classic AID (MAD): " _YELLOW_("%02X"), short_aid); - PrintAndLogEx(SUCCESS, " MAD AID Cluster 0x%02X : " _YELLOW_("%s"), short_aid >> 8, nxp_cluster_to_text(short_aid >> 8)); + PrintAndLogEx(SUCCESS, " MAD AID Cluster 0x%02X..... " _YELLOW_("%s"), short_aid >> 8, nxp_cluster_to_text(short_aid >> 8)); MADDFDecodeAndPrint(short_aid, false); } else { AIDDFDecodeAndPrint(aid); @@ -1016,53 +1016,64 @@ void DesfirePrintAIDFunctions(uint32_t appid) { } int DesfireSelectAndAuthenticateEx(DesfireContext_t *dctx, DesfireSecureChannel secureChannel, uint32_t aid, bool noauth, bool verbose) { - if (verbose) + if (verbose) { DesfirePrintContext(dctx); + } // needs card uid for diversification - if (dctx->kdfAlgo == MFDES_KDF_ALGO_GALLAGHER) + if (dctx->kdfAlgo == MFDES_KDF_ALGO_GALLAGHER) { DesfireGetCardUID(dctx); + } bool isosw = false; if (dctx->cmdSet == DCCISO) { dctx->cmdSet = DCCNativeISO; isosw = true; - if (verbose) + if (verbose) { PrintAndLogEx(INFO, "Switch to " _CYAN_("native") " for select"); } + } int res; if (aid == 0x000000) { res = DesfireAnticollision(verbose); if (res != PM3_SUCCESS) { - PrintAndLogEx(ERR, "Desfire anticollision " _RED_("error") "."); + PrintAndLogEx(ERR, "Desfire anticollision " _RED_("fail")); return 200; } - if (verbose) + + if (verbose) { PrintAndLogEx(INFO, "Anticollision " _GREEN_("ok")); + } + } else { res = DesfireSelectAIDHex(dctx, aid, false, 0); if (res != PM3_SUCCESS) { - PrintAndLogEx(ERR, "Desfire select " _RED_("error") "."); + PrintAndLogEx(ERR, "Desfire select " _RED_("fail")); return 200; } - if (verbose) + + if (verbose) { PrintAndLogEx(INFO, "App %06x " _GREEN_("selected"), aid); } + } - if (isosw) + if (isosw) { dctx->cmdSet = DCCISO; + } if (noauth == false) { + res = DesfireAuthenticate(dctx, secureChannel, verbose); if (res != PM3_SUCCESS) { - PrintAndLogEx(ERR, "Desfire authenticate " _RED_("error") ". Result: [%d] %s", res, DesfireAuthErrorToStr(res)); + PrintAndLogEx(ERR, "Desfire authenticate " _RED_("fail") ". Result: [%d] %s", res, DesfireAuthErrorToStr(res)); return res; } if (DesfireIsAuthenticated(dctx)) { - if (verbose) + if (verbose) { PrintAndLogEx(INFO, "Desfire " _GREEN_("authenticated")); + } } else { return 201; } @@ -1087,7 +1098,7 @@ int DesfireSelectAndAuthenticateW(DesfireContext_t *dctx, DesfireSecureChannel s res = DesfireSelectAIDHex(dctx, id, false, 0); if (res != PM3_SUCCESS) { - PrintAndLogEx(ERR, "Desfire select " _RED_("error") "."); + PrintAndLogEx(ERR, "Desfire select " _RED_("error")); return 200; } if (verbose) @@ -1097,7 +1108,7 @@ int DesfireSelectAndAuthenticateW(DesfireContext_t *dctx, DesfireSecureChannel s } else { res = DesfireSelectEx(dctx, true, way, id, NULL); if (res != PM3_SUCCESS) { - PrintAndLogEx(ERR, "Desfire %s select " _RED_("error") ".", DesfireSelectWayToStr(way)); + PrintAndLogEx(ERR, "Desfire %s select " _RED_("error"), DesfireSelectWayToStr(way)); return 202; } if (verbose) @@ -1107,13 +1118,14 @@ int DesfireSelectAndAuthenticateW(DesfireContext_t *dctx, DesfireSecureChannel s if (selectfile) { res = DesfireSelectEx(dctx, false, ISWIsoID, isofileid, NULL); if (res != PM3_SUCCESS) { - PrintAndLogEx(ERR, "Desfire iso file select " _RED_("error") "."); + PrintAndLogEx(ERR, "Desfire iso file select " _RED_("error")); return 203; } - if (verbose) + if (verbose) { PrintAndLogEx(INFO, "Application %s file iso id %04x is " _GREEN_("selected"), DesfireWayIDStr(way, id), isofileid); } + } if (!noauth) { res = DesfireAuthenticate(dctx, secureChannel, verbose); @@ -1123,8 +1135,9 @@ int DesfireSelectAndAuthenticateW(DesfireContext_t *dctx, DesfireSecureChannel s } if (DesfireIsAuthenticated(dctx)) { - if (verbose) + if (verbose) { PrintAndLogEx(INFO, "Desfire " _GREEN_("authenticated")); + } } else { return 201; } @@ -1864,17 +1877,21 @@ int DesfireFillAppList(DesfireContext_t *dctx, PICCInfo_t *PICCInfo, AppListS ap void DesfirePrintPICCInfo(DesfireContext_t *dctx, PICCInfo_t *PICCInfo) { PrintAndLogEx(SUCCESS, "------------------------------------ " _CYAN_("PICC level") " -------------------------------------"); - if (PICCInfo->freemem == 0xffffffff) - PrintAndLogEx(SUCCESS, "Applications count: " _GREEN_("%zu") " free memory " _YELLOW_("n/a"), PICCInfo->appCount); - else - PrintAndLogEx(SUCCESS, "Applications count: " _GREEN_("%zu") " free memory " _GREEN_("%d") " bytes", PICCInfo->appCount, PICCInfo->freemem); + if (PICCInfo->freemem == 0xffffffff) { + PrintAndLogEx(SUCCESS, "# applications....... " _YELLOW_("%zu"), PICCInfo->appCount); + } else { + PrintAndLogEx(SUCCESS, "# applications....... " _YELLOW_("%zu"), PICCInfo->appCount); + } + PrintAndLogEx(SUCCESS, ""); + if (PICCInfo->authCmdCheck.checked) { - PrintAndLogEx(SUCCESS, "PICC level auth commands: "); + PrintAndLogEx(SUCCESS, "PICC level auth commands"); DesfireCheckAuthCommandsPrint(&PICCInfo->authCmdCheck); } + if (PICCInfo->numberOfKeys > 0) { PrintKeySettings(PICCInfo->keySettings, PICCInfo->numKeysRaw, false, true); - PrintAndLogEx(SUCCESS, "PICC key 0 version: %d (0x%02x)", PICCInfo->keyVersion0, PICCInfo->keyVersion0); + PrintAndLogEx(SUCCESS, "PICC key "_YELLOW_("0") " version: %d (0x%02x)", PICCInfo->keyVersion0, PICCInfo->keyVersion0); } } @@ -1886,14 +1903,14 @@ void DesfirePrintAppList(DesfireContext_t *dctx, PICCInfo_t *PICCInfo, AppListS PrintAndLogEx(SUCCESS, "--------------------------------- " _CYAN_("Applications list") " ---------------------------------"); for (int i = 0; i < PICCInfo->appCount; i++) { - PrintAndLogEx(SUCCESS, _CYAN_("Application number: 0x%02X"), appList[i].appNum); - PrintAndLogEx(SUCCESS, " ISO id.... " _GREEN_("0x%04X"), appList[i].appISONum); - PrintAndLogEx(SUCCESS, " DF name... " _GREEN_("%s") " ( %s)", appList[i].appDFName, sprint_hex((uint8_t *)appList[i].appDFName, sizeof(appList[i].appDFName))); + PrintAndLogEx(SUCCESS, "Application ID....... " _CYAN_("0x%02X"), appList[i].appNum); + PrintAndLogEx(SUCCESS, " ISO id............ " _GREEN_("0x%04X"), appList[i].appISONum); + PrintAndLogEx(SUCCESS, " DF name........... " _GREEN_("%s") " ( %s )", appList[i].appDFName, sprint_hex_inrow((uint8_t *)appList[i].appDFName, sizeof(appList[i].appDFName))); DesfirePrintAIDFunctions(appList[i].appNum); if (PICCInfo->authCmdCheck.checked) { - PrintAndLogEx(SUCCESS, "Auth commands: "); + PrintAndLogEx(SUCCESS, "Auth commands"); DesfireCheckAuthCommandsPrint(&appList[i].authCmdCheck); PrintAndLogEx(SUCCESS, ""); } @@ -1902,7 +1919,7 @@ void DesfirePrintAppList(DesfireContext_t *dctx, PICCInfo_t *PICCInfo, AppListS PrintKeySettings(appList[i].keySettings, appList[i].numKeysRaw, true, true); if (appList[i].numberOfKeys > 0) { - PrintAndLogEx(SUCCESS, "Key versions [0..%d]: " NOLF, appList[i].numberOfKeys - 1); + PrintAndLogEx(SUCCESS, "Key versions [0..%d] " NOLF, appList[i].numberOfKeys - 1); for (uint8_t keyn = 0; keyn < appList[i].numberOfKeys; keyn++) { PrintAndLogEx(NORMAL, "%s %02x" NOLF, (keyn == 0) ? "" : ",", appList[i].keyVersions[keyn]); } @@ -2254,7 +2271,7 @@ int DesfireUpdateRecord(DesfireContext_t *dctx, uint8_t fnum, uint32_t recnum, u } static void PrintKeySettingsPICC(uint8_t keysettings, uint8_t numkeys, bool print2ndbyte) { - PrintAndLogEx(SUCCESS, "PICC level rights:"); + PrintAndLogEx(SUCCESS, "PICC level rights"); PrintAndLogEx(SUCCESS, "[%c...] CMK Configuration changeable : %s", (keysettings & (1 << 3)) ? '1' : '0', (keysettings & (1 << 3)) ? _GREEN_("YES") : _RED_("NO (frozen)")); PrintAndLogEx(SUCCESS, "[.%c..] CMK required for create/delete : %s", (keysettings & (1 << 2)) ? '1' : '0', (keysettings & (1 << 2)) ? _GREEN_("NO") : "YES"); PrintAndLogEx(SUCCESS, "[..%c.] Directory list access with CMK : %s", (keysettings & (1 << 1)) ? '1' : '0', (keysettings & (1 << 1)) ? _GREEN_("NO") : "YES"); @@ -2263,27 +2280,27 @@ static void PrintKeySettingsPICC(uint8_t keysettings, uint8_t numkeys, bool prin if (print2ndbyte) { DesfirePrintCardKeyType(numkeys >> 6); - PrintAndLogEx(SUCCESS, "key count: %d", numkeys & 0x0f); + PrintAndLogEx(SUCCESS, "Key cnt.... " _YELLOW_("%d"), numkeys & 0x0F); } } static void PrintKeySettingsApp(uint8_t keysettings, uint8_t numkeys, bool print2ndbyte) { // Access rights. - PrintAndLogEx(SUCCESS, "Application level rights:"); + PrintAndLogEx(SUCCESS, "Application level rights"); uint8_t rights = ((keysettings >> 4) & 0x0F); switch (rights) { case 0x0: - PrintAndLogEx(SUCCESS, "-- AMK authentication is necessary to change any key (default)"); + PrintAndLogEx(SUCCESS, " - AMK authentication is necessary to change any key (default)"); break; case 0xE: - PrintAndLogEx(SUCCESS, "-- Authentication with the key to be changed (same KeyNo) is necessary to change a key"); + PrintAndLogEx(SUCCESS, " - Authentication with the key to be changed (same KeyNo) is necessary to change a key"); break; case 0xF: - PrintAndLogEx(SUCCESS, "-- All keys (except AMK,see Bit0) within this application are frozen"); + PrintAndLogEx(SUCCESS, " - All keys (except AMK,see Bit0) within this application are frozen"); break; default: PrintAndLogEx(SUCCESS, - "-- Authentication with the specified key " _YELLOW_("(0x%02x)") " is necessary to change any key.\n" + " - Authentication with the specified key " _YELLOW_("(0x%02x)") " is necessary to change any key.\n" "A change key and a PICC master key (CMK) can only be changed after authentication with the master key.\n" "For keys other then the master or change key, an authentication with the same key is needed.", rights & 0x0f @@ -2299,10 +2316,10 @@ static void PrintKeySettingsApp(uint8_t keysettings, uint8_t numkeys, bool print if (print2ndbyte) { DesfirePrintCardKeyType(numkeys >> 6); - PrintAndLogEx(SUCCESS, "key count: %d", numkeys & 0x0f); - if (numkeys & 0x20) + PrintAndLogEx(SUCCESS, "Key cnt.... " _YELLOW_("%d"), numkeys & 0x0F); + if (numkeys & 0x20) { PrintAndLogEx(SUCCESS, "iso file id: enabled"); - PrintAndLogEx(SUCCESS, ""); + } } } diff --git a/client/src/mifare/desfirecrypto.c b/client/src/mifare/desfirecrypto.c index 6263c2321..98c3de0e9 100644 --- a/client/src/mifare/desfirecrypto.c +++ b/client/src/mifare/desfirecrypto.c @@ -504,16 +504,16 @@ uint8_t DesfireKeyAlgoToType(DesfireCryptoAlgorithm keyType) { void DesfirePrintCardKeyType(uint8_t keyType) { switch (keyType) { case 00: - PrintAndLogEx(SUCCESS, "Key: 2TDEA"); + PrintAndLogEx(SUCCESS, "Key type... " _YELLOW_("2TDEA")); break; case 01: - PrintAndLogEx(SUCCESS, "Key: 3TDEA"); + PrintAndLogEx(SUCCESS, "Key type... " _YELLOW_("3TDEA")); break; case 02: - PrintAndLogEx(SUCCESS, "Key: AES"); + PrintAndLogEx(SUCCESS, "Key type... " _YELLOW_("AES")); break; default: - PrintAndLogEx(SUCCESS, "Key: unknown: 0x%02x", keyType); + PrintAndLogEx(SUCCESS, "Key type... " _YELLOW_("unknown") " - 0x%02x", keyType); break; } } diff --git a/client/src/mifare/mad.c b/client/src/mifare/mad.c index 54f67a6a4..e254fd187 100644 --- a/client/src/mifare/mad.c +++ b/client/src/mifare/mad.c @@ -69,8 +69,13 @@ static int open_mad_file(json_t **root, bool verbose) { goto out; } - if (verbose) - PrintAndLogEx(SUCCESS, "Loaded file " _YELLOW_("`%s`") " (%s) %zu records.", path, _GREEN_("ok"), json_array_size(*root)); + if (verbose) { + PrintAndLogEx(SUCCESS, "Loaded file `" _YELLOW_("%s") "` " _GREEN_("%zu") " records ( " _GREEN_("ok") " )" + , path + , json_array_size(*root) + ); + } + out: free(path); return retval; @@ -415,7 +420,7 @@ int MADDFDecodeAndPrint(uint32_t short_aid, bool verbose) { open_mad_file(&mad_known_aids, false); char fmt[128]; - snprintf(fmt, sizeof(fmt), " MAD AID Function 0x%04X :" _YELLOW_("%s"), short_aid, "%s"); + snprintf(fmt, sizeof(fmt), " MAD AID Function 0x%04X... " _YELLOW_("%s"), short_aid, "%s"); print_aid_description(mad_known_aids, short_aid, fmt, verbose); close_mad_file(mad_known_aids); return PM3_SUCCESS; @@ -429,8 +434,9 @@ bool HasMADKey(uint8_t *d) { } int DetectHID(uint8_t *d, uint16_t manufacture) { - if (d == NULL) + if (d == NULL) { return -1; + } // find HID for (int i = 1; i < 16; i++) { diff --git a/client/src/mifare/mifarehost.c b/client/src/mifare/mifarehost.c index 18f22af7c..0a9d7aba9 100644 --- a/client/src/mifare/mifarehost.c +++ b/client/src/mifare/mifarehost.c @@ -291,9 +291,16 @@ int mf_check_keys_fast_ex(uint8_t sectorsCnt, uint8_t firstChunk, uint8_t lastCh if ((singleSectorParams >> 15) & 1) { if (curr_keys) { - uint64_t foo = bytes_to_num(resp.data.asBytes, 6); + // uint64_t foo = bytes_to_num(resp.data.asBytes, 6); PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(SUCCESS, _GREEN_("Key %s for block %2i found: %012" PRIx64), (singleSectorParams >> 8) & 1 ? "B" : "A", singleSectorParams & 0xFF, foo); +// PrintAndLogEx(SUCCESS, "found Key %s for block %2i found: " _GREEN_("%012" PRIx64), (singleSectorParams >> 8) & 1 ? "B" : "A", singleSectorParams & 0xFF, foo); + + PrintAndLogEx(SUCCESS, "\nTarget block %4u key type %c -- found valid key [ " _GREEN_("%s") " ]", + singleSectorParams & 0xFF, + ((singleSectorParams >> 8) & 1) ? 'B' : 'A', + sprint_hex_inrow(resp.data.asBytes, MIFARE_KEY_SIZE) + ); + return PM3_SUCCESS; } } From f2fe3768b817991f63df0ff545d5c8246713ab85 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Tue, 18 Feb 2025 18:48:49 +0100 Subject: [PATCH 043/105] should be unsigned varibles --- client/src/scripting.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/client/src/scripting.c b/client/src/scripting.c index 4f7705eb3..9b7647377 100644 --- a/client/src/scripting.c +++ b/client/src/scripting.c @@ -345,11 +345,11 @@ static int l_WaitForResponseTimeout(lua_State *L) { // extract first param. cmd byte to look for if (n >= 1) - cmd = (uint32_t)luaL_checkinteger(L, 1); + cmd = luaL_checkunsigned(L, 1); // extract second param. timeout value if (n >= 2) - ms_timeout = luaL_checkinteger(L, 2); + ms_timeout = luaL_checkunsigned(L, 2); PacketResponseNG resp; if (WaitForResponseTimeout(cmd, &resp, ms_timeout) == false) { @@ -647,7 +647,7 @@ static int l_crc8legic(lua_State *L) { size_t size; const char *p_hexstr = luaL_checklstring(L, 1, &size); uint16_t retval = CRC8Legic((uint8_t *)p_hexstr, size); - lua_pushinteger(L, retval); + lua_pushunsigned(L, retval); return 1; } @@ -663,7 +663,7 @@ static int l_crc16legic(lua_State *L) { init_table(CRC_LEGIC_16); uint16_t retval = crc16_legic((uint8_t *)p_hexstr, hexsize, uidcrc); - lua_pushinteger(L, retval); + lua_pushunsigned(L, retval); return 1; } @@ -673,7 +673,7 @@ static int l_crc16(lua_State *L) { const char *p_str = luaL_checklstring(L, 1, &size); uint16_t checksum = Crc16ex(CRC_CCITT, (uint8_t *) p_str, size); - lua_pushinteger(L, checksum); + lua_pushunsigned(L, checksum); return 1; } @@ -739,7 +739,7 @@ static int l_reveng_models(lua_State *L) { #define NMODELS 106 int count = 0; - uint8_t in_width = (uint8_t)luaL_checkinteger(L, 1); + uint8_t in_width = luaL_checkunsigned(L, 1); if (in_width > 89) return returnToLuaWithError(L, "Width cannot exceed 89, got %d", in_width); @@ -920,8 +920,8 @@ static int l_keygen_algoB(lua_State *L) { uint32_t pwd = ul_ev1_pwdgenB(uid); uint16_t pack = ul_ev1_packgenB(uid); - lua_pushinteger(L, pwd); - lua_pushinteger(L, pack); + lua_pushunsigned(L, pwd); + lua_pushunsigned(L, pack); return 2; } @@ -953,8 +953,8 @@ static int l_keygen_algoD(lua_State *L) { uint32_t pwd = ul_ev1_pwdgenD(uid); uint16_t pack = ul_ev1_packgenD(uid); - lua_pushinteger(L, pwd); - lua_pushinteger(L, pack); + lua_pushunsigned(L, pwd); + lua_pushunsigned(L, pack); return 2; } @@ -1041,7 +1041,7 @@ static int l_T55xx_readblock(lua_State *L) { return returnToLuaWithError(L, "Failed to get actual data"); } - lua_pushinteger(L, blockData); + lua_pushunsigned(L, blockData); return 1; } From 4c6e74c3cea8873870d9f725df5a147e028e7ff0 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Tue, 18 Feb 2025 19:47:51 +0100 Subject: [PATCH 044/105] revert --- armsrc/hitagS.c | 10 +++++----- include/protocols.h | 44 ++++++++++++++++++++++---------------------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/armsrc/hitagS.c b/armsrc/hitagS.c index 5e7420566..4b8b62545 100644 --- a/armsrc/hitagS.c +++ b/armsrc/hitagS.c @@ -1029,11 +1029,11 @@ static int hts_send_receive(const uint8_t *tx, size_t txlen, uint8_t *rx, size_t response_bit[i] = (rx[i / 8] >> (7 - (i % 8))) & 1; } - DBG Dbprintf("htS: rxlen...... %zu", *rxlen); - DBG Dbprintf("htS: sizeofrx... %zu", sizeofrx); - DBG DbpString("htS: response_bit:"); - DBG Dbhexdump(*rxlen, response_bit, false); - DBG Dbprintf("htS: skipping %d bit SOF", sof_bits); + Dbprintf("htS: rxlen...... %zu", *rxlen); + Dbprintf("htS: sizeofrx... %zu", sizeofrx); + DbpString("htS: response_bit:"); + Dbhexdump(*rxlen, response_bit, false); + Dbprintf("htS: skipping %d bit SOF", sof_bits); if ((rx[0] >> (8 - sof_bits)) != ((1 << sof_bits) - 1)) { DBG DbpString("htS: Warning, not all bits of SOF are 1"); diff --git a/include/protocols.h b/include/protocols.h index 0e6dbb3f3..eb22a89a7 100644 --- a/include/protocols.h +++ b/include/protocols.h @@ -473,30 +473,30 @@ ISO 7816-4 Basic interindustry commands. For command APDU's. #define PICOPASS_SECURE_PAGEMODE_KEYS_MODIFIABLE 0x03 // ISO 7816-4 Basic interindustry commands. For command APDU's. -#define ISO7816_READ_BINARY 0xB0 -#define ISO7816_WRITE_BINARY 0xD0 -#define ISO7816_UPDATE_BINARY 0xD6 -#define ISO7816_ERASE_BINARY 0x0E -#define ISO7816_READ_RECORDS 0xB2 -#define ISO7816_WRITE_RECORDS 0xD2 -#define ISO7816_APPEND_RECORD 0xE2 -#define ISO7816_UPDATE_RECORD 0xDC -#define ISO7816_GET_DATA 0xCA -#define ISO7816_PUT_DATA 0xDA -#define ISO7816_SELECT_FILE 0xA4 -#define ISO7816_VERIFY 0x20 -#define ISO7816_INTERNAL_AUTHENTICATION 0x88 -#define ISO7816_EXTERNAL_AUTHENTICATION 0x82 -#define ISO7816_GET_CHALLENGE 0x84 -#define ISO7816_MANAGE_CHANNEL 0x70 -#define ISO7816_APPLICATION_BLOCK 0x1E -#define ISO7816_APPLICATION_UNBLOCK 0x18 -#define ISO7816_CARD_BLOCK 0x16 +#define ISO7816_READ_BINARY 0xB0 +#define ISO7816_WRITE_BINARY 0xD0 +#define ISO7816_UPDATE_BINARY 0xD6 +#define ISO7816_ERASE_BINARY 0x0E +#define ISO7816_READ_RECORDS 0xB2 +#define ISO7816_WRITE_RECORDS 0xD2 +#define ISO7816_APPEND_RECORD 0xE2 +#define ISO7816_UPDATE_RECORD 0xDC +#define ISO7816_GET_DATA 0xCA +#define ISO7816_PUT_DATA 0xDA +#define ISO7816_SELECT_FILE 0xA4 +#define ISO7816_VERIFY 0x20 +#define ISO7816_INTERNAL_AUTHENTICATION 0x88 +#define ISO7816_EXTERNAL_AUTHENTICATION 0x82 +#define ISO7816_GET_CHALLENGE 0x84 +#define ISO7816_MANAGE_CHANNEL 0x70 +#define ISO7816_APPLICATION_BLOCK 0x1E +#define ISO7816_APPLICATION_UNBLOCK 0x18 +#define ISO7816_CARD_BLOCK 0x16 #define ISO7816_GENERATE_APPLICATION_CRYPTOGRAM 0xAE -#define ISO7816_GET_PROCESSING_OPTIONS 0xA8 -#define ISO7816_PIN_CHANGE 0x24 +#define ISO7816_GET_PROCESSING_OPTIONS 0xA8 +#define ISO7816_PIN_CHANGE 0x24 -#define ISO7816_GET_RESPONSE 0xC0 +#define ISO7816_GET_RESPONSE 0xC0 // ISO7816-4 For response APDU's #define ISO7816_OK 0x9000 From 1a91d42b7db43a1b80021b72e239208320221352 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Tue, 18 Feb 2025 19:48:34 +0100 Subject: [PATCH 045/105] style --- client/src/cmdcrc.c | 45 +++++++++++++++++++++++++++++++++++--------- client/src/cmdhfmf.c | 2 +- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/client/src/cmdcrc.c b/client/src/cmdcrc.c index 7cbee8d2f..8d8969584 100644 --- a/client/src/cmdcrc.c +++ b/client/src/cmdcrc.c @@ -82,20 +82,25 @@ int GetModels(char *Models[], int *count, uint8_t *width) { SETBMP(); if (width[0] == 0) { //reveng -D + *count = mcount(); if (!*count) { PrintAndLogEx(WARNING, "no preset models available"); return 0; } + for (int mode = 0; mode < *count; ++mode) { + mbynum(&model, mode); mcanon(&model); size_t size = (model.name && *model.name) ? strlen(model.name) : 7; + char *tmp = calloc(size + 1, sizeof(char)); if (tmp == NULL) { PrintAndLogEx(WARNING, "out of memory?"); return 0; } + if (model.name != NULL) { memcpy(tmp, model.name, size); Models[mode] = tmp; @@ -113,18 +118,21 @@ int GetModels(char *Models[], int *count, uint8_t *width) { PrintAndLogEx(WARNING, "cannot search for non-Williams compliant models"); return 0; } + praloc(&model.spoly, (unsigned long)width[0]); praloc(&model.init, (unsigned long)width[0]); praloc(&model.xorout, (unsigned long)width[0]); - if (!plen(model.spoly)) + if (!plen(model.spoly)) { palloc(&model.spoly, (unsigned long)width[0]); - else + } else { width[0] = (uint8_t)plen(model.spoly); + } /* special case if qpoly is zero, search to end of range */ - if (!ptst(qpoly)) + if (!ptst(qpoly)) { rflags &= ~R_HAVEQ; + } int pass; @@ -135,31 +143,41 @@ int GetModels(char *Models[], int *count, uint8_t *width) { */ /* scan against preset models */ if (~uflags & C_NOPCK) { + pass = 0; int Cnt = 0; + do { int psets = mcount(); while (psets) { + mbynum(&pset, --psets); /* skip if different width, or refin or refout don't match */ - if (plen(pset.spoly) != width[0] || (model.flags ^ pset.flags) & (P_REFIN | P_REFOUT)) + if (plen(pset.spoly) != width[0] || (model.flags ^ pset.flags) & (P_REFIN | P_REFOUT)) { continue; + } + /* skip if the preset doesn't match specified parameters */ - if (rflags & R_HAVEP && pcmp(&model.spoly, &pset.spoly)) + if (rflags & R_HAVEP && pcmp(&model.spoly, &pset.spoly)) { continue; - if (rflags & R_HAVEI && psncmp(&model.init, &pset.init)) + } + + if (rflags & R_HAVEI && psncmp(&model.init, &pset.init)) { continue; - if (rflags & R_HAVEX && psncmp(&model.xorout, &pset.xorout)) + } + + if (rflags & R_HAVEX && psncmp(&model.xorout, &pset.xorout)) { continue; + } //for additional args (not used yet, maybe future?) apoly = pclone(pset.xorout); - if (pset.flags & P_REFOUT) + if (pset.flags & P_REFOUT) { prev(&apoly); - + } for (qptr = apolys; qptr < pptr; ++qptr) { crc = pcrc(*qptr, pset.spoly, pset.init, apoly, 0); @@ -183,6 +201,7 @@ int GetModels(char *Models[], int *count, uint8_t *width) { PrintAndLogEx(WARNING, "out of memory?"); return 0; } + width[Cnt] = width[0]; memcpy(tmp, pset.name, size); Models[Cnt++] = tmp; @@ -199,6 +218,7 @@ int GetModels(char *Models[], int *count, uint8_t *width) { prevch(qptr, ibperhx); } } + } while (~rflags & R_HAVERI && ++pass < 2); } //got everything now free the memory... @@ -208,6 +228,7 @@ int GetModels(char *Models[], int *count, uint8_t *width) { pfree(qptr); } } + if (uflags & C_NOBFS && ~rflags & R_HAVEP) { PrintAndLogEx(WARNING, "no models found"); return 0; @@ -217,24 +238,30 @@ int GetModels(char *Models[], int *count, uint8_t *width) { PrintAndLogEx(WARNING, "cannot search for crossed-endian models"); return 0; } + pass = 0; int args = 0; do { + model_t *candmods = reveng(&model, qpoly, rflags, args, apolys); model_t *mptr = candmods; if (mptr && plen(mptr->spoly)) { uflags |= C_RESULT; } + while (mptr && plen(mptr->spoly)) { mfree(mptr++); } + free(candmods); + if (~rflags & R_HAVERI) { model.flags ^= P_REFIN | P_REFOUT; for (qptr = apolys; qptr < pptr; ++qptr) { prevch(qptr, ibperhx); } } + } while (~rflags & R_HAVERI && ++pass < 2); for (qptr = apolys; qptr < pptr; ++qptr) { diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 096d7c7e3..70e0bbf40 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -2739,7 +2739,7 @@ static int CmdHF14AMfAutoPWN(const char *Cmd) { } // read uid to generate a filename for the key file - char suffix[FILE_PATH_SIZE]; + char suffix[FILE_PATH_SIZE + strlen(outfilename)]; if (outfnlen) { snprintf(suffix, sizeof(suffix) - strlen(outfilename), "-key-%s.bin", outfilename); } else { From 7a730ec57b66ad6e3f8fdcb04c0a01a35983ba78 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Tue, 18 Feb 2025 19:48:55 +0100 Subject: [PATCH 046/105] text layout --- client/src/cmdhfmfdes.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/client/src/cmdhfmfdes.c b/client/src/cmdhfmfdes.c index 25544ffbe..8a63b75ed 100644 --- a/client/src/cmdhfmfdes.c +++ b/client/src/cmdhfmfdes.c @@ -175,14 +175,15 @@ typedef struct aidhdr { } PACKED aidhdr_t; typedef struct { + const uint32_t aidnum; const char *aid; const char *comment; } mfdesCommonAID_t; static const mfdesCommonAID_t commonAids[] = { // AID, name/comment - { "\xf4\x81\x2f", "Gallagher card data application" }, - { "\xf4\x81\x20", "Gallagher card application directory" }, // Can be 0xF48120 - 0xF4812B, but I've only ever seen 0xF48120 + { 0xF4812F, "\xf4\x81\x2f", "Gallagher card data application" }, + { 0xF48120, "\xf4\x81\x20", "Gallagher card application directory" }, // Can be 0xF48120 - 0xF4812B, but I've only ever seen 0xF48120 }; static int CmdHelp(const char *Cmd); @@ -311,15 +312,13 @@ static char *getTypeStr(uint8_t type) { return buf; } - -static char noCommentStr[1] = { 0x00 }; -static const char *getAidCommentStr(uint8_t *aid) { +static const char *getAidCommentStr(uint32_t aid) { for (int i = 0; i < ARRAYLEN(commonAids); i++) { - if (memcmp(aid, commonAids[i].aid, 3) == 0) { + if (aid == commonAids[i].aidnum) { return commonAids[i].comment; } } - return noCommentStr; + return ""; } static nxp_cardtype_t getCardType(uint8_t type, uint8_t major, uint8_t minor) { @@ -458,7 +457,6 @@ int desfire_print_signature(uint8_t *uid, uint8_t uidlen, uint8_t *signature, si } int index = originality_check_verify(uid, uidlen, signature, signature_len, PK_MFDES); - PrintAndLogEx(NORMAL, ""); return originality_check_print(signature, signature_len, index); } @@ -3273,11 +3271,8 @@ static int CmdHF14ADesGetAIDs(const char *Cmd) { if (buflen >= 3) { PrintAndLogEx(INFO, "---- " _CYAN_("AID list") " ----"); for (int i = 0; i < buflen; i += 3) { - const char *commentStr = getAidCommentStr(&buf[i]); - if ((void *) commentStr == &noCommentStr) - PrintAndLogEx(INFO, "AID: %06x", DesfireAIDByteToUint(&buf[i])); - else - PrintAndLogEx(INFO, "AID: %06x (%s)", DesfireAIDByteToUint(&buf[i]), commentStr); + uint32_t aid = DesfireAIDByteToUint(&buf[i]); + PrintAndLogEx(SUCCESS, _YELLOW_("%06X") " %s", aid, getAidCommentStr(aid)); } } else { PrintAndLogEx(INFO, "There is no applications on the card"); From 776eac5e5a10123c0a309c433fe9b4d10fd603fc Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Tue, 18 Feb 2025 19:49:11 +0100 Subject: [PATCH 047/105] revert --- client/src/scripting.c | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/client/src/scripting.c b/client/src/scripting.c index 9b7647377..4bc86fe3d 100644 --- a/client/src/scripting.c +++ b/client/src/scripting.c @@ -344,12 +344,14 @@ static int l_WaitForResponseTimeout(lua_State *L) { return returnToLuaWithError(L, "You need to supply at least command to wait for"); // extract first param. cmd byte to look for - if (n >= 1) - cmd = luaL_checkunsigned(L, 1); + if (n >= 1) { + cmd = (uint32_t)luaL_checkinteger(L, 1); + } // extract second param. timeout value - if (n >= 2) - ms_timeout = luaL_checkunsigned(L, 2); + if (n >= 2) { + ms_timeout = (size_t)luaL_checkinteger(L, 2); + } PacketResponseNG resp; if (WaitForResponseTimeout(cmd, &resp, ms_timeout) == false) { @@ -647,7 +649,7 @@ static int l_crc8legic(lua_State *L) { size_t size; const char *p_hexstr = luaL_checklstring(L, 1, &size); uint16_t retval = CRC8Legic((uint8_t *)p_hexstr, size); - lua_pushunsigned(L, retval); + lua_pushinteger(L, retval); return 1; } @@ -663,7 +665,7 @@ static int l_crc16legic(lua_State *L) { init_table(CRC_LEGIC_16); uint16_t retval = crc16_legic((uint8_t *)p_hexstr, hexsize, uidcrc); - lua_pushunsigned(L, retval); + lua_pushinteger(L, retval); return 1; } @@ -673,7 +675,7 @@ static int l_crc16(lua_State *L) { const char *p_str = luaL_checklstring(L, 1, &size); uint16_t checksum = Crc16ex(CRC_CCITT, (uint8_t *) p_str, size); - lua_pushunsigned(L, checksum); + lua_pushinteger(L, checksum); return 1; } @@ -739,9 +741,10 @@ static int l_reveng_models(lua_State *L) { #define NMODELS 106 int count = 0; - uint8_t in_width = luaL_checkunsigned(L, 1); - if (in_width > 89) + uint8_t in_width = (uint8_t)luaL_checkinteger(L, 1); + if (in_width > 89) { return returnToLuaWithError(L, "Width cannot exceed 89, got %d", in_width); + } uint8_t width[NMODELS]; memset(width, 0, sizeof(width)); @@ -749,8 +752,9 @@ static int l_reveng_models(lua_State *L) { width[0] = in_width; - if (!GetModels(models, &count, width)) + if (!GetModels(models, &count, width)) { return returnToLuaWithError(L, "didn't find any models"); + } lua_newtable(L); for (int i = 0; i < count; i++) { @@ -920,8 +924,8 @@ static int l_keygen_algoB(lua_State *L) { uint32_t pwd = ul_ev1_pwdgenB(uid); uint16_t pack = ul_ev1_packgenB(uid); - lua_pushunsigned(L, pwd); - lua_pushunsigned(L, pack); + lua_pushinteger(L, pwd); + lua_pushinteger(L, pack); return 2; } @@ -953,8 +957,8 @@ static int l_keygen_algoD(lua_State *L) { uint32_t pwd = ul_ev1_pwdgenD(uid); uint16_t pack = ul_ev1_packgenD(uid); - lua_pushunsigned(L, pwd); - lua_pushunsigned(L, pack); + lua_pushinteger(L, pwd); + lua_pushinteger(L, pack); return 2; } @@ -1041,7 +1045,7 @@ static int l_T55xx_readblock(lua_State *L) { return returnToLuaWithError(L, "Failed to get actual data"); } - lua_pushunsigned(L, blockData); + lua_pushinteger(L, blockData); return 1; } From 35a67cf2610e3c147e62f0f2419f36ac996632cc Mon Sep 17 00:00:00 2001 From: ry4000 <154689120+ry4000@users.noreply.github.com> Date: Wed, 19 Feb 2025 16:16:20 +1100 Subject: [PATCH 048/105] Added YWG peggo card to `aid_desfire.json` # Additions - **Added** YWG peggo card to `F21201`. Signed-off-by: ry4000 <154689120+ry4000@users.noreply.github.com> --- client/resources/aid_desfire.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/resources/aid_desfire.json b/client/resources/aid_desfire.json index a03f7312f..f66e3df4e 100644 --- a/client/resources/aid_desfire.json +++ b/client/resources/aid_desfire.json @@ -1449,10 +1449,10 @@ }, { "AID": "F21201", - "Vendor": "Green Bay Metro Transit via Genfare", - "Country": "US", - "Name": "Tap-N-Go Card (GRB)", - "Description": "GRB Tap-N-Go Card", + "Vendor": "Green Bay Metro Transit via Genfare / Winnipeg Transit", + "Country": "US / CA", + "Name": "Tap-N-Go Card (GRB) / peggo card (YWG)", + "Description": "GRB Tap-N-Go Card / YWG peggo card", "Type": "transport" }, { From c505f57b3bc5fb5357427560d5eb9382f73f3173 Mon Sep 17 00:00:00 2001 From: Jean-Michel Picod Date: Wed, 19 Feb 2025 12:13:17 +0100 Subject: [PATCH 049/105] Add support for proprietary 46bit wiegand H800002 format --- client/src/wiegand_formats.c | 45 ++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/client/src/wiegand_formats.c b/client/src/wiegand_formats.c index b64aa0e74..0810c40a2 100644 --- a/client/src/wiegand_formats.c +++ b/client/src/wiegand_formats.c @@ -877,6 +877,50 @@ static bool Unpack_HGeneric37(wiegand_message_t *packed, wiegand_card_t *card) { return true; } +static bool Pack_H800002(int format_idx, wiegand_card_t *card, + wiegand_message_t *packed, bool preamble) { + uint32_t parity = 0; + memset(packed, 0, sizeof(wiegand_message_t)); + + if (!validate_card_limit(format_idx, card)) { + return false; + } + + packed->Length = 46; + set_linear_field(packed, card->FacilityCode, 1, 14); + set_linear_field(packed, card->CardNumber, 15, 30); + + parity = get_linear_field(packed, 1, 32); + parity ^= get_linear_field(packed, 33, 12); + set_bit_by_position(packed, evenparity32(parity), 0); + parity = get_linear_field(packed, 1, 32); + parity ^= get_linear_field(packed, 33, 12); + set_bit_by_position(packed, oddparity32(parity), 45); + if (preamble) { + return add_HID_header(packed); + } + return true; +} + +static bool Unpack_H800002(wiegand_message_t *packed, wiegand_card_t *card) { + uint32_t parity = 0; + memset(card, 0, sizeof(wiegand_card_t)); + + if (packed->Length != 46) { + return false; // Wrong length? Stop here. + } + + card->FacilityCode = get_linear_field(packed, 1, 14); + card->CardNumber = get_linear_field(packed, 15, 30); + parity = get_linear_field(packed, 1, 32); + parity ^= get_linear_field(packed, 33, 12); + card->ParityValid = get_bit_by_position(packed, 0) == evenparity32(parity); + parity = get_linear_field(packed, 1, 32); + parity ^= get_linear_field(packed, 33, 12); + card->ParityValid &= get_bit_by_position(packed, 45) == oddparity32(parity); + return true; +} + static bool Pack_MDI37(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); @@ -1413,6 +1457,7 @@ static const cardformat_t FormatTable[] = { {"H10320", Pack_H10320, Unpack_H10320, "HID H10320 37-bit BCD", {1, 0, 0, 0, 1, 0, 99999999, 0, 0}}, // from Proxmark forums {"H10302", Pack_H10302, Unpack_H10302, "HID H10302 37-bit huge ID", {1, 0, 0, 0, 1, 0, 0x7FFFFFFFF, 0, 0}}, // from Proxmark forums {"H10304", Pack_H10304, Unpack_H10304, "HID H10304 37-bit", {1, 1, 0, 0, 1, 0xFFFF, 0x7FFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"H800002", Pack_H800002, Unpack_H800002, "HID H800002 46-bit", {1, 1, 0, 0, 1, 0x3FFF, 0x3FFFFFFF, 0, 0}}, {"P10004", Pack_P10004, Unpack_P10004, "HID P10004 37-bit PCSC", {1, 1, 0, 0, 0, 0x1FFF, 0x3FFFF, 0, 0}}, // from @bthedorff; PR #1559 {"HGen37", Pack_HGeneric37, Unpack_HGeneric37, "HID Generic 37-bit", {1, 0, 0, 0, 1, 0, 0x7FFFF, 0, 0}}, // from cardinfo.barkweb.com.au {"MDI37", Pack_MDI37, Unpack_MDI37, "PointGuard MDI 37-bit", {1, 1, 0, 0, 1, 0xF, 0x1FFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au From e836e4bda37ef6c4d0c70046a8949067a2640915 Mon Sep 17 00:00:00 2001 From: Jean-Michel Picod Date: Thu, 20 Feb 2025 09:38:32 +0100 Subject: [PATCH 050/105] Simplify code and parity --- client/src/wiegand_formats.c | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/client/src/wiegand_formats.c b/client/src/wiegand_formats.c index 0810c40a2..69d869785 100644 --- a/client/src/wiegand_formats.c +++ b/client/src/wiegand_formats.c @@ -879,7 +879,7 @@ static bool Unpack_HGeneric37(wiegand_message_t *packed, wiegand_card_t *card) { static bool Pack_H800002(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { - uint32_t parity = 0; + int even_parity = 0; memset(packed, 0, sizeof(wiegand_message_t)); if (!validate_card_limit(format_idx, card)) { @@ -890,12 +890,11 @@ static bool Pack_H800002(int format_idx, wiegand_card_t *card, set_linear_field(packed, card->FacilityCode, 1, 14); set_linear_field(packed, card->CardNumber, 15, 30); - parity = get_linear_field(packed, 1, 32); - parity ^= get_linear_field(packed, 33, 12); - set_bit_by_position(packed, evenparity32(parity), 0); - parity = get_linear_field(packed, 1, 32); - parity ^= get_linear_field(packed, 33, 12); - set_bit_by_position(packed, oddparity32(parity), 45); + // Parity over 44 bits + even_parity = evenparity32((packed->Bot >> 1) ^ (packed->Mid & 0x1fff)); + set_bit_by_position(packed, even_parity, 0); + // Invert parity for setting odd parity + set_bit_by_position(packed, even_parity ^ 1, 45); if (preamble) { return add_HID_header(packed); } @@ -903,7 +902,7 @@ static bool Pack_H800002(int format_idx, wiegand_card_t *card, } static bool Unpack_H800002(wiegand_message_t *packed, wiegand_card_t *card) { - uint32_t parity = 0; + int even_parity = 0; memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 46) { @@ -912,12 +911,10 @@ static bool Unpack_H800002(wiegand_message_t *packed, wiegand_card_t *card) { card->FacilityCode = get_linear_field(packed, 1, 14); card->CardNumber = get_linear_field(packed, 15, 30); - parity = get_linear_field(packed, 1, 32); - parity ^= get_linear_field(packed, 33, 12); - card->ParityValid = get_bit_by_position(packed, 0) == evenparity32(parity); - parity = get_linear_field(packed, 1, 32); - parity ^= get_linear_field(packed, 33, 12); - card->ParityValid &= get_bit_by_position(packed, 45) == oddparity32(parity); + even_parity = evenparity32((packed->Bot >> 1) ^ (packed->Mid & 0x1fff)); + card->ParityValid = get_bit_by_position(packed, 0) == even_parity; + // Invert logic to compare against oddparity + card->ParityValid &= get_bit_by_position(packed, 45) != even_parity; return true; } From ffbf033937a4a9b40aff54583682a18775017850 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Thu, 20 Feb 2025 13:16:19 +0100 Subject: [PATCH 051/105] fix #2547 - compilation error on arm-none-eabi-gcc 15.6 for error: dereferencing type-punned pointer will break strict-aliasing rules, by making a u32 we dont get that any longer --- CHANGELOG.md | 2 ++ armsrc/hitagS.c | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b8f34610..eb37c1199 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ 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] +- Fix compilation warning in hitagS (@iceman1001) +- Added new wiegand format H800002 (@jmichelp) - Changed `Makefile.platform.sample` file - now have clear instructions for generating images for other proxmark3 hardware (@iceman1001) - Changed `doc/magic_cards_notes.md` - now contains documentation for iKey LLC's MF4 tag (@team-orangeBlue) - Changed `hf mf cload` - now accepts MFC Ev1 sized dumps (@iceman1001) diff --git a/armsrc/hitagS.c b/armsrc/hitagS.c index 4b8b62545..932f4bcc1 100644 --- a/armsrc/hitagS.c +++ b/armsrc/hitagS.c @@ -1134,7 +1134,8 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz key_le = *(uint64_t *)packet->key; - uint64_t state = ht2_hitag2_init(REV64(key_le), REV32(tag.data.s.uid_le), REV32(*(uint32_t *)rnd)); + uint32_t le_val = MemLeToUint4byte(rnd); + uint64_t state = ht2_hitag2_init(REV64(key_le), REV32(tag.data.s.uid_le), REV32(le_val)); uint8_t auth_ks[4]; for (int i = 0; i < 4; i++) { @@ -1226,7 +1227,9 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz pwdl1 = 0; if (packet->cmd == HTSF_KEY) { - uint64_t state = ht2_hitag2_init(REV64(key_le), REV32(tag.data.s.uid_le), REV32(*(uint32_t *)rnd)); + uint32_t le_val = MemLeToUint4byte(rnd); + uint64_t state = ht2_hitag2_init(REV64(key_le), REV32(tag.data.s.uid_le), REV32(le_val)); + for (int i = 0; i < 4; i++) { ht2_hitag2_byte(&state); } From 4e5d68851befee5c0806bc99cbdb2dd789df459c Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Thu, 20 Feb 2025 15:06:46 +0100 Subject: [PATCH 052/105] Add pm3_resources helpers for Python scripts to find tools & dicts --- client/Makefile | 5 ++ client/pyscripts/fm11rf08s_recovery.py | 61 ++++++----------- client/pyscripts/pm3_resources.py | 92 ++++++++++++++++++++++++++ 3 files changed, 117 insertions(+), 41 deletions(-) create mode 100644 client/pyscripts/pm3_resources.py diff --git a/client/Makefile b/client/Makefile index 46fa40439..cf4b65b5f 100644 --- a/client/Makefile +++ b/client/Makefile @@ -887,7 +887,12 @@ ifneq (,$(INSTALLBIN)) endif ifneq (,$(INSTALLSHARE)) $(Q)$(INSTALLSUDO) $(MKDIR) $(DESTDIR)$(PREFIX)$(PATHSEP)$(INSTALLSHARERELPATH) + # hack ahead: inject installation path into pm3_resources.py + $(Q)sed -i 's|^TOOLS_PATH \?= \?None|TOOLS_PATH="$(DESTDIR)$(PREFIX)$(PATHSEP)$(INSTALLTOOLSRELPATH)"|' pyscripts/pm3_resources.py + $(Q)sed -i 's|^DICTS_PATH \?= \?None|DICTS_PATH="$(DESTDIR)$(PREFIX)$(PATHSEP)$(INSTALLSHARERELPATH)/dictionaries"|' pyscripts/pm3_resources.py $(Q)$(INSTALLSUDO) $(CP) $(INSTALLSHARE) $(DESTDIR)$(PREFIX)$(PATHSEP)$(INSTALLSHARERELPATH) + $(Q)sed -i 's|^TOOLS_PATH \?=.*|TOOLS_PATH = None|' pyscripts/pm3_resources.py + $(Q)sed -i 's|^DICTS_PATH \?=.*|DICTS_PATH = None|' pyscripts/pm3_resources.py endif @true diff --git a/client/pyscripts/fm11rf08s_recovery.py b/client/pyscripts/fm11rf08s_recovery.py index b94381117..19407ec12 100755 --- a/client/pyscripts/fm11rf08s_recovery.py +++ b/client/pyscripts/fm11rf08s_recovery.py @@ -20,6 +20,8 @@ import subprocess import argparse import json import pm3 +from pm3_resources import find_tool, find_dict + # optional color support try: # pip install ansicolors @@ -42,38 +44,11 @@ BACKDOOR_KEYS = ["A396EFA4E24F", "A31667A8CEC1", "518B3354E760"] NUM_SECTORS = 16 NUM_EXTRA_SECTORS = 1 -DICT_DEF = "mfc_default_keys.dic" DEFAULT_KEYS = set() -if __name__ == '__main__': - DIR_PATH = os.path.dirname(os.path.abspath(sys.argv[0])) -else: - DIR_PATH = os.path.dirname(os.path.abspath(__file__)) -if os.path.basename(os.path.dirname(DIR_PATH)) == 'client': - # dev setup - TOOLS_PATH = os.path.normpath(os.path.join(DIR_PATH, - "..", "..", "tools", "mfc", "card_only")) - DICT_DEF_PATH = os.path.normpath(os.path.join(DIR_PATH, - "..", "dictionaries", DICT_DEF)) -else: - # assuming installed - TOOLS_PATH = os.path.normpath(os.path.join(DIR_PATH, - "..", "tools")) - DICT_DEF_PATH = os.path.normpath(os.path.join(DIR_PATH, - "dictionaries", DICT_DEF)) - -tools = { - "staticnested_1nt": os.path.join(f"{TOOLS_PATH}", "staticnested_1nt"), - "staticnested_2x1nt": os.path.join(f"{TOOLS_PATH}", "staticnested_2x1nt_rf08s"), - "staticnested_2x1nt1key": os.path.join(f"{TOOLS_PATH}", "staticnested_2x1nt_rf08s_1key"), -} -for tool, bin in tools.items(): - if not os.path.isfile(bin): - if os.path.isfile(bin + ".exe"): - tools[tool] = bin + ".exe" - else: - print(f"Cannot find {bin}, abort!") - exit() +staticnested_1nt_path = find_tool("staticnested_1nt") +staticnested_2x1nt_path = find_tool("staticnested_2x1nt_rf08s") +staticnested_2x1nt1key_path = find_tool("staticnested_2x1nt_rf08s_1key") def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debug=False, supply_chain=False, quiet=True, keyset=False): @@ -193,14 +168,18 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu show("----Step 1: " + color(f"{minutes:2}", fg="yellow") + " minutes " + color(f"{seconds:2}", fg="yellow") + " seconds -----------") - if os.path.isfile(DICT_DEF_PATH): - show(f"Loading {DICT_DEF}") - with open(DICT_DEF_PATH, 'r', encoding='utf-8') as file: + dict_def = "mfc_default_keys.dic" + try: + dict_path = find_dict(dict_def) + with open(dict_path, 'r', encoding='utf-8') as file: for line in file: if line[0] != '#' and len(line) >= 12: DEFAULT_KEYS.add(line[:12]) - else: - show(f"Warning, {DICT_DEF} not found.") + show(f"Loaded {dict_def}") + except FileNotFoundError: + show(f"Warning, {dict_def} not found.") + except Exception as e: + raise Exception(f"Error loading {dict_def}: {e}") dict_dnwd = None def_nt = ["" for _ in range(NUM_SECTORS)] @@ -233,12 +212,12 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu continue if found_keys[sec][0] == "" and found_keys[sec][1] == "" and nt[sec][0] != nt[sec][1]: for key_type in [0, 1]: - cmd = [tools["staticnested_1nt"], f"{uid:08X}", f"{real_sec}", + cmd = [staticnested_1nt_path, f"{uid:08X}", f"{real_sec}", nt[sec][key_type], nt_enc[sec][key_type], par_err[sec][key_type]] if debug: print(' '.join(cmd)) subprocess.run(cmd, capture_output=True) - cmd = [tools["staticnested_2x1nt"], + cmd = [staticnested_2x1nt_path, f"keys_{uid:08x}_{real_sec:02}_{nt[sec][0]}.dic", f"keys_{uid:08x}_{real_sec:02}_{nt[sec][1]}.dic"] if debug: print(' '.join(cmd)) @@ -254,7 +233,7 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu all_keys.update(keys_set) if dict_dnwd is not None and sec < NUM_SECTORS: # Prioritize keys from supply-chain attack - cmd = [tools["staticnested_2x1nt1key"], def_nt[sec], "FFFFFFFFFFFF", + cmd = [staticnested_2x1nt1key_path, def_nt[sec], "FFFFFFFFFFFF", f"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type]}_filtered.dic"] if debug: print(' '.join(cmd)) @@ -285,7 +264,7 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu key_type = 0 else: key_type = 1 - cmd = [tools["staticnested_1nt"], f"{uid:08X}", f"{real_sec}", + cmd = [staticnested_1nt_path, f"{uid:08X}", f"{real_sec}", nt[sec][key_type], nt_enc[sec][key_type], par_err[sec][key_type]] if debug: print(' '.join(cmd)) @@ -299,7 +278,7 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu all_keys.update(keys_set) if dict_dnwd is not None and sec < NUM_SECTORS: # Prioritize keys from supply-chain attack - cmd = [tools["staticnested_2x1nt1key"], def_nt[sec], "FFFFFFFFFFFF", + cmd = [staticnested_2x1nt1key_path, def_nt[sec], "FFFFFFFFFFFF", f"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type]}.dic"] if debug: print(' '.join(cmd)) @@ -509,7 +488,7 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu dic = f"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type_target]}_filtered.dic" else: dic = f"keys_{uid:08x}_{real_sec:02}_{nt[sec][key_type_target]}.dic" - cmd = [tools["staticnested_2x1nt1key"], nt[sec][key_type_source], found_keys[sec][key_type_source], dic] + cmd = [staticnested_2x1nt1key_path, nt[sec][key_type_source], found_keys[sec][key_type_source], dic] if debug: print(' '.join(cmd)) result = subprocess.run(cmd, capture_output=True, text=True).stdout diff --git a/client/pyscripts/pm3_resources.py b/client/pyscripts/pm3_resources.py new file mode 100644 index 000000000..c50ede801 --- /dev/null +++ b/client/pyscripts/pm3_resources.py @@ -0,0 +1,92 @@ +""" +Helper library to locate resources for pm3 scripts. + +This module provides functionality to locate tools and dictionaries required +for pm3 scripts. It determines the paths based on the directory structure +and whether the script is being run in a development setup or an installed setup. + +Functions: + find_tool(tool_name): + Finds the specified tool in the tools directory. + Args: + tool_name (str): The name of the tool to find. + Returns: + str: The full path to the tool if found, otherwise None. + + find_dict(dict_name): + Find the specified dictionary in the dicts directory. + Args: + dict_name (str): The name of the dict to find. + Returns: + str: The full path to the dict if found, otherwise None. +""" + +import os + +# Install script can hardcode paths in the following variables +TOOLS_PATH = None +DICTS_PATH = None + +if __name__ == "__main__": + print("This is a library, don't use it as a script") + exit() + +DIR_PATH = os.path.dirname(os.path.abspath(__file__)) + +if TOOLS_PATH is None: + if os.path.basename(os.path.dirname(DIR_PATH)) == 'client': + # dev setup + DEV_TOOLS_PATH = os.path.normpath(os.path.join(DIR_PATH, "..", "..", "tools", "mfc", "card_only")) + if os.path.isdir(DEV_TOOLS_PATH): + TOOLS_PATH = DEV_TOOLS_PATH + +if TOOLS_PATH is None: + # assuming installed without having defined TOOLS_PATH + TEST_TOOLS_PATH = os.path.normpath(os.path.join(DIR_PATH, "..", "tools")) + if os.path.isdir(TEST_TOOLS_PATH): + TOOLS_PATH = TEST_TOOLS_PATH + + +if DICTS_PATH is None: + DEV_DICTS_PATH = os.path.normpath(os.path.join(DIR_PATH, "..", "dictionaries")) + if os.path.isdir(DEV_DICTS_PATH): + DICTS_PATH = DEV_DICTS_PATH + + +def find_tool(tool_name): + """Find the specified tool in the tools directory. + + Args: + tool_name (str): The name of the tool to find. + Returns: + str: The full path to the tool if found, otherwise None. + """ + if TOOLS_PATH is not None: + tool = os.path.join(TOOLS_PATH, tool_name) + if os.path.isfile(tool): + return tool + elif os.path.isfile(tool + ".exe"): + return tool + ".exe" + # if not found, search in the user PATH + for path in os.environ["PATH"].split(os.pathsep): + env_tool = os.path.join(path, tool_name) + if os.path.isfile(env_tool): + return env_tool + elif os.path.isfile(env_tool + ".exe"): + return env_tool + ".exe" + raise FileNotFoundError(f"Cannot find {tool_name}, abort!") + + +def find_dict(dict_name): + """Find the specified dictionary in the dicts directory. + + Args: + dict_name (str): The name of the dict to find. + Returns: + str: The full path to the dict if found, otherwise None. + """ + if DICTS_PATH is not None: + dictionary = os.path.join(DICTS_PATH, dict_name) + if os.path.isfile(dictionary): + return dictionary + raise FileNotFoundError(f"Cannot find {dict_name}, abort!") From e3b2bc9839dbb4b880d6fdcefe7187d2528a23f7 Mon Sep 17 00:00:00 2001 From: n-hutton Date: Thu, 20 Feb 2025 14:39:31 +0000 Subject: [PATCH 053/105] strip date time files --- fpga/Makefile | 2 +- fpga/strip_date_time_from_binary.py | 52 +++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 fpga/strip_date_time_from_binary.py diff --git a/fpga/Makefile b/fpga/Makefile index 963fd1e28..dd9849a19 100644 --- a/fpga/Makefile +++ b/fpga/Makefile @@ -188,7 +188,7 @@ work: $(Q)$(RM) $@ $*.drc $*.rbt $(info [=] BITGEN $@) $(Q)$(XILINX_TOOLS_PREFIX)bitgen $(VERBOSITY) -w $* $@ - echo "FFFFFFFFFFFFFFFFFFFFFF" | dd of=fpga_pm3_hf.bit bs=1 seek=48 conv=notrunc + #python3 ../strip_date_time_from_binary.py $@ || true $(Q)$(CP) $@ .. # Build all targets diff --git a/fpga/strip_date_time_from_binary.py b/fpga/strip_date_time_from_binary.py new file mode 100644 index 000000000..12e34c9dd --- /dev/null +++ b/fpga/strip_date_time_from_binary.py @@ -0,0 +1,52 @@ +import sys + +# File to take a .bit file generated by xilinx webpack ISE +# and replace the date and time embedded within with 'F'. +# The header of the bitfile is seperated by ascii markers +# 'a', 'b', 'c', 'd' etc. +# see fpga_compress.c for comparison + +def parse_and_split_file(filename): + split_chars = ['a', 'b', 'c', 'd'] # Characters to split on + extracted_data = [] # for debug + + print("Overwriting date and time in bitfile {}".format(filename)) + + with open(filename, 'rb') as f: # Read as binary to handle non-text files + data = f.read(100) # Read first 100 bytes which should contain all information + + decoded_data = list(data.decode(errors='ignore')) + + for i in range(len(decoded_data) - 3): + # subsequent two bytes after marker are null and the length + next_byte = ord(decoded_data[i+1]) + data_length = ord(decoded_data[i+2]) + + if decoded_data[i] == split_chars[0] and next_byte == 0x0: + start = i+3 + extracted_data.append(''.join(decoded_data[start:start+data_length])) + + # time, date + if split_chars[0] == 'c' or split_chars[0] == 'd': + decoded_data[start:start+data_length] = 'F' * data_length + + split_chars.pop(0) + + if not split_chars: + break + + print("Extracted data from bitfile: {}".format(extracted_data)) + decoded_data = ''.join(decoded_data).encode() + + with open(filename, 'r+b') as f: # Write back modified bytes + f.seek(0) + f.write(decoded_data.ljust(100, b' ')) + print("writing complete") + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: python script.py ") + sys.exit(1) + + filename = sys.argv[1] + parse_and_split_file(filename) From 280b3301ee27be5b815b39f45f702ab5bd3306ae Mon Sep 17 00:00:00 2001 From: n-hutton Date: Thu, 20 Feb 2025 14:51:08 +0000 Subject: [PATCH 054/105] PR feedback and make things work generally --- fpga/Makefile | 2 +- fpga/fpga_icopyx_hf.bit | Bin 72749 -> 72749 bytes fpga/fpga_pm3_felica.bit | Bin 42176 -> 42176 bytes fpga/fpga_pm3_hf.bit | Bin 42172 -> 42172 bytes fpga/fpga_pm3_hf_15.bit | Bin 42175 -> 42175 bytes fpga/fpga_pm3_lf.bit | Bin 42172 -> 42172 bytes 6 files changed, 1 insertion(+), 1 deletion(-) diff --git a/fpga/Makefile b/fpga/Makefile index dd9849a19..769185260 100644 --- a/fpga/Makefile +++ b/fpga/Makefile @@ -188,7 +188,7 @@ work: $(Q)$(RM) $@ $*.drc $*.rbt $(info [=] BITGEN $@) $(Q)$(XILINX_TOOLS_PREFIX)bitgen $(VERBOSITY) -w $* $@ - #python3 ../strip_date_time_from_binary.py $@ || true + python3 ../strip_date_time_from_binary.py $@ || true $(Q)$(CP) $@ .. # Build all targets diff --git a/fpga/fpga_icopyx_hf.bit b/fpga/fpga_icopyx_hf.bit index db2878bbffeeffd6d31f339e742b9525125b5bc9..77d436c59aea22951de6630419eb22e532a03267 100644 GIT binary patch literal 72749 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 literal 72749 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 AoB#j- diff --git a/fpga/fpga_pm3_felica.bit b/fpga/fpga_pm3_felica.bit index f79babab68f3c97bee1693154350734905216534..b8f8e80cc20bd4fab9dcb9b29b0158e300ec32a1 100644 GIT binary patch literal 42176 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 diff --git a/fpga/fpga_pm3_hf.bit b/fpga/fpga_pm3_hf.bit index 2e7a94059e14d96cc2ced2d9ee0e6f9c66048842..fdd4bb33305a996ef5de8d90075bc1e49a576146 100644 GIT binary patch literal 42172 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 V`0|k)Rr!0EOL09XK7QqQ{}-nE2EYIS literal 42172 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 diff --git a/fpga/fpga_pm3_hf_15.bit b/fpga/fpga_pm3_hf_15.bit index f2b1e4ff32bebb6caea4b14e26355a496e184997..f6ee54ecba5cfdde0c58b585fe0e5d6214cf0be3 100644 GIT binary patch literal 42175 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 literal 42175 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 P-B(QS)-(0hD}Vcc8H#sR diff --git a/fpga/fpga_pm3_lf.bit b/fpga/fpga_pm3_lf.bit index b87ea9489e83fff194e39fcd747353d7aa0840b2..862b3148e58c107427150588d7e2960149acc3c5 100644 GIT binary patch literal 42172 zcmeIbeRx#YbuYYTpCkD&Ghz-x@~imsXr$0F8N?t6CK!v2!0rTSP#w47z0Gqoc4#iQ zFX@%ty!Rz(-ehYe+u|#X328~{_8Bl~m4voqz&8GfeZaCKY;3D8Pl8kG3@&lxgg9eN z9Ptr&e`}wQ839RpZ~aG~=Z*d_%SU_8?60+c>$le0TQn(wAEL+|WPhsnzuobP-cQf| z#F|fa{OKoG(VZ0S{Il7a|9RJ}Ool#5v0JM@t7uaAKYvWr`v;%OP=tuC$V77dujSPI zWFk(4)WR7Va`|_ZA;ter$`GDc|38u;Nj=p5NoutD@vrzXmHtIz&#(ABKQ)1noS+R_Xgp5)DCtB8g=1#8 z&0yUjd?xM2GU5nlNED0{xp6sP#19#^soztMJV_U5p_`}}=U9-5vhl9-ExF4-y`i^{GL&~=Y)b#fbb^0ynMEduZr2G!(TO0m*)Nqqd)(a*+(5RrW*f^{>a_o zS|!nm4TPU^-SS{uWhzlx#)u9QjXD&`h_%>a_;DiF(W7ccIkr~32I1312;Y}~S#WZb z){3n6o~#w8cW8l%WsE~C$VAd8=boREaU00E&D%KgT0Jg>aaHJ?Sf~=Ck+pS*s|A#azbo5x$mTVjl)-u_UQ<()jSZuz zQ!VrG9=+~k%|mpCx(4D!V~n1suE7KgFJZq-E^qjp_gz`6^0?V$Clrs17SiSec;R^V zN`WV@K!x#R*~jU$H=G}0g1cm*F!e3^u*}G`Z3yqXl(~u?<7Fy{H;i?j@WcE;XNO!~ zh&p8Rds^z+Ewt%In^Wc{^MAOp(fZ?R9VMkz5cS?#SW8TLh?mk9hv^=gA!9van6{^! zrIsy3gkMAZ8qh=PqL{)w4?;+|hTAgYbXpsg(P8Ly-*zqc8B19(j+fARxuP)Tc=5&5 zLOJ=A@kV_wzo(^6kBi%!^2Rj^;YI3{@oh%Ew_fw?GPJ1!-zCOqy(g5#5GB=mB_?}g zx9W!dKGzQ$eWvdGaAZ5BWVWeWc>2}Syp!jr=jwtuq1HK=s4d;Y^bPu^jAHEGS`?dh z@;sxf2Ig7vJR8SXy*cuDZ~TE+%G0kD={7EY^#+|*y3HH(wlk+UagFi3r(Y?%$n(Q1 z=uK2YwH;FWyQvEEyRkAXT_J?zv#1yBI>P0s&2XE+l{vz zUwEl;sv_*VXw=uQMc43tc4NKbsKdh5z2aZ#FX{cfGgJP1tn26(>gIA{ZTD-w06X>r zl_@u@HKu#|WnHUnf(uh{4#wd+2ry`GV%JSB83#m;`z+ZW_BEV}kK^MnD;UPLMi+$H zWjp~mahoAezg+0osPny+520hmXXswK!HxEc>E1jOu@263t-CxOJb=!(Kp7p^>P2rH zeowB+vzhP9hy(QHZ0bntL!z6MHRML;8FA+YtMkqH zUc>(`McO8?%^Y*|xOPJa-(hXaV0AM5T@hQGN)@jd$K}h~c((oAeu-xDr)Y_8(`T+} zVf#E-68mX`XQ+?S5huwO+mSZEN7b!ioB0eZ(?3; zBj8uR#4=@)hiJEn&XagOz^@_oxbvqHvjH6Br7T@p9)QLXo>aVbm(8!!I8jBxj&KGE54~$HRUpv`cunr!zp22&t0aC1S z`Go2p_4I3`E@iE!Kc!d^du;Q{HaA-_rqL6l9l6-P?vvhHli~TmtyWQ5VEiG2?fcITB_;sAtxf+sw zhaPh~dSefWvaR*2rEU&=n`X+`s6d-|a*?&Qd#>~C!I^i)j^v;7^~-GH`H`_6F!@(#HOu#S3_v2`j zWZ{%I8%M-*K7KX-P@T>NeV)*tzc9wUKBpoYzk0o{T47@?OvEqp@XOpyab;xe&&!3B z7&e~w^ea{8;TQC4jGl6neGNsx4I|oyqIlk03*Z-Qoi=eW<}UQhiAqcYG>*_l&vP*n z;8*=odQ>`NlU9q5TRx(VdG8qlemx5O+Pv0a{F*fupJ)76AIKK}SnzTY@XNyLu!TH< zwdi_fMad|7YpRR`o_-PIS7Bi}F~0b`ym0RmugBknWA9r?H^i@8fT#4d-8ww|lL8&H zG{XM42EVLN1KPw<`w>b3wvuSWD12Am_4LbWdePaFT~m(Df^8|myhk%)zbv_F5sQlv zZ{3HQ|B=p0`1AYF=d*N^8!xgF^}rkaYs3GpDt`5!pz}OH$IwoDIZ>QC5x>~-0~FK; z-FU^ghE}lgEKhZpWO#mnUje^3VeV5&cH@iYpn!cYh;onCFIA^~enzVk^iXP=3s=OU zA?k>58*eQvlkv-6Qy!O9Ozw(3NNe0!O8ELk4ZyEpEyg1^0?3Rs{RB@|6b%=i0)DFa zCC*te7yGqie-sYziqY9)SM+ilkAK}*-ZUP-mJ5HwXJnW1FQ14FiX`n*z%Laodh^Wq zwTIeWAT0JFcbHv8;9s_N@E(KWgE#3umm~Wo@UH`jd5PEYq_$Y zSN&@d7Oq}NH%U{?aAGU-S#M3VmMQ;@=DF~QHd^3c5T;P~Vp^b96sLy#Yp*x1w~cc$ zAei#=HW2OAi};tGpI`$Vw?^`vIT(=Xo_=Mg6g*9V?gYXS_7LSnj_J|2pmyv1q%!&g_sI9_X&}uYjXz zG9p1n=Flc%=AzhWKHy)_FQ0$;q^2=H$Gj(^@yquQL;O;KiOWxQM3y`6#zOveDnM8t zl17bTdYcx=L}Pyq|AMK+?gebU>Io)31%R$xhJOV_Eb5s1sEr0M!@q!E_j};m1Z--P zEp~mCe@UO8`tvg?KFDoc=AHq5h3M(`XItd`n6cfrr3)editM^*Tvws<%-S%DTKp2c zlz~s;f?OZsHB}-1s>Lsct=JARrt(gDUF^8A)YFSpon*CQ{nC3(#Vn8!rP7_TC&bbK zznX$=9Pa1y5d;?8{$8=#d(V*7=y?1~0Sl#SgF&_I9!KM!QwLMT7#sUw> z?6W2W_(%bMouu!8Z6O||-S}c+wQ&`_q-GzFkGF;VYb$+^A}(W-*Rk8hRuK7Cb1+xs zUk^FZxUh|imBl>y57ai?Qpw8zzg+qbaQwtU?fli#1xRoGnWFOtTlh+UzVMKxxn;yuP3!~qbG zDp&bej=sfaFtCTwoyi*p*WZ~eSgOjuoPe5Xo2g(5()3r9R954?j<*(;+0G%jU|bz8 zf`1MT!lAM#8t^aphd%AnYoXwNr^Ua}Zk1M5{xv$m&bu%d73-9^86>L8zYr}{3!R3n zaT|TdnKjaI(l~E^B9DD|#t8B2g!M$e^g+%=DjM;TZgA>b&XK0I? z=0*7z`eGdXE5xrS;XchAh#?fCc_nc96h_*W!wS~m7nWOU{Q8-F)9~a<8&A|iOjH%W z(j2!@aCaTrJlcB7`h|E-1DRE+!7nONyP8B|e~N(&KGGoxjZ>+TnCI!2jvp3gX$%Vj!EGF%HRhA`9Gj#%T2P3-QC|^G#or z2}a=y*&BxwZ%+*vbd`T$epIK+cwzm*x$y|hV9Goz7E*e56~7*UXWmM&oV8;wtWQ+4^iW#IF-nQjWa=CdJ-kI{UGvMg(I& zo-IW~{6hTD+C9)v>>mM;%~k1QY+mjR%~Vu0YVeEEY6Y9P3N4@nvC;FaUlqTE7dC{c ze3Q+4!I-8vIv1aSUkDp6GQcm)b&HZc*{z~ovBe&I04u2dL&OhhlgGbK(KlqW$3h!= zOms*HA%2-b*zhp*rBX|+#=NN!Ep=s}UyNS}&%zpiv|^1pyRCFS=FnQ|id>_r_;tMK z@39kfIx-hQxy6iVhy&ceIK;0IgiXp z*+(L{2i8G7&u!>W1oKnCM$+%``Im&pc)b20ECGB843HKuBdf(PiTN2Y-bVOpHpN}T zq4CIdQzqb-(y&$R=C~+KT)@9#Wh2C|TRr@8gbUbm9Ls{rXhfTr_v44fh>u@6;KhBi zb=3Ndup^qwIQbC2u4J}#EU|j(E8>_c=F%-OJQ=vToLE_nAMW&EtGM`(=W7+G{?O4t z=2r1b!&W&lW_*oa>YG*EG-jN^!Y%}U86kc>Ao1-75SHio^LAc~QTm=e!)>@IemG+| zc1M1jWFVW=i-7ihQ_}9B6d`^AvhPeES(Xyd$^tMAMrN38=Ex+9hWNFM&RSjN#E|iI z%ut3Jl<{L)Cb~GH7~#P6@7Z;ByrZCr)uJ{Q?`gs?YB*d?C^fUT%Jo)lo`lXmH zca6549AqbXV~qHvEJcw)p@dT6lkbh+pwwO%Gs; z7Nuo(kLV{B`&#fM@B{v(`9)Fl3mqRTYyK5ii^=HWCISCiQC?gzPeru5d&EODckx)g z-mA>NPN}<`_=wR+7vOk6=ww98!2|K*hc5D8pp#B&FquSRBUw((i((Y2qlk=H+0(C) zI;Ox50bvP427VP`lyw{+Q;i?)vi3!G@UNKoK(O;^=Lt(Aei%PIVONsLX>nmV1MeUn z6<25s$NmVAD2yLIN#`VnQ)z>p=kOZi7vgMq65v-4=fB+DBEHi&;k=OP9E|TUQtC`b zC&ETehv!GdE}i#0Iyx!`gZP@u=>OyMd>E{BR8M4U8)TkLVeWi&iWEbQX3a z1zh&yho#7M*(X)&aqHva6~y9_WozuJtLPD60nlM2#4mb>G6&*h3_H(IhGEO}eD%!Y zFn;)?8PG2J1-8~q7sEO44RF-+50U>uM>;9q0yFr6!u%98F2gZH{LtfHBZ$wl%7ATM zL-$1-)sPcCw3nUsUc~)!Ja$EY({!JTFw|aW->2G&0p@a_D_BVUApb@ED;+mAFW->w zr{Wgq*8w0}+QHas;)l<B*-U#`*1 zky2#DrdII_=3T?q8}gv)THSEJ5&Hbq_@O{1tH=6^c-CB3hRNL~0wN~L0e+ci^Ua=A zLHtl~4rtP-7ehlvtUHV!wv*0?+4y)Lz|L9;L-a6xTEhGC;~O4+&8Ou>qiivdH7Leh zk6*my>DRVvhq3E!mWfg0F#VjJC6KI$&r?GD;!$Kc-^2pP53c}Tc#Uh~hcE?HFWfp#D07#jTBx_6&Jp_Arbt4^8dvfU!@s#|fTIj$YhVjE~Gywe?{0#K# zd$N_R-o-t+M`*_IvR)C!54U-4hSot=#<%m=ScqD8LkENSp*;dUbdc&2f&~R5vxtyd zP|&Y1ez*%fZO3Zjhi7Eh;l$pypNg}xt0%D=<2r43WihTY zK7$`W{{^ewzd$0dr{MSYTCgohDj9zKP?>A2$Hn|YERt-6AJtJwsqQqmIXtV9C`+5) zqv}$$&+2#{PiD{tr(-?(jHsjCF~DuSwXjUf<1*}zA#d)2qiHOVSW{oWs9`nd#MKIg z%*+T}S=hrho_A3~{IG|=t9b1ZsMSp}(TkYpFIdso0p`sHJ70re7u23|d_2Z!%01=u z$*FhSKTr#0;w+4e)(bEH1=!-S;UIv_I^CV*LJk@GzbDpjXxQ?&6*gqff-m95I>nuy zetGp9NseZ;V}6o^aXAk7R~ci^cy^WRHvn61ybw!wzmO>%f}8Ov`7e>KF|393{KK$* zqZ|yU1pK-{GuBdTsXY>_l=|U3wvQXQF-Pxas9@M zO31(b`DrK^t7t@ZQsNQghu*qZ`Pcg%0NdK`W^;|4GDZ;N&tRXmzTcgn5I<}!Ps(=R zEjFs{PV93?jWK78eQ0vV$1jsZI#|<;h!b;ajD5d@u@5ifI*<(ad8`in3#!*J)V~_` zrc(j7e&U>=g?k~?@UQVmS7e1d6=6e(?Wc@mFw|Z4CJONjF_6K43&v10NFTovC#|Thur4{@*}o!2(AbW z6 zoLX8vq6y$TO-}Oq3=rp<6RoG3e?{5=*~!n)S9zi^cD6q~ldADU6WBy0^k-_Fxu5n* zY@{FJNmmXUm-6(&uiseIQCQ}s^MK1dF4#jG{LA5WPf-zQeiMDf-GEHK4Tp*+3af0V zm9gG`kC~#~lq_1i#B^FO=jMI>HDenDxVid0@DKOmZ3r(&SQ zbq&W8Mq5rbUTbfp_q*2GrOkRjTXzTgl>&aft3KpR&KiF~$JMNIdhOIkAlkxQ{Is#& z(=V>y&>B}VM(A`Fakk9VV@fmVhXeeQ@aNAl4z4x+3mwkiSO#n@WP=Y2Y1p1U^h{Ioienc@-#jhvf2_sr4jlCQW9RpruIA$dgkBThOx{Umnxrd5~EH$Rg zJk0^_G&E0*|b%0-Mpt(A9Hbm>}J7t#Rhp?GtHs;=Xas9?aSW`O z`PXY$ui4IKrd@|1VBsp_#r&)y6s-oon196v8-4r&?ZQm&C|eWi4@*>ZVg!b$C`T=s zdN(o%%QnfW#V@9u@V?SE#}6^&*CXp~2-^ev;`m|3fmuelMvQRWW}opo9dt8!C|XYy zzs@aMxEh&9&=U*%>unhP3v@jpSY3@DItN#FQDR?zN}f?&L(st?6g18+!6J=%`ZZEF zLjZ+eiOpM@pgk&Gj{T*$p*J5-mY`og|0>sgL@Ei!VG2p0lXh$#_|HSi8I0P(=UNlRm zD1cVI28fucgVo#UN5HQ{ImE9zwj{`QZ9_N<=E4E}Z%2H!)7{i!g!wNIzo04Cn2$zg zxLI}r?`7+ktBoH%t=h`WzZkYW;cryy+wU96_j>xp{EP8x^=uKBd+hXZ3iTV5A8^V@ zvft+87wQk+?U}9OCye=YUM(!fi^kQ?cSrV=8p_7!JpI!3hq9|h!xkU___erfk6SDl zTY%;G;rhda#ZFW{K=lqXK`0nIomb`jrIV4H2=Hr3{knnVT^qKD;qJcDjO=lz+UppO83OtMWPrBz9erqwK;i`c zg?)&w(g?9&45=eDpC;#xfPXa{r=Lf@EfYPO{@?5-K*WkaXGd`~#IHIGhw-bhSPP5d z>}&lio?KHfs{G4B*f5e+-DP{w1b(^QP%QviOUS>hy2FB9kyNi>VLJ<)2y^6M4gcCp zo7}cV)?x8;dsBCtv_^qnm!m{k8Z}cHL;_XW}4Y$yfU_t5REP>Nm{O zkuLc%*1|k(0GGdNe1}eNeX5K_3HjG<*bTTMu5nVH%PpJ&;TY=|Saw@jzhTki5#-f& zLJM{;f>0D^=)0rIZMFPsIqiw055_u^FR}1yER%duJ*ZLw&Qi#~Y}yNVe;N2!9AN~C z<+%RPYE=M*8vX^gg{*_ezZ`@O&(n{9Xo-(_@vLjJW+yNmRS;7xG`Uu)MKiETA8V zo5~x@ZPyU;Ujm@u^RGVh(=_O$t&|m|JiG~O_3p-`S~r+9W2Gwpn#Rm!#Cizs)drOu zv4%v_0;%cdHYBw!o0hH2H1W7d4LA`iGYx`SR9I7Azo=me#s%8NZV&5MT(z}hn{+M$ z_=fxo;k5{?UoZRf1pVrXd3p2eYxvhWI?k|_1%Byz(POa>(sOFI8$W6EMtZqF%0A(2 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 Date: Thu, 20 Feb 2025 15:55:43 +0000 Subject: [PATCH 055/105] fix build failure on linux machines with fwd decl --- fpga/fpga_icopyx_hf.bit | Bin 72749 -> 72749 bytes fpga/fpga_pm3_felica.bit | Bin 42176 -> 42176 bytes fpga/fpga_pm3_hf.bit | Bin 42172 -> 42172 bytes fpga/fpga_pm3_hf_15.bit | Bin 42175 -> 42175 bytes fpga/fpga_pm3_lf.bit | Bin 42172 -> 42172 bytes fpga/strip_date_time_from_binary.py | 13 ++++++------- tools/fpga_compress/fpga_compress.c | 4 ++++ 7 files changed, 10 insertions(+), 7 deletions(-) diff --git a/fpga/fpga_icopyx_hf.bit b/fpga/fpga_icopyx_hf.bit index 77d436c59aea22951de6630419eb22e532a03267..a7824dd7466bff3e48074d9a3463301d0c6de14d 100644 GIT binary patch delta 65 zcmZ3xgJtaw7A^)({tpm1keju30Wvc0x!ocYU1E~y*(xGVv42%p64D1Gs2?|J{F=cBC<4I`% D(T)wM diff --git a/fpga/fpga_pm3_felica.bit b/fpga/fpga_pm3_felica.bit index b8f8e80cc20bd4fab9dcb9b29b0158e300ec32a1..38663847852f0ef934a04c61b3f553bf415a61e9 100644 GIT binary patch delta 63 zcmX?blIg%nCN2g}{tpm1k66eju30WuoPl!ocYU1E~xQ1)*sM42%p64D1Gs35*IzU}H+m5&+Fh B4RZhh diff --git a/fpga/fpga_pm3_hf.bit b/fpga/fpga_pm3_hf.bit index fdd4bb33305a996ef5de8d90075bc1e49a576146..1c159a6129b8d66e077a1a652f2ae83924c8d731 100644 GIT binary patch delta 67 zcmdmUl4;LLCN2g}{tpm1k)u56@mZ& delta 67 zcmdmUl4;LLCN>66eju30WuWJl!ocYU1E~xQ1)*sM42%p64D1Gs2_Tw70SRnOiC6*v D^D_-^ diff --git a/fpga/fpga_pm3_hf_15.bit b/fpga/fpga_pm3_hf_15.bit index f6ee54ecba5cfdde0c58b585fe0e5d6214cf0be3..055615e712c43a20698267dbd42fbe529c388fd4 100644 GIT binary patch delta 64 zcmdmgl4<`*CN2g}{tpm1k66eju30WvuO%!ocYU1E~xQ1)*sM42%p64D1Gs35*O1NMK`1^b!Ep CsSR)d diff --git a/fpga/fpga_pm3_lf.bit b/fpga/fpga_pm3_lf.bit index 862b3148e58c107427150588d7e2960149acc3c5..79485cac164a31399ca0a04e2ff485f67b97ddf7 100644 GIT binary patch delta 67 zcmdmUl4;LLCN2g}{tpm1k)u56@mZ& delta 67 zcmdmUl4;LLCN>66eju30WuWJl!ocYU1E~xQ1)*sM42%p64D1Gs2_Tw70SRnOiC6*v D^D_-^ diff --git a/fpga/strip_date_time_from_binary.py b/fpga/strip_date_time_from_binary.py index 12e34c9dd..cc469ca72 100644 --- a/fpga/strip_date_time_from_binary.py +++ b/fpga/strip_date_time_from_binary.py @@ -15,20 +15,20 @@ def parse_and_split_file(filename): with open(filename, 'rb') as f: # Read as binary to handle non-text files data = f.read(100) # Read first 100 bytes which should contain all information - decoded_data = list(data.decode(errors='ignore')) + decoded_data = bytearray(data) for i in range(len(decoded_data) - 3): # subsequent two bytes after marker are null and the length - next_byte = ord(decoded_data[i+1]) - data_length = ord(decoded_data[i+2]) + next_byte = decoded_data[i+1] + data_length = decoded_data[i+2] - 1 # Don't overwrite terminating char - if decoded_data[i] == split_chars[0] and next_byte == 0x0: + if decoded_data[i] == ord(split_chars[0]) and next_byte == 0x0: start = i+3 - extracted_data.append(''.join(decoded_data[start:start+data_length])) + extracted_data.append(decoded_data[start:start+data_length]) # time, date if split_chars[0] == 'c' or split_chars[0] == 'd': - decoded_data[start:start+data_length] = 'F' * data_length + decoded_data[start:start+data_length] = bytes('F', encoding='ascii') * data_length split_chars.pop(0) @@ -36,7 +36,6 @@ def parse_and_split_file(filename): break print("Extracted data from bitfile: {}".format(extracted_data)) - decoded_data = ''.join(decoded_data).encode() with open(filename, 'r+b') as f: # Write back modified bytes f.seek(0) diff --git a/tools/fpga_compress/fpga_compress.c b/tools/fpga_compress/fpga_compress.c index a623b0b32..a24b035df 100644 --- a/tools/fpga_compress/fpga_compress.c +++ b/tools/fpga_compress/fpga_compress.c @@ -23,6 +23,8 @@ #include "fpga.h" #include "lz4hc.h" +int fileno(FILE *); + #ifndef MIN #define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif @@ -356,6 +358,8 @@ static int FpgaGatherVersion(FILE *infile, char *infile_name, char *dst, int len for (uint16_t i = 0; i < FPGA_BITSTREAM_FIXED_HEADER_SIZE; i++) { if (fgetc(infile) != bitparse_fixed_header[i]) { fprintf(stderr, "Invalid FPGA file. Aborting...\n\n"); + fprintf(stderr, "File: %s\n", infile_name); + return (EXIT_FAILURE); } } From c36b352c2f16fb3f0b670a3156d3059526dfc6f2 Mon Sep 17 00:00:00 2001 From: Donny <107092000+Donny-Guo@users.noreply.github.com> Date: Wed, 19 Feb 2025 16:04:19 -0800 Subject: [PATCH 056/105] Fix incorrect encoding for HID with long format on sim and clone --- armsrc/lfops.c | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/armsrc/lfops.c b/armsrc/lfops.c index e81d5570c..d28e4f4e7 100644 --- a/armsrc/lfops.c +++ b/armsrc/lfops.c @@ -968,13 +968,7 @@ void CmdHIDsimTAGEx(uint32_t hi2, uint32_t hi, uint32_t lo, uint8_t longFMT, boo uint16_t n = 8; if (longFMT) { - // Ensure no more than 84 bits supplied - if (hi2 > 0xFFFFF) { - DbpString("Tags can only have 84 bits."); - return; - } bitlen = 8 + 8 * 2 + 84 * 2; - hi2 |= 0x9E00000; // 9E: long format identifier manchesterEncodeUint32(hi2, 16 + 12, bits, &n); manchesterEncodeUint32(hi, 32, bits, &n); manchesterEncodeUint32(lo, 32, bits, &n); @@ -2270,15 +2264,10 @@ void CopyHIDtoT55x7(uint32_t hi2, uint32_t hi, uint32_t lo, uint8_t longFMT, boo uint8_t last_block = 0; if (longFMT) { - // Ensure no more than 84 bits supplied - if (hi2 > 0xFFFFF) { - DbpString("Tags can only have 84 bits"); - return; - } // Build the 6 data blocks for supplied 84bit ID last_block = 6; - // load preamble (1D) & long format identifier (9E manchester encoded) - data[1] = 0x1D96A900 | (manchesterEncode2Bytes((hi2 >> 16) & 0xF) & 0xFF); + // load preamble (1D) + data[1] = 0x1D000000 | (manchesterEncode2Bytes((hi2 >> 16) & 0xFFFF) & 0xFFFFFF); // load raw id from hi2, hi, lo to data blocks (manchester encoded) data[2] = manchesterEncode2Bytes(hi2 & 0xFFFF); data[3] = manchesterEncode2Bytes(hi >> 16); From 7923d07ed01b2036283da085c339d289c60ba1fe Mon Sep 17 00:00:00 2001 From: Donny <107092000+Donny-Guo@users.noreply.github.com> Date: Thu, 20 Feb 2025 13:57:57 -0800 Subject: [PATCH 057/105] Add hid preamble handle function for standalone --- armsrc/Standalone/lf_hidbrute.c | 5 +++-- armsrc/Standalone/lf_hidfcbrute.c | 3 +-- armsrc/Standalone/lf_prox2brute.c | 10 +++++----- armsrc/lfops.c | 27 +++++++++++++++++++++++++++ armsrc/lfops.h | 3 ++- 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/armsrc/Standalone/lf_hidbrute.c b/armsrc/Standalone/lf_hidbrute.c index 87878cfa3..6bdf27384 100644 --- a/armsrc/Standalone/lf_hidbrute.c +++ b/armsrc/Standalone/lf_hidbrute.c @@ -150,7 +150,7 @@ void RunMod(void) { } else if (playing && selected == 2) { // Now it work only with HID Corporate 1000 (35bit), but is easily extensible to others RFID. // It is necessary only to calculate the correct parity. - + // Brute force code // Check if the badge is an HID Corporate 1000 if ((high[selected] & 0xFFFFFFF8) != 0x28) { @@ -257,7 +257,7 @@ void hid_corporate_1000_calculate_checksum_and_set(uint32_t *high, uint32_t *low // Calculate new high and low base value from card number and facility code, without parity new_low = (fc << 21) | (cardnum << 1); - new_high = 0x28 | ((fc >> 11) & 1); // 0x28 is 101000 + new_high = (fc >> 11) & 1; int n_ones; uint32_t i; @@ -319,6 +319,7 @@ void hid_corporate_1000_calculate_checksum_and_set(uint32_t *high, uint32_t *low new_high = new_high | 0x4; // Setting new calculated values + add_HID_preamble(0, &new_high, &new_low, 35); *low = new_low; *high = new_high; } diff --git a/armsrc/Standalone/lf_hidfcbrute.c b/armsrc/Standalone/lf_hidfcbrute.c index 75c97e0bf..ef7102fdb 100644 --- a/armsrc/Standalone/lf_hidfcbrute.c +++ b/armsrc/Standalone/lf_hidfcbrute.c @@ -176,8 +176,7 @@ void hid_calculate_checksum_and_set(uint32_t *high, uint32_t *low, uint32_t card newlow |= oddparity32((newlow >> 1) & 0xFFF); newlow |= (evenparity32((newlow >> 13) & 0xFFF)) << 25; - newhigh |= 0x20; // Bit 37; standard header - newlow |= 1U << 26; // leading 1: start bit + add_HID_preamble(NULL, &newhigh, &newlow, 26); *low = newlow; *high = newhigh; diff --git a/armsrc/Standalone/lf_prox2brute.c b/armsrc/Standalone/lf_prox2brute.c index 851dd597a..ab736a9d1 100644 --- a/armsrc/Standalone/lf_prox2brute.c +++ b/armsrc/Standalone/lf_prox2brute.c @@ -16,8 +16,8 @@ //----------------------------------------------------------------------------- // LF HID ProxII Brutforce v2 by lnv42 - based on Proxbrute by Brad antoniewicz // -// Following code is a trivial brute forcer for when you know the facility -// code and want to find valid(s) card number(s). It will try all card +// Following code is a trivial brute forcer (H10301 26-bit) when you know the +// facility code and want to find valid(s) card number(s). It will try all card // fnumbers rom CARDNUM_START to CARDNUM_END one by one (max. ~65k tries). // This brute force will be a lot faster than Proxbrute that will try all // possibles values for LF low, even those with bad checksum (~4g tries). @@ -46,8 +46,7 @@ void RunMod(void) { StandAloneMode(); Dbprintf(">> LF HID proxII bruteforce v2 a.k.a Prox2Brute Started <<"); FpgaDownloadAndGo(FPGA_BITSTREAM_LF); - - const uint32_t high = 0x20; // LF high value is always 0x20 here + uint32_t high = 0, low = 0; uint32_t fac = FACILITY_CODE, cardnum = 0; @@ -79,9 +78,10 @@ void RunMod(void) { if (BUTTON_HELD(1000) == BUTTON_HOLD) break; // long button press (>=1sec) exit // calculate the new LF low value including Card number, Facility code and checksum - uint32_t low = (cardnum << 1) | (fac << 17); + low = (cardnum << 1) | (fac << 17); low |= oddparity32((low >> 1) & 0xFFF); low |= evenparity32((low >> 13) & 0xFFF) << 25; + add_HID_preamble(NULL, &high, &low, 26); Dbprintf("[=] trying Facility = %08x, Card = %08x, raw = %08x%08x", fac, cardnum, high, low); diff --git a/armsrc/lfops.c b/armsrc/lfops.c index d28e4f4e7..8215a07ea 100644 --- a/armsrc/lfops.c +++ b/armsrc/lfops.c @@ -944,6 +944,33 @@ static void fcAll(uint8_t fc, int *n, uint8_t clock, int16_t *remainder) { } } +bool add_HID_preamble(uint32_t *hi2, uint32_t *hi, uint32_t *lo, uint8_t length){ + // Invalid value + if (length > 84 || length == 0) + return false; + + if (length == 48) { + *hi |= 1U << (length - 32); // Example leading 1: start bit + return true; + } + if (length >= 64) { + *hi2 |= 0x09e00000; // Extended-length header + *hi2 |= 1U << (length - 64); // leading 1: start bit + } else if (length > 37) { + *hi2 |= 0x09e00000; // Extended-length header + *hi |= 1U << (length - 32); // leading 1: start bit + } else if (length == 37) { + // No header bits added to 37-bit cards + } else if (length >= 32) { + *hi |= 0x20; // Bit 37; standard header + *hi |= 1U << (length - 32); // leading 1: start bit + } else { + *hi |= 0x20; // Bit 37; standard header + *lo |= 1U << length; // leading 1: start bit + } + return true; +} + // prepare a waveform pattern in the buffer based on the ID given then // simulate a HID tag until the button is pressed void CmdHIDsimTAGEx(uint32_t hi2, uint32_t hi, uint32_t lo, uint8_t longFMT, bool ledcontrol, int numcycles) { diff --git a/armsrc/lfops.h b/armsrc/lfops.h index c3f00b4af..9d9a029d4 100644 --- a/armsrc/lfops.h +++ b/armsrc/lfops.h @@ -24,7 +24,7 @@ void ModThenAcquireRawAdcSamples125k(uint32_t delay_off, uint16_t period_0, uint16_t period_1, const uint8_t *symbol_extra, uint16_t *period_extra, uint8_t *command, bool verbose, bool keep_field_on, uint32_t samples, bool ledcontrol); - + void ReadTItag(bool ledcontrol); void WriteTItag(uint32_t idhi, uint32_t idlo, uint16_t crc, bool ledcontrol); @@ -34,6 +34,7 @@ void SimulateTagLowFrequencyEx(int period, int gap, bool ledcontrol, int numcycl void SimulateTagLowFrequency(int period, int gap, bool ledcontrol); void SimulateTagLowFrequencyBidir(int divisor, int max_bitlen); +bool add_HID_preamble(uint32_t *hi2, uint32_t *hi, uint32_t *lo, uint8_t length); void CmdHIDsimTAGEx(uint32_t hi2, uint32_t hi, uint32_t lo, uint8_t longFMT, bool ledcontrol, int numcycles); void CmdHIDsimTAG(uint32_t hi2, uint32_t hi, uint32_t lo, uint8_t longFMT, bool ledcontrol); From cef07dedf6161809c26b523e1d6892a98261faf6 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Fri, 21 Feb 2025 15:38:33 +0100 Subject: [PATCH 058/105] code style, code clean up of redundant functions, comments, its many minor fixes across the platform. Sorry for not making 20 commits --- .github/workflows/codeql-analysis.yml | 7 + armsrc/BigBuf.c | 6 +- armsrc/Standalone/hf_colin.c | 8 +- armsrc/Standalone/hf_mattyrun.c | 2 +- armsrc/Standalone/lf_hidbrute.c | 2 +- armsrc/Standalone/lf_prox2brute.c | 4 +- armsrc/appmain.c | 57 ++++- armsrc/desfire_crypto.c | 181 +++++++++------ armsrc/desfire_crypto.h | 4 +- armsrc/emvsim.c | 7 +- armsrc/hitagS.c | 2 +- armsrc/iso14443a.c | 20 +- armsrc/iso14443a.h | 2 +- armsrc/iso15693.c | 8 - armsrc/iso15693.h | 1 - armsrc/lfops.c | 4 +- armsrc/lfops.h | 2 +- armsrc/mifarecmd.c | 293 +++++++++++------------- armsrc/mifarecmd.h | 2 - armsrc/mifaresim.c | 127 ++++++---- armsrc/mifareutil.c | 13 +- armsrc/mifareutil.h | 5 +- armsrc/sam_common.c | 4 +- armsrc/sam_picopass.c | 20 +- armsrc/sam_seos.c | 6 +- armsrc/spiffs_check.c | 8 +- client/luascripts/lf_t55xx_chk.lua | 16 +- client/luascripts/lf_t55xx_chk_date.lua | 8 +- client/luascripts/lf_t55xx_fix.lua | 12 +- client/pyscripts/PAXTON_NET.py | 2 +- client/pyscripts/Paxton_convert.py | 2 +- client/pyscripts/Paxton_switch.py | 2 +- client/pyscripts/intertic.py | 4 +- client/src/cmdhf14a.c | 17 +- client/src/cmdhf15.c | 7 +- client/src/cmdhficlass.c | 22 +- client/src/cmdhfmf.c | 81 ++++--- client/src/cmdhfmfp.c | 1 - client/src/cmdhfmfu.c | 2 - client/src/cmdhfseos.c | 24 +- client/src/cmdhfst25ta.c | 1 - client/src/crypto/originality.c | 21 +- client/src/mifare/desfirecore.c | 6 +- client/src/mifare/mad.c | 27 ++- client/src/mifare/mad.h | 4 +- client/src/mifare/mifarehost.c | 17 +- client/src/mifare/mifarehost.h | 1 + client/src/wiegand_formats.c | 12 +- client/src/wiegand_formatutils.c | 10 +- common/commonutil.c | 22 +- common/commonutil.h | 5 +- common/mbedtls/asn1parse.c | 6 +- common/mbedtls/cmac.c | 9 +- doc/commands.json | 46 ++-- doc/commands.md | 2 +- include/pm3_cmd.h | 3 + include/protocols.h | 6 + 57 files changed, 672 insertions(+), 521 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 340b53555..26bac8435 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -45,6 +45,13 @@ jobs: - name: Install dependencies run: sudo apt-get install -yqq make autoconf build-essential ca-certificates pkg-config libreadline-dev gcc-arm-none-eabi libnewlib-dev qtbase5-dev libbz2-dev liblz4-dev libbluetooth-dev libpython3-dev python3 python3-dev libpython3-all-dev liblua5.4-dev liblua5.4-0 lua5.4 sed libssl-dev + - name: Install Python dependencies + run: | + python3 -m pip install --upgrade pip + python3 -m pip install setuptools + python3 -m pip install ansicolors sslcrypto + if [ -f requirements.txt ]; then python3 -m pip install -r requirements.txt; fi + - name: Checkout repository uses: actions/checkout@v4 diff --git a/armsrc/BigBuf.c b/armsrc/BigBuf.c index f3f84af13..26af3af0d 100644 --- a/armsrc/BigBuf.c +++ b/armsrc/BigBuf.c @@ -321,9 +321,10 @@ bool RAMFUNC LogTraceBits(const uint8_t *btBytes, uint16_t bitLen, uint32_t time // Emulator memory int emlSet(const uint8_t *data, uint32_t offset, uint32_t length) { uint8_t *mem = BigBuf_get_EM_addr(); - if (!mem) { + if (mem == NULL) { return PM3_EMALLOC; } + if (offset + length <= CARD_MEMORY_SIZE) { memcpy(mem + offset, data, length); return PM3_SUCCESS; @@ -335,9 +336,10 @@ int emlSet(const uint8_t *data, uint32_t offset, uint32_t length) { int emlGet(uint8_t *out, uint32_t offset, uint32_t length) { uint8_t *mem = BigBuf_get_EM_addr(); - if (!mem) { + if (mem == NULL) { return PM3_EMALLOC; } + if (offset + length <= CARD_MEMORY_SIZE) { memcpy(out, mem + offset, length); return PM3_SUCCESS; diff --git a/armsrc/Standalone/hf_colin.c b/armsrc/Standalone/hf_colin.c index ad45cda1a..61c93514b 100644 --- a/armsrc/Standalone/hf_colin.c +++ b/armsrc/Standalone/hf_colin.c @@ -311,7 +311,7 @@ void WriteTagToFlash(uint32_t uid, size_t size) { uint32_t len = size; uint8_t data[(size * (16 * 64)) / 1024]; - emlGetMem(data, 0, (size * 64) / 1024); + emlGetMem_xt(data, 0, (size * 64) / 1024, MIFARE_BLOCK_SIZE); char dest[SPIFFS_OBJ_NAME_LEN]; uint8_t buid[4]; @@ -646,7 +646,7 @@ failtag: emlClearMem(); uint8_t mblock[16]; for (uint8_t sectorNo = 0; sectorNo < sectorsCnt; sectorNo++) { - emlGetMem(mblock, FirstBlockOfSector(sectorNo) + NumBlocksPerSector(sectorNo) - 1, 1); + emlGetMem_xt(mblock, FirstBlockOfSector(sectorNo) + NumBlocksPerSector(sectorNo) - 1, 1, MIFARE_BLOCK_SIZE); for (uint8_t t = 0; t < 2; t++) { memcpy(mblock + t * 10, foundKey[t][sectorNo], 6); } @@ -807,7 +807,7 @@ int e_MifareECardLoad(uint32_t numofsectors, uint8_t keytype) { emlSetMem_xt(dataoutbuf, FirstBlockOfSector(s) + blockNo, 1, 16); } else { // sector trailer, keep the keys, set only the AC - emlGetMem(dataoutbuf2, FirstBlockOfSector(s) + blockNo, 1); + emlGetMem_xt(dataoutbuf2, FirstBlockOfSector(s) + blockNo, 1, MIFARE_BLOCK_SIZE); memcpy(&dataoutbuf2[6], &dataoutbuf[6], 4); emlSetMem_xt(dataoutbuf2, FirstBlockOfSector(s) + blockNo, 1, 16); } @@ -878,7 +878,7 @@ void saMifareMakeTag(void) { int flags = 0; for (int blockNum = 0; blockNum < 16 * 4; blockNum++) { uint8_t mblock[16]; - emlGetMem(mblock, blockNum, 1); + emlGetMem_xt(mblock, blockNum, 1, MIFARE_BLOCK_SIZE); // switch on field and send magic sequence if (blockNum == 0) flags = 0x08 + 0x02; diff --git a/armsrc/Standalone/hf_mattyrun.c b/armsrc/Standalone/hf_mattyrun.c index ad1e6b863..615d79718 100644 --- a/armsrc/Standalone/hf_mattyrun.c +++ b/armsrc/Standalone/hf_mattyrun.c @@ -500,7 +500,7 @@ void RunMod(void) { uint8_t mblock[MIFARE_BLOCK_SIZE]; for (uint8_t sectorNo = 0; sectorNo < sectorsCnt; ++sectorNo) { if (validKey[0][sectorNo] || validKey[1][sectorNo]) { - emlGetMem(mblock, FirstBlockOfSector(sectorNo) + NumBlocksPerSector(sectorNo) - 1, 1); + emlGetMem_xt(mblock, FirstBlockOfSector(sectorNo) + NumBlocksPerSector(sectorNo) - 1, 1, MIFARE_BLOCK_SIZE); for (uint8_t keyType = 0; keyType < 2; ++keyType) { if (validKey[keyType][sectorNo]) { memcpy(mblock + keyType * 10, foundKey[keyType][sectorNo], 6); diff --git a/armsrc/Standalone/lf_hidbrute.c b/armsrc/Standalone/lf_hidbrute.c index 6bdf27384..822df3bfb 100644 --- a/armsrc/Standalone/lf_hidbrute.c +++ b/armsrc/Standalone/lf_hidbrute.c @@ -150,7 +150,7 @@ void RunMod(void) { } else if (playing && selected == 2) { // Now it work only with HID Corporate 1000 (35bit), but is easily extensible to others RFID. // It is necessary only to calculate the correct parity. - + // Brute force code // Check if the badge is an HID Corporate 1000 if ((high[selected] & 0xFFFFFFF8) != 0x28) { diff --git a/armsrc/Standalone/lf_prox2brute.c b/armsrc/Standalone/lf_prox2brute.c index ab736a9d1..86c06a91a 100644 --- a/armsrc/Standalone/lf_prox2brute.c +++ b/armsrc/Standalone/lf_prox2brute.c @@ -46,7 +46,7 @@ void RunMod(void) { StandAloneMode(); Dbprintf(">> LF HID proxII bruteforce v2 a.k.a Prox2Brute Started <<"); FpgaDownloadAndGo(FPGA_BITSTREAM_LF); - uint32_t high = 0, low = 0; + uint32_t high = 0; uint32_t fac = FACILITY_CODE, cardnum = 0; @@ -78,7 +78,7 @@ void RunMod(void) { if (BUTTON_HELD(1000) == BUTTON_HOLD) break; // long button press (>=1sec) exit // calculate the new LF low value including Card number, Facility code and checksum - low = (cardnum << 1) | (fac << 17); + uint32_t low = (cardnum << 1) | (fac << 17); low |= oddparity32((low >> 1) & 0xFFF); low |= evenparity32((low >> 13) & 0xFFF) << 25; add_HID_preamble(NULL, &high, &low, 26); diff --git a/armsrc/appmain.c b/armsrc/appmain.c index aeff70df2..9140c03c3 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -1370,7 +1370,11 @@ static void PacketReceived(PacketCommandNG *packet) { // involved in dealing with emulator memory. But if it is called later, it might // destroy the Emulator Memory. //----------------------------------------------------------------------------- - EmlClearIso15693(); + // Resetting the bitstream also frees the BigBuf memory, so we do this here to prevent + // an inconvenient reset in the future by Iso15693InitTag + FpgaDownloadAndGo(FPGA_BITSTREAM_HF_15); + BigBuf_Clear_EM(); + reply_ng(CMD_HF_ISO15693_EML_CLEAR, PM3_SUCCESS, NULL, 0); break; } case CMD_HF_ISO15693_EML_SETMEM: { @@ -1402,7 +1406,7 @@ static void PacketReceived(PacketCommandNG *packet) { return; } - uint8_t *buf = BigBuf_malloc(payload->length); + uint8_t *buf = BigBuf_calloc(payload->length); emlGet(buf, payload->offset, payload->length); LED_B_ON(); reply_ng(CMD_HF_ISO15693_EML_GETMEM, PM3_SUCCESS, buf, payload->length); @@ -1677,7 +1681,7 @@ static void PacketReceived(PacketCommandNG *packet) { EMVsim(payload->flags, payload->exitAfter, payload->uid, payload->atqa, payload->sak); break; } -#endif +#endif case CMD_HF_ISO14443A_SIMULATE: { struct p { uint8_t tagtype; @@ -1890,36 +1894,65 @@ static void PacketReceived(PacketCommandNG *packet) { break; } case CMD_HF_MIFARE_EML_MEMCLR: { - MifareEMemClr(); - reply_ng(CMD_HF_MIFARE_EML_MEMCLR, PM3_SUCCESS, NULL, 0); + + //----------------------------------------------------------------------------- + // Work with emulator memory + // + // Note: we call FpgaDownloadAndGo(FPGA_BITSTREAM_HF) here although FPGA is not + // involved in dealing with emulator memory. But if it is called later, it might + // destroy the Emulator Memory. + //----------------------------------------------------------------------------- FpgaDownloadAndGo(FPGA_BITSTREAM_HF); + + // Not only clears the emulator memory, + // also sets default MIFARE values for sector trailers. + emlClearMem(); + reply_ng(CMD_HF_MIFARE_EML_MEMCLR, PM3_SUCCESS, NULL, 0); break; } case CMD_HF_MIFARE_EML_MEMSET: { + FpgaDownloadAndGo(FPGA_BITSTREAM_HF); struct p { - uint8_t blockno; + uint16_t blockno; uint8_t blockcnt; uint8_t blockwidth; uint8_t data[]; } PACKED; struct p *payload = (struct p *) packet->data.asBytes; - FpgaDownloadAndGo(FPGA_BITSTREAM_HF); - // backwards compat... default bytewidth - if (payload->blockwidth == 0) - payload->blockwidth = 16; + if (payload->blockwidth == 0) { + payload->blockwidth = MIFARE_BLOCK_SIZE; + } emlSetMem_xt(payload->data, payload->blockno, payload->blockcnt, payload->blockwidth); break; } case CMD_HF_MIFARE_EML_MEMGET: { + + FpgaDownloadAndGo(FPGA_BITSTREAM_HF); struct p { - uint8_t blockno; + uint16_t blockno; uint8_t blockcnt; + uint8_t blockwidth; } PACKED; struct p *payload = (struct p *) packet->data.asBytes; - MifareEMemGet(payload->blockno, payload->blockcnt); + + // + size_t size = payload->blockno * payload->blockwidth; + if (size > PM3_CMD_DATA_SIZE) { + reply_ng(CMD_HF_MIFARE_EML_MEMGET, PM3_EMALLOC, NULL, 0); + return; + } + + uint8_t *buf = BigBuf_calloc(size); + + emlGetMem_xt(buf, payload->blockno, payload->blockcnt, payload->blockwidth); // data, block num, blocks count (max 4) + + LED_B_ON(); + reply_ng(CMD_HF_MIFARE_EML_MEMGET, PM3_SUCCESS, buf, size); + LED_B_OFF(); + BigBuf_free_keep_EM(); break; } case CMD_HF_MIFARE_EML_LOAD: { diff --git a/armsrc/desfire_crypto.c b/armsrc/desfire_crypto.c index 24da62747..0e9dc9466 100644 --- a/armsrc/desfire_crypto.c +++ b/armsrc/desfire_crypto.c @@ -133,7 +133,9 @@ void tdes_nxp_send(const void *in, void *out, size_t length, const void *key, ui } void aes128_nxp_receive(const void *in, void *out, size_t length, const void *key, unsigned char iv[16]) { - if (length % 8) return; + if (length % 8) { + return; + } uint8_t *tin = (uint8_t *) in; uint8_t *tout = (uint8_t *) out; @@ -143,7 +145,9 @@ void aes128_nxp_receive(const void *in, void *out, size_t length, const void *ke } void aes128_nxp_send(const void *in, void *out, size_t length, const void *key, unsigned char iv[16]) { - if (length % 8) return; + if (length % 8) { + return; + } uint8_t *tin = (uint8_t *) in; uint8_t *tout = (uint8_t *) out; @@ -152,12 +156,15 @@ void aes128_nxp_send(const void *in, void *out, size_t length, const void *key, mbedtls_aes_crypt_cbc(&actx, MBEDTLS_AES_ENCRYPT, length, iv, tin, tout); } -void Desfire_des_key_new(const uint8_t value[8], desfirekey_t key) { - uint8_t data[8]; - memcpy(data, value, 8); - for (int n = 0; n < 8; n++) { +void Desfire_des_key_new(const uint8_t *value, desfirekey_t key) { + + uint8_t data[8] = {0}; + memcpy(data, value, sizeof(data)); + + for (size_t n = 0; n < sizeof(data); n++) { data[n] &= 0xFE; } + Desfire_des_key_new_with_version(data, key); } @@ -246,22 +253,24 @@ void Desfire_key_set_version(desfirekey_t key, uint8_t version) { void Desfire_session_key_new(const uint8_t rnda[], const uint8_t rndb[], desfirekey_t authkey, desfirekey_t key) { - uint8_t buffer[24]; + uint8_t buffer[24] = {0}; switch (authkey->type) { - case T_DES: + case T_DES: { memcpy(buffer, rnda, 4); memcpy(buffer + 4, rndb, 4); Desfire_des_key_new_with_version(buffer, key); break; - case T_3DES: + } + case T_3DES: { memcpy(buffer, rnda, 4); memcpy(buffer + 4, rndb, 4); memcpy(buffer + 8, rnda + 4, 4); memcpy(buffer + 12, rndb + 4, 4); Desfire_3des_key_new_with_version(buffer, key); break; - case T_3K3DES: + } + case T_3K3DES: { memcpy(buffer, rnda, 4); memcpy(buffer + 4, rndb, 4); memcpy(buffer + 8, rnda + 6, 4); @@ -270,22 +279,15 @@ void Desfire_session_key_new(const uint8_t rnda[], const uint8_t rndb[], desfire memcpy(buffer + 20, rndb + 12, 4); Desfire_3k3des_key_new(buffer, key); break; - case T_AES: + } + case T_AES: { memcpy(buffer, rnda, 4); memcpy(buffer + 4, rndb, 4); memcpy(buffer + 8, rnda + 12, 4); memcpy(buffer + 12, rndb + 12, 4); Desfire_aes_key_new(buffer, key); break; - } -} - -static size_t key_macing_length(desfirekey_t key); - -// iceman, see memxor inside string.c, dest/src swapped.. -static void xor(const uint8_t *ivect, uint8_t *data, const size_t len) { - for (size_t i = 0; i < len; i++) { - data[i] ^= ivect[i]; + } } } @@ -306,7 +308,7 @@ void cmac_generate_subkeys(desfirekey_t key) { // Used to compute CMAC on complete blocks memcpy(key->cmac_sk1, l, kbs); - txor = l[0] & 0x80; + txor = (l[0] & 0x80); lsl(key->cmac_sk1, kbs); @@ -317,7 +319,7 @@ void cmac_generate_subkeys(desfirekey_t key) { // Used to compute CMAC on the last block if non-complete memcpy(key->cmac_sk2, key->cmac_sk1, kbs); - txor = key->cmac_sk1[0] & 0x80; + txor = (key->cmac_sk1[0] & 0x80); lsl(key->cmac_sk2, kbs); @@ -341,15 +343,14 @@ void cmac(const desfirekey_t key, uint8_t *ivect, const uint8_t *data, size_t le while (len % kbs) { buffer[len++] = 0x00; } - xor(key->cmac_sk2, buffer + len - kbs, kbs); + xor(buffer + len - kbs, key->cmac_sk2, kbs); } else { - xor(key->cmac_sk1, buffer + len - kbs, kbs); + xor(buffer + len - kbs, key->cmac_sk1, kbs); } mifare_cypher_blocks_chained(NULL, key, ivect, buffer, len, MCD_SEND, MCO_ENCYPHER); memcpy(cmac, ivect, kbs); - //free(buffer); } size_t key_block_size(const desfirekey_t key) { @@ -374,7 +375,7 @@ size_t key_block_size(const desfirekey_t key) { /* * Size of MACing produced with the key. */ -static size_t key_macing_length(const desfirekey_t key) { +size_t key_macing_length(const desfirekey_t key) { size_t mac_length = DESFIRE_MAC_LENGTH; switch (key->type) { case T_DES: @@ -393,10 +394,11 @@ static size_t key_macing_length(const desfirekey_t key) { * Size required to store nbytes of data in a buffer of size n*block_size. */ size_t padded_data_length(const size_t nbytes, const size_t block_size) { - if ((!nbytes) || (nbytes % block_size)) + if ((!nbytes) || (nbytes % block_size)) { return ((nbytes / block_size) + 1) * block_size; - else + } else { return nbytes; + } } /* @@ -412,12 +414,14 @@ size_t enciphered_data_length(const desfiretag_t tag, const size_t nbytes, int c size_t crc_length = 0; if (!(communication_settings & NO_CRC)) { switch (DESFIRE(tag)->authentication_scheme) { - case AS_LEGACY: + case AS_LEGACY: { crc_length = 2; break; - case AS_NEW: + } + case AS_NEW: { crc_length = 4; break; + } } } @@ -428,18 +432,20 @@ size_t enciphered_data_length(const desfiretag_t tag, const size_t nbytes, int c void *mifare_cryto_preprocess_data(desfiretag_t tag, void *data, size_t *nbytes, size_t offset, int communication_settings) { uint8_t *res = data; - uint8_t mac[4]; + uint8_t mac[4] = {0}; size_t edl; bool append_mac = true; - desfirekey_t key = DESFIRE(tag)->session_key; - if (!key) + desfirekey_t key = DESFIRE(tag)->session_key; + if (!key) { return data; + } switch (communication_settings & MDCM_MASK) { - case MDCM_PLAIN: - if (AS_LEGACY == DESFIRE(tag)->authentication_scheme) + case MDCM_PLAIN: { + if (AS_LEGACY == DESFIRE(tag)->authentication_scheme) { break; + } /* * When using new authentication methods, PLAIN data transmission from @@ -452,13 +458,16 @@ void *mifare_cryto_preprocess_data(desfiretag_t tag, void *data, size_t *nbytes, */ append_mac = false; - + } /* pass through */ - case MDCM_MACED: + case MDCM_MACED: { switch (DESFIRE(tag)->authentication_scheme) { - case AS_LEGACY: - if (!(communication_settings & MAC_COMMAND)) + case AS_LEGACY: { + + if (!(communication_settings & MAC_COMMAND)) { break; + } + /* pass through */ edl = padded_data_length(*nbytes - offset, key_block_size(DESFIRE(tag)->session_key)) + offset; @@ -475,8 +484,9 @@ void *mifare_cryto_preprocess_data(desfiretag_t tag, void *data, size_t *nbytes, // Copy again provided data (was overwritten by mifare_cypher_blocks_chained) memcpy(res, data, *nbytes); - if (!(communication_settings & MAC_COMMAND)) + if (!(communication_settings & MAC_COMMAND)) { break; + } // Append MAC size_t bla = maced_data_length(DESFIRE(tag)->session_key, *nbytes - offset) + offset; (void)bla++; @@ -485,9 +495,12 @@ void *mifare_cryto_preprocess_data(desfiretag_t tag, void *data, size_t *nbytes, *nbytes += 4; break; - case AS_NEW: - if (!(communication_settings & CMAC_COMMAND)) + } + case AS_NEW: { + if (!(communication_settings & CMAC_COMMAND)) { break; + } + cmac(key, DESFIRE(tag)->ivect, res, *nbytes, DESFIRE(tag)->cmac); if (append_mac) { @@ -498,10 +511,11 @@ void *mifare_cryto_preprocess_data(desfiretag_t tag, void *data, size_t *nbytes, *nbytes += DESFIRE_CMAC_LENGTH; } break; + } } - break; - case MDCM_ENCIPHERED: + } + case MDCM_ENCIPHERED: { /* |<-------------- data -------------->| * |<--- offset -->| | * +---------------+--------------------+-----+---------+ @@ -517,8 +531,10 @@ void *mifare_cryto_preprocess_data(desfiretag_t tag, void *data, size_t *nbytes, * encypher()/decypher() */ - if (!(communication_settings & ENC_COMMAND)) + if (!(communication_settings & ENC_COMMAND)) { break; + } + edl = enciphered_data_length(tag, *nbytes - offset, communication_settings) + offset; // Fill in the crypto buffer with data ... @@ -526,14 +542,16 @@ void *mifare_cryto_preprocess_data(desfiretag_t tag, void *data, size_t *nbytes, if (!(communication_settings & NO_CRC)) { // ... CRC ... switch (DESFIRE(tag)->authentication_scheme) { - case AS_LEGACY: + case AS_LEGACY: { AddCrc14A(res + offset, *nbytes - offset); *nbytes += 2; break; - case AS_NEW: + } + case AS_NEW: { crc32_append(res, *nbytes); *nbytes += 4; break; + } } } // ... and padding @@ -543,32 +561,34 @@ void *mifare_cryto_preprocess_data(desfiretag_t tag, void *data, size_t *nbytes, mifare_cypher_blocks_chained(tag, NULL, NULL, res + offset, *nbytes - offset, MCD_SEND, (AS_NEW == DESFIRE(tag)->authentication_scheme) ? MCO_ENCYPHER : MCO_DECYPHER); break; - default: - + } + default: { *nbytes = -1; res = NULL; break; + } } return res; - } void *mifare_cryto_postprocess_data(desfiretag_t tag, void *data, size_t *nbytes, int communication_settings) { + void *res = data; - uint8_t first_cmac_byte = 0x00; - - desfirekey_t key = DESFIRE(tag)->session_key; - - if (!key) { - return data; - } // Return directly if we just have a status code. if (1 == *nbytes) { return res; } + + desfirekey_t key = DESFIRE(tag)->session_key; + if (!key) { + return data; + } + + uint8_t first_cmac_byte = 0x00; + switch (communication_settings & MDCM_MASK) { case MDCM_PLAIN: { @@ -659,11 +679,12 @@ void *mifare_cryto_postprocess_data(desfiretag_t tag, void *data, size_t *nbytes break; } case MDCM_ENCIPHERED: { + (*nbytes)--; + bool verified = false; int crc_pos = 0x00; int end_crc_pos = 0x00; - uint8_t x; /* * AS_LEGACY: @@ -742,8 +763,9 @@ void *mifare_cryto_postprocess_data(desfiretag_t tag, void *data, size_t *nbytes verified = true; for (int n = end_crc_pos; n < *nbytes - 1; n++) { uint8_t byte = ((uint8_t *)res)[n]; - if (!((0x00 == byte) || ((0x80 == byte) && (n == end_crc_pos)))) + if (!((0x00 == byte) || ((0x80 == byte) && (n == end_crc_pos)))) { verified = false; + } } } @@ -768,7 +790,7 @@ void *mifare_cryto_postprocess_data(desfiretag_t tag, void *data, size_t *nbytes break; } case AS_NEW: { - x = ((uint8_t *)res)[crc_pos - 1]; + uint8_t x = ((uint8_t *)res)[crc_pos - 1]; ((uint8_t *)res)[crc_pos - 1] = ((uint8_t *)res)[crc_pos]; ((uint8_t *)res)[crc_pos] = x; break; @@ -802,9 +824,10 @@ void *mifare_cryto_postprocess_data(desfiretag_t tag, void *data, size_t *nbytes void mifare_cypher_single_block(desfirekey_t key, uint8_t *data, uint8_t *ivect, MifareCryptoDirection direction, MifareCryptoOperation operation, size_t block_size) { + uint8_t ovect[DESFIRE_MAX_CRYPTO_BLOCK_SIZE]; if (direction == MCD_SEND) { - xor(ivect, data, block_size); + xor(data, ivect, block_size); } else { memcpy(ovect, data, block_size); } @@ -812,70 +835,80 @@ void mifare_cypher_single_block(desfirekey_t key, uint8_t *data, uint8_t *ivect, uint8_t edata[DESFIRE_MAX_CRYPTO_BLOCK_SIZE] = {0}; switch (key->type) { - case T_DES: + case T_DES: { switch (operation) { - case MCO_ENCYPHER: + case MCO_ENCYPHER: { //DES_ecb_encrypt ((DES_cblock *) data, (DES_cblock *) edata, &(key->ks1), DES_ENCRYPT); des_encrypt(edata, data, key->data); break; - case MCO_DECYPHER: + } + case MCO_DECYPHER: { //DES_ecb_encrypt ((DES_cblock *) data, (DES_cblock *) edata, &(key->ks1), DES_DECRYPT); des_decrypt(edata, data, key->data); break; + } } break; - case T_3DES: + } + case T_3DES: { switch (operation) { - case MCO_ENCYPHER: + case MCO_ENCYPHER: { mbedtls_des3_set2key_enc(&ctx3, key->data); mbedtls_des3_crypt_ecb(&ctx3, data, edata); // DES_ecb_encrypt ((DES_cblock *) data, (DES_cblock *) edata, &(key->ks1), DES_ENCRYPT); // DES_ecb_encrypt ((DES_cblock *) edata, (DES_cblock *) data, &(key->ks2), DES_DECRYPT); // DES_ecb_encrypt ((DES_cblock *) data, (DES_cblock *) edata, &(key->ks1), DES_ENCRYPT); break; - case MCO_DECYPHER: + } + case MCO_DECYPHER: { mbedtls_des3_set2key_dec(&ctx3, key->data); mbedtls_des3_crypt_ecb(&ctx3, data, edata); // DES_ecb_encrypt ((DES_cblock *) data, (DES_cblock *) edata, &(key->ks1), DES_DECRYPT); // DES_ecb_encrypt ((DES_cblock *) edata, (DES_cblock *) data, &(key->ks2), DES_ENCRYPT); // DES_ecb_encrypt ((DES_cblock *) data, (DES_cblock *) edata, &(key->ks1), DES_DECRYPT); break; + } } break; - case T_3K3DES: + } + case T_3K3DES: { switch (operation) { - case MCO_ENCYPHER: + case MCO_ENCYPHER: { mbedtls_des3_set3key_enc(&ctx3, key->data); mbedtls_des3_crypt_ecb(&ctx3, data, edata); // DES_ecb_encrypt ((DES_cblock *) data, (DES_cblock *) edata, &(key->ks1), DES_ENCRYPT); // DES_ecb_encrypt ((DES_cblock *) edata, (DES_cblock *) data, &(key->ks2), DES_DECRYPT); // DES_ecb_encrypt ((DES_cblock *) data, (DES_cblock *) edata, &(key->ks3), DES_ENCRYPT); break; - case MCO_DECYPHER: + } + case MCO_DECYPHER: { mbedtls_des3_set3key_dec(&ctx3, key->data); mbedtls_des3_crypt_ecb(&ctx3, data, edata); // DES_ecb_encrypt ((DES_cblock *) data, (DES_cblock *) edata, &(key->ks3), DES_DECRYPT); // DES_ecb_encrypt ((DES_cblock *) edata, (DES_cblock *) data, &(key->ks2), DES_ENCRYPT); // DES_ecb_encrypt ((DES_cblock *) data, (DES_cblock *) edata, &(key->ks1), DES_DECRYPT); break; + } } break; - case T_AES: + } + case T_AES: { switch (operation) { case MCO_ENCYPHER: { mbedtls_aes_init(&actx); mbedtls_aes_setkey_enc(&actx, key->data, 128); - mbedtls_aes_crypt_cbc(&actx, MBEDTLS_AES_ENCRYPT, sizeof(edata), ivect, data, edata); + mbedtls_aes_crypt_ecb(&actx, MBEDTLS_AES_ENCRYPT, data, edata); break; } case MCO_DECYPHER: { mbedtls_aes_init(&actx); mbedtls_aes_setkey_dec(&actx, key->data, 128); - mbedtls_aes_crypt_cbc(&actx, MBEDTLS_AES_DECRYPT, sizeof(edata), ivect, edata, data); + mbedtls_aes_crypt_ecb(&actx, MBEDTLS_AES_DECRYPT, data, edata); break; } } break; + } } memcpy(data, edata, block_size); @@ -883,7 +916,7 @@ void mifare_cypher_single_block(desfirekey_t key, uint8_t *data, uint8_t *ivect, if (direction == MCD_SEND) { memcpy(ivect, data, block_size); } else { - xor(ivect, data, block_size); + xor(data, ivect, block_size); memcpy(ivect, ovect, block_size); } } diff --git a/armsrc/desfire_crypto.h b/armsrc/desfire_crypto.h index 3413125d4..10a15fbf9 100644 --- a/armsrc/desfire_crypto.h +++ b/armsrc/desfire_crypto.h @@ -183,7 +183,7 @@ void tdes_nxp_send(const void *in, void *out, size_t length, const void *key, ui void aes128_nxp_receive(const void *in, void *out, size_t length, const void *key, unsigned char iv[16]); void aes128_nxp_send(const void *in, void *out, size_t length, const void *key, unsigned char iv[16]); -void Desfire_des_key_new(const uint8_t value[8], desfirekey_t key); +void Desfire_des_key_new(const uint8_t *value, desfirekey_t key); void Desfire_3des_key_new(const uint8_t value[16], desfirekey_t key); void Desfire_des_key_new_with_version(const uint8_t value[8], desfirekey_t key); void Desfire_3des_key_new_with_version(const uint8_t value[16], desfirekey_t key); @@ -207,4 +207,6 @@ size_t enciphered_data_length(const desfiretag_t tag, const size_t nbytes, int c void cmac_generate_subkeys(desfirekey_t key); void cmac(const desfirekey_t key, uint8_t *ivect, const uint8_t *data, size_t len, uint8_t *cmac); +size_t key_macing_length(desfirekey_t key); + #endif diff --git a/armsrc/emvsim.c b/armsrc/emvsim.c index fd50b040d..5b2c2b58e 100644 --- a/armsrc/emvsim.c +++ b/armsrc/emvsim.c @@ -125,7 +125,7 @@ void ExecuteEMVSim(uint8_t *receivedCmd, uint16_t receivedCmd_len, uint8_t *rece Dbprintf(""); // use annotate to give some hints about the command - annotate(&receivedCmd[1], receivedCmd_len-1); + annotate(&receivedCmd[1], receivedCmd_len - 1); // This is a common request from the reader which we can just immediately respond to since we know we can't // handle it. @@ -141,7 +141,7 @@ void ExecuteEMVSim(uint8_t *receivedCmd, uint16_t receivedCmd_len, uint8_t *rece currentState = GENERATE_AC; - memcpy(receivedCmd, (unsigned char[]){ 0x03, 0x80, 0xae, 0x80, 0x00, 0x1d }, 6); + memcpy(receivedCmd, (unsigned char[]) { 0x03, 0x80, 0xae, 0x80, 0x00, 0x1d }, 6); for (int i = 0; i < 29; i++) { receivedCmd[6 + i] = receivedCmd[12 + i]; @@ -240,7 +240,8 @@ void ExecuteEMVSim(uint8_t *receivedCmd, uint16_t receivedCmd_len, uint8_t *rece 0x20, 0x00, 0x9f, 0x26, 0x08, 0x56, 0xcb, 0x4e, 0xe1, 0xa4, 0xef, 0xac, 0x74, 0x9f, 0x27, 0x01, 0x80, 0x9f, 0x36, 0x02, 0x00, 0x07, 0x9f, 0x6c, 0x02, 0x3e, 0x00, 0x9f, 0x6e, 0x04, - 0x20, 0x70, 0x00, 0x00, 0x90, 0x00, 0xff, 0xff}; + 0x20, 0x70, 0x00, 0x00, 0x90, 0x00, 0xff, 0xff + }; // do the replacement template[0] = responseToReader[0]; // class bit 0 diff --git a/armsrc/hitagS.c b/armsrc/hitagS.c index 932f4bcc1..b80210f66 100644 --- a/armsrc/hitagS.c +++ b/armsrc/hitagS.c @@ -1035,7 +1035,7 @@ static int hts_send_receive(const uint8_t *tx, size_t txlen, uint8_t *rx, size_t Dbhexdump(*rxlen, response_bit, false); Dbprintf("htS: skipping %d bit SOF", sof_bits); - if ((rx[0] >> (8 - sof_bits)) != ((1 << sof_bits) - 1)) { + if ((rx[0] >> (8 - sof_bits)) != ((1 << sof_bits) - 1)) { DBG DbpString("htS: Warning, not all bits of SOF are 1"); } } diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index ba66eb063..f469061d7 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -1455,7 +1455,7 @@ bool SimulateIso14443aInit(uint8_t tagType, uint16_t flags, uint8_t *data, // response to send, and send it. // 'hf 14a sim' //----------------------------------------------------------------------------- -void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *data, uint8_t exitAfterNReads, +void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uint8_t exitAfterNReads, uint8_t *ats, size_t ats_len) { #define ATTACK_KEY_COUNT 16 @@ -1508,7 +1508,7 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *data, uint8_ .modulation_n = 0 }; - if (SimulateIso14443aInit(tagType, flags, data, ats, ats_len, &responses, &cuid, counters, tearings, &pages) == false) { + if (SimulateIso14443aInit(tagType, flags, useruid, ats, ats_len, &responses, &cuid, counters, tearings, &pages) == false) { BigBuf_free_keep_EM(); reply_ng(CMD_HF_MIFARE_SIMULATE, PM3_EINIT, NULL, 0); return; @@ -1684,8 +1684,8 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *data, uint8_ // first blocks of emu are header uint16_t start = block * 4 + MFU_DUMP_PREFIX_LENGTH; uint8_t emdata[MAX_MIFARE_FRAME_SIZE]; - emlGet(emdata, start, 16); - AddCrc14A(emdata, 16); + emlGet(emdata, start, MIFARE_BLOCK_SIZE); + AddCrc14A(emdata, MIFARE_BLOCK_SIZE); EmSendCmd(emdata, sizeof(emdata)); numReads++; // Increment number of times reader requested a block @@ -1703,8 +1703,8 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *data, uint8_ p_response = &responses[RESP_INDEX_UIDC1]; } else { // all other tags (16 byte block tags) uint8_t emdata[MAX_MIFARE_FRAME_SIZE] = {0}; - emlGet(emdata, block, 16); - AddCrc14A(emdata, 16); + emlGet(emdata, block, MIFARE_BLOCK_SIZE); + AddCrc14A(emdata, MIFARE_BLOCK_SIZE); EmSendCmd(emdata, sizeof(emdata)); // We already responded, do not send anything with the EmSendCmd14443aRaw() that is called below p_response = NULL; @@ -1847,7 +1847,7 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *data, uint8_ } if (memcmp(pwd, "\x00\x00\x00\x00", 4) == 0) { - Uint4byteToMemLe(pwd, ul_ev1_pwdgenB(data)); + Uint4byteToMemLe(pwd, ul_ev1_pwdgenB(useruid)); if (g_dbglevel >= DBG_DEBUG) Dbprintf("Calc pwd... %02X %02X %02X %02X", pwd[0], pwd[1], pwd[2], pwd[3]); } @@ -4080,7 +4080,7 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid, dynamic_response_info.response[0] = receivedCmd[0]; dynamic_response_info.response[1] = 0x00; - switch (receivedCmd[2+offset]) { // APDU Class Byte + switch (receivedCmd[2 + offset]) { // APDU Class Byte // receivedCmd in this case is expecting to structured with possibly a CID, then the APDU command for SelectFile // | IBlock (CID) | CID | APDU Command | CRC | // or | IBlock (noCID) | APDU Command | CRC | @@ -4092,8 +4092,8 @@ void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid, // xx in this case is len of the AID value in hex // aid len is found as a hex value in receivedCmd[6] (Index Starts at 0) - int received_aid_len = receivedCmd[5+offset]; - uint8_t *received_aid = &receivedCmd[6+offset]; + int received_aid_len = receivedCmd[5 + offset]; + uint8_t *received_aid = &receivedCmd[6 + offset]; // aid enumeration flag if ((flags & FLAG_ENUMERATE_AID) == FLAG_ENUMERATE_AID) { diff --git a/armsrc/iso14443a.h b/armsrc/iso14443a.h index 34a94dbb5..7c9eed81b 100644 --- a/armsrc/iso14443a.h +++ b/armsrc/iso14443a.h @@ -142,7 +142,7 @@ RAMFUNC bool MillerDecoding(uint8_t bit, uint32_t non_real_time); RAMFUNC int ManchesterDecoding(uint8_t bit, uint16_t offset, uint32_t non_real_time); void RAMFUNC SniffIso14443a(uint8_t param); -void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *data, uint8_t exitAfterNReads, +void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uint8_t exitAfterNReads, uint8_t *iRATs, size_t irats_len); void SimulateIso14443aTagAID(uint8_t tagType, uint16_t flags, uint8_t *uid, diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index cbf9e8f88..390766099 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -2117,14 +2117,6 @@ void Iso15693InitTag(void) { StartCountSspClk(); } -void EmlClearIso15693(void) { - // Resetting the bitstream also frees the BigBuf memory, so we do this here to prevent - // an inconvenient reset in the future by Iso15693InitTag - FpgaDownloadAndGo(FPGA_BITSTREAM_HF_15); - BigBuf_Clear_EM(); - reply_ng(CMD_HF_ISO15693_EML_CLEAR, PM3_SUCCESS, NULL, 0); -} - // Simulate an ISO15693 TAG, perform anti-collision and then print any reader commands // all demodulation performed in arm rather than host. - greg void SimTagIso15693(const uint8_t *uid, uint8_t block_size) { diff --git a/armsrc/iso15693.h b/armsrc/iso15693.h index 0f04fddf9..81bc2d1a0 100644 --- a/armsrc/iso15693.h +++ b/armsrc/iso15693.h @@ -46,7 +46,6 @@ int GetIso15693AnswerFromTag(uint8_t *response, uint16_t max_len, uint16_t timeo //void RecordRawAdcSamplesIso15693(void); void AcquireRawAdcSamplesIso15693(void); void ReaderIso15693(iso15_card_select_t *p_card); // ISO15693 reader -void EmlClearIso15693(void); void SimTagIso15693(const uint8_t *uid, uint8_t block_size); // simulate an ISO15693 tag void BruteforceIso15693Afi(uint32_t flags); // find an AFI of a tag void SendRawCommand15693(iso15_raw_cmd_t *packet); // send arbitrary commands from CLI diff --git a/armsrc/lfops.c b/armsrc/lfops.c index 8215a07ea..46c1e0d3f 100644 --- a/armsrc/lfops.c +++ b/armsrc/lfops.c @@ -944,7 +944,7 @@ static void fcAll(uint8_t fc, int *n, uint8_t clock, int16_t *remainder) { } } -bool add_HID_preamble(uint32_t *hi2, uint32_t *hi, uint32_t *lo, uint8_t length){ +bool add_HID_preamble(uint32_t *hi2, uint32_t *hi, uint32_t *lo, uint8_t length) { // Invalid value if (length > 84 || length == 0) return false; @@ -963,7 +963,7 @@ bool add_HID_preamble(uint32_t *hi2, uint32_t *hi, uint32_t *lo, uint8_t length) // No header bits added to 37-bit cards } else if (length >= 32) { *hi |= 0x20; // Bit 37; standard header - *hi |= 1U << (length - 32); // leading 1: start bit + *hi |= 1U << (length - 32); // leading 1: start bit } else { *hi |= 0x20; // Bit 37; standard header *lo |= 1U << length; // leading 1: start bit diff --git a/armsrc/lfops.h b/armsrc/lfops.h index 9d9a029d4..5a25a74d8 100644 --- a/armsrc/lfops.h +++ b/armsrc/lfops.h @@ -24,7 +24,7 @@ void ModThenAcquireRawAdcSamples125k(uint32_t delay_off, uint16_t period_0, uint16_t period_1, const uint8_t *symbol_extra, uint16_t *period_extra, uint8_t *command, bool verbose, bool keep_field_on, uint32_t samples, bool ledcontrol); - + void ReadTItag(bool ledcontrol); void WriteTItag(uint32_t idhi, uint32_t idlo, uint16_t crc, bool ledcontrol); diff --git a/armsrc/mifarecmd.c b/armsrc/mifarecmd.c index 8e9d93bc7..a027dcecc 100644 --- a/armsrc/mifarecmd.c +++ b/armsrc/mifarecmd.c @@ -2223,7 +2223,7 @@ OUT: blockno = (32 * 4 + (i - 32) * 16) ^ 0xF; } // get ST - emlGetMem(block, blockno, 1); + emlGetMem_xt(block, blockno, 1, MIFARE_BLOCK_SIZE); memcpy(block, k_sector[i].keyA, 6); memcpy(block + 10, k_sector[i].keyB, 6); @@ -2427,39 +2427,6 @@ void MifarePersonalizeUID(uint8_t keyType, uint8_t perso_option, uint64_t key) { } -//----------------------------------------------------------------------------- -// Work with emulator memory -// -// Note: we call FpgaDownloadAndGo(FPGA_BITSTREAM_HF) here although FPGA is not -// involved in dealing with emulator memory. But if it is called later, it might -// destroy the Emulator Memory. -//----------------------------------------------------------------------------- - -void MifareEMemClr(void) { - FpgaDownloadAndGo(FPGA_BITSTREAM_HF); - emlClearMem(); -} - -void MifareEMemGet(uint8_t blockno, uint8_t blockcnt) { - FpgaDownloadAndGo(FPGA_BITSTREAM_HF); - - // - size_t size = blockcnt * 16; - if (size > PM3_CMD_DATA_SIZE) { - reply_ng(CMD_HF_MIFARE_EML_MEMGET, PM3_EMALLOC, NULL, 0); - return; - } - - uint8_t *buf = BigBuf_malloc(size); - - emlGetMem(buf, blockno, blockcnt); // data, block num, blocks count (max 4) - - LED_B_ON(); - reply_ng(CMD_HF_MIFARE_EML_MEMGET, PM3_SUCCESS, buf, size); - LED_B_OFF(); - BigBuf_free_keep_EM(); -} - //----------------------------------------------------------------------------- // Load a card into the emulator memory // @@ -2471,12 +2438,15 @@ int MifareECardLoadExt(uint8_t sectorcnt, uint8_t keytype, uint8_t *key) { } int MifareECardLoad(uint8_t sectorcnt, uint8_t keytype, uint8_t *key) { + if ((keytype > MF_KEY_B) && (key == NULL)) { + if (g_dbglevel >= DBG_ERROR) { Dbprintf("Error, missing key"); } return PM3_EINVARG; } + LED_A_ON(); iso14443a_setup(FPGA_HF_ISO14443A_READER_LISTEN); @@ -2510,10 +2480,10 @@ int MifareECardLoad(uint8_t sectorcnt, uint8_t keytype, uint8_t *key) { // MFC 1K EV1, skip sector 16 since its lockdown if (s == 16) { // unknown sector trailer, keep the keys, set only the AC - uint8_t st[16] = {0x00}; - emlGetMem(st, FirstBlockOfSector(s) + 3, 1); + uint8_t st[MIFARE_BLOCK_SIZE] = {0x00}; + emlGetMem_xt(st, FirstBlockOfSector(s) + 3, 1, MIFARE_BLOCK_SIZE); memcpy(st + 6, "\x70\xF0\xF8\x69", 4); - emlSetMem_xt(st, FirstBlockOfSector(s) + 3, 1, 16); + emlSetMem_xt(st, FirstBlockOfSector(s) + 3, 1, MIFARE_BLOCK_SIZE); continue; } @@ -2556,7 +2526,8 @@ int MifareECardLoad(uint8_t sectorcnt, uint8_t keytype, uint8_t *key) { } have_uid = true; } else { // no need for anticollision. We can directly select the card - if (!bd_authenticated) { // no need to select if bd_authenticated with backdoor + + if (bd_authenticated == false) { // no need to select if bd_authenticated with backdoor if (iso14443a_fast_select_card(uid, cascade_levels) == 0) { continue; } @@ -2565,7 +2536,7 @@ int MifareECardLoad(uint8_t sectorcnt, uint8_t keytype, uint8_t *key) { // Auth if (keytype > MF_KEY_B) { - if (! bd_authenticated) { + if (bd_authenticated == false) { ui64Key = bytes_to_num(key, 6); if (mifare_classic_auth(pcs, cuid, 0, keytype, ui64Key, AUTH_FIRST)) { retval = PM3_EFAILED; @@ -2592,7 +2563,7 @@ int MifareECardLoad(uint8_t sectorcnt, uint8_t keytype, uint8_t *key) { #define MAX_RETRIES 2 - uint8_t data[16] = {0x00}; + uint8_t data[MIFARE_BLOCK_SIZE] = {0x00}; for (uint8_t b = 0; b < NumBlocksPerSector(s); b++) { memset(data, 0x00, sizeof(data)); @@ -2614,18 +2585,18 @@ int MifareECardLoad(uint8_t sectorcnt, uint8_t keytype, uint8_t *key) { } // No need to copy empty - if (memcmp(data, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16) == 0) { + if (memcmp(data, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", sizeof(data)) == 0) { break; } if (IsSectorTrailer(b)) { // sector trailer, keep the keys, set only the AC - uint8_t st[16] = {0x00}; - emlGetMem(st, tb, 1); + uint8_t st[MIFARE_BLOCK_SIZE] = {0x00}; + emlGetMem_xt(st, tb, 1, MIFARE_BLOCK_SIZE); memcpy(st + 6, data + 6, 4); - emlSetMem_xt(st, tb, 1, 16); + emlSetMem_xt(st, tb, 1, MIFARE_BLOCK_SIZE); } else { - emlSetMem_xt(data, tb, 1, 16); + emlSetMem_xt(data, tb, 1, MIFARE_BLOCK_SIZE); } break; } @@ -2927,7 +2898,6 @@ void MifareCIdent(bool is_mfc, uint8_t keytype, uint8_t *key) { // variables uint8_t rec[1] = {0x00}; uint8_t recpar[1] = {0x00}; - uint8_t rats[4] = {ISO14443A_CMD_RATS, 0x80, 0x31, 0x73}; uint8_t rdblf0[4] = {ISO14443A_CMD_READBLOCK, 0xF0, 0x8D, 0x5f}; uint8_t rdbl00[4] = {ISO14443A_CMD_READBLOCK, 0x00, 0x02, 0xa8}; uint8_t gen4gdmAuth[4] = {MIFARE_MAGIC_GDM_AUTH_KEY, 0x00, 0x6C, 0x92}; @@ -2940,6 +2910,8 @@ void MifareCIdent(bool is_mfc, uint8_t keytype, uint8_t *key) { uint8_t *par = BigBuf_calloc(MAX_PARITY_SIZE); uint8_t *buf = BigBuf_calloc(PM3_CMD_DATA_SIZE); uint8_t *uid = BigBuf_calloc(10); + iso14a_card_select_t *card = (iso14a_card_select_t *) BigBuf_calloc(sizeof(iso14a_card_select_t)); + uint16_t flag = MAGIC_FLAG_NONE; uint32_t cuid = 0; int res = 0; @@ -2991,144 +2963,141 @@ void MifareCIdent(bool is_mfc, uint8_t keytype, uint8_t *key) { // reset card mf_reset_card(); - res = iso14443a_select_card(uid, NULL, &cuid, true, 0, false); + res = iso14443a_select_card(uid, card, &cuid, true, 0, false); if (res) { if (cuid == 0xAA55C396) { flag |= MAGIC_FLAG_GEN_UNFUSED; } - ReaderTransmit(rats, sizeof(rats), NULL); - res = ReaderReceive(buf, PM3_CMD_DATA_SIZE, par); + if (memcmp(card->ats, "\x09\x78\x00\x91\x02\xDA\xBC\x19\x10", 9) == 0) { + // test for some MFC gen2 + isGen2 = true; + flag |= MAGIC_FLAG_GEN_2; + } else if (memcmp(card->ats, "\x0D\x78\x00\x71\x02\x88\x49\xA1\x30\x20\x15\x06\x08\x56\x3D", 15) == 0) { + // test for some MFC 7b gen2 + isGen2 = true; + flag |= MAGIC_FLAG_GEN_2; + } else if (memcmp(card->ats, "\x0A\x78\x00\x81\x02\xDB\xA0\xC1\x19\x40\x2A\xB5", 12) == 0) { + // test for Ultralight magic gen2 + isGen2 = true; + flag |= MAGIC_FLAG_GEN_2; + } else if (memcmp(card->ats, "\x85\x00\x00\xA0\x00\x00\x0A\xC3\x00\x04\x03\x01\x01\x00\x0B\x03\x41\xDF", 18) == 0) { + // test for Ultralight EV1 magic gen2 + isGen2 = true; + flag |= MAGIC_FLAG_GEN_2; + } else if (memcmp(card->ats, "\x85\x00\x00\xA0\x0A\x00\x0A\xC3\x00\x04\x03\x01\x01\x00\x0B\x03\x16\xD7", 18) == 0) { + // test for some other Ultralight EV1 magic gen2 + isGen2 = true; + flag |= MAGIC_FLAG_GEN_2; + } else if (memcmp(card->ats, "\x85\x00\x00\xA0\x0A\x00\x0A\xB0\x00\x00\x00\x00\x00\x00\x00\x00\x18\x4D", 18) == 0) { + // test for some other Ultralight magic gen2 + isGen2 = true; + flag |= MAGIC_FLAG_GEN_2; + } else if (memcmp(card->ats, "\x85\x00\x00\xA0\x00\x00\x0A\xA5\x00\x04\x04\x02\x01\x00\x0F\x03\x79\x0C", 18) == 0) { + // test for NTAG213 magic gen2 + isGen2 = true; + flag |= MAGIC_FLAG_GEN_2; + } - if (res) { - if (memcmp(buf, "\x09\x78\x00\x91\x02\xDA\xBC\x19\x10", 9) == 0) { - // test for some MFC gen2 - isGen2 = true; - flag |= MAGIC_FLAG_GEN_2; - } else if (memcmp(buf, "\x0D\x78\x00\x71\x02\x88\x49\xA1\x30\x20\x15\x06\x08\x56\x3D", 15) == 0) { - // test for some MFC 7b gen2 - isGen2 = true; - flag |= MAGIC_FLAG_GEN_2; - } else if (memcmp(buf, "\x0A\x78\x00\x81\x02\xDB\xA0\xC1\x19\x40\x2A\xB5", 12) == 0) { - // test for Ultralight magic gen2 - isGen2 = true; - flag |= MAGIC_FLAG_GEN_2; - } else if (memcmp(buf, "\x85\x00\x00\xA0\x00\x00\x0A\xC3\x00\x04\x03\x01\x01\x00\x0B\x03\x41\xDF", 18) == 0) { - // test for Ultralight EV1 magic gen2 - isGen2 = true; - flag |= MAGIC_FLAG_GEN_2; - } else if (memcmp(buf, "\x85\x00\x00\xA0\x0A\x00\x0A\xC3\x00\x04\x03\x01\x01\x00\x0B\x03\x16\xD7", 18) == 0) { - // test for some other Ultralight EV1 magic gen2 - isGen2 = true; - flag |= MAGIC_FLAG_GEN_2; - } else if (memcmp(buf, "\x85\x00\x00\xA0\x0A\x00\x0A\xB0\x00\x00\x00\x00\x00\x00\x00\x00\x18\x4D", 18) == 0) { - // test for some other Ultralight magic gen2 - isGen2 = true; - flag |= MAGIC_FLAG_GEN_2; - } else if (memcmp(buf, "\x85\x00\x00\xA0\x00\x00\x0A\xA5\x00\x04\x04\x02\x01\x00\x0F\x03\x79\x0C", 18) == 0) { - // test for NTAG213 magic gen2 - isGen2 = true; - flag |= MAGIC_FLAG_GEN_2; + // test for super card + ReaderTransmit(superGen1, sizeof(superGen1), NULL); + res = ReaderReceive(buf, PM3_CMD_DATA_SIZE, par); + if (res == 22) { + uint8_t isGen = MAGIC_FLAG_SUPER_GEN1; + + // check for super card gen2 + // not available after RATS, reset card before executing + mf_reset_card(); + + iso14443a_select_card(uid, NULL, &cuid, true, 0, true); + ReaderTransmit(rdbl00, sizeof(rdbl00), NULL); + res = ReaderReceive(buf, PM3_CMD_DATA_SIZE, par); + if (res == 18) { + isGen = MAGIC_FLAG_SUPER_GEN2; } - // test for super card - ReaderTransmit(superGen1, sizeof(superGen1), NULL); + flag |= isGen; + } + } + + if (is_mfc == false) { + // magic ntag test + mf_reset_card(); + + res = iso14443a_select_card(uid, NULL, &cuid, true, 0, true); + if (res == 2) { + ReaderTransmit(rdblf0, sizeof(rdblf0), NULL); res = ReaderReceive(buf, PM3_CMD_DATA_SIZE, par); - if (res == 22) { - uint8_t isGen = MAGIC_FLAG_SUPER_GEN1; - - // check for super card gen2 - // not available after RATS, reset card before executing - mf_reset_card(); - - iso14443a_select_card(uid, NULL, &cuid, true, 0, true); - ReaderTransmit(rdbl00, sizeof(rdbl00), NULL); - res = ReaderReceive(buf, PM3_CMD_DATA_SIZE, par); - if (res == 18) { - isGen = MAGIC_FLAG_SUPER_GEN2; - } - - flag |= isGen; + if (res == 18) { + flag |= MAGIC_FLAG_NTAG21X; } } - if (is_mfc == false) { - // magic ntag test + } else { + + struct Crypto1State mpcs = {0, 0}; + struct Crypto1State *pcs; + pcs = &mpcs; + + // CUID (with default sector 0 B key) test + // regular cards will NAK the WRITEBLOCK(0) command, while DirectWrite will ACK it + // if we do get an ACK, we immediately abort to ensure nothing is ever actually written + // only perform test if we haven't already identified Gen2. No need test if we have a positive identification already + if (isGen2 == false) { mf_reset_card(); res = iso14443a_select_card(uid, NULL, &cuid, true, 0, true); - if (res == 2) { - ReaderTransmit(rdblf0, sizeof(rdblf0), NULL); - res = ReaderReceive(buf, PM3_CMD_DATA_SIZE, par); - if (res == 18) { - flag |= MAGIC_FLAG_NTAG21X; - } - } - } else { + if (res) { - struct Crypto1State mpcs = {0, 0}; - struct Crypto1State *pcs; - pcs = &mpcs; + uint64_t tmpkey = bytes_to_num(key, 6); + if (mifare_classic_authex(pcs, cuid, 0, keytype, tmpkey, AUTH_FIRST, NULL, NULL) == 0) { - // CUID (with default sector 0 B key) test - // regular cards will NAK the WRITEBLOCK(0) command, while DirectWrite will ACK it - // if we do get an ACK, we immediately abort to ensure nothing is ever actually written - // only perform test if we haven't already identified Gen2. No need test if we have a positive identification already - if (isGen2 == false) { - mf_reset_card(); - - res = iso14443a_select_card(uid, NULL, &cuid, true, 0, true); - if (res) { - - uint64_t tmpkey = bytes_to_num(key, 6); - if (mifare_classic_authex(pcs, cuid, 0, keytype, tmpkey, AUTH_FIRST, NULL, NULL) == 0) { - - if ((mifare_sendcmd_short(pcs, 1, ISO14443A_CMD_WRITEBLOCK, 0, buf, PM3_CMD_DATA_SIZE, par, NULL) == 1) && (buf[0] == 0x0A)) { - flag |= MAGIC_FLAG_GEN_2; - // turn off immediately to ensure nothing ever accidentally writes to the block - FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); - } + if ((mifare_sendcmd_short(pcs, 1, ISO14443A_CMD_WRITEBLOCK, 0, buf, PM3_CMD_DATA_SIZE, par, NULL) == 1) && (buf[0] == 0x0A)) { + flag |= MAGIC_FLAG_GEN_2; + // turn off immediately to ensure nothing ever accidentally writes to the block + FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); } - crypto1_deinit(pcs); - } - } - - // magic MFC Gen3 test 1 - mf_reset_card(); - - res = iso14443a_select_card(uid, NULL, &cuid, true, 0, true); - if (res) { - ReaderTransmit(rdbl00, sizeof(rdbl00), NULL); - res = ReaderReceive(buf, PM3_CMD_DATA_SIZE, par); - if (res == 18) { - flag |= MAGIC_FLAG_GEN_3; - } - } - - // magic MFC Gen4 GDM magic auth test - mf_reset_card(); - - res = iso14443a_select_card(uid, NULL, &cuid, true, 0, true); - if (res) { - ReaderTransmit(gen4gdmAuth, sizeof(gen4gdmAuth), NULL); - res = ReaderReceive(buf, PM3_CMD_DATA_SIZE, par); - if (res == 4) { - flag |= MAGIC_FLAG_GDM_AUTH; - } - } - - // QL88 test - mf_reset_card(); - - res = iso14443a_select_card(uid, NULL, &cuid, true, 0, true); - if (res) { - if (mifare_classic_authex(pcs, cuid, 68, MF_KEY_B, 0x707B11FC1481, AUTH_FIRST, NULL, NULL) == 0) { - flag |= MAGIC_FLAG_QL88; } crypto1_deinit(pcs); } } - }; + + // magic MFC Gen3 test 1 + mf_reset_card(); + + res = iso14443a_select_card(uid, NULL, &cuid, true, 0, true); + if (res) { + ReaderTransmit(rdbl00, sizeof(rdbl00), NULL); + res = ReaderReceive(buf, PM3_CMD_DATA_SIZE, par); + if (res == 18) { + flag |= MAGIC_FLAG_GEN_3; + } + } + + // magic MFC Gen4 GDM magic auth test + mf_reset_card(); + + res = iso14443a_select_card(uid, NULL, &cuid, true, 0, true); + if (res) { + ReaderTransmit(gen4gdmAuth, sizeof(gen4gdmAuth), NULL); + res = ReaderReceive(buf, PM3_CMD_DATA_SIZE, par); + if (res == 4) { + flag |= MAGIC_FLAG_GDM_AUTH; + } + } + + // QL88 test + mf_reset_card(); + + res = iso14443a_select_card(uid, NULL, &cuid, true, 0, true); + if (res) { + + if (mifare_classic_authex(pcs, cuid, 68, MF_KEY_B, 0x707B11FC1481, AUTH_FIRST, NULL, NULL) == 0) { + flag |= MAGIC_FLAG_QL88; + } + crypto1_deinit(pcs); + } + } // GDM alt magic wakeup (20) ReaderTransmitBitsPar(wupGDM1, 7, NULL, NULL); diff --git a/armsrc/mifarecmd.h b/armsrc/mifarecmd.h index 2dcfa4e4b..f6d190d82 100644 --- a/armsrc/mifarecmd.h +++ b/armsrc/mifarecmd.h @@ -43,8 +43,6 @@ void MifareChkKeys(uint8_t *datain, uint8_t reserved_mem); void MifareChkKeys_fast(uint32_t arg0, uint32_t arg1, uint32_t arg2, uint8_t *datain); void MifareChkKeys_file(uint8_t *fn); -void MifareEMemClr(void); -void MifareEMemGet(uint8_t blockno, uint8_t blockcnt); int MifareECardLoad(uint8_t sectorcnt, uint8_t keytype, uint8_t *key); int MifareECardLoadExt(uint8_t sectorcnt, uint8_t keytype, uint8_t *key); diff --git a/armsrc/mifaresim.c b/armsrc/mifaresim.c index 4ee88ec34..c58025473 100644 --- a/armsrc/mifaresim.c +++ b/armsrc/mifaresim.c @@ -48,8 +48,8 @@ #include "parity.h" static bool IsKeyBReadable(uint8_t blockNo) { - uint8_t sector_trailer[16]; - emlGetMem(sector_trailer, SectorTrailer(blockNo), 1); + uint8_t sector_trailer[MIFARE_BLOCK_SIZE] = {0}; + emlGetMem_xt(sector_trailer, SectorTrailer(blockNo), 1, MIFARE_BLOCK_SIZE); uint8_t AC = ((sector_trailer[7] >> 5) & 0x04) | ((sector_trailer[8] >> 2) & 0x02) | ((sector_trailer[8] >> 7) & 0x01); @@ -57,55 +57,64 @@ static bool IsKeyBReadable(uint8_t blockNo) { } static bool IsTrailerAccessAllowed(uint8_t blockNo, uint8_t keytype, uint8_t action) { - uint8_t sector_trailer[16]; - emlGetMem(sector_trailer, blockNo, 1); + uint8_t sector_trailer[MIFARE_BLOCK_SIZE] = {0}; + emlGetMem_xt(sector_trailer, blockNo, 1, MIFARE_BLOCK_SIZE); + uint8_t AC = ((sector_trailer[7] >> 5) & 0x04) | ((sector_trailer[8] >> 2) & 0x02) | ((sector_trailer[8] >> 7) & 0x01); + switch (action) { case AC_KEYA_READ: { - if (g_dbglevel >= DBG_EXTENDED) + if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("IsTrailerAccessAllowed: AC_KEYA_READ"); + } return false; } case AC_KEYA_WRITE: { - if (g_dbglevel >= DBG_EXTENDED) + if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("IsTrailerAccessAllowed: AC_KEYA_WRITE"); + } return ((keytype == AUTHKEYA && (AC == 0x00 || AC == 0x01)) || (keytype == AUTHKEYB && (AC == 0x04 || AC == 0x03))); } case AC_KEYB_READ: { - if (g_dbglevel >= DBG_EXTENDED) + if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("IsTrailerAccessAllowed: AC_KEYB_READ"); + } return (keytype == AUTHKEYA && (AC == 0x00 || AC == 0x02 || AC == 0x01)); } case AC_KEYB_WRITE: { - if (g_dbglevel >= DBG_EXTENDED) + if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("IsTrailerAccessAllowed: AC_KEYB_WRITE"); + } return ((keytype == AUTHKEYA && (AC == 0x00 || AC == 0x01)) || (keytype == AUTHKEYB && (AC == 0x04 || AC == 0x03))); } case AC_AC_READ: { - if (g_dbglevel >= DBG_EXTENDED) + if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("IsTrailerAccessAllowed: AC_AC_READ"); + } return ((keytype == AUTHKEYA) || (keytype == AUTHKEYB && !(AC == 0x00 || AC == 0x02 || AC == 0x01))); } case AC_AC_WRITE: { - if (g_dbglevel >= DBG_EXTENDED) + if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("IsTrailerAccessAllowed: AC_AC_WRITE"); + } return ((keytype == AUTHKEYA && (AC == 0x01)) || (keytype == AUTHKEYB && (AC == 0x03 || AC == 0x05))); } - default: + default: { return false; + } } } static bool IsDataAccessAllowed(uint8_t blockNo, uint8_t keytype, uint8_t action) { - uint8_t sector_trailer[16]; - emlGetMem(sector_trailer, SectorTrailer(blockNo), 1); + uint8_t sector_trailer[MIFARE_BLOCK_SIZE] = {0}; + emlGetMem_xt(sector_trailer, SectorTrailer(blockNo), 1, MIFARE_BLOCK_SIZE); uint8_t sector_block; if (blockNo <= MIFARE_2K_MAXBLOCK) { @@ -120,54 +129,62 @@ static bool IsDataAccessAllowed(uint8_t blockNo, uint8_t keytype, uint8_t action AC = ((sector_trailer[7] >> 2) & 0x04) | ((sector_trailer[8] << 1) & 0x02) | ((sector_trailer[8] >> 4) & 0x01); - if (g_dbglevel >= DBG_EXTENDED) + if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("IsDataAccessAllowed: case 0x00 - %02x", AC); + } break; } case 0x01: { AC = ((sector_trailer[7] >> 3) & 0x04) | ((sector_trailer[8] >> 0) & 0x02) | ((sector_trailer[8] >> 5) & 0x01); - if (g_dbglevel >= DBG_EXTENDED) + if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("IsDataAccessAllowed: case 0x01 - %02x", AC); + } break; } case 0x02: { AC = ((sector_trailer[7] >> 4) & 0x04) | ((sector_trailer[8] >> 1) & 0x02) | ((sector_trailer[8] >> 6) & 0x01); - if (g_dbglevel >= DBG_EXTENDED) + if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("IsDataAccessAllowed: case 0x02 - %02x", AC); + } break; } default: - if (g_dbglevel >= DBG_EXTENDED) + if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("IsDataAccessAllowed: Error"); + } return false; } switch (action) { case AC_DATA_READ: { - if (g_dbglevel >= DBG_EXTENDED) + if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("IsDataAccessAllowed - AC_DATA_READ: OK"); + } return ((keytype == AUTHKEYA && !(AC == 0x03 || AC == 0x05 || AC == 0x07)) || (keytype == AUTHKEYB && !(AC == 0x07))); } case AC_DATA_WRITE: { - if (g_dbglevel >= DBG_EXTENDED) + if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("IsDataAccessAllowed - AC_DATA_WRITE: OK"); + } return ((keytype == AUTHKEYA && (AC == 0x00)) || (keytype == AUTHKEYB && (AC == 0x00 || AC == 0x04 || AC == 0x06 || AC == 0x03))); } case AC_DATA_INC: { - if (g_dbglevel >= DBG_EXTENDED) + if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("IsDataAccessAllowed - AC_DATA_INC: OK"); + } return ((keytype == AUTHKEYA && (AC == 0x00)) || (keytype == AUTHKEYB && (AC == 0x00 || AC == 0x06))); } case AC_DATA_DEC_TRANS_REST: { - if (g_dbglevel >= DBG_EXTENDED) + if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("AC_DATA_DEC_TRANS_REST: OK"); + } return ((keytype == AUTHKEYA && (AC == 0x00 || AC == 0x06 || AC == 0x01)) || (keytype == AUTHKEYB && (AC == 0x00 || AC == 0x06 || AC == 0x01))); } @@ -252,29 +269,33 @@ bool MifareSimInit(uint16_t flags, uint8_t *uid, uint16_t atqa, uint8_t sak, tag // Length: 4,7,or 10 bytes if (IS_FLAG_UID_IN_EMUL(flags)) { + if (uid == NULL) { uid = uid_tmp; } // Get UID, SAK, ATQA from EMUL - uint8_t block0[16]; - emlGet(block0, 0, 16); + uint8_t block0[MIFARE_BLOCK_SIZE]; + emlGet(block0, 0, MIFARE_BLOCK_SIZE); + // Check for 4 bytes uid: bcc corrected and single size uid bits in ATQA if ((block0[0] ^ block0[1] ^ block0[2] ^ block0[3]) == block0[4] && (block0[6] & 0xc0) == 0) { FLAG_SET_UID_IN_DATA(flags, 4); memcpy(uid, block0, 4); rSAK[0] = block0[5]; memcpy(rATQA, &block0[6], sizeof(rATQA)); - } - // Check for 7 bytes UID: double size uid bits in ATQA - else if ((block0[8] & 0xc0) == 0x40) { + + } else if ((block0[8] & 0xc0) == 0x40) { + // Check for 7 bytes UID: double size uid bits in ATQA FLAG_SET_UID_IN_DATA(flags, 7); memcpy(uid, block0, 7); rSAK[0] = block0[7]; memcpy(rATQA, &block0[8], sizeof(rATQA)); + } else { Dbprintf("ERROR: " _RED_("Invalid dump. UID/SAK/ATQA not found")); return false; } + } else { if (uid == NULL) { Dbprintf("ERROR: " _RED_("Missing UID")); @@ -288,16 +309,19 @@ bool MifareSimInit(uint16_t flags, uint8_t *uid, uint16_t atqa, uint8_t sak, tag memcpy(rATQA, rATQA_Mini, sizeof(rATQA)); rSAK[0] = rSAK_Mini; if (g_dbglevel > DBG_NONE) Dbprintf("Enforcing Mifare Mini ATQA/SAK"); + } else if (IS_FLAG_MF_SIZE(flags, MIFARE_1K_MAX_BYTES)) { memcpy(rATQA, rATQA_1k, sizeof(rATQA)); rSAK[0] = rSAK_1k; if (g_dbglevel > DBG_NONE) Dbprintf("Enforcing Mifare 1K ATQA/SAK"); + } else if (IS_FLAG_MF_SIZE(flags, MIFARE_2K_MAX_BYTES)) { memcpy(rATQA, rATQA_2k, sizeof(rATQA)); rSAK[0] = rSAK_2k; *rats = rRATS; *rats_len = sizeof(rRATS); if (g_dbglevel > DBG_NONE) Dbprintf("Enforcing Mifare 2K ATQA/SAK with RATS support"); + } else if (IS_FLAG_MF_SIZE(flags, MIFARE_4K_MAX_BYTES)) { memcpy(rATQA, rATQA_4k, sizeof(rATQA)); rSAK[0] = rSAK_4k; @@ -825,8 +849,8 @@ void Mifare1ksim(uint16_t flags, uint8_t exitAfterNReads, uint8_t *uid, uint16_t // if key not known and FLAG_NESTED_AUTH_ATTACK and we have nt/nt_enc/parity, send recorded nt_enc and parity if ((flags & FLAG_NESTED_AUTH_ATTACK) == FLAG_NESTED_AUTH_ATTACK) { if (emlGetKey(cardAUTHSC, cardAUTHKEY) == 0) { - uint8_t buf[16] = {0}; - emlGetMem(buf, (CARD_MEMORY_RF08S_OFFSET / MIFARE_BLOCK_SIZE) + cardAUTHSC, 1); + uint8_t buf[MIFARE_BLOCK_SIZE] = {0}; + emlGetMem_xt(buf, (CARD_MEMORY_RF08S_OFFSET / MIFARE_BLOCK_SIZE) + cardAUTHSC, 1, MIFARE_BLOCK_SIZE); if (buf[(cardAUTHKEY * 8) + 3] == 0xAA) { // extra check to tell we have nt/nt_enc/par_err running_nested_auth_attack = true; // nt @@ -955,7 +979,7 @@ void Mifare1ksim(uint16_t flags, uint8_t exitAfterNReads, uint8_t *uid, uint16_t // first block if (blockNo == 4) { - p_em += blockNo * 16; + p_em += (blockNo * MIFARE_BLOCK_SIZE); // TLV in NDEF, flip length between // 4 | 03 21 D1 02 1C 53 70 91 01 09 54 02 65 6E 4C 69 // 0xFF means long length @@ -970,7 +994,7 @@ void Mifare1ksim(uint16_t flags, uint8_t exitAfterNReads, uint8_t *uid, uint16_t } } - emlGetMem(response, blockNo, 1); + emlGetMem_xt(response, blockNo, 1, MIFARE_BLOCK_SIZE); if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("[MFEMUL_WORK - ISO14443A_CMD_READBLOCK] Data Block[%d]: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", blockNo, @@ -1010,11 +1034,11 @@ void Mifare1ksim(uint16_t flags, uint8_t exitAfterNReads, uint8_t *uid, uint16_t } } else { if (IsAccessAllowed(blockNo, cardAUTHKEY, AC_DATA_READ) == false) { - memset(response, 0x00, 16); // datablock cannot be read + memset(response, 0x00, MIFARE_BLOCK_SIZE); // datablock cannot be read if (g_dbglevel >= DBG_EXTENDED) Dbprintf("[MFEMUL_WORK - IsAccessAllowed] Data block %d (0x%02x) cannot be read", blockNo, blockNo); } } - AddCrc14A(response, 16); + AddCrc14A(response, MIFARE_BLOCK_SIZE); mf_crypto1_encrypt(pcs, response, MAX_MIFARE_FRAME_SIZE, response_par); EmSendCmdPar(response, MAX_MIFARE_FRAME_SIZE, response_par); FpgaDisableTracing(); @@ -1109,7 +1133,9 @@ void Mifare1ksim(uint16_t flags, uint8_t exitAfterNReads, uint8_t *uid, uint16_t // case MFEMUL_WORK => CMD RATS if (receivedCmd_len == 4 && receivedCmd_dec[0] == ISO14443A_CMD_RATS && (receivedCmd_dec[1] & 0xF0) <= 0x80 && (receivedCmd_dec[1] & 0x0F) <= 0x0e) { + if (rats && rats_len) { + if (encrypted_data) { memcpy(response, rats, rats_len); mf_crypto1_encrypt(pcs, response, rats_len, response_par); @@ -1117,46 +1143,58 @@ void Mifare1ksim(uint16_t flags, uint8_t exitAfterNReads, uint8_t *uid, uint16_t } else { EmSendCmd(rats, rats_len); } + FpgaDisableTracing(); - if (g_dbglevel >= DBG_EXTENDED) + + if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("[MFEMUL_WORK] RCV RATS => ACK"); + } + } else { EmSend4bit(encrypted_data ? mf_crypto1_encrypt4bit(pcs, CARD_NACK_NA) : CARD_NACK_NA); FpgaDisableTracing(); cardSTATE_TO_IDLE(); - if (g_dbglevel >= DBG_EXTENDED) + if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("[MFEMUL_WORK] RCV RATS => NACK"); + } } break; } // case MFEMUL_WORK => ISO14443A_CMD_NXP_DESELECT if (receivedCmd_len == 3 && receivedCmd_dec[0] == ISO14443A_CMD_NXP_DESELECT) { + if (rats && rats_len) { + // response back NXP_DESELECT if (encrypted_data) { memcpy(response, receivedCmd_dec, receivedCmd_len); mf_crypto1_encrypt(pcs, response, receivedCmd_len, response_par); EmSendCmdPar(response, receivedCmd_len, response_par); - } else + } else { EmSendCmd(receivedCmd_dec, receivedCmd_len); + } FpgaDisableTracing(); - if (g_dbglevel >= DBG_EXTENDED) + if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("[MFEMUL_WORK] RCV NXP DESELECT => ACK"); + } + } else { EmSend4bit(encrypted_data ? mf_crypto1_encrypt4bit(pcs, CARD_NACK_NA) : CARD_NACK_NA); FpgaDisableTracing(); cardSTATE_TO_IDLE(); - if (g_dbglevel >= DBG_EXTENDED) + if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("[MFEMUL_WORK] RCV NXP DESELECT => NACK"); + } } break; } // case MFEMUL_WORK => command not allowed - if (g_dbglevel >= DBG_EXTENDED) + if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("Received command not allowed, nacking"); + } EmSend4bit(encrypted_data ? mf_crypto1_encrypt4bit(pcs, CARD_NACK_NA) : CARD_NACK_NA); FpgaDisableTracing(); break; @@ -1164,14 +1202,16 @@ void Mifare1ksim(uint16_t flags, uint8_t exitAfterNReads, uint8_t *uid, uint16_t // AUTH1 case MFEMUL_AUTH1: { - if (g_dbglevel >= DBG_EXTENDED) + if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("[MFEMUL_AUTH1] Enter case"); + } if (receivedCmd_len != 8) { cardSTATE_TO_IDLE(); LogTrace(uart->output, uart->len, uart->startTime * 16 - DELAY_AIR2ARM_AS_TAG, uart->endTime * 16 - DELAY_AIR2ARM_AS_TAG, uart->parity, true); - if (g_dbglevel >= DBG_EXTENDED) + if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("MFEMUL_AUTH1: receivedCmd_len != 8 (%d) => cardSTATE_TO_IDLE())", receivedCmd_len); + } break; } @@ -1191,6 +1231,7 @@ void Mifare1ksim(uint16_t flags, uint8_t exitAfterNReads, uint8_t *uid, uint16_t ar_nr_resp[0].state = NESTED; finished = true; } + if ((flags & FLAG_NR_AR_ATTACK) == FLAG_NR_AR_ATTACK) { for (uint8_t i = 0; i < ATTACK_KEY_COUNT; i++) { @@ -1267,12 +1308,16 @@ void Mifare1ksim(uint16_t flags, uint8_t exitAfterNReads, uint8_t *uid, uint16_t // WRITE BL2 case MFEMUL_WRITEBL2: { + if (receivedCmd_len == MAX_MIFARE_FRAME_SIZE) { + mf_crypto1_decryptEx(pcs, receivedCmd, receivedCmd_len, receivedCmd_dec); + if (CheckCrc14A(receivedCmd_dec, receivedCmd_len)) { + if (IsSectorTrailer(cardWRBL)) { - emlGetMem(response, cardWRBL, 1); + emlGetMem_xt(response, cardWRBL, 1, MIFARE_BLOCK_SIZE); if (IsAccessAllowed(cardWRBL, cardAUTHKEY, AC_KEYA_WRITE) == false) { memcpy(receivedCmd_dec, response, 6); // don't change KeyA diff --git a/armsrc/mifareutil.c b/armsrc/mifareutil.c index b8cb4838d..d19233f0a 100644 --- a/armsrc/mifareutil.c +++ b/armsrc/mifareutil.c @@ -756,14 +756,16 @@ uint8_t FirstBlockOfSector(uint8_t sectorNo) { } // work with emulator memory -void emlSetMem_xt(uint8_t *data, int blockNum, int blocksCount, int block_width) { +void emlSetMem_xt(uint8_t *data, uint16_t blockNum, uint8_t blocksCount, uint8_t block_width) { uint32_t offset = blockNum * block_width; uint32_t len = blocksCount * block_width; emlSet(data, offset, len); } -void emlGetMem(uint8_t *data, int blockNum, int blocksCount) { - emlGet(data, (blockNum * 16), (blocksCount * 16)); +void emlGetMem_xt(uint8_t *data, uint16_t blockNum, uint8_t blocksCount, uint8_t block_width) { + uint32_t offset = blockNum * block_width; + uint32_t len = blocksCount * block_width; + emlGet(data, offset, len); } bool emlCheckValBl(int blockNum) { @@ -817,10 +819,11 @@ uint64_t emlGetKey(int sectorNum, int keyType) { } void emlClearMem(void) { + + BigBuf_Clear_EM(); + const uint8_t trailer[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x07, 0x80, 0x69, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; const uint8_t uid[] = {0xe6, 0x84, 0x87, 0xf3, 0x16, 0x88, 0x04, 0x00, 0x46, 0x8e, 0x45, 0x55, 0x4d, 0x70, 0x41, 0x04}; - uint8_t *mem = BigBuf_get_EM_addr(); - memset(mem, 0, CARD_MEMORY_SIZE); // fill sectors trailer data for (uint16_t b = 3; b < MIFARE_4K_MAXBLOCK; ((b < MIFARE_2K_MAXBLOCK - 4) ? (b += 4) : (b += 16))) { diff --git a/armsrc/mifareutil.h b/armsrc/mifareutil.h index d2d259c73..28987c363 100644 --- a/armsrc/mifareutil.h +++ b/armsrc/mifareutil.h @@ -131,8 +131,9 @@ uint8_t SectorTrailer(uint8_t blockNo); // emulator functions void emlClearMem(void); -void emlSetMem_xt(uint8_t *data, int blockNum, int blocksCount, int block_width); -void emlGetMem(uint8_t *data, int blockNum, int blocksCount); +void emlSetMem_xt(uint8_t *data, uint16_t blockNum, uint8_t blocksCount, uint8_t block_width); +void emlGetMem_xt(uint8_t *data, uint16_t blockNum, uint8_t blocksCount, uint8_t block_width); + uint64_t emlGetKey(int sectorNum, int keyType); int emlGetValBl(uint32_t *blReg, uint8_t *blBlock, int blockNum); void emlSetValBl(uint32_t blReg, uint8_t blBlock, int blockNum); diff --git a/armsrc/sam_common.c b/armsrc/sam_common.c index ebedfc3b4..567520ee3 100644 --- a/armsrc/sam_common.c +++ b/armsrc/sam_common.c @@ -437,10 +437,10 @@ uint16_t sam_copy_payload_sam2nfc(uint8_t *nfc_tx_buf, uint8_t *sam_rx_buf) { // 90 00 // NFC req: - // 0C 05 DE 64 + // 0C 05 DE 64 // copy data out of c1->a1>->a1->80 node uint16_t nfc_tx_len = (uint8_t) * (sam_rx_buf + 10); memcpy(nfc_tx_buf, sam_rx_buf + 11, nfc_tx_len); return nfc_tx_len; -} \ No newline at end of file +} diff --git a/armsrc/sam_picopass.c b/armsrc/sam_picopass.c index 450e46253..270468f66 100644 --- a/armsrc/sam_picopass.c +++ b/armsrc/sam_picopass.c @@ -49,9 +49,9 @@ static int sam_send_request_iso15(const uint8_t *const request, const uint8_t re if (g_dbglevel >= DBG_DEBUG) DbpString("start sam_send_request_iso14a"); - uint8_t * buf1 = BigBuf_malloc(ISO7816_MAX_FRAME); - uint8_t * buf2 = BigBuf_malloc(ISO7816_MAX_FRAME); - if(buf1 == NULL || buf2 == NULL){ + uint8_t *buf1 = BigBuf_malloc(ISO7816_MAX_FRAME); + uint8_t *buf2 = BigBuf_malloc(ISO7816_MAX_FRAME); + if (buf1 == NULL || buf2 == NULL) { res = PM3_EMALLOC; goto out; } @@ -103,19 +103,19 @@ static int sam_send_request_iso15(const uint8_t *const request, const uint8_t re nfc_tx_len = sam_copy_payload_sam2nfc(nfc_tx_buf, sam_rx_buf); bool is_cmd_check = (nfc_tx_buf[0] & 0x0F) == ICLASS_CMD_CHECK; - if(is_cmd_check && break_on_nr_mac){ + if (is_cmd_check && break_on_nr_mac) { memcpy(response, nfc_tx_buf, nfc_tx_len); *response_len = nfc_tx_len; if (g_dbglevel >= DBG_INFO) { DbpString("NR-MAC: "); - Dbhexdump((*response_len)-1, response+1, false); + Dbhexdump((*response_len) - 1, response + 1, false); } res = PM3_SUCCESS; goto out; } bool is_cmd_update = (nfc_tx_buf[0] & 0x0F) == ICLASS_CMD_UPDATE; - if(is_cmd_update && prevent_epurse_update && nfc_tx_buf[0] == 0x87 && nfc_tx_buf[1] == 0x02){ + if (is_cmd_update && prevent_epurse_update && nfc_tx_buf[0] == 0x87 && nfc_tx_buf[1] == 0x02) { // block update(2) command and fake the response to prevent update of epurse // NFC TX BUFFERS PREPARED BY SAM LOOKS LIKE: @@ -124,8 +124,8 @@ static int sam_send_request_iso15(const uint8_t *const request, const uint8_t re // NFC RX BUFFERS EXPECTED BY SAM WOULD LOOK LIKE: // #2(FF FF FF FF) #1(C9 FD FF FF) 3A 47 - memcpy(nfc_rx_buf+0, nfc_tx_buf+6, 4); - memcpy(nfc_rx_buf+4, nfc_tx_buf+0, 4); + memcpy(nfc_rx_buf + 0, nfc_tx_buf + 6, 4); + memcpy(nfc_rx_buf + 4, nfc_tx_buf + 0, 4); AddCrc(nfc_rx_buf, 8); nfc_rx_len = 10; @@ -155,7 +155,7 @@ static int sam_send_request_iso15(const uint8_t *const request, const uint8_t re } - if (res != PM3_SUCCESS ) { + if (res != PM3_SUCCESS) { res = PM3_ECARDEXCHANGE; goto out; } @@ -358,7 +358,7 @@ int sam_picopass_get_pacs(PacketCommandNG *c) { // implicit StartSspClk() happens here Iso15693InitReader(); - if(!select_iclass_tag(&card_a_info, false, &eof_time, shallow_mod)){ + if (!select_iclass_tag(&card_a_info, false, &eof_time, shallow_mod)) { goto err; } diff --git a/armsrc/sam_seos.c b/armsrc/sam_seos.c index facdb2545..7d4a018be 100644 --- a/armsrc/sam_seos.c +++ b/armsrc/sam_seos.c @@ -129,9 +129,9 @@ static int sam_send_request_iso14a(const uint8_t *const request, const uint8_t r if (g_dbglevel >= DBG_DEBUG) DbpString("start sam_send_request_iso14a"); - uint8_t * buf1 = BigBuf_malloc(ISO7816_MAX_FRAME); - uint8_t * buf2 = BigBuf_malloc(ISO7816_MAX_FRAME); - if(buf1 == NULL || buf2 == NULL){ + uint8_t *buf1 = BigBuf_malloc(ISO7816_MAX_FRAME); + uint8_t *buf2 = BigBuf_malloc(ISO7816_MAX_FRAME); + if (buf1 == NULL || buf2 == NULL) { res = PM3_EMALLOC; goto out; } diff --git a/armsrc/spiffs_check.c b/armsrc/spiffs_check.c index 636feb769..e082fefbb 100644 --- a/armsrc/spiffs_check.c +++ b/armsrc/spiffs_check.c @@ -548,7 +548,7 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) { SPIFFS_API_CHECK_RES(fs, SPIFFS_ERR_INTERNAL); } // this checks for overflow of the multiplication of block_count+1 with SPIFFS_PAGES_PER_BLOCK(fs) - if (((uint32_t)(-1)) / SPIFFS_PAGES_PER_BLOCK(fs) > (block_count+1)) { + if (((uint32_t)(-1)) / SPIFFS_PAGES_PER_BLOCK(fs) > (block_count + 1)) { // checking with +1 block count to avoid overflow also in inner loop, which adds one page... // would exceed value storable in uint32_t SPIFFS_DBG("Overflow: pages per block %04x with block count "_SPIPRIbl" results in overflow\n", SPIFFS_PAGES_PER_BLOCK(fs), block_count); @@ -556,13 +556,13 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) { } // because loop indices are using spiffs_page_ix type, // that type can hold a large enough value - if (total_blocks > ((spiffs_page_ix)-1)) { + if (total_blocks > ((spiffs_page_ix) - 1)) { SPIFFS_DBG("Avoiding infinite loop, total_blocks "_SPIPRIpg" too large for spiffs_page_ix type\n", total_blocks); SPIFFS_CHECK_RES(SPIFFS_ERR_INTERNAL); } // because loop indices are using spiffs_page_ix type, // that type can hold a large enough value - if (total_blocks_plus_one_page > ((spiffs_page_ix)-1) || total_blocks_plus_one_page < total_blocks) { + if (total_blocks_plus_one_page > ((spiffs_page_ix) - 1) || total_blocks_plus_one_page < total_blocks) { SPIFFS_DBG("Avoiding infinite loop, total_blocks_plus_one_page "_SPIPRIpg" too large for spiffs_page_ix type\n", total_blocks_plus_one_page); SPIFFS_CHECK_RES(SPIFFS_ERR_INTERNAL); } @@ -586,7 +586,7 @@ static s32_t spiffs_page_consistency_check_i(spiffs *fs) { 0); // traverse each page except for lookup pages spiffs_page_ix cur_pix = SPIFFS_OBJ_LOOKUP_PAGES(fs) + SPIFFS_PAGES_PER_BLOCK(fs) * cur_block; - while (!restart && cur_pix < SPIFFS_PAGES_PER_BLOCK(fs) * (cur_block+1)) { + while (!restart && cur_pix < SPIFFS_PAGES_PER_BLOCK(fs) * (cur_block + 1)) { //if ((cur_pix & 0xff) == 0) // SPIFFS_CHECK_DBG("PA: processing pix "_SPIPRIpg", block "_SPIPRIbl" of pix "_SPIPRIpg", block "_SPIPRIbl"\n", // cur_pix, cur_block, total_blocks, block_count); diff --git a/client/luascripts/lf_t55xx_chk.lua b/client/luascripts/lf_t55xx_chk.lua index 9343b0cde..6c1da33ce 100644 --- a/client/luascripts/lf_t55xx_chk.lua +++ b/client/luascripts/lf_t55xx_chk.lua @@ -10,7 +10,7 @@ local green = ac.green author = ' Author: jareckib - created 04.02.2025' version = ' version v1.05' -desc = [[ +desc = [[ A simple script for searching the password for T5577. The script creates a dictionary starting from the entered starting year to the entered ending year. There are two search methods - DDMMYYYY or YYYYMMDD. Checking the entire year @@ -102,11 +102,11 @@ local function main(args) for o, a in getopt.getopt(args, 'hs:e:dy') do if o == 'h' then return help() end - if o == 's' then + if o == 's' then start_year = tonumber(a) if not start_year then return oops(' Invalid start year') end end - if o == 'e' then + if o == 'e' then end_year = tonumber(a) if not end_year then return oops(' Invalid end year') end end @@ -115,13 +115,13 @@ local function main(args) end if not start_year then return oops(' Starting year is required') end - if start_year < 1900 or start_year > 2100 then - return oops(' Start year must be between 1900 and 2100') + if start_year < 1900 or start_year > 2100 then + return oops(' Start year must be between 1900 and 2100') end if args[#args] == "-e" then return oops(' Ending year cannot be empty') end if not end_year then end_year = current_year end - if end_year < 1900 or end_year > 2100 then - return oops(' End year must be between 1900 and 2100') + if end_year < 1900 or end_year > 2100 then + return oops(' End year must be between 1900 and 2100') end if end_year < start_year then return oops(' End year cannot be earlier than start year') end @@ -130,7 +130,7 @@ local function main(args) if generate_dictionary(start_year, end_year, mode) then print(ac.green .. " File created: " .. dictionary_path .. res) print(cyan .. " Starting password testing on T5577..." .. res) - core.console('lf t55 chk -f ' .. dictionary_path) + core.console('lf t55 chk -f ' .. dictionary_path) else return oops('Problem saving the file') end diff --git a/client/luascripts/lf_t55xx_chk_date.lua b/client/luascripts/lf_t55xx_chk_date.lua index dcd5124d2..29b26d506 100644 --- a/client/luascripts/lf_t55xx_chk_date.lua +++ b/client/luascripts/lf_t55xx_chk_date.lua @@ -6,7 +6,7 @@ local dash = string.rep('--', 32) author = ' Author: jareckib - created 01.02.2025' version = ' version v1.01' -desc = [[ +desc = [[ A simple script for searching the password for T5577. The script creates a dictionary starting from the entered starting year to the entered ending year. There are two search methods - DDMMYYYY or YYYYMMDD. Checking the entire year @@ -84,11 +84,11 @@ local function get_valid_year_input(prompt) local year while true do io.write(prompt) - local input = io.read() + local input = io.read() if input == "" then print(ac.yellow .. ' ERROR: ' .. ac.reset .. 'Year cannot be empty') else - year = tonumber(input) + year = tonumber(input) if not year then print(ac.yellow .. ' ERROR: ' .. ac.reset .. 'Invalid input (digits only)') elseif year < 1900 then @@ -155,7 +155,7 @@ local function main(args) if generate_dictionary(start_year, end_year, mode) then print(ac.green .. " File created: " .. dictionary_path .. ac.reset) print(ac.cyan .. " Starting password testing on T5577..." .. ac.reset) - core.console('lf t55 chk -f ' .. dictionary_path) + core.console('lf t55 chk -f ' .. dictionary_path) else print(ac.yellow .. ' ERROR: ' .. ac.reset .. 'Problem saving the file.') end diff --git a/client/luascripts/lf_t55xx_fix.lua b/client/luascripts/lf_t55xx_fix.lua index bed267eac..a77cb2b9b 100644 --- a/client/luascripts/lf_t55xx_fix.lua +++ b/client/luascripts/lf_t55xx_fix.lua @@ -9,11 +9,11 @@ local command = core.console author = ' Author: jareckib - 15.02.2025' version = ' version v1.00' -desc = [[ - This simple script first checks if a password has been set for the T5577. - It uses the dictionary t55xx_default_pwds.dic for this purpose. If a password - is found, it uses the wipe command to erase the T5577. Then the reanimation - procedure is applied. If the password is not found or doesn't exist the script +desc = [[ + This simple script first checks if a password has been set for the T5577. + It uses the dictionary t55xx_default_pwds.dic for this purpose. If a password + is found, it uses the wipe command to erase the T5577. Then the reanimation + procedure is applied. If the password is not found or doesn't exist the script only performs the reanimation procedure. The script revives 99% of blocked tags. ]] usage = [[ @@ -91,7 +91,7 @@ end local function main(args) for o, a in getopt.getopt(args, 'h') do if o == 'h' then return help() end - end + end command('lf t55 chk') local log_content = read_log_file(logfile) local password = log_content and extract_password(log_content) or nil diff --git a/client/pyscripts/PAXTON_NET.py b/client/pyscripts/PAXTON_NET.py index 532ca276e..921cc9459 100644 --- a/client/pyscripts/PAXTON_NET.py +++ b/client/pyscripts/PAXTON_NET.py @@ -1,5 +1,5 @@ # paxton_net.py - Convert Paxton Net2 to EM4102 -# Author jareckib +# Author jareckib # Based on Equipter's tutorial - Downgrade Paxton Net to EM410x # # This code is copyright (c) jareckib, 2025, All rights reserved. diff --git a/client/pyscripts/Paxton_convert.py b/client/pyscripts/Paxton_convert.py index 9c00d5326..0a0415bff 100644 --- a/client/pyscripts/Paxton_convert.py +++ b/client/pyscripts/Paxton_convert.py @@ -1,5 +1,5 @@ # paxton_convert.py - Convert Paxton Net2 and Switch2 to EM4102 -# Author jareckib +# Author jareckib # Based on Equipter's tutorial - Downgrade Paxton Net to EM410x # # This code is copyright (c) jareckib, 2025, All rights reserved. diff --git a/client/pyscripts/Paxton_switch.py b/client/pyscripts/Paxton_switch.py index b65b1d39b..e51def0f7 100644 --- a/client/pyscripts/Paxton_switch.py +++ b/client/pyscripts/Paxton_switch.py @@ -1,5 +1,5 @@ # paxton_switch.py - Convert Paxton Switch2 to EM4102 -# Author jareckib +# Author jareckib # Based on Equipter's tutorial - Downgrade Paxton Net to EM410x # # This code is copyright (c) jareckib, 2025, All rights reserved. diff --git a/client/pyscripts/intertic.py b/client/pyscripts/intertic.py index f262040c2..2793aee43 100644 --- a/client/pyscripts/intertic.py +++ b/client/pyscripts/intertic.py @@ -194,7 +194,7 @@ def Describe_Usage_2_1(Usage, ContractMediumEndDate, Certificate): EventGeoRoute_Direction = Usage.nom(2) EventGeoVehicleId = Usage.nom(16) EventCountPassengers_mb = Usage.nom(4) - + EventValidityTimeFirstStamp = Usage.nom(11) print(' EventDateStamp : {} ({})'.format(EventDateStamp, (datetime(1997, 1, 1) + timedelta(days = ContractMediumEndDate - EventDateStamp)).strftime('%Y-%m-%d'))) @@ -446,7 +446,7 @@ def main(): oa = MAR_OrganizationalAuthority_Contract_Provider.get(OrganizationalAuthority) else: oa = None - + if (oa is not None): s = oa.get(ContractProvider) if (s is not None): diff --git a/client/src/cmdhf14a.c b/client/src/cmdhf14a.c index 33ce1a28d..bb551687e 100644 --- a/client/src/cmdhf14a.c +++ b/client/src/cmdhf14a.c @@ -3784,22 +3784,22 @@ int CmdHF14AAIDSim(const char *Cmd) { if (ats_len > sizeof(ats)) { PrintAndLogEx(ERR, "Provided ATS too long"); - return PM3_EINVARG; + return PM3_EINVARG; } if (aid_len > sizeof(aid)) { PrintAndLogEx(ERR, "Provided AID too long"); - return PM3_EINVARG; + return PM3_EINVARG; } if (selectaid_response_len > sizeof(selectaid_response)) { PrintAndLogEx(ERR, "Provided SelectAID response too long"); - return PM3_EINVARG; + return PM3_EINVARG; } if (getdata_response_len > sizeof(getdata_response)) { PrintAndLogEx(ERR, "Provided GetData response too long"); - return PM3_EINVARG; + return PM3_EINVARG; } if (ats_len > 0) { @@ -3857,14 +3857,17 @@ int CmdHF14AAIDSim(const char *Cmd) { bool keypress = kbd_enter_pressed(); while (keypress == false) { - if (WaitForResponseTimeout(CMD_HF_MIFARE_SIMULATE, &resp, 1500) == 0) + if (WaitForResponseTimeout(CMD_HF_MIFARE_SIMULATE, &resp, 1500) == 0) { continue; + } - if (resp.status != PM3_SUCCESS) + if (resp.status != PM3_SUCCESS) { break; + } - if ((flags & FLAG_NR_AR_ATTACK) != FLAG_NR_AR_ATTACK) + if ((flags & FLAG_NR_AR_ATTACK) != FLAG_NR_AR_ATTACK) { break; + } keypress = kbd_enter_pressed(); } diff --git a/client/src/cmdhf15.c b/client/src/cmdhf15.c index e2295c697..c50f6b5e0 100644 --- a/client/src/cmdhf15.c +++ b/client/src/cmdhf15.c @@ -282,9 +282,9 @@ static const productName_t uidmapping[] = { static int CmdHF15Help(const char *Cmd); static int nxp_15693_print_signature(uint8_t *uid, uint8_t *signature) { + int reason = 0; - int index = -1; - index = originality_check_verify(uid, 8, signature, 32, PK_MFC); + int index = originality_check_verify(uid, 8, signature, 32, PK_MFC); if (index >= 0) { reason = 1; } else { @@ -306,11 +306,12 @@ static int nxp_15693_print_signature(uint8_t *uid, uint8_t *signature) { } } } - PrintAndLogEx(NORMAL, ""); + int ret = originality_check_print(signature, 32, index); if (ret != PM3_SUCCESS) { return ret; } + switch (reason) { case 1: PrintAndLogEx(INFO, " Params used: UID and signature, plain"); diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index d99cbe986..136d5abea 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -3246,14 +3246,14 @@ void print_iclass_sio(uint8_t *iclass_dump, size_t dump_len, bool verbose) { } PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(INFO, "---------------------------- " _CYAN_("SIO - RAW") " ----------------------------"); + PrintAndLogEx(INFO, "--------------------------- " _CYAN_("SIO - RAW") " -----------------------------"); print_hex_noascii_break(sio_start, sio_length, 32); PrintAndLogEx(NORMAL, ""); if (verbose) { PrintAndLogEx(INFO, "----------------------- " _CYAN_("SIO - ASN1 TLV") " ---------------------------"); - asn1_print(sio_start, sio_length, " "); - PrintAndLogEx(NORMAL, ""); - } + asn1_print(sio_start, sio_length, " "); + PrintAndLogEx(NORMAL, ""); + } } void printIclassDumpContents(uint8_t *iclass_dump, uint8_t startblock, uint8_t endblock, size_t filesize, bool dense_output) { @@ -3869,7 +3869,7 @@ static int CmdHFiClassCheckKeys(const char *Cmd) { arg_lit0(NULL, "vb6kdf", "use the VB6 elite KDF instead of a file"), arg_param_end }; - CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIExecWithReturn(ctx, Cmd, argtable, false); int fnlen = 0; char filename[FILE_PATH_SIZE] = {0}; @@ -5425,7 +5425,7 @@ static int CmdHFiClassSAM(const char *Cmd) { data[0] = flags; int cmdlen = 0; - if (CLIParamHexToBuf(arg_get_str(ctx, 8), data+1, PM3_CMD_DATA_SIZE-1, &cmdlen) != PM3_SUCCESS){ + if (CLIParamHexToBuf(arg_get_str(ctx, 8), data + 1, PM3_CMD_DATA_SIZE - 1, &cmdlen) != PM3_SUCCESS) { CLIParserFree(ctx); return PM3_ESOFT; } @@ -5437,7 +5437,7 @@ static int CmdHFiClassSAM(const char *Cmd) { } clearCommandBuffer(); - SendCommandNG(CMD_HF_SAM_PICOPASS, data, cmdlen+1); + SendCommandNG(CMD_HF_SAM_PICOPASS, data, cmdlen + 1); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_HF_SAM_PICOPASS, &resp, 4000) == false) { PrintAndLogEx(WARNING, "SAM timeout"); @@ -5495,11 +5495,11 @@ static int CmdHFiClassSAM(const char *Cmd) { const uint8_t *mediaType = oid + 2 + oid_length; const uint8_t mediaType_data = mediaType[2]; PrintAndLogEx(SUCCESS, "SIO Media Type: " _GREEN_("%s"), getSioMediaTypeInfo(mediaType_data)); - } else if(breakOnNrMac && d[0] == 0x05) { - PrintAndLogEx(SUCCESS, "Nr-MAC: " _GREEN_("%s"), sprint_hex_inrow(d+1, 8)); - if(verbose){ + } else if (breakOnNrMac && d[0] == 0x05) { + PrintAndLogEx(SUCCESS, "Nr-MAC: " _GREEN_("%s"), sprint_hex_inrow(d + 1, 8)); + if (verbose) { PrintAndLogEx(INFO, "Replay Nr-MAC to dump SIO:"); - PrintAndLogEx(SUCCESS, " hf iclass dump -k \"%s\" --nr", sprint_hex_inrow(d+1, 8)); + PrintAndLogEx(SUCCESS, " hf iclass dump -k \"%s\" --nr", sprint_hex_inrow(d + 1, 8)); } } else { print_hex(d, resp.length); diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 70e0bbf40..7034ab6f3 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -70,7 +70,6 @@ static int usage_hf14_keybrute(void) { int mfc_ev1_print_signature(uint8_t *uid, uint8_t uidlen, uint8_t *signature, int signature_len) { int index = originality_check_verify(uid, uidlen, signature, signature_len, PK_MFC); - PrintAndLogEx(NORMAL, ""); return originality_check_print(signature, signature_len, index); } @@ -306,31 +305,31 @@ static void mf_print_block(uint8_t blockno, uint8_t *d, bool verbose) { ascii_to_buffer((uint8_t *)ascii, d, MFBLOCK_SIZE, sizeof(ascii) - 1, 1); if (blockno >= MIFARE_1K_MAXBLOCK) { - PrintAndLogEx(INFO, - _BACK_BLUE_("%s| %3d | " _YELLOW_("%s")) - _BACK_BLUE_(_MAGENTA_("%s")) - _BACK_BLUE_("%02X ") - _BACK_BLUE_(_YELLOW_("%s")) - _BACK_BLUE_("| " _YELLOW_("%s")) - , - secstr, - blockno, - keya, - acl, - d[9], - keyb, - ascii - ); + PrintAndLogEx(INFO, + _BACK_BLUE_("%s| %3d | " _YELLOW_("%s")) + _BACK_BLUE_(_MAGENTA_("%s")) + _BACK_BLUE_("%02X ") + _BACK_BLUE_(_YELLOW_("%s")) + _BACK_BLUE_("| " _YELLOW_("%s")) + , + secstr, + blockno, + keya, + acl, + d[9], + keyb, + ascii + ); } else { PrintAndLogEx(INFO, "%s| %3d | " _YELLOW_("%s") _MAGENTA_("%s") "%02X " _YELLOW_("%s") "| " _YELLOW_("%s"), - secstr, - blockno, - keya, - acl, - d[9], - keyb, - ascii - ); + secstr, + blockno, + keya, + acl, + d[9], + keyb, + ascii + ); } } else { @@ -746,6 +745,7 @@ static int mfc_read_tag(iso14a_card_select_t *card, uint8_t *carddata, uint8_t n free(fptr); free(keyA); free(keyB); + PrintAndLogEx(SUCCESS, "\nSucceeded in dumping all blocks"); return PM3_SUCCESS ; } @@ -2138,7 +2138,7 @@ static int CmdHF14AMfNestedStatic(const char *Cmd) { e_sector[sectorNo].foundKey[trgKeyType] = 1; e_sector[sectorNo].Key[trgKeyType] = bytes_to_num(keyBlock, 6); - // mfCheckKeys_fast(SectorsCnt, true, true, 2, 1, keyBlock, e_sector, false, false); + // mf_check_keys_fast(SectorsCnt, true, true, 2, 1, keyBlock, e_sector, false, false); continue; default : PrintAndLogEx(ERR, "unknown error.\n"); @@ -2955,7 +2955,7 @@ noValidKeyFound: // Try the found keys are reused if (bytes_to_num(tmp_key, MIFARE_KEY_SIZE) != 0) { - // The fast check --> mfCheckKeys_fast(sector_cnt, true, true, 2, 1, tmp_key, e_sector, false, verbose); + // The fast check --> mf_check_keys_fast(sector_cnt, true, true, 2, 1, tmp_key, e_sector, false, verbose); // Returns false keys, so we just stick to the slower mfchk. for (int i = 0; i < sector_cnt; i++) { for (int j = MF_KEY_A; j <= MF_KEY_B; j++) { @@ -4251,6 +4251,7 @@ static int CmdHF14AMfSim(const char *Cmd) { PrintAndLogEx(INFO, "Note: option -e implies -i"); flags |= FLAG_INTERACTIVE; } + if ((flags & FLAG_NR_AR_ATTACK) != FLAG_NR_AR_ATTACK) { PrintAndLogEx(WARNING, "Option -e requires -x or -y"); return PM3_EINVARG; @@ -4278,22 +4279,27 @@ static int CmdHF14AMfSim(const char *Cmd) { payload.flags = flags; payload.exitAfter = exitAfterNReads; + memcpy(payload.uid, uid, uidlen); + payload.atqa = (atqa[1] << 8) | atqa[0]; payload.sak = sak[0]; clearCommandBuffer(); - if (flags & FLAG_INTERACTIVE) { + if ((flags & FLAG_INTERACTIVE) == FLAG_INTERACTIVE) { PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " or a key to abort simulation"); } else { PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " or send another cmd to abort simulation"); } + bool cont; do { + cont = false; SendCommandNG(CMD_HF_MIFARE_SIMULATE, (uint8_t *)&payload, sizeof(payload)); - if (flags & FLAG_INTERACTIVE) { + + if ((flags & FLAG_INTERACTIVE) == FLAG_INTERACTIVE) { PacketResponseNG resp; sector_t *k_sector = NULL; @@ -4305,19 +4311,24 @@ static int CmdHF14AMfSim(const char *Cmd) { continue; } - if (resp.status != PM3_SUCCESS) + if (resp.status != PM3_SUCCESS) { break; + } - if ((flags & FLAG_NR_AR_ATTACK) != FLAG_NR_AR_ATTACK) + if ((flags & FLAG_NR_AR_ATTACK) != FLAG_NR_AR_ATTACK) { break; + } const nonces_t *data = (nonces_t *)resp.data.asBytes; readerAttack(k_sector, k_sectors_cnt, data[0], setEmulatorMem, verbose); + if (setEmulatorMem) { cont = true; } + break; } + if (keypress) { if ((flags & FLAG_NR_AR_ATTACK) == FLAG_NR_AR_ATTACK) { // inform device to break the sim loop since client has exited @@ -7851,7 +7862,7 @@ static int CmdHF14AMfView(const char *Cmd) { if (bytes_read == MIFARE_MINI_MAX_BYTES) block_cnt = MIFARE_MINI_MAXBLOCK; else if (bytes_read == MIFARE_1K_EV1_MAX_BYTES) - block_cnt = MIFARE_1K_EV1_MAXBLOCK; + block_cnt = MIFARE_1K_EV1_MAXBLOCK; else if (bytes_read == MIFARE_2K_MAX_BYTES) block_cnt = MIFARE_2K_MAXBLOCK; else if (bytes_read == MIFARE_4K_MAX_BYTES) @@ -9623,7 +9634,7 @@ static int CmdHF14AMfInfo(const char *Cmd) { PacketResponseNG resp; if (WaitForResponseTimeout(CMD_ACK, &resp, 2500) == false) { PrintAndLogEx(DEBUG, "iso14443a card select timeout"); - return 0; + return PM3_ETIMEOUT; } iso14a_card_select_t card; @@ -9721,9 +9732,9 @@ static int CmdHF14AMfInfo(const char *Cmd) { } } - uint8_t k08s[6] = {0xA3, 0x96, 0xEF, 0xA4, 0xE2, 0x4F}; - uint8_t k08[6] = {0xA3, 0x16, 0x67, 0xA8, 0xCE, 0xC1}; - uint8_t k32[6] = {0x51, 0x8B, 0x33, 0x54, 0xE7, 0x60}; + uint8_t k08s[MIFARE_KEY_SIZE] = {0xA3, 0x96, 0xEF, 0xA4, 0xE2, 0x4F}; + uint8_t k08[MIFARE_KEY_SIZE] = {0xA3, 0x16, 0x67, 0xA8, 0xCE, 0xC1}; + uint8_t k32[MIFARE_KEY_SIZE] = {0x51, 0x8B, 0x33, 0x54, 0xE7, 0x60}; if (mf_read_block(0, 4, k08s, blockdata) == PM3_SUCCESS) { PrintAndLogEx(SUCCESS, "Backdoor key..... " _YELLOW_("%s"), sprint_hex_inrow(k08s, sizeof(k08s))); fKeyType = MF_KEY_BD; diff --git a/client/src/cmdhfmfp.c b/client/src/cmdhfmfp.c index 70bc28e09..bb3bbe58d 100644 --- a/client/src/cmdhfmfp.c +++ b/client/src/cmdhfmfp.c @@ -192,7 +192,6 @@ static nxp_cardtype_t getCardType(uint8_t type, uint8_t major, uint8_t minor) { // --- GET SIGNATURE static int plus_print_signature(uint8_t *uid, uint8_t uidlen, uint8_t *signature, int signature_len) { int index = originality_check_verify(uid, uidlen, signature, signature_len, PK_MFP); - PrintAndLogEx(NORMAL, ""); return originality_check_print(signature, signature_len, index); } diff --git a/client/src/cmdhfmfu.c b/client/src/cmdhfmfu.c index ed8ddaf01..dc6a982b4 100644 --- a/client/src/cmdhfmfu.c +++ b/client/src/cmdhfmfu.c @@ -1417,7 +1417,6 @@ static int ulev1_print_signature(uint64_t tagtype, uint8_t *uid, uint8_t *signat } else if (signature_len == 48) { index = originality_check_verify(uid, 7, signature, signature_len, PK_MFULAES); } - PrintAndLogEx(NORMAL, ""); return originality_check_print(signature, signature_len, index); } @@ -2451,7 +2450,6 @@ static int CmdHF14AMfUInfo(const char *Cmd) { } // check signature int index = originality_check_verify_ex(card.uid, 7, signature, sizeof(signature), PK_ST25TN, false, true); - PrintAndLogEx(NORMAL, ""); originality_check_print(signature, sizeof(signature), index); } diff --git a/client/src/cmdhfseos.c b/client/src/cmdhfseos.c index 3160c1c84..efbb37657 100644 --- a/client/src/cmdhfseos.c +++ b/client/src/cmdhfseos.c @@ -453,7 +453,7 @@ static int seos_challenge_get(uint8_t *RNDICC, uint8_t RNDICC_len, uint8_t keysl char getChallengePre[21]; strcpy(getChallengePre, "008700"); - + // const char keyslot_str[3] = "01"; //strcat(getChallengePre, keyslot_str); snprintf(getChallengePre + strlen(getChallengePre), 3, "%02u", keyslot); @@ -547,7 +547,7 @@ static int select_DF_verify(uint8_t *response, uint8_t response_length, uint8_t // Response is an ASN.1 encoded structure // Extract everything before the 8E tag - int res = PM3_EWRONGANSWER; + int res = PM3_EWRONGANSWER; for (int i = 0; i < response_length; i++) { // extract MAC if (response[i] == 0x8E) { @@ -558,7 +558,7 @@ static int select_DF_verify(uint8_t *response, uint8_t response_length, uint8_t } } if (res != PM3_SUCCESS) { - goto out; + return res; } // ----------------- MAC Key Generation ----------------- @@ -579,7 +579,6 @@ static int select_DF_verify(uint8_t *response, uint8_t response_length, uint8_t // PrintAndLogEx(INFO, "Supp MAC......................... " _YELLOW_("%s"), sprint_hex_inrow(MAC_value, MAC_value_len)); // PrintAndLogEx(INFO, "Calc MAC......................... " _YELLOW_("%s"), sprint_hex_inrow(cmac, sizeof(cmac))); -out: PrintAndLogEx(INFO, "--- " _CYAN_("MAC") " ---------------------------"); PrintAndLogEx(ERR, _RED_("MAC Verification Failed")); return PM3_ESOFT; @@ -760,9 +759,8 @@ static int seos_mutual_auth(uint8_t *randomICC, uint8_t *CRYPTOGRAM_Diversifier, uint8_t response[PM3_CMD_DATA_SIZE]; // ---------------- Diversify Keys ---------------- - uint8_t undiversified_key[16] = { 0x00 }; - memcpy(undiversified_key, keys[key_index].readKey, 16); - + uint8_t mk[16] = { 0x00 }; + memcpy(mk, keys[key_index].readKey, 16); uint8_t keyslot = 0x01; // up to 0x0F uint8_t AES_key[24] = {0x00}; uint8_t MAC_key[24] = {0x00}; @@ -776,8 +774,8 @@ static int seos_mutual_auth(uint8_t *randomICC, uint8_t *CRYPTOGRAM_Diversifier, return PM3_ESOFT; } - seos_kdf(true, undiversified_key, keyslot, adfOID, sizeof(adfOID), CRYPTOGRAM_Diversifier, diversifier_len, AES_key, encryption_algorithm, hash_algorithm); - seos_kdf(false, undiversified_key, keyslot, adfOID, sizeof(adfOID), CRYPTOGRAM_Diversifier, diversifier_len, MAC_key, encryption_algorithm, hash_algorithm); + seos_kdf(true, mk, keyslot, adfOID, sizeof(adfOID), CRYPTOGRAM_Diversifier, diversifier_len, AES_key, encryption_algorithm, hash_algorithm); + seos_kdf(false, mk, keyslot, adfOID, sizeof(adfOID), CRYPTOGRAM_Diversifier, diversifier_len, MAC_key, encryption_algorithm, hash_algorithm); memcpy(&MAC_key[16], &MAC_key[0], 8); memcpy(&AES_key[16], &AES_key[0], 8); @@ -976,7 +974,9 @@ static int seos_aid_select(void) { // if we made it here, its a success and we break :) break; } - + if (i == ARRAYLEN(known_AID_map)) { + return PM3_ESOFT; + } return res; }; @@ -1668,7 +1668,7 @@ static int CmdHfSeosSAM(const char *Cmd) { data[0] = flags; int cmdlen = 0; - if (CLIParamHexToBuf(arg_get_str(ctx, 5), data+1, PM3_CMD_DATA_SIZE-1, &cmdlen) != PM3_SUCCESS){ + if (CLIParamHexToBuf(arg_get_str(ctx, 5), data + 1, PM3_CMD_DATA_SIZE - 1, &cmdlen) != PM3_SUCCESS) { CLIParserFree(ctx); return PM3_ESOFT; } @@ -1680,7 +1680,7 @@ static int CmdHfSeosSAM(const char *Cmd) { } clearCommandBuffer(); - SendCommandNG(CMD_HF_SAM_SEOS, data, cmdlen+1); + SendCommandNG(CMD_HF_SAM_SEOS, data, cmdlen + 1); PacketResponseNG resp; if (WaitForResponseTimeout(CMD_HF_SAM_SEOS, &resp, 4000) == false) { PrintAndLogEx(WARNING, "SAM timeout"); diff --git a/client/src/cmdhfst25ta.c b/client/src/cmdhfst25ta.c index 7dff65067..b2846b689 100644 --- a/client/src/cmdhfst25ta.c +++ b/client/src/cmdhfst25ta.c @@ -150,7 +150,6 @@ static void print_st25ta_system_info(uint8_t *d, uint8_t n) { static int print_st25ta_signature(uint8_t *uid, uint8_t *signature) { int index = originality_check_verify_ex(uid, 7, signature, 32, PK_ST25TA, false, true); - PrintAndLogEx(NORMAL, ""); return originality_check_print(signature, 32, index); } diff --git a/client/src/crypto/originality.c b/client/src/crypto/originality.c index 5d667420f..6b6e687a0 100644 --- a/client/src/crypto/originality.c +++ b/client/src/crypto/originality.c @@ -136,25 +136,28 @@ const ecdsa_publickey_ng_t manufacturer_public_keys[] = { }; -// return pk if match index else -1 +// returns index of pk if match else -1 int originality_check_verify(uint8_t *data, uint8_t data_len, uint8_t *signature, uint8_t signature_len, pk_type_t type) { return originality_check_verify_ex(data, data_len, signature, signature_len, type, false, false); } int originality_check_verify_ex(uint8_t *data, uint8_t data_len, uint8_t *signature, uint8_t signature_len, pk_type_t type, bool reverse, bool hash) { - // test if signature is null + // test if signature is all zeros bool is_zero = true; for (uint8_t i = 0; i < signature_len; i++) { if (signature[i] != 0) { is_zero = false; + break; } } + if (is_zero) { return -1; } uint8_t tmp_data[data_len]; uint8_t tmp_signature[signature_len]; + if (reverse) { reverse_array_copy(data, data_len, tmp_data); reverse_array_copy(signature, signature_len, tmp_signature); @@ -180,17 +183,24 @@ int originality_check_verify_ex(uint8_t *data, uint8_t data_len, uint8_t *signat } int originality_check_print(uint8_t *signature, int signature_len, int index) { + + PrintAndLogEx(NORMAL, ""); + if ((index < 0) || (index >= ARRAYLEN(manufacturer_public_keys))) { + PrintAndLogEx(INFO, " TAG IC Signature: %s", sprint_hex_inrow(signature, 16)); if (signature_len > 16) { PrintAndLogEx(INFO, " : %s", sprint_hex_inrow(signature + 16, 16)); } + if (signature_len > 32) { PrintAndLogEx(INFO, " : %s", sprint_hex_inrow(signature + 32, 16)); } + if (signature_len > 48) { PrintAndLogEx(INFO, " : %s", sprint_hex_inrow(signature + 48, signature_len - 48)); } + PrintAndLogEx(SUCCESS, " Signature verification: " _RED_("failed")); return PM3_ESOFT; } @@ -200,23 +210,30 @@ int originality_check_print(uint8_t *signature, int signature_len, int index) { if (manufacturer_public_keys[index].keylen > 16) { PrintAndLogEx(INFO, " : %.32s", manufacturer_public_keys[index].value + 32); } + if (manufacturer_public_keys[index].keylen > 32) { PrintAndLogEx(INFO, " : %.32s", manufacturer_public_keys[index].value + 64); } + if (manufacturer_public_keys[index].keylen > 48) { PrintAndLogEx(INFO, " : %.32s", manufacturer_public_keys[index].value + 96); } + PrintAndLogEx(INFO, " Elliptic curve parameters: %s", mbedtls_ecp_curve_info_from_grp_id(manufacturer_public_keys[index].grp_id)->name); PrintAndLogEx(INFO, " TAG IC Signature: %s", sprint_hex_inrow(signature, 16)); + if (signature_len > 16) { PrintAndLogEx(INFO, " : %s", sprint_hex_inrow(signature + 16, 16)); } + if (signature_len > 32) { PrintAndLogEx(INFO, " : %s", sprint_hex_inrow(signature + 32, 16)); } + if (signature_len > 48) { PrintAndLogEx(INFO, " : %s", sprint_hex_inrow(signature + 48, signature_len - 48)); } + PrintAndLogEx(SUCCESS, " Signature verification: " _GREEN_("successful")); return PM3_SUCCESS; } diff --git a/client/src/mifare/desfirecore.c b/client/src/mifare/desfirecore.c index 07297d05a..f390c3ca2 100644 --- a/client/src/mifare/desfirecore.c +++ b/client/src/mifare/desfirecore.c @@ -1031,7 +1031,7 @@ int DesfireSelectAndAuthenticateEx(DesfireContext_t *dctx, DesfireSecureChannel isosw = true; if (verbose) { PrintAndLogEx(INFO, "Switch to " _CYAN_("native") " for select"); - } + } } int res; @@ -1055,7 +1055,7 @@ int DesfireSelectAndAuthenticateEx(DesfireContext_t *dctx, DesfireSecureChannel if (verbose) { PrintAndLogEx(INFO, "App %06x " _GREEN_("selected"), aid); - } + } } if (isosw) { @@ -1124,7 +1124,7 @@ int DesfireSelectAndAuthenticateW(DesfireContext_t *dctx, DesfireSecureChannel s if (verbose) { PrintAndLogEx(INFO, "Application %s file iso id %04x is " _GREEN_("selected"), DesfireWayIDStr(way, id), isofileid); - } + } } if (!noauth) { diff --git a/client/src/mifare/mad.c b/client/src/mifare/mad.c index e254fd187..10335994f 100644 --- a/client/src/mifare/mad.c +++ b/client/src/mifare/mad.c @@ -188,7 +188,7 @@ static uint16_t madGetAID(const uint8_t *sector, bool swapmad, int MADver, int s } } -int MADCheck(uint8_t *sector0, uint8_t *sector10, bool verbose, bool *haveMAD2) { +int MADCheck(uint8_t *sector0, uint8_t *sector16, bool verbose, bool *haveMAD2) { if (sector0 == NULL) return PM3_EINVARG; @@ -222,13 +222,13 @@ int MADCheck(uint8_t *sector0, uint8_t *sector10, bool verbose, bool *haveMAD2) PrintAndLogEx(SUCCESS, "CRC8...... 0x%02X ( %s )", sector0[16], _GREEN_("ok")); } - if (mad_ver == 2 && sector10) { - int res2 = madCRCCheck(sector10, true, 2); + if (mad_ver == 2 && sector16) { + int res2 = madCRCCheck(sector16, true, 2); if (res == PM3_SUCCESS) res = res2; if (verbose && !res2) - PrintAndLogEx(SUCCESS, "CRC8...... 0x%02X ( %s )", sector10[0], _GREEN_("ok")); + PrintAndLogEx(SUCCESS, "CRC8...... 0x%02X ( %s )", sector16[0], _GREEN_("ok")); } // MA (multi-application card) @@ -241,27 +241,30 @@ int MADCheck(uint8_t *sector0, uint8_t *sector10, bool verbose, bool *haveMAD2) return res; } -int MADDecode(uint8_t *sector0, uint8_t *sector10, uint16_t *mad, size_t *madlen, bool swapmad) { +int MADDecode(uint8_t *sector0, uint8_t *sector16, uint16_t *mad, size_t *madlen, bool swapmad) { *madlen = 0; bool haveMAD2 = false; - int res = MADCheck(sector0, sector10, false, &haveMAD2); + int res = MADCheck(sector0, sector16, false, &haveMAD2); if (res != PM3_SUCCESS) { PrintAndLogEx(WARNING, "Not a valid MAD"); return res; } - for (int i = 1; i < 16; i++) { + // 7 + 8 == 15 + for (int i = 1; i <= 16; i++) { mad[*madlen] = madGetAID(sector0, swapmad, 1, i); (*madlen)++; } if (haveMAD2) { - // mad2 sector (0x10 == 16dec) here + // mad2 sector (0x10 == 16) here mad[*madlen] = 0x0005; (*madlen)++; + // 7 + 8 + 8 == 23 for (int i = 1; i < 24; i++) { - mad[*madlen] = madGetAID(sector10, swapmad, 2, i); + mad[*madlen] = madGetAID(sector16, swapmad, 2, i); + (*madlen)++; } } @@ -462,16 +465,16 @@ int convert_mad_to_arr(uint8_t *in, uint16_t ilen, uint8_t *out, uint16_t *olen) } uint8_t sector0[MFBLOCK_SIZE * 4] = {0}; - uint8_t sector10[MFBLOCK_SIZE * 4] = {0}; + uint8_t sector16[MFBLOCK_SIZE * 4] = {0}; memcpy(sector0, in, sizeof(sector0)); if (ilen == MIFARE_4K_MAX_BYTES) { - memcpy(sector10, in + (MF_MAD2_SECTOR * 4 * MFBLOCK_SIZE), sizeof(sector10)); + memcpy(sector16, in + (MF_MAD2_SECTOR * 4 * MFBLOCK_SIZE), sizeof(sector16)); } uint16_t mad[7 + 8 + 8 + 8 + 8] = {0}; size_t madlen = 0; - if (MADDecode(sector0, sector10, mad, &madlen, false)) { + if (MADDecode(sector0, sector16, mad, &madlen, false)) { PrintAndLogEx(ERR, "can't decode MAD"); return PM3_ESOFT; } diff --git a/client/src/mifare/mad.h b/client/src/mifare/mad.h index d1f5240f4..1da097527 100644 --- a/client/src/mifare/mad.h +++ b/client/src/mifare/mad.h @@ -21,8 +21,8 @@ #include "common.h" -int MADCheck(uint8_t *sector0, uint8_t *sector10, bool verbose, bool *haveMAD2); -int MADDecode(uint8_t *sector0, uint8_t *sector10, uint16_t *mad, size_t *madlen, bool swapmad); +int MADCheck(uint8_t *sector0, uint8_t *sector16, bool verbose, bool *haveMAD2); +int MADDecode(uint8_t *sector0, uint8_t *sector16, uint16_t *mad, size_t *madlen, bool swapmad); int MAD1DecodeAndPrint(uint8_t *sector, bool swapmad, bool verbose, bool *haveMAD2); int MAD2DecodeAndPrint(uint8_t *sector, bool swapmad, bool verbose); int MADDFDecodeAndPrint(uint32_t short_aid, bool verbose); diff --git a/client/src/mifare/mifarehost.c b/client/src/mifare/mifarehost.c index 0a9d7aba9..a2a8de47a 100644 --- a/client/src/mifare/mifarehost.c +++ b/client/src/mifare/mifarehost.c @@ -1036,19 +1036,25 @@ int mf_write_sector(uint8_t sectorNo, uint8_t keyType, const uint8_t *key, uint8 // EMULATOR int mf_eml_get_mem(uint8_t *data, int blockNum, int blocksCount) { + return mf_eml_get_mem_xt(data, blockNum, blocksCount, MFBLOCK_SIZE); +} - size_t size = blocksCount * MFBLOCK_SIZE; +int mf_eml_get_mem_xt(uint8_t *data, int blockNum, int blocksCount, int blockBtWidth) { + + size_t size = ((size_t) blocksCount) * blockBtWidth; if (size > PM3_CMD_DATA_SIZE) { return PM3_ESOFT; } struct { - uint8_t blockno; + uint16_t blockno; uint8_t blockcnt; + uint8_t blockwidth; } PACKED payload; payload.blockno = blockNum; payload.blockcnt = blocksCount; + payload.blockwidth = blockBtWidth; clearCommandBuffer(); SendCommandNG(CMD_HF_MIFARE_EML_MEMGET, (uint8_t *)&payload, sizeof(payload)); @@ -1059,8 +1065,9 @@ int mf_eml_get_mem(uint8_t *data, int blockNum, int blocksCount) { return PM3_ETIMEOUT; } - if (resp.status == PM3_SUCCESS) + if (resp.status == PM3_SUCCESS) { memcpy(data, resp.data.asBytes, size); + } return resp.status; } @@ -1072,7 +1079,7 @@ int mf_elm_set_mem(uint8_t *data, int blockNum, int blocksCount) { int mf_eml_set_mem_xt(uint8_t *data, int blockNum, int blocksCount, int blockBtWidth) { struct p { - uint8_t blockno; + uint16_t blockno; uint8_t blockcnt; uint8_t blockwidth; uint8_t data[]; @@ -1226,7 +1233,7 @@ int mf_chinese_set_block(uint8_t blockNo, uint8_t *data, uint8_t *uid, uint8_t p if (!isOK) { uint8_t reason = (resp.oldarg[1] & 0xFF); - if ( reason == 4) { + if (reason == 4) { PrintAndLogEx(NORMAL, ""); PrintAndLogEx(WARNING, "GDM magic write signature block failed"); } else if (reason == 5) { diff --git a/client/src/mifare/mifarehost.h b/client/src/mifare/mifarehost.h index 6dc12da52..e043db324 100644 --- a/client/src/mifare/mifarehost.h +++ b/client/src/mifare/mifarehost.h @@ -92,6 +92,7 @@ int mf_write_block(uint8_t blockno, uint8_t keyType, const uint8_t *key, uint8_t int mf_write_sector(uint8_t sectorNo, uint8_t keyType, const uint8_t *key, uint8_t *sector); int mf_eml_get_mem(uint8_t *data, int blockNum, int blocksCount); +int mf_eml_get_mem_xt(uint8_t *data, int blockNum, int blocksCount, int blockBtWidth); int mf_elm_set_mem(uint8_t *data, int blockNum, int blocksCount); int mf_eml_set_mem_xt(uint8_t *data, int blockNum, int blocksCount, int blockBtWidth); diff --git a/client/src/wiegand_formats.c b/client/src/wiegand_formats.c index 69d869785..df1127ec1 100644 --- a/client/src/wiegand_formats.c +++ b/client/src/wiegand_formats.c @@ -883,7 +883,7 @@ static bool Pack_H800002(int format_idx, wiegand_card_t *card, memset(packed, 0, sizeof(wiegand_message_t)); if (!validate_card_limit(format_idx, card)) { - return false; + return false; } packed->Length = 46; @@ -896,7 +896,7 @@ static bool Pack_H800002(int format_idx, wiegand_card_t *card, // Invert parity for setting odd parity set_bit_by_position(packed, even_parity ^ 1, 45); if (preamble) { - return add_HID_header(packed); + return add_HID_header(packed); } return true; } @@ -906,7 +906,7 @@ static bool Unpack_H800002(wiegand_message_t *packed, wiegand_card_t *card) { memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 46) { - return false; // Wrong length? Stop here. + return false; // Wrong length? Stop here. } card->FacilityCode = get_linear_field(packed, 1, 14); @@ -1528,7 +1528,7 @@ int HIDFindCardFormat(const char *format) { static bool validate_card_limit(int format_idx, wiegand_card_t *card) { cardformatdescriptor_t card_descriptor = FormatTable[format_idx].Fields; return !((card->FacilityCode > card_descriptor.MaxFC) || - (card->CardNumber > card_descriptor.MaxCN)|| + (card->CardNumber > card_descriptor.MaxCN) || (card->IssueLevel > card_descriptor.MaxIL) || (card->OEM > card_descriptor.MaxOEM)); } @@ -1586,7 +1586,7 @@ bool HIDTryUnpack(wiegand_message_t *packed) { if (found_cnt) { PrintAndLogEx(INFO, "found %u matching format%c with bit len %d", found_cnt, (found_cnt > 1) ? 's' : ' ', packed->Length); } - + if (packed->Length && ((found_cnt - found_invalid_par) == 0)) { // if length > 0 and no valid parity matches PrintAndLogEx(WARNING, "Wiegand unknown bit len %d", packed->Length); PrintAndLogEx(HINT, "Try 0xFFFF's http://cardinfo.barkweb.com.au/"); @@ -1607,7 +1607,7 @@ void HIDUnpack(int idx, wiegand_message_t *packed) { // return true if at least one valid matching formats found bool decode_wiegand(uint32_t top, uint32_t mid, uint32_t bot, int n) { bool decode_result; - + if (top == 0 && mid == 0 && bot == 0) { decode_result = false; } else if ((n > 0) || ((mid & 0xFFFFFFC0) > 0)) { // if n > 0 or there's more than 38 bits diff --git a/client/src/wiegand_formatutils.c b/client/src/wiegand_formatutils.c index fed31d9fb..0a17f654d 100644 --- a/client/src/wiegand_formatutils.c +++ b/client/src/wiegand_formatutils.c @@ -134,7 +134,7 @@ static uint8_t get_length_from_header(wiegand_message_t *data) { * Right now we just calculate the highest bit set * 38 bits format is handled by directly setting n=38 in initialize_message_object() * since it's hard to distinguish 38 bits with formats with preamble bit (26-36 bits) - * + * * (from http://www.proxmark.org/forum/viewtopic.php?pid=5368#p5368) * 0000 0010 0000 0000 01xx xxxx xxxx xxxx xxxx xxxx xxxx 26-bit * 0000 0010 0000 0000 1xxx xxxx xxxx xxxx xxxx xxxx xxxx 27-bit @@ -156,7 +156,7 @@ static uint8_t get_length_from_header(wiegand_message_t *data) { if ((data->Top & 0x000FFFFF) > 0) { // > 64 bits hfmt = data->Top & 0x000FFFFF; len = 64; - } else if (data->Mid > 0) { + } else if (data->Mid > 0) { // detect HID format b38 set if (data->Mid & 0xFFFFFFC0) { // 39-64 bits hfmt = data->Mid; @@ -165,14 +165,14 @@ static uint8_t get_length_from_header(wiegand_message_t *data) { PrintAndLogEx(DEBUG, "hid preamble detected"); // if bit 38 is set: => 26-36 bits - if (((data->Mid >> 5) & 1) == 1) { + if (((data->Mid >> 5) & 1) == 1) { hfmt = (((data->Mid & 31) << 12) | (data->Bot >> 26)); //get bits 27-37 to check for format len bit len = 19; } else { // if bit 38 is not set => 37 bits hfmt = 0; len = 37; - } - } + } + } } else { hfmt = data->Bot; len = 0; diff --git a/common/commonutil.c b/common/commonutil.c index 7ed34067f..d4eed608e 100644 --- a/common/commonutil.c +++ b/common/commonutil.c @@ -17,6 +17,7 @@ //----------------------------------------------------------------------------- #include "commonutil.h" #include +#include "stdbool.h" /* Similar to FpgaGatherVersion this formats stored version information * into a string representation. It takes a pointer to the struct version_information_t, @@ -403,25 +404,34 @@ void Uint8byteToMemBe(uint8_t *data, uint64_t value) { } // Rotate Left - Ultralight, Desfire -void rol(uint8_t *data, const size_t len) { +void rol(uint8_t *data, const size_t n) { uint8_t first = data[0]; - for (size_t i = 0; i < len - 1; i++) { + for (size_t i = 0; i < n - 1; i++) { data[i] = data[i + 1]; } - data[len - 1] = first; + data[n - 1] = first; } // Rotate Right - Ultralight, Desfire -void ror(uint8_t *data, const size_t len) { - uint8_t last = data[len - 1]; +void ror(uint8_t *data, const size_t n) { + uint8_t last = data[n - 1]; - for (int i = len - 1; i > 0; i--) { + for (int i = n - 1; i > 0; i--) { data[i] = data[i - 1]; } data[0] = last; } +void xor(uint8_t *dest, const uint8_t *src, size_t n) { + + const uint8_t *s = src; + uint8_t *d = dest; + + for (; n > 0; n--) { + *d++ ^= *s++; + } +} void lsl(uint8_t *data, size_t len) { for (size_t n = 0; n < len - 1; n++) { diff --git a/common/commonutil.h b/common/commonutil.h index 0b187ef10..2ab4fbd79 100644 --- a/common/commonutil.h +++ b/common/commonutil.h @@ -128,8 +128,9 @@ void Uint7byteToMemBe(uint8_t *data, uint64_t value); void Uint8byteToMemBe(uint8_t *data, uint64_t value); // rotate left byte array -void rol(uint8_t *data, const size_t len); -void ror(uint8_t *data, const size_t len); +void rol(uint8_t *data, const size_t n); +void ror(uint8_t *data, const size_t n); +void xor(uint8_t *dest, const uint8_t *src, size_t n); void lsl(uint8_t *data, size_t len); uint32_t le24toh(const uint8_t data[3]); diff --git a/common/mbedtls/asn1parse.c b/common/mbedtls/asn1parse.c index 98460e055..84a558bdc 100644 --- a/common/mbedtls/asn1parse.c +++ b/common/mbedtls/asn1parse.c @@ -321,11 +321,11 @@ static int asn1_get_sequence_of_cb(void *ctx, cb_ctx->cur; if (cur->buf.p != NULL) { - cur->next = - mbedtls_calloc(1, sizeof(mbedtls_asn1_sequence)); + cur->next = mbedtls_calloc(1, sizeof(mbedtls_asn1_sequence)); - if (cur->next == NULL) + if (cur->next == NULL) { return (MBEDTLS_ERR_ASN1_ALLOC_FAILED); + } cur = cur->next; } diff --git a/common/mbedtls/cmac.c b/common/mbedtls/cmac.c index 32a5937b0..b179ad483 100644 --- a/common/mbedtls/cmac.c +++ b/common/mbedtls/cmac.c @@ -180,8 +180,7 @@ int mbedtls_cipher_cmac_starts(mbedtls_cipher_context_t *ctx, if (ctx == NULL || ctx->cipher_info == NULL || key == NULL) return (MBEDTLS_ERR_CIPHER_BAD_INPUT_DATA); - if ((retval = mbedtls_cipher_setkey(ctx, key, (int)keybits, - MBEDTLS_ENCRYPT)) != 0) + if ((retval = mbedtls_cipher_setkey(ctx, key, (int)keybits, MBEDTLS_ENCRYPT)) != 0) return (retval); type = ctx->cipher_info->type; @@ -405,14 +404,12 @@ int mbedtls_aes_cmac_prf_128(const unsigned char *key, size_t key_length, } else { memset(zero_key, 0, MBEDTLS_AES_BLOCK_SIZE); - ret = mbedtls_cipher_cmac(cipher_info, zero_key, 128, key, - key_length, int_key); + ret = mbedtls_cipher_cmac(cipher_info, zero_key, 128, key, key_length, int_key); if (ret != 0) goto exit; } - ret = mbedtls_cipher_cmac(cipher_info, int_key, 128, input, in_len, - output); + ret = mbedtls_cipher_cmac(cipher_info, int_key, 128, input, in_len, output); exit: mbedtls_platform_zeroize(int_key, sizeof(int_key)); diff --git a/doc/commands.json b/doc/commands.json index d3b4e711e..d540c2bc8 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -920,7 +920,7 @@ }, "emv help": { "command": "emv help", - "description": "----------- ----------------------- General ----------------------- help This help list List ISO7816 history test Crypto logic selftest --------------------------------------------------------------------------------------- emv list available offline: yes Alias of `trace list -t 7816` 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", + "description": "----------- ----------------------- General ----------------------- help This help list List ISO7816 history test Perform crypto logic self tests --------------------------------------------------------------------------------------- emv list available offline: yes Alias of `trace list -t 7816` 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", "notes": [ "emv list --frame -> show frame delay times", "emv list -1 -> use trace buffer" @@ -1025,11 +1025,11 @@ "offline": false, "options": [ "-h, --help This help", - "-t, --selftest Self test", + "--test Perform self tests", "-a, --apdu Show APDU requests and responses", "-w, --wired Send data via contact (iso7816) interface. (def: Contactless interface)" ], - "usage": "emv roca [-htaw]" + "usage": "emv roca [-haw] [--test]" }, "emv scan": { "command": "emv scan", @@ -1410,19 +1410,19 @@ "hf 14a simaid -t 3 -> MIFARE Desfire", "hf 14a simaid -t 4 -> ISO/IEC 14443-4", "hf 14a simaid -t 11 -> Javacard (JCOP)", - "hf 14a simaid -t 3 --aid a000000000000000000000 --response 9000 --apdu 9000 -> AID, Response and APDU", - "hf 14a simaid -t 3 --rats 05788172220101 --response 01009000 --apdu 86009000 -> Custom RATS Added", - "hf 14a simaid -t 3 --rats 05788172220101 -x -> Enumerate AID Values" + "hf 14a simaid -t 3 --aid a000000000000000000000 --selectaid_response 9000 --getdata_response 9000 -> Custom AID and responses", + "hf 14a simaid -t 3 --ats 0578817222 --selectaid_response 01009000 --getdata_response 86009000 -> Custom ATS and responses", + "hf 14a simaid -t 3 --ats 0578817222 -x -> Enumerate AID Values" ], "offline": false, "options": [ "-h, --help This help", "-t, --type <1-12> Simulation type to use", "-u, --uid <4|7|10> hex bytes UID", - "-r, --rats <0-20> hex bytes RATS", - "-a, --aid <0-100> hex bytes for AID to respond to (Default: A000000000000000000000)", - "-e, --response <0-100> hex bytes for APDU Response to AID Select (Default: 9000)", - "-p, --apdu <0-100> hex bytes for APDU Response to Get Data request after AID (Default: 9000)", + "-r, --ats <0-20> hex bytes ATS", + "-a, --aid <0-30> hex bytes for AID to respond to (Default: A000000000000000000000)", + "-e, --selectaid_response <0-100> hex bytes for APDU Response to AID Select (Default: 9000)", + "-p, --getdata_response <0-100> hex bytes for APDU Response to Get Data request after AID (Default: 9000)", "-x, --enumerate Enumerate all AID values via returning Not Found and print them to console" ], "usage": "hf 14a simaid [-hx] -t <1-12> [-u ] [-r ] [-a ] [-e ] [-p ]" @@ -3657,14 +3657,23 @@ "command": "hf iclass sam", "description": "Extract PACS via a HID SAM", "notes": [ - "hf iclass sam" + "hf iclass sam", + "hf iclass sam -p -d a005a103800104 -> get PACS data, but ensure that epurse will stay unchanged", + "hf iclass sam --break-on-nr-mac -> get Nr-MAC for extracting encrypted SIO" ], "offline": false, "options": [ "-h, --help This help", - "-v, --verbose verbose output" + "-v, --verbose verbose output", + "-k, --keep keep the field active after command executed", + "-n, --nodetect skip selecting the card and sending card details to SAM", + "-t, --tlv decode TLV", + "--break-on-nr-mac stop tag interaction on nr-mac", + "-p, --prevent-epurse-update fake epurse update", + "--shallow shallow mod", + "-d, --data DER encoded command to send to SAM" ], - "usage": "hf iclass sam [-hv]" + "usage": "hf iclass sam [-hvkntp] [--break-on-nr-mac] [--shallow] [-d ]..." }, "hf iclass sim": { "command": "hf iclass sim", @@ -4486,12 +4495,13 @@ "-f, --file Specify a filename for dump file", "--mini MIFARE Classic Mini / S20", "--1k MIFARE Classic 1k / S50 (def)", + "--1k+ MIFARE Classic Ev1 1k / S50", "--2k MIFARE Classic/Plus 2k", "--4k MIFARE Classic 4k / S70", "--emu from emulator memory", "--gdm use gdm alt (20/23) magic wakeup" ], - "usage": "hf mf cload [-h] [-f ] [--mini] [--1k] [--2k] [--4k] [--emu] [--gdm]" + "usage": "hf mf cload [-h] [-f ] [--mini] [--1k] [--1k+] [--2k] [--4k] [--emu] [--gdm]" }, "hf mf csave": { "command": "hf mf csave", @@ -7693,7 +7703,7 @@ "description": "Extract PACS via a HID SAM", "notes": [ "hf seos sam", - "hd seos sam -d a005a103800104 -> get PACS data" + "hf seos sam -d a005a103800104 -> get PACS data" ], "offline": false, "options": [ @@ -9851,8 +9861,8 @@ "command": "lf hitag chk", "description": "Run dictionary key or password recovery against Hitag card.", "notes": [ - "lf hitag chk", - "-> checks for both pwd / crypto keyslf hitag chk --crypto -> use def dictionary", + "lf hitag chk -> checks for both pwd / crypto keys", + "lf hitag chk --crypto -> use def dictionary", "lf hitag chk --pwd -f my.dic -> pwd mode, custom dictionary" ], "offline": false, @@ -13202,6 +13212,6 @@ "metadata": { "commands_extracted": 759, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-01-14T15:42:02" + "extracted_on": "2025-02-21T14:36:13" } } diff --git a/doc/commands.md b/doc/commands.md index 8650508ff..032f7dd6d 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -150,7 +150,7 @@ Check column "offline" for their availability. |------- |------- |----------- |`emv help `|Y |`This help` |`emv list `|Y |`List ISO7816 history` -|`emv test `|Y |`Crypto logic selftest` +|`emv test `|Y |`Perform crypto logic self tests` |`emv challenge `|N |`Generate challenge` |`emv exec `|N |`Executes EMV contactless transaction` |`emv genac `|N |`Generate ApplicationCryptogram` diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index 9128dbb90..a592fb5c0 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -924,6 +924,9 @@ typedef struct { // No key available client/pm3: no cryptographic key available. #define PM3_ENOKEY -28 +// Cryptographic error client/pm3: cryptographic operation failed +#define PM3_ECRYPTO -29 + // No data client/pm3: no data available, no host frame available (not really an error) #define PM3_ENODATA -98 // Quit program client: reserved, order to quit the program diff --git a/include/protocols.h b/include/protocols.h index eb22a89a7..7048263d5 100644 --- a/include/protocols.h +++ b/include/protocols.h @@ -205,6 +205,12 @@ ISO 7816-4 Basic interindustry commands. For command APDU's. #define MIFARE_EV1_UIDF1 0x40 #define MIFARE_EV1_UIDF2 0x20 #define MIFARE_EV1_UIDF3 0x60 +#define MIFARE_EV1_SELECT_APP 0x5A +#define MIFARE_EV1_AUTH_AES 0xAA +#define MIFARE_EV1_AUTH_AES_2 0xAF +#define MIFARE_EV1_GET_FILE_INFO 0xF5 +#define MIFARE_EV1_READ_DATA 0xBD + #define MIFARE_ULC_WRITE 0xA2 #define MIFARE_ULC_COMP_WRITE 0xA0 From 145b3ac8d6699d7b4384337b95fe9b885a2e1037 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Fri, 21 Feb 2025 15:43:28 +0100 Subject: [PATCH 059/105] rename paxton scripts and some code styling --- .../{Paxton_convert.py => paxton_convert.py} | 32 +++++++++++++++++++ .../{PAXTON_NET.py => paxton_net.py} | 19 +++++++++++ .../{Paxton_switch.py => paxton_switch.py} | 18 +++++++++++ 3 files changed, 69 insertions(+) rename client/pyscripts/{Paxton_convert.py => paxton_convert.py} (98%) mode change 100644 => 100755 rename client/pyscripts/{PAXTON_NET.py => paxton_net.py} (98%) mode change 100644 => 100755 rename client/pyscripts/{Paxton_switch.py => paxton_switch.py} (98%) mode change 100644 => 100755 diff --git a/client/pyscripts/Paxton_convert.py b/client/pyscripts/paxton_convert.py old mode 100644 new mode 100755 similarity index 98% rename from client/pyscripts/Paxton_convert.py rename to client/pyscripts/paxton_convert.py index 0a0415bff..d28f5ae52 --- a/client/pyscripts/Paxton_convert.py +++ b/client/pyscripts/paxton_convert.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + # paxton_convert.py - Convert Paxton Net2 and Switch2 to EM4102 # Author jareckib # Based on Equipter's tutorial - Downgrade Paxton Net to EM410x @@ -19,68 +21,95 @@ import sys def hex_to_bin(hex_string): return ''.join(format(byte, '08b') for byte in bytearray.fromhex(hex_string)) + def remove_last_two_bits(binary_str): return binary_str[:-2] + def split_into_5bit_chunks(binary_str): return [binary_str[i:i+5] for i in range(0, len(binary_str), 5)] + def remove_parity_bit(chunks): return [chunk[1:] for chunk in chunks if len(chunk) == 5] + def convert_to_hex(chunks): return [format(int(chunk, 2), 'X') for chunk in chunks] + def convert_to_decimal(chunks): return [int(chunk, 2) for chunk in chunks] + def find_until_before_f(hex_values): result = [] for value in hex_values: if value == 'F': break result.append(value) + return result + def process_block(block): binary_str = hex_to_bin(block) binary_str = remove_last_two_bits(binary_str) chunks = split_into_5bit_chunks(binary_str) no_parity_chunks = remove_parity_bit(chunks) + return no_parity_chunks + def calculate_id_net(blocks): + all_hex_values = [] for block in blocks: hex_values = convert_to_hex(process_block(block)) all_hex_values.extend(hex_values) + selected_hex_values = find_until_before_f(all_hex_values) + if not selected_hex_values: raise ValueError("Error: No valid data found in blocks 4 and 5.") + combined_hex = ''.join(selected_hex_values) + if not combined_hex.isdigit(): raise ValueError("Error: Invalid data in blocks 4 and 5.") + decimal_id = int(combined_hex) stripped_hex_id = format(decimal_id, 'X').upper() padded_hex_id = stripped_hex_id.zfill(10) + return decimal_id, padded_hex_id + def calculate_id_switch(blocks): + all_decimal_values = [] for block in blocks: decimal_values = convert_to_decimal(process_block(block)) all_decimal_values.extend(decimal_values) + if len(all_decimal_values) < 15: raise ValueError("Error: Not enough data after processing blocks 4, 5, 6, and 7.") + id_positions = [9, 11, 13, 15, 2, 4, 6, 8] id_numbers = [all_decimal_values[pos-1] for pos in id_positions] decimal_id = int(''.join(map(str, id_numbers))) padded_hex_id = format(decimal_id, 'X').upper().zfill(10) + return decimal_id, padded_hex_id + def input_block_data(block_number): + while True: block_data = input("Enter data for block {} (4 bytes in hex): ".format(block_number)).strip() if len(block_data) != 8 or not all(c in '0123456789abcdefABCDEF' for c in block_data): print("Error: Data must be 4 bytes (8 characters) in hex. Try again.") else: return block_data + block_4 = input_block_data(4) block_5 = input_block_data(5) + if block_5[3] == 'F' or block_5[3] == 'f': print("Identified Paxton Net2") blocks = [block_4, block_5] + try: decimal_id, padded_hex_id = calculate_id_net(blocks) print('Calculations for block 4 and block 5:') @@ -89,11 +118,13 @@ if block_5[3] == 'F' or block_5[3] == 'f': print('Use the following command in Proxmark3: lf em 410x clone --id {}'.format(padded_hex_id)) except ValueError as e: print(e) + else: print("Identified Paxton Switch2") block_6 = input_block_data(6) block_7 = input_block_data(7) blocks = [block_4, block_5, block_6, block_7] + try: decimal_id, padded_hex_id = calculate_id_switch(blocks) print('Calculated data from blocks 4, 5, 6, 7:') @@ -102,4 +133,5 @@ else: print('Use the following command in Proxmark3: lf em 410x clone --id {}'.format(padded_hex_id)) except ValueError as e: print(e) + print('If EM4102 does not work, this option is probably disabled. Sorry for the inconvenience.') diff --git a/client/pyscripts/PAXTON_NET.py b/client/pyscripts/paxton_net.py old mode 100644 new mode 100755 similarity index 98% rename from client/pyscripts/PAXTON_NET.py rename to client/pyscripts/paxton_net.py index 921cc9459..26978b4e7 --- a/client/pyscripts/PAXTON_NET.py +++ b/client/pyscripts/paxton_net.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + # paxton_net.py - Convert Paxton Net2 to EM4102 # Author jareckib # Based on Equipter's tutorial - Downgrade Paxton Net to EM410x @@ -17,16 +19,22 @@ # GNU General Public License for more details. import sys + def hex_to_bin(hex_string): return ''.join(format(byte, '08b') for byte in bytearray.fromhex(hex_string)) + def remove_last_two_bits(binary_str): return binary_str[:-2] + def split_into_5bit_chunks(binary_str): return [binary_str[i:i+5] for i in range(0, len(binary_str), 5)] + def remove_parity_bit(chunks): return [chunk[1:] for chunk in chunks if len(chunk) == 5] + def convert_to_hex(chunks): return [format(int(chunk, 2), 'X') for chunk in chunks] + def find_until_before_f(hex_values): result = [] for value in hex_values: @@ -34,6 +42,7 @@ def find_until_before_f(hex_values): break result.append(value) return result + def process_block(block): binary_str = hex_to_bin(block) binary_str = remove_last_two_bits(binary_str) @@ -41,21 +50,30 @@ def process_block(block): no_parity_chunks = remove_parity_bit(chunks) hex_values = convert_to_hex(no_parity_chunks) return hex_values + def calculate_id(blocks): + all_hex_values = [] + for block in blocks: hex_values = process_block(block) all_hex_values.extend(hex_values) + selected_hex_values = find_until_before_f(all_hex_values) + if not selected_hex_values: raise ValueError("Error: No valid data found in blocks 4 and 5.") + combined_hex = ''.join(selected_hex_values) + if not combined_hex.isdigit(): raise ValueError("Error: Invalid data in blocks 4 and 5.") + decimal_id = int(combined_hex) stripped_hex_id = format(decimal_id, 'X').upper() padded_hex_id = stripped_hex_id.zfill(10) return combined_hex, decimal_id, stripped_hex_id, padded_hex_id + def input_block_data(block_number): while True: block_data = input("Enter data for block {} (4 bytes in hex): ".format(block_number)).strip() @@ -75,6 +93,7 @@ blocks = [ block_4, block_5, ] + try: result_hex, result_decimal, stripped_hex_id, padded_hex_id = calculate_id(blocks) print('Calculations for block 4 and block 5:') diff --git a/client/pyscripts/Paxton_switch.py b/client/pyscripts/paxton_switch.py old mode 100644 new mode 100755 similarity index 98% rename from client/pyscripts/Paxton_switch.py rename to client/pyscripts/paxton_switch.py index e51def0f7..639e40402 --- a/client/pyscripts/Paxton_switch.py +++ b/client/pyscripts/paxton_switch.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + # paxton_switch.py - Convert Paxton Switch2 to EM4102 # Author jareckib # Based on Equipter's tutorial - Downgrade Paxton Net to EM410x @@ -16,35 +18,48 @@ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. import sys + def hex_to_bin(hex_string): return ''.join(format(byte, '08b') for byte in bytearray.fromhex(hex_string)) + def remove_last_two_bits(binary_str): return binary_str[:-2] + def split_into_5bit_chunks(binary_str): return [binary_str[i:i+5] for i in range(0, len(binary_str), 5)] + def remove_parity_bit(chunks): return [chunk[1:] for chunk in chunks if len(chunk) == 5] + def convert_to_decimal(chunks): return [int(chunk, 2) for chunk in chunks] + def process_block(block): binary_str = hex_to_bin(block) binary_str = remove_last_two_bits(binary_str) chunks = split_into_5bit_chunks(binary_str) no_parity_chunks = remove_parity_bit(chunks) decimal_values = convert_to_decimal(no_parity_chunks) + return decimal_values + def calculate_id(blocks): + all_decimal_values = [] for block in blocks: decimal_values = process_block(block) all_decimal_values.extend(decimal_values) + if len(all_decimal_values) < 15: raise ValueError("Error: Not enough data after processing blocks 4, 5, 6, and 7.") + id_positions = [9, 11, 13, 15, 2, 4, 6, 8] id_numbers = [all_decimal_values[pos-1] for pos in id_positions] decimal_id = int(''.join(map(str, id_numbers))) padded_hex_id = format(decimal_id, 'X').upper().zfill(10) + return decimal_id, padded_hex_id + def input_block_data(block_number): while True: block_data = input("Enter data for block {} (4 bytes in hex): ".format(block_number)).strip() @@ -52,6 +67,8 @@ def input_block_data(block_number): print("Error: Data must be 4 bytes (8 characters) in hex. Try again.") else: return block_data + + block_4 = input_block_data(4) block_5 = input_block_data(5) block_6 = input_block_data(6) @@ -62,6 +79,7 @@ blocks = [ block_6, block_7, ] + try: decimal_id, padded_hex_id = calculate_id(blocks) print('Calculated data from blocks 4, 5, 6, 7:') From 442210124374ab5bf842dec889e943aa4286ac21 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Fri, 21 Feb 2025 16:33:22 +0100 Subject: [PATCH 060/105] fix #2547 - compilation warning error: dereferencing type-punned pointer will break strict-aliasing rules [-Werror=strict-aliasing] --- armsrc/hitag2.c | 13 +++++++++---- armsrc/hitagS.c | 10 +++++----- common/hitag2/hitag2_crypto.c | 13 ++++--------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/armsrc/hitag2.c b/armsrc/hitag2.c index 18e6866dd..0c880685c 100644 --- a/armsrc/hitag2.c +++ b/armsrc/hitag2.c @@ -821,12 +821,17 @@ static bool hitag2_crypto(uint8_t *rx, const size_t rxlen, uint8_t *tx, size_t * // stage 1, got UID if (bCrypto == false) { + uint64_t ui64key = key[0] | + ((uint64_t)key[1]) << 8 | + ((uint64_t)key[2]) << 16 | + ((uint64_t)key[3]) << 24 | + ((uint64_t)key[4]) << 32 | + ((uint64_t)key[5]) << 40; + + uint32_t ui32uid = MemLeToUint4byte(rx); + DBG Dbprintf("hitag2_crypto: key array "); DBG Dbhexdump(6, key, false); - - uint64_t ui64key = key[0] | ((uint64_t)key[1]) << 8 | ((uint64_t)key[2]) << 16 | ((uint64_t)key[3]) << 24 | ((uint64_t)key[4]) << 32 | ((uint64_t)key[5]) << 40; - - uint32_t ui32uid = rx[0] | ((uint32_t)rx[1]) << 8 | ((uint32_t)rx[2]) << 16 | ((uint32_t)rx[3]) << 24; DBG Dbprintf("hitag2_crypto: key=0x%x%x uid=0x%x" , (uint32_t)((REV64(ui64key)) >> 32) , (uint32_t)((REV64(ui64key)) & 0xffffffff) diff --git a/armsrc/hitagS.c b/armsrc/hitagS.c index b80210f66..924240ea7 100644 --- a/armsrc/hitagS.c +++ b/armsrc/hitagS.c @@ -538,7 +538,8 @@ static void hts_handle_reader_command(uint8_t *rx, const size_t rxlen, rotate_uid++; *txlen = 32; // init crypt engine - state = ht2_hitag2_init(REV64(tag.data.s.key), REV32(tag.data.s.uid_le), REV32(*(uint32_t *)rx)); + uint32_t le_rx = MemLeToUint4byte(rx); + state = ht2_hitag2_init(REV64(tag.data.s.key), REV32(tag.data.s.uid_le), REV32(le_rx)); DBG Dbhexdump(8, tx, false); for (int i = 0; i < 4; i++) { @@ -1129,10 +1130,7 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz // if the tag is in authentication mode try the key or challenge if (packet->cmd == HTSF_KEY) { - DBG DbpString("Authenticating using key:"); - DBG Dbhexdump(6, packet->key, false); - - key_le = *(uint64_t *)packet->key; + key_le = MemLeToUint6byte(packet->key); uint32_t le_val = MemLeToUint4byte(rnd); uint64_t state = ht2_hitag2_init(REV64(key_le), REV32(tag.data.s.uid_le), REV32(le_val)); @@ -1146,6 +1144,8 @@ static int hts_select_tag(const lf_hitag_data_t *packet, uint8_t *tx, size_t siz txlen = concatbits(tx, txlen, rnd, 0, 32); txlen = concatbits(tx, txlen, auth_ks, 0, 32); + DBG DbpString("Authenticating using key:"); + DBG Dbhexdump(6, packet->key, false); DBG Dbprintf("%02X %02X %02X %02X %02X %02X %02X %02X", tx[0], tx[1], tx[2], tx[3], tx[4], tx[5], tx[6], tx[7]); } else if (packet->cmd == HTSF_CHALLENGE) { diff --git a/common/hitag2/hitag2_crypto.c b/common/hitag2/hitag2_crypto.c index 9e9499426..381451645 100644 --- a/common/hitag2/hitag2_crypto.c +++ b/common/hitag2/hitag2_crypto.c @@ -404,15 +404,10 @@ void ht2_hitag2_cipher_reset(hitag2_t *tag, const uint8_t *iv) { ((uint64_t)tag->sectors[1][1] << 24) | ((uint64_t)tag->sectors[1][2] << 32) | ((uint64_t)tag->sectors[1][3] << 40); - uint32_t uid = ((uint32_t)tag->sectors[0][0]) | - ((uint32_t)tag->sectors[0][1] << 8) | - ((uint32_t)tag->sectors[0][2] << 16) | - ((uint32_t)tag->sectors[0][3] << 24); - uint32_t iv_ = (((uint32_t)(iv[0]))) | - (((uint32_t)(iv[1])) << 8) | - (((uint32_t)(iv[2])) << 16) | - (((uint32_t)(iv[3])) << 24); - tag->cs = ht2_hitag2_init(REV64(key), REV32(uid), REV32(iv_)); + uint32_t uid = MemLeToUint4byte(tag->sectors[0]); + uint32_t riv = MemLeToUint4byte(iv); + + tag->cs = ht2_hitag2_init(REV64(key), REV32(uid), REV32(riv)); } int ht2_hitag2_cipher_authenticate(uint64_t *state, const uint8_t *authenticator_is) { From 72a9f0a0a80ed379e69c8c397dcf909d2a49373b Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Fri, 21 Feb 2025 16:33:40 +0100 Subject: [PATCH 061/105] code style --- fpga/strip_date_time_from_binary.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fpga/strip_date_time_from_binary.py b/fpga/strip_date_time_from_binary.py index cc469ca72..aba9c0dc5 100644 --- a/fpga/strip_date_time_from_binary.py +++ b/fpga/strip_date_time_from_binary.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + import sys # File to take a .bit file generated by xilinx webpack ISE From 1cf98096e6cef1b30dfff9fa971b3e586ddc7639 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Fri, 21 Feb 2025 16:41:05 +0100 Subject: [PATCH 062/105] style --- armsrc/hitag2.c | 10 +++++----- doc/commands.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/armsrc/hitag2.c b/armsrc/hitag2.c index 0c880685c..a8f04e8a2 100644 --- a/armsrc/hitag2.c +++ b/armsrc/hitag2.c @@ -822,11 +822,11 @@ static bool hitag2_crypto(uint8_t *rx, const size_t rxlen, uint8_t *tx, size_t * if (bCrypto == false) { uint64_t ui64key = key[0] | - ((uint64_t)key[1]) << 8 | - ((uint64_t)key[2]) << 16 | - ((uint64_t)key[3]) << 24 | - ((uint64_t)key[4]) << 32 | - ((uint64_t)key[5]) << 40; + ((uint64_t)key[1]) << 8 | + ((uint64_t)key[2]) << 16 | + ((uint64_t)key[3]) << 24 | + ((uint64_t)key[4]) << 32 | + ((uint64_t)key[5]) << 40; uint32_t ui32uid = MemLeToUint4byte(rx); diff --git a/doc/commands.json b/doc/commands.json index d540c2bc8..f0cb693ca 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -13212,6 +13212,6 @@ "metadata": { "commands_extracted": 759, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-02-21T14:36:13" + "extracted_on": "2025-02-21T15:40:14" } } From 717ceecf1fcb53623e009ddd74c0917b98d473f4 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Fri, 21 Feb 2025 17:08:44 +0100 Subject: [PATCH 063/105] denote MAD sectors as decimal since public documentation --- client/src/cmdhfmf.c | 7 ------- client/src/cmdhfmfdes.c | 13 ++++++++++++- client/src/cmdhfmfp.c | 14 +++++++------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 7034ab6f3..977a9cad2 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -121,13 +121,6 @@ static char *GenerateFilename(const char *prefix, const char *suffix) { // Each entry also stores whether the key was "found", defaults to false (0) static int initSectorTable(sector_t **src, size_t items) { - - // typedef struct { - // uint64_t Key[2]; - // uint8_t foundKey[2]; - // } sector_t; - - // This allocates based on the size of a single item (*src) = calloc(items, sizeof(sector_t)); if (*src == NULL) { return PM3_EMALLOC; diff --git a/client/src/cmdhfmfdes.c b/client/src/cmdhfmfdes.c index 8a63b75ed..123760fcd 100644 --- a/client/src/cmdhfmfdes.c +++ b/client/src/cmdhfmfdes.c @@ -180,10 +180,21 @@ typedef struct { const char *comment; } mfdesCommonAID_t; +/* +PACS application id(s) - HID Factory, CP1000 Standard, Mobile, Custom and Elite +We have HID Factory, Field Encoder == CP1000 (?) +No mobile, Custom or Elite +*/ + static const mfdesCommonAID_t commonAids[] = { - // AID, name/comment + { 0x53494F, "\x53\x49\x4F", "SIO DESFire EV1 - HID Factory" }, + { 0xD3494F, "\xD3\x49\x4F", "SIO DESFire EV1 - Field Encoder" }, + { 0xD9494F, "\xD9\x49\x4F", "SIO DESFire EV1 - Field Encoder" }, + { 0xF484E3, "\xF4\x84\xE3", "SE Enhanced" }, + { 0xF484E4, "\xF4\x84\xE4", "SE Enhanced" }, { 0xF4812F, "\xf4\x81\x2f", "Gallagher card data application" }, { 0xF48120, "\xf4\x81\x20", "Gallagher card application directory" }, // Can be 0xF48120 - 0xF4812B, but I've only ever seen 0xF48120 + { 0xF47300, "\xf4\x73\x00", "Inner Range card application" }, }; static int CmdHelp(const char *Cmd); diff --git a/client/src/cmdhfmfp.c b/client/src/cmdhfmfp.c index bb3bbe58d..37e76a0f7 100644 --- a/client/src/cmdhfmfp.c +++ b/client/src/cmdhfmfp.c @@ -1812,7 +1812,7 @@ static int CmdHFMFPMAD(const char *Cmd) { } uint8_t sector0[16 * 4] = {0}; - uint8_t sector10[16 * 4] = {0}; + uint8_t sector16[16 * 4] = {0}; if (mfpReadSector(MF_MAD1_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector0, verbose)) { PrintAndLogEx(NORMAL, ""); @@ -1832,19 +1832,19 @@ static int CmdHFMFPMAD(const char *Cmd) { MAD1DecodeAndPrint(sector0, swapmad, verbose, &haveMAD2); if (haveMAD2) { - if (mfpReadSector(MF_MAD2_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector10, verbose)) { + if (mfpReadSector(MF_MAD2_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector16, verbose)) { PrintAndLogEx(NORMAL, ""); PrintAndLogEx(ERR, "error, read sector " _YELLOW_("0x10") ". Card doesn't have MAD or doesn't have MAD on default keys"); return PM3_ESOFT; } - MAD2DecodeAndPrint(sector10, swapmad, verbose); + MAD2DecodeAndPrint(sector16, swapmad, verbose); } if (aidlen == 2 || decodeholder) { uint16_t mad[7 + 8 + 8 + 8 + 8] = {0}; size_t madlen = 0; - if (MADDecode(sector0, sector10, mad, &madlen, swapmad)) { + if (MADDecode(sector0, sector16, mad, &madlen, swapmad)) { PrintAndLogEx(ERR, "can't decode MAD"); return PM3_EWRONGANSWER; } @@ -1990,7 +1990,7 @@ int CmdHFMFPNDEFRead(const char *Cmd) { } uint8_t sector0[MIFARE_1K_MAXBLOCK] = {0}; - uint8_t sector10[MIFARE_1K_MAXBLOCK] = {0}; + uint8_t sector16[MIFARE_1K_MAXBLOCK] = {0}; uint8_t data[MIFARE_4K_MAX_BYTES] = {0}; int datalen = 0; @@ -2015,7 +2015,7 @@ int CmdHFMFPNDEFRead(const char *Cmd) { if (verbose) PrintAndLogEx(INFO, "reading MAD v2 sector"); - if (mfpReadSector(MF_MAD2_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector10, verbose)) { + if (mfpReadSector(MF_MAD2_SECTOR, MF_KEY_A, (uint8_t *)g_mifarep_mad_key, sector16, verbose)) { PrintAndLogEx(ERR, "error, read sector 0x10. card doesn't have MAD or doesn't have MAD on default keys"); PrintAndLogEx(HINT, "Try " _YELLOW_("`hf mfp ndefread -k `") " with your custom key"); return PM3_ESOFT; @@ -2024,7 +2024,7 @@ int CmdHFMFPNDEFRead(const char *Cmd) { uint16_t mad[7 + 8 + 8 + 8 + 8] = {0}; size_t madlen = 0; - res = MADDecode(sector0, (haveMAD2 ? sector10 : NULL), mad, &madlen, false); + res = MADDecode(sector0, (haveMAD2 ? sector16 : NULL), mad, &madlen, false); if (res != PM3_SUCCESS) { PrintAndLogEx(ERR, "can't decode MAD"); return res; From 2d930f921a2bcb4e33d007fd15a49a551a5b1339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tiago=20Esperan=C3=A7a=20Triques?= Date: Sat, 22 Feb 2025 10:53:18 -0300 Subject: [PATCH 064/105] Remove duplicated block print in verbose mode in hf mf view --- client/src/cmdhfmf.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 977a9cad2..8aab08019 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -326,16 +326,16 @@ static void mf_print_block(uint8_t blockno, uint8_t *d, bool verbose) { } } else { - int32_t value = 0; - if (verbose && mfc_value(d, &value)) { - PrintAndLogEx(INFO, "%s| %3d | " _CYAN_("%s") " %"PRIi32, secstr, blockno, sprint_hex_ascii(d, MFBLOCK_SIZE), value); - } - if (blockno >= MIFARE_1K_MAXBLOCK) { // MFC Ev1 signature blocks. PrintAndLogEx(INFO, _BACK_BLUE_("%s| %3d | %s"), secstr, blockno, sprint_hex_ascii(d, MFBLOCK_SIZE)); } else { - PrintAndLogEx(INFO, "%s| %3d | %s", secstr, blockno, sprint_hex_ascii(d, MFBLOCK_SIZE)); + int32_t value = 0; + if (verbose && mfc_value(d, &value)) { + PrintAndLogEx(INFO, "%s| %3d | " _CYAN_("%s") " %"PRIi32, secstr, blockno, sprint_hex_ascii(d, MFBLOCK_SIZE), value); + } else { + PrintAndLogEx(INFO, "%s| %3d | %s", secstr, blockno, sprint_hex_ascii(d, MFBLOCK_SIZE)); + } } } } From a7643eef7cb56f4d54c8e2842671201b62279ee2 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Sat, 22 Feb 2025 18:01:07 +0100 Subject: [PATCH 065/105] style --- armsrc/iso14443a.c | 7 ++++--- armsrc/mifarecmd.c | 18 ++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/armsrc/iso14443a.c b/armsrc/iso14443a.c index f469061d7..9d282eee6 100644 --- a/armsrc/iso14443a.c +++ b/armsrc/iso14443a.c @@ -1682,8 +1682,8 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin EmSend4bit(CARD_NACK_IV); } else { // first blocks of emu are header - uint16_t start = block * 4 + MFU_DUMP_PREFIX_LENGTH; - uint8_t emdata[MAX_MIFARE_FRAME_SIZE]; + uint16_t start = (block * 4) + MFU_DUMP_PREFIX_LENGTH; + uint8_t emdata[MAX_MIFARE_FRAME_SIZE] = {0}; emlGet(emdata, start, MIFARE_BLOCK_SIZE); AddCrc14A(emdata, MIFARE_BLOCK_SIZE); EmSendCmd(emdata, sizeof(emdata)); @@ -1728,13 +1728,14 @@ void SimulateIso14443aTag(uint8_t tagType, uint16_t flags, uint8_t *useruid, uin } else if (receivedCmd[0] == MIFARE_ULC_WRITE && len == 8 && (tagType == 2 || tagType == 7)) { // Received a WRITE // cmd + block + 4 bytes data + 2 bytes crc if (CheckCrc14A(receivedCmd, len)) { + uint8_t block = receivedCmd[1]; if (block > pages) { // send NACK 0x0 == invalid argument EmSend4bit(CARD_NACK_IV); } else { // first blocks of emu are header - emlSetMem_xt(&receivedCmd[2], block + MFU_DUMP_PREFIX_LENGTH / 4, 1, 4); + emlSetMem_xt(&receivedCmd[2], block + (MFU_DUMP_PREFIX_LENGTH / 4), 1, 4); // send ACK EmSend4bit(CARD_ACK); } diff --git a/armsrc/mifarecmd.c b/armsrc/mifarecmd.c index a027dcecc..a2aa2450d 100644 --- a/armsrc/mifarecmd.c +++ b/armsrc/mifarecmd.c @@ -2376,8 +2376,8 @@ void MifareChkKeys_file(uint8_t *fn) { void MifarePersonalizeUID(uint8_t keyType, uint8_t perso_option, uint64_t key) { uint16_t isOK = PM3_EUNDEF; - uint8_t uid[10]; - uint32_t cuid; + uint8_t uid[10] = { 0 }; + uint32_t cuid = 0; struct Crypto1State mpcs = {0, 0}; struct Crypto1State *pcs; pcs = &mpcs; @@ -2388,8 +2388,12 @@ void MifarePersonalizeUID(uint8_t keyType, uint8_t perso_option, uint64_t key) { LED_A_ON(); + uint8_t rec_answer[MAX_MIFARE_FRAME_SIZE] = {0}; + uint8_t rec_answer_par[MAX_MIFARE_PARITY_SIZE] = {0}; + while (true) { - if (!iso14443a_select_card(uid, NULL, &cuid, true, 0, true)) { + + if (iso14443a_select_card(uid, NULL, &cuid, true, 0, true) == false) { if (g_dbglevel >= DBG_ERROR) Dbprintf("Can't select card"); break; } @@ -2400,11 +2404,9 @@ void MifarePersonalizeUID(uint8_t keyType, uint8_t perso_option, uint64_t key) { break; } - uint8_t receivedAnswer[MAX_MIFARE_FRAME_SIZE]; - uint8_t receivedAnswerPar[MAX_MIFARE_PARITY_SIZE]; - int len = mifare_sendcmd_short(pcs, true, MIFARE_EV1_PERSONAL_UID, perso_option, receivedAnswer, sizeof(receivedAnswer), receivedAnswerPar, NULL); - if (len != 1 || receivedAnswer[0] != CARD_ACK) { - if (g_dbglevel >= DBG_ERROR) Dbprintf("Cmd Error: %02x", receivedAnswer[0]); + int len = mifare_sendcmd_short(pcs, true, MIFARE_EV1_PERSONAL_UID, perso_option, rec_answer, sizeof(rec_answer), rec_answer_par, NULL); + if (len != 1 || rec_answer[0] != CARD_ACK) { + if (g_dbglevel >= DBG_ERROR) Dbprintf("Cmd Error: %02x", rec_answer[0]); break; } From 323a4284ff1d862c125c2bbdcbed56a9e00c1069 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Sat, 22 Feb 2025 18:28:38 +0100 Subject: [PATCH 066/105] modified weigand formats to include number of bits --- CHANGELOG.md | 1 + client/src/cmdhficlass.c | 2 +- client/src/cmdlfpyramid.c | 1 + client/src/cmdpiv.c | 2 +- client/src/cmdsmartcard.c | 4 +- client/src/wiegand_formats.c | 107 +++++++++++++++++------------------ client/src/wiegand_formats.h | 4 +- doc/commands.json | 2 +- 8 files changed, 64 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb37c1199..2923c4b01 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 Wiegand formats to include number of bits (@iceman1001) - Fix compilation warning in hitagS (@iceman1001) - Added new wiegand format H800002 (@jmichelp) - Changed `Makefile.platform.sample` file - now have clear instructions for generating images for other proxmark3 hardware (@iceman1001) diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index 136d5abea..992a818f7 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -1445,7 +1445,7 @@ static int iclass_decode_credentials_new_pacs(uint8_t *d) { free(binstr); PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(INFO, "Wiegand decode"); + PrintAndLogEx(INFO, "------------------------- " _CYAN_("SIO - Wiegand") " ----------------------------"); decode_wiegand(top, mid, bot, 0); return PM3_SUCCESS; diff --git a/client/src/cmdlfpyramid.c b/client/src/cmdlfpyramid.c index dcc87e1fa..f61914e66 100644 --- a/client/src/cmdlfpyramid.c +++ b/client/src/cmdlfpyramid.c @@ -305,6 +305,7 @@ static int CmdPyramidClone(const char *Cmd) { uint8_t *bs = calloc(128, sizeof(uint8_t)); if (bs == NULL) { + PrintAndLogEx(WARNING, "failed to allocate memory"); return PM3_EMALLOC; } diff --git a/client/src/cmdpiv.c b/client/src/cmdpiv.c index 4c297124b..423dcfdab 100644 --- a/client/src/cmdpiv.c +++ b/client/src/cmdpiv.c @@ -596,8 +596,8 @@ static int PivGetData(Iso7816CommandChannel channel, const uint8_t tag[], size_t // Answer can be chained. Let's use a dynamically allocated buffer. size_t capacity = PM3_CMD_DATA_SIZE; struct tlvdb_root *root = calloc(1, sizeof(*root) + capacity); - if (root == NULL) { + PrintAndLogEx(WARNING, "failed to allocate memory"); return PM3_EMALLOC; } root->len = 0; diff --git a/client/src/cmdsmartcard.c b/client/src/cmdsmartcard.c index 04b92a8f6..aa27fe6d4 100644 --- a/client/src/cmdsmartcard.c +++ b/client/src/cmdsmartcard.c @@ -1067,8 +1067,10 @@ static int CmdSmartBruteforceSFI(const char *Cmd) { smart_loadjson("aidlist", &root); uint8_t *buf = calloc(PM3_CMD_DATA_SIZE, sizeof(uint8_t)); - if (!buf) + if (buf == NULL) { + PrintAndLogEx(WARNING, "failed to allocate memory"); return PM3_EMALLOC; + } PrintAndLogEx(INFO, "Selecting card"); if (!smart_select(false, NULL)) { diff --git a/client/src/wiegand_formats.c b/client/src/wiegand_formats.c index df1127ec1..57eb1c741 100644 --- a/client/src/wiegand_formats.c +++ b/client/src/wiegand_formats.c @@ -19,8 +19,6 @@ #include #include "commonutil.h" -static bool validate_card_limit(int format_idx, wiegand_card_t *card); - static bool Pack_Defcon32(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); @@ -81,7 +79,6 @@ static bool Unpack_Defcon32(wiegand_message_t *packed, wiegand_card_t *card) { return true; } - static bool Pack_H10301(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); @@ -1249,7 +1246,6 @@ static bool Unpack_pw39(wiegand_message_t *packed, wiegand_card_t *card) { return true; } - static bool Pack_bc40(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); @@ -1287,7 +1283,6 @@ static bool Unpack_bc40(wiegand_message_t *packed, wiegand_card_t *card) { return true; } - static bool step_parity_check(wiegand_message_t *packed, int start, int length, bool even_parity) { bool parity = even_parity; for (int i = start; i < start + length; i += 2) { @@ -1349,7 +1344,7 @@ void print_desc_wiegand(cardformat_t *fmt, wiegand_message_t *packed) { size_t s_len = 128; char *s = calloc(s_len, sizeof(uint8_t)); - snprintf(s, s_len * sizeof(uint8_t), _YELLOW_("%-10s")" %-32s", fmt->Name, fmt->Descrp); + snprintf(s, s_len * sizeof(uint8_t), _YELLOW_("%-10s")" %-32s", fmt->Name, fmt->Description); if (packed->Top != 0) { PrintAndLogEx(SUCCESS, "%s -> " _GREEN_("%X%08X%08X"), @@ -1389,7 +1384,7 @@ void print_wiegand_code(wiegand_message_t *packed) { static void hid_print_card(wiegand_card_t *card, const cardformat_t format) { /* - PrintAndLogEx(SUCCESS, " Format: %s (%s)", format.Name, format.Descrp); + PrintAndLogEx(SUCCESS, " Format: %s (%s)", format.Name, format.Description); if (format.Fields.hasFacilityCode) PrintAndLogEx(SUCCESS, "Facility Code: %d",card->FacilityCode); @@ -1423,51 +1418,51 @@ static void hid_print_card(wiegand_card_t *card, const cardformat_t format) { if (format.Fields.hasParity) snprintf(s + strlen(s), sizeof(s) - strlen(s), " parity ( %s )", card->ParityValid ? _GREEN_("ok") : _RED_("fail")); - PrintAndLogEx(SUCCESS, "[%-8s] %-32s %s", format.Name, format.Descrp, s); + PrintAndLogEx(SUCCESS, "[%-8s] %-32s %s", format.Name, format.Description, s); } static const cardformat_t FormatTable[] = { - {"H10301", Pack_H10301, Unpack_H10301, "HID H10301 26-bit", {1, 1, 0, 0, 1, 0xFF, 0xFFFF, 0, 0}}, // imported from old pack/unpack - {"ind26", Pack_ind26, Unpack_ind26, "Indala 26-bit", {1, 1, 0, 0, 1, 0xFFF, 0xFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"ind27", Pack_ind27, Unpack_ind27, "Indala 27-bit", {1, 1, 0, 0, 0, 0x1FFF, 0x3FFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"indasc27", Pack_indasc27, Unpack_indasc27, "Indala ASC 27-bit", {1, 1, 0, 0, 0, 0x1FFF, 0x3FFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"Tecom27", Pack_Tecom27, Unpack_Tecom27, "Tecom 27-bit", {1, 1, 0, 0, 0, 0x7FF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"2804W", Pack_2804W, Unpack_2804W, "2804 Wiegand 28-bit", {1, 1, 0, 0, 1, 0xFF, 0x7FFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"ind29", Pack_ind29, Unpack_ind29, "Indala 29-bit", {1, 1, 0, 0, 0, 0x1FFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"ATSW30", Pack_ATSW30, Unpack_ATSW30, "ATS Wiegand 30-bit", {1, 1, 0, 0, 1, 0xFFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"ADT31", Pack_ADT31, Unpack_ADT31, "HID ADT 31-bit", {1, 1, 0, 0, 0, 0xF, 0x7FFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"HCP32", Pack_hcp32, Unpack_hcp32, "HID Check Point 32-bit", {1, 0, 0, 0, 0, 0, 0x3FFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"HPP32", Pack_hpp32, Unpack_hpp32, "HID Hewlett-Packard 32-bit", {1, 1, 0, 0, 0, 0xFFF, 0x1FFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"Kastle", Pack_Kastle, Unpack_Kastle, "Kastle 32-bit", {1, 1, 1, 0, 1, 0xFF, 0xFFFF, 0x1F, 0}}, // from @xilni; PR #23 on RfidResearchGroup/proxmark3 - {"Kantech", Pack_Kantech, Unpack_Kantech, "Indala/Kantech KFS 32-bit", {1, 1, 0, 0, 0, 0xFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"WIE32", Pack_wie32, Unpack_wie32, "Wiegand 32-bit", {1, 1, 0, 0, 0, 0xFFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"D10202", Pack_D10202, Unpack_D10202, "HID D10202 33-bit", {1, 1, 0, 0, 1, 0x7F, 0xFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"H10306", Pack_H10306, Unpack_H10306, "HID H10306 34-bit", {1, 1, 0, 0, 1, 0xFFFF, 0xFFFF, 0, 0}}, // imported from old pack/unpack - {"N10002", Pack_N10002, Unpack_N10002, "Honeywell/Northern N10002 34-bit", {1, 1, 0, 0, 1, 0xFFFF, 0xFFFF, 0, 0}}, // from proxclone.com - {"Optus34", Pack_Optus, Unpack_Optus, "Indala Optus 34-bit", {1, 1, 0, 0, 0, 0x3FF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"SMP34", Pack_Smartpass, Unpack_Smartpass, "Cardkey Smartpass 34-bit", {1, 1, 1, 0, 0, 0x3FF, 0xFFFF, 0x7, 0}}, // from cardinfo.barkweb.com.au - {"BQT34", Pack_bqt34, Unpack_bqt34, "BQT 34-bit", {1, 1, 0, 0, 1, 0xFF, 0xFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"C1k35s", Pack_C1k35s, Unpack_C1k35s, "HID Corporate 1000 35-bit std", {1, 1, 0, 0, 1, 0xFFF, 0xFFFFF, 0, 0}}, // imported from old pack/unpack - {"C15001", Pack_C15001, Unpack_C15001, "HID KeyScan 36-bit", {1, 1, 0, 1, 1, 0xFF, 0xFFFF, 0, 0x3FF}}, // from Proxmark forums - {"S12906", Pack_S12906, Unpack_S12906, "HID Simplex 36-bit", {1, 1, 1, 0, 1, 0xFF, 0x3, 0xFFFFFF, 0}}, // from cardinfo.barkweb.com.au - {"Sie36", Pack_Sie36, Unpack_Sie36, "HID 36-bit Siemens", {1, 1, 0, 0, 1, 0x3FFFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"H10320", Pack_H10320, Unpack_H10320, "HID H10320 37-bit BCD", {1, 0, 0, 0, 1, 0, 99999999, 0, 0}}, // from Proxmark forums - {"H10302", Pack_H10302, Unpack_H10302, "HID H10302 37-bit huge ID", {1, 0, 0, 0, 1, 0, 0x7FFFFFFFF, 0, 0}}, // from Proxmark forums - {"H10304", Pack_H10304, Unpack_H10304, "HID H10304 37-bit", {1, 1, 0, 0, 1, 0xFFFF, 0x7FFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"H800002", Pack_H800002, Unpack_H800002, "HID H800002 46-bit", {1, 1, 0, 0, 1, 0x3FFF, 0x3FFFFFFF, 0, 0}}, - {"P10004", Pack_P10004, Unpack_P10004, "HID P10004 37-bit PCSC", {1, 1, 0, 0, 0, 0x1FFF, 0x3FFFF, 0, 0}}, // from @bthedorff; PR #1559 - {"HGen37", Pack_HGeneric37, Unpack_HGeneric37, "HID Generic 37-bit", {1, 0, 0, 0, 1, 0, 0x7FFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"MDI37", Pack_MDI37, Unpack_MDI37, "PointGuard MDI 37-bit", {1, 1, 0, 0, 1, 0xF, 0x1FFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"BQT38", Pack_bqt38, Unpack_bqt38, "BQT 38-bit", {1, 1, 1, 0, 1, 0xFFF, 0x3FFFF, 0x7, 0}}, // from cardinfo.barkweb.com.au - {"ISCS", Pack_iscs38, Unpack_iscs38, "ISCS 38-bit", {1, 1, 0, 1, 1, 0x3FF, 0xFFFFFF, 0, 0x7}}, // from cardinfo.barkweb.com.au - {"PW39", Pack_pw39, Unpack_pw39, "Pyramid 39-bit wiegand format", {1, 1, 0, 0, 1, 0xFFFF, 0xFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"P10001", Pack_P10001, Unpack_P10001, "HID P10001 Honeywell 40-bit", {1, 1, 0, 0, 0, 0xFFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"Casi40", Pack_CasiRusco40, Unpack_CasiRusco40, "Casi-Rusco 40-bit", {1, 0, 0, 0, 0, 0, 0xFFFFFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au - {"C1k48s", Pack_C1k48s, Unpack_C1k48s, "HID Corporate 1000 48-bit std", {1, 1, 0, 0, 1, 0x003FFFFF, 0x007FFFFF, 0, 0}}, // imported from old pack/unpack - {"BC40", Pack_bc40, Unpack_bc40, "Bundy TimeClock 40-bit", {1, 1, 0, 1, 1, 0xFFF, 0xFFFFF, 0, 0x7F}}, // from - {"Avig56", Pack_Avig56, Unpack_Avig56, "Avigilon 56-bit", {1, 1, 0, 0, 1, 0xFFFFF, 0x3FFFFFFFF, 0, 0}}, - {"Defcon32", Pack_Defcon32, Unpack_Defcon32, "Custom Defcon RFCTF 42 BIT format", {1, 1, 1, 0, 1, 0xFFFF, 0xFFFFF, 0xF, 0}}, // Created by (@micsen) for the CTF - {NULL, NULL, NULL, NULL, {0, 0, 0, 0, 0, 0, 0, 0, 0}} // Must null terminate array + {"H10301", Pack_H10301, Unpack_H10301, "HID H10301 26-bit", 26, {1, 1, 0, 0, 1, 0xFF, 0xFFFF, 0, 0}}, // imported from old pack/unpack + {"ind26", Pack_ind26, Unpack_ind26, "Indala 26-bit", 26, {1, 1, 0, 0, 1, 0xFFF, 0xFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"ind27", Pack_ind27, Unpack_ind27, "Indala 27-bit", 27, {1, 1, 0, 0, 0, 0x1FFF, 0x3FFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"indasc27", Pack_indasc27, Unpack_indasc27, "Indala ASC 27-bit", 27, {1, 1, 0, 0, 0, 0x1FFF, 0x3FFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"Tecom27", Pack_Tecom27, Unpack_Tecom27, "Tecom 27-bit", 27, {1, 1, 0, 0, 0, 0x7FF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"2804W", Pack_2804W, Unpack_2804W, "2804 Wiegand 28-bit", 28, {1, 1, 0, 0, 1, 0xFF, 0x7FFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"ind29", Pack_ind29, Unpack_ind29, "Indala 29-bit", 29, {1, 1, 0, 0, 0, 0x1FFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"ATSW30", Pack_ATSW30, Unpack_ATSW30, "ATS Wiegand 30-bit", 30, {1, 1, 0, 0, 1, 0xFFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"ADT31", Pack_ADT31, Unpack_ADT31, "HID ADT 31-bit", 31, {1, 1, 0, 0, 0, 0xF, 0x7FFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"HCP32", Pack_hcp32, Unpack_hcp32, "HID Check Point 32-bit", 32, {1, 0, 0, 0, 0, 0, 0x3FFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"HPP32", Pack_hpp32, Unpack_hpp32, "HID Hewlett-Packard 32-bit", 32, {1, 1, 0, 0, 0, 0xFFF, 0x1FFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"Kastle", Pack_Kastle, Unpack_Kastle, "Kastle 32-bit", 32, {1, 1, 1, 0, 1, 0xFF, 0xFFFF, 0x1F, 0}}, // from @xilni; PR #23 on RfidResearchGroup/proxmark3 + {"Kantech", Pack_Kantech, Unpack_Kantech, "Indala/Kantech KFS 32-bit", 32, {1, 1, 0, 0, 0, 0xFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"WIE32", Pack_wie32, Unpack_wie32, "Wiegand 32-bit", 32, {1, 1, 0, 0, 0, 0xFFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"D10202", Pack_D10202, Unpack_D10202, "HID D10202 33-bit", 33, {1, 1, 0, 0, 1, 0x7F, 0xFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"H10306", Pack_H10306, Unpack_H10306, "HID H10306 34-bit", 34, {1, 1, 0, 0, 1, 0xFFFF, 0xFFFF, 0, 0}}, // imported from old pack/unpack + {"N10002", Pack_N10002, Unpack_N10002, "Honeywell/Northern N10002 34-bit", 34, {1, 1, 0, 0, 1, 0xFFFF, 0xFFFF, 0, 0}}, // from proxclone.com + {"Optus34", Pack_Optus, Unpack_Optus, "Indala Optus 34-bit", 34, {1, 1, 0, 0, 0, 0x3FF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"SMP34", Pack_Smartpass, Unpack_Smartpass, "Cardkey Smartpass 34-bit", 34, {1, 1, 1, 0, 0, 0x3FF, 0xFFFF, 0x7, 0}}, // from cardinfo.barkweb.com.au + {"BQT34", Pack_bqt34, Unpack_bqt34, "BQT 34-bit", 34, {1, 1, 0, 0, 1, 0xFF, 0xFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"C1k35s", Pack_C1k35s, Unpack_C1k35s, "HID Corporate 1000 35-bit std", 35, {1, 1, 0, 0, 1, 0xFFF, 0xFFFFF, 0, 0}}, // imported from old pack/unpack + {"C15001", Pack_C15001, Unpack_C15001, "HID KeyScan 36-bit", 36, {1, 1, 0, 1, 1, 0xFF, 0xFFFF, 0, 0x3FF}}, // from Proxmark forums + {"S12906", Pack_S12906, Unpack_S12906, "HID Simplex 36-bit", 36, {1, 1, 1, 0, 1, 0xFF, 0x3, 0xFFFFFF, 0}}, // from cardinfo.barkweb.com.au + {"Sie36", Pack_Sie36, Unpack_Sie36, "HID 36-bit Siemens", 36, {1, 1, 0, 0, 1, 0x3FFFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"H10320", Pack_H10320, Unpack_H10320, "HID H10320 37-bit BCD", 37, {1, 0, 0, 0, 1, 0, 99999999, 0, 0}}, // from Proxmark forums + {"H10302", Pack_H10302, Unpack_H10302, "HID H10302 37-bit huge ID", 37, {1, 0, 0, 0, 1, 0, 0x7FFFFFFFF, 0, 0}}, // from Proxmark forums + {"H10304", Pack_H10304, Unpack_H10304, "HID H10304 37-bit", 37, {1, 1, 0, 0, 1, 0xFFFF, 0x7FFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"P10004", Pack_P10004, Unpack_P10004, "HID P10004 37-bit PCSC", 37, {1, 1, 0, 0, 0, 0x1FFF, 0x3FFFF, 0, 0}}, // from @bthedorff; PR #1559 + {"HGen37", Pack_HGeneric37, Unpack_HGeneric37, "HID Generic 37-bit", 37, {1, 0, 0, 0, 1, 0, 0x7FFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"MDI37", Pack_MDI37, Unpack_MDI37, "PointGuard MDI 37-bit", 37, {1, 1, 0, 0, 1, 0xF, 0x1FFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"BQT38", Pack_bqt38, Unpack_bqt38, "BQT 38-bit", 38, {1, 1, 1, 0, 1, 0xFFF, 0x3FFFF, 0x7, 0}}, // from cardinfo.barkweb.com.au + {"ISCS", Pack_iscs38, Unpack_iscs38, "ISCS 38-bit", 38, {1, 1, 0, 1, 1, 0x3FF, 0xFFFFFF, 0, 0x7}}, // from cardinfo.barkweb.com.au + {"PW39", Pack_pw39, Unpack_pw39, "Pyramid 39-bit wiegand format", 39, {1, 1, 0, 0, 1, 0xFFFF, 0xFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"P10001", Pack_P10001, Unpack_P10001, "HID P10001 Honeywell 40-bit", 40, {1, 1, 0, 0, 0, 0xFFF, 0xFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"Casi40", Pack_CasiRusco40, Unpack_CasiRusco40, "Casi-Rusco 40-bit", 40, {1, 0, 0, 0, 0, 0, 0xFFFFFFFFFF, 0, 0}}, // from cardinfo.barkweb.com.au + {"BC40", Pack_bc40, Unpack_bc40, "Bundy TimeClock 40-bit", 40, {1, 1, 0, 1, 1, 0xFFF, 0xFFFFF, 0, 0x7F}}, // from + {"Defcon32", Pack_Defcon32, Unpack_Defcon32, "Custom Defcon RFCTF 42-bit", 42, {1, 1, 1, 0, 1, 0xFFFF, 0xFFFFF, 0xF, 0}}, // Created by (@micsen) for the CTF + {"H800002", Pack_H800002, Unpack_H800002, "HID H800002 46-bit", 46, {1, 1, 0, 0, 1, 0x3FFF, 0x3FFFFFFF, 0, 0}}, + {"C1k48s", Pack_C1k48s, Unpack_C1k48s, "HID Corporate 1000 48-bit std", 48, {1, 1, 0, 0, 1, 0x003FFFFF, 0x007FFFFF, 0, 0}}, // imported from old pack/unpack + {"Avig56", Pack_Avig56, Unpack_Avig56, "Avigilon 56-bit", 56, {1, 1, 0, 0, 1, 0xFFFFF, 0x3FFFFFFFF, 0, 0}}, + {NULL, NULL, NULL, NULL, 0, {0, 0, 0, 0, 0, 0, 0, 0, 0}} // Must null terminate array }; void HIDListFormats(void) { @@ -1480,7 +1475,7 @@ void HIDListFormats(void) { int i = 0; while (FormatTable[i].Name) { - PrintAndLogEx(INFO, _YELLOW_("%-10s")" %-30s", FormatTable[i].Name, FormatTable[i].Descrp); + PrintAndLogEx(INFO, _YELLOW_("%-10s")" %-30s", FormatTable[i].Name, FormatTable[i].Description); ++i; } PrintAndLogEx(INFO, "------------------------------------------------------------"); @@ -1525,7 +1520,7 @@ int HIDFindCardFormat(const char *format) { // validate if the card's FC, CN, IL, OEM are within the limit of its format // return true if the card is valid -static bool validate_card_limit(int format_idx, wiegand_card_t *card) { +bool validate_card_limit(int format_idx, wiegand_card_t *card) { cardformatdescriptor_t card_descriptor = FormatTable[format_idx].Fields; return !((card->FacilityCode > card_descriptor.MaxFC) || (card->CardNumber > card_descriptor.MaxCN) || @@ -1584,13 +1579,17 @@ bool HIDTryUnpack(wiegand_message_t *packed) { } if (found_cnt) { - PrintAndLogEx(INFO, "found %u matching format%c with bit len %d", found_cnt, (found_cnt > 1) ? 's' : ' ', packed->Length); + PrintAndLogEx(INFO, "found " _YELLOW_("%u") " matching " _YELLOW_("%d bit") " format%s" + , found_cnt + , packed->Length + , (found_cnt) ? "s" : "" + ); } if (packed->Length && ((found_cnt - found_invalid_par) == 0)) { // if length > 0 and no valid parity matches - PrintAndLogEx(WARNING, "Wiegand unknown bit len %d", packed->Length); - PrintAndLogEx(HINT, "Try 0xFFFF's http://cardinfo.barkweb.com.au/"); + PrintAndLogEx(FAILED, "Parity tests failed"); } + PrintAndLogEx(NORMAL, ""); return ((found_cnt - found_invalid_par) > 0); } diff --git a/client/src/wiegand_formats.h b/client/src/wiegand_formats.h index 503e85daf..97b097e4a 100644 --- a/client/src/wiegand_formats.h +++ b/client/src/wiegand_formats.h @@ -47,10 +47,12 @@ typedef struct { const char *Name; bool (*Pack)(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble); bool (*Unpack)(wiegand_message_t *packed, wiegand_card_t *card); - const char *Descrp; + const char *Description; + uint32_t Bits; // Number of bits in this format cardformatdescriptor_t Fields; } cardformat_t; +bool validate_card_limit(int format_idx, wiegand_card_t *card); void HIDListFormats(void); int HIDFindCardFormat(const char *format); cardformat_t HIDGetCardFormat(int idx); diff --git a/doc/commands.json b/doc/commands.json index f0cb693ca..cc7ce0ca8 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -13212,6 +13212,6 @@ "metadata": { "commands_extracted": 759, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-02-21T15:40:14" + "extracted_on": "2025-02-22T17:26:54" } } From 14d50f7ac5fd1c7e3a9cb44f56af7da3bfa413e0 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Sun, 23 Feb 2025 21:05:09 +0100 Subject: [PATCH 067/105] change mem spiffs tree to show id number in decimal. Previously shown in hex. --- CHANGELOG.md | 2 ++ armsrc/spiffs.c | 2 +- client/src/wiegand_formats.c | 47 +++++++++++++++++++++--------------- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2923c4b01..515f17e41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ 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 `mem spiffs tree` - ID is now shown in decimal (@iceman1001) +- Added sample wiegand format 56bit (@iceman1001) - Changed Wiegand formats to include number of bits (@iceman1001) - Fix compilation warning in hitagS (@iceman1001) - Added new wiegand format H800002 (@jmichelp) diff --git a/armsrc/spiffs.c b/armsrc/spiffs.c index 1e589554c..90275de16 100644 --- a/armsrc/spiffs.c +++ b/armsrc/spiffs.c @@ -658,7 +658,7 @@ void rdv40_spiffs_safe_print_tree(void) { } } - Dbprintf("[%04x] " _YELLOW_("%5i") " B |-- %s%s", pe->obj_id, pe->size, pe->name, resolvedlink); + Dbprintf("[%04u] " _YELLOW_("%5i") " B |-- %s%s", pe->obj_id, pe->size, pe->name, resolvedlink); printed = true; } if (printed == false) { diff --git a/client/src/wiegand_formats.c b/client/src/wiegand_formats.c index 57eb1c741..21c2aa438 100644 --- a/client/src/wiegand_formats.c +++ b/client/src/wiegand_formats.c @@ -1333,6 +1333,30 @@ static bool Unpack_Avig56(wiegand_message_t *packed, wiegand_card_t *card) { return true; } +static bool Pack_IR56(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { + memset(packed, 0, sizeof(wiegand_message_t)); + packed->Length = 56; + + if (!validate_card_limit(format_idx, card)) return false; + + set_linear_field(packed, card->FacilityCode, 1, 24); + set_linear_field(packed, card->CardNumber, 25, 32); + + if (preamble) + return add_HID_header(packed); + + return true; +} + +static bool Unpack_IR56(wiegand_message_t *packed, wiegand_card_t *card) { + memset(card, 0, sizeof(wiegand_card_t)); + + if (packed->Length != 56) return false; + + card->FacilityCode = get_linear_field(packed, 1, 24); + card->CardNumber = get_linear_field(packed, 25, 32); + return true; +} // --------------------------------------------------------------------------------------------------- void print_desc_wiegand(cardformat_t *fmt, wiegand_message_t *packed) { @@ -1383,25 +1407,6 @@ void print_wiegand_code(wiegand_message_t *packed) { static void hid_print_card(wiegand_card_t *card, const cardformat_t format) { - /* - PrintAndLogEx(SUCCESS, " Format: %s (%s)", format.Name, format.Description); - - if (format.Fields.hasFacilityCode) - PrintAndLogEx(SUCCESS, "Facility Code: %d",card->FacilityCode); - - if (format.Fields.hasCardNumber) - PrintAndLogEx(SUCCESS, " Card Number: %d",card->CardNumber); - - if (format.Fields.hasIssueLevel) - PrintAndLogEx(SUCCESS, " Issue Level: %d",card->IssueLevel); - - if (format.Fields.hasOEMCode) - PrintAndLogEx(SUCCESS, " OEM Code: %d",card->OEM); - - if (format.Fields.hasParity) - PrintAndLogEx(SUCCESS, " Parity: %s",card->ParityValid ? "Valid" : "Invalid"); - */ - char s[110] = {0}; if (format.Fields.hasFacilityCode) snprintf(s, sizeof(s), "FC: " _GREEN_("%u"), card->FacilityCode); @@ -1462,9 +1467,11 @@ static const cardformat_t FormatTable[] = { {"H800002", Pack_H800002, Unpack_H800002, "HID H800002 46-bit", 46, {1, 1, 0, 0, 1, 0x3FFF, 0x3FFFFFFF, 0, 0}}, {"C1k48s", Pack_C1k48s, Unpack_C1k48s, "HID Corporate 1000 48-bit std", 48, {1, 1, 0, 0, 1, 0x003FFFFF, 0x007FFFFF, 0, 0}}, // imported from old pack/unpack {"Avig56", Pack_Avig56, Unpack_Avig56, "Avigilon 56-bit", 56, {1, 1, 0, 0, 1, 0xFFFFF, 0x3FFFFFFFF, 0, 0}}, - {NULL, NULL, NULL, NULL, 0, {0, 0, 0, 0, 0, 0, 0, 0, 0}} // Must null terminate array + {"IR56", Pack_IR56, Unpack_IR56, "Inner Range 56-bit", 56, {1, 1, 0, 0, 0, 0xFFFFFF, 0xFFFFFFFF, 0, 0}}, + {NULL, NULL, NULL, NULL, 0, {0, 0, 0, 0, 0, 0, 0, 0, 0}} // Must null terminate array }; + void HIDListFormats(void) { if (FormatTable[0].Name == NULL) return; From 2fcdea68d1fa7dd2ab25fc3cc84584c5dcb2b366 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Sun, 23 Feb 2025 21:08:16 +0100 Subject: [PATCH 068/105] style and text --- client/src/cmdhficlass.c | 15 ++++++++------- client/src/cmdhfseos.c | 5 +++-- client/src/wiegand_formats.c | 4 ++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index 992a818f7..c08244966 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -4650,11 +4650,11 @@ static int CmdHFiClassLookUp(const char *Cmd) { memcpy(CCNR + 8, macs, 4); memcpy(MAC_TAG, macs + 4, 4); - PrintAndLogEx(SUCCESS, " CSN: " _GREEN_("%s"), sprint_hex(csn, sizeof(csn))); - PrintAndLogEx(SUCCESS, " Epurse: %s", sprint_hex(epurse, sizeof(epurse))); - PrintAndLogEx(SUCCESS, " MACS: %s", sprint_hex(macs, sizeof(macs))); - PrintAndLogEx(SUCCESS, " CCNR: " _GREEN_("%s"), sprint_hex(CCNR, sizeof(CCNR))); - PrintAndLogEx(SUCCESS, "TAG MAC: %s", sprint_hex(MAC_TAG, sizeof(MAC_TAG))); + PrintAndLogEx(SUCCESS, "CSN....... " _GREEN_("%s"), sprint_hex(csn, sizeof(csn))); + PrintAndLogEx(SUCCESS, "Epurse.... %s", sprint_hex(epurse, sizeof(epurse))); + PrintAndLogEx(SUCCESS, "MACS...... %s", sprint_hex(macs, sizeof(macs))); + PrintAndLogEx(SUCCESS, "CCNR...... " _GREEN_("%s"), sprint_hex(CCNR, sizeof(CCNR))); + PrintAndLogEx(SUCCESS, "TAG MAC... %s", sprint_hex(MAC_TAG, sizeof(MAC_TAG))); // Run time uint64_t t1 = msclock(); @@ -4733,7 +4733,7 @@ typedef struct { uint8_t use_raw; uint8_t use_elite; uint32_t keycnt; - uint8_t csn[8]; + uint8_t csn[PICOPASS_BLOCK_SIZE]; uint8_t cc_nr[12]; uint8_t *keys; union { @@ -4810,8 +4810,9 @@ void GenerateMacFrom(uint8_t *CSN, uint8_t *CCNR, bool use_raw, bool use_elite, } } - for (int i = 0; i < iclass_tc; i++) + for (int i = 0; i < iclass_tc; i++) { pthread_join(threads[i], NULL); + } } static void *bf_generate_mackey(void *thread_arg) { diff --git a/client/src/cmdhfseos.c b/client/src/cmdhfseos.c index efbb37657..d97e708da 100644 --- a/client/src/cmdhfseos.c +++ b/client/src/cmdhfseos.c @@ -558,7 +558,7 @@ static int select_DF_verify(uint8_t *response, uint8_t response_length, uint8_t } } if (res != PM3_SUCCESS) { - return res; + goto out; } // ----------------- MAC Key Generation ----------------- @@ -579,6 +579,7 @@ static int select_DF_verify(uint8_t *response, uint8_t response_length, uint8_t // PrintAndLogEx(INFO, "Supp MAC......................... " _YELLOW_("%s"), sprint_hex_inrow(MAC_value, MAC_value_len)); // PrintAndLogEx(INFO, "Calc MAC......................... " _YELLOW_("%s"), sprint_hex_inrow(cmac, sizeof(cmac))); +out: PrintAndLogEx(INFO, "--- " _CYAN_("MAC") " ---------------------------"); PrintAndLogEx(ERR, _RED_("MAC Verification Failed")); return PM3_ESOFT; @@ -752,7 +753,7 @@ static int select_ADF_decrypt(const char *selectADFOID, uint8_t *CRYPTOGRAM_encr } } - return PM3_SUCCESS; + return PM3_ESOFT; }; static int seos_mutual_auth(uint8_t *randomICC, uint8_t *CRYPTOGRAM_Diversifier, uint8_t diversifier_len, uint8_t *mutual_auth_randomIFD, uint8_t *mutual_auth_keyICC, uint8_t *randomIFD, uint8_t randomIFD_len, uint8_t *keyIFD, uint8_t keyIFD_len, int encryption_algorithm, int hash_algorithm, int key_index) { diff --git a/client/src/wiegand_formats.c b/client/src/wiegand_formats.c index 21c2aa438..d3ecacc12 100644 --- a/client/src/wiegand_formats.c +++ b/client/src/wiegand_formats.c @@ -1467,8 +1467,8 @@ static const cardformat_t FormatTable[] = { {"H800002", Pack_H800002, Unpack_H800002, "HID H800002 46-bit", 46, {1, 1, 0, 0, 1, 0x3FFF, 0x3FFFFFFF, 0, 0}}, {"C1k48s", Pack_C1k48s, Unpack_C1k48s, "HID Corporate 1000 48-bit std", 48, {1, 1, 0, 0, 1, 0x003FFFFF, 0x007FFFFF, 0, 0}}, // imported from old pack/unpack {"Avig56", Pack_Avig56, Unpack_Avig56, "Avigilon 56-bit", 56, {1, 1, 0, 0, 1, 0xFFFFF, 0x3FFFFFFFF, 0, 0}}, - {"IR56", Pack_IR56, Unpack_IR56, "Inner Range 56-bit", 56, {1, 1, 0, 0, 0, 0xFFFFFF, 0xFFFFFFFF, 0, 0}}, - {NULL, NULL, NULL, NULL, 0, {0, 0, 0, 0, 0, 0, 0, 0, 0}} // Must null terminate array + {"IR56", Pack_IR56, Unpack_IR56, "Inner Range 56-bit", 56, {1, 1, 0, 0, 0, 0xFFFFFF, 0xFFFFFFFF, 0, 0}}, + {NULL, NULL, NULL, NULL, 0, {0, 0, 0, 0, 0, 0, 0, 0, 0}} // Must null terminate array }; From 66b1a4853679e2499886349c76dd65d95544cc83 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Sun, 23 Feb 2025 21:16:34 +0100 Subject: [PATCH 069/105] style --- client/src/cmdhf14a.c | 9 +++++---- client/src/cmdhfseos.c | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/client/src/cmdhf14a.c b/client/src/cmdhf14a.c index bb551687e..30b45c68c 100644 --- a/client/src/cmdhf14a.c +++ b/client/src/cmdhf14a.c @@ -1300,18 +1300,18 @@ int ExchangeAPDU14a(const uint8_t *datain, int datainlen, bool activateField, bo *dataoutlen = 0; res = CmdExchangeAPDU(chainBlockNotLast, &datain[clen], vlen, vActivateField, dataout, maxdataoutlen, dataoutlen, &chaining); if (res != PM3_SUCCESS) { - if (leaveSignalON == false) + if (leaveSignalON == false) { DropField(); - + } return 200; } // check R-block ACK // TODO check this one... if ((*dataoutlen == 0) && (chaining != chainBlockNotLast)) { - if (leaveSignalON == false) + if (leaveSignalON == false) { DropField(); - + } return 201; } @@ -1323,6 +1323,7 @@ int ExchangeAPDU14a(const uint8_t *datain, int datainlen, bool activateField, bo } break; } + } while (clen < datainlen); } else { diff --git a/client/src/cmdhfseos.c b/client/src/cmdhfseos.c index d97e708da..72c9e129e 100644 --- a/client/src/cmdhfseos.c +++ b/client/src/cmdhfseos.c @@ -842,7 +842,7 @@ static int seos_mutual_auth(uint8_t *randomICC, uint8_t *CRYPTOGRAM_Diversifier, bool activate_field = false; bool keep_field_on = true; - uint8_t aMUTUAL_AUTH[102]; + uint8_t aMUTUAL_AUTH[102] = {0}; int aMUTUAL_AUTH_n = 0; param_gethex_to_eol(mutual_auth, 0, aMUTUAL_AUTH, sizeof(aMUTUAL_AUTH), &aMUTUAL_AUTH_n); int res = ExchangeAPDU14a(aMUTUAL_AUTH, aMUTUAL_AUTH_n, activate_field, keep_field_on, response, sizeof(response), &resplen); From 98f6b263ffe8629d2f7bb9c312030bf48c3031a5 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Sun, 23 Feb 2025 23:10:13 +0100 Subject: [PATCH 070/105] wiegand decode now accepts `--new` parameter to decode new padding format where len denotes number of zeros bits added in the end of the pacs bytes. You will need to shift accordingly. --- CHANGELOG.md | 1 + client/src/cmdhficlass.c | 12 ++++---- client/src/cmdhfseos.c | 2 +- client/src/cmdwiegand.c | 62 ++++++++++++++++++++++++++++++++++++++-- doc/commands.json | 10 ++++--- tools/pm3_tests.sh | 2 ++ 6 files changed, 74 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 515f17e41..f3af0004e 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 `wiegand decode` - now accepts new padding format (@iceman1001) - Changed `mem spiffs tree` - ID is now shown in decimal (@iceman1001) - Added sample wiegand format 56bit (@iceman1001) - Changed Wiegand formats to include number of bits (@iceman1001) diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index c08244966..70dc6b1ab 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -66,6 +66,7 @@ static uint8_t empty[PICOPASS_BLOCK_SIZE] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, static uint8_t zeros[PICOPASS_BLOCK_SIZE] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; static int CmdHelp(const char *Cmd); + static uint8_t iClass_Key_Table[ICLASS_KEYS_MAX][PICOPASS_BLOCK_SIZE] = { { 0xAE, 0xA6, 0x84, 0xA6, 0xDA, 0xB2, 0x32, 0x78 }, { 0xFD, 0xCB, 0x5A, 0x52, 0xEA, 0x8F, 0x30, 0x90 }, @@ -1395,7 +1396,7 @@ static int CmdHFiClassESetBlk(const char *Cmd) { static bool iclass_detect_new_pacs(uint8_t *d) { uint8_t n = 0; - while (n++ < (PICOPASS_BLOCK_SIZE / 2)) { + while (n++ < (PICOPASS_BLOCK_SIZE >> 1)) { if (d[n] && d[n + 1] == 0xA6) { return true; } @@ -1467,8 +1468,7 @@ static void iclass_decode_credentials(uint8_t *data) { if (has_values && encryption == None) { // todo: remove preamble/sentinel - PrintAndLogEx(INFO, "Block 7 decoder"); - + PrintAndLogEx(INFO, "------------------------ " _CYAN_("Block 7 decoder") " --------------------------"); if (has_new_pacs) { iclass_decode_credentials_new_pacs(b7); } else { @@ -1483,9 +1483,7 @@ static void iclass_decode_credentials(uint8_t *data) { char *pbin = binstr; while (strlen(pbin) && *(++pbin) == '0'); - PrintAndLogEx(SUCCESS, "Binary..................... " _GREEN_("%s"), pbin); - - PrintAndLogEx(INFO, "Wiegand decode"); + PrintAndLogEx(SUCCESS, "Binary... %zu - " _GREEN_("%s"), strlen(pbin), pbin); decode_wiegand(top, mid, bot, 0); } @@ -1736,7 +1734,7 @@ static int CmdHFiClassDecrypt(const char *Cmd) { } } - PrintAndLogEx(INFO, "-----------------------------------------------------------------"); + PrintAndLogEx(INFO, "-------------------------------------------------------------------"); free(decrypted); } diff --git a/client/src/cmdhfseos.c b/client/src/cmdhfseos.c index 72c9e129e..27285629c 100644 --- a/client/src/cmdhfseos.c +++ b/client/src/cmdhfseos.c @@ -1756,7 +1756,7 @@ static command_t CommandTable[] = { {"list", CmdHfSeosList, AlwaysAvailable, "List SEOS history"}, {"sam", CmdHfSeosSAM, IfPm3Smartcard, "SAM tests"}, {"-----------", CmdHelp, AlwaysAvailable, "----------------------- " _CYAN_("Operations") " -----------------------"}, - {"info", CmdHfSeosInfo, IfPm3NfcBarcode, "Tag information"}, + {"info", CmdHfSeosInfo, IfPm3Iso14443a, "Tag information"}, {"pacs", CmdHfSeosPACS, AlwaysAvailable, "Extract PACS Information from card"}, {"adf", CmdHfSeosADF, AlwaysAvailable, "Read an ADF from the card"}, {"gdf", CmdHfSeosGDF, AlwaysAvailable, "Read an GDF from card"}, diff --git a/client/src/cmdwiegand.c b/client/src/cmdwiegand.c index 7853b71cd..404754715 100644 --- a/client/src/cmdwiegand.c +++ b/client/src/cmdwiegand.c @@ -33,6 +33,47 @@ static int CmdHelp(const char *Cmd); +#define PACS_EXTRA_LONG_FORMAT 18 // 144 bits +#define PACS_LONG_FORMAT 12 // 96 bits +#define PACS_FORMAT 6 // 44 bits +static int wiegand_new_pacs(uint8_t *padded_pacs, uint8_t plen) { + + uint8_t d[PACS_EXTRA_LONG_FORMAT] = {0}; + memcpy(d, padded_pacs, plen); + + uint8_t pad = d[0]; + + char *binstr = (char *)calloc((PACS_EXTRA_LONG_FORMAT * 8) + 1, sizeof(uint8_t)); + if (binstr == NULL) { + PrintAndLogEx(INFO, "failed to allocate memory"); + return PM3_EMALLOC; + } + + uint8_t n = plen - 1; + + bytes_2_binstr(binstr, d + 1, n); + + binstr[strlen(binstr) - pad] = '\0'; + + size_t tlen = 0; + uint8_t tmp[16] = {0}; + binstr_2_bytes(tmp, &tlen, binstr); + PrintAndLogEx(SUCCESS, "Wiegand raw.... " _YELLOW_("%s"), sprint_hex_inrow(tmp, tlen)); + + uint32_t top = 0, mid = 0, bot = 0; + if (binstring_to_u96(&top, &mid, &bot, binstr) != strlen(binstr)) { + PrintAndLogEx(ERR, "Binary string contains none <0|1> chars"); + free(binstr); + return PM3_EINVARG; + } + + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "------------------------- " _CYAN_("SIO - Wiegand") " ---------------------------"); + wiegand_message_t packed = initialize_message_object(top, mid, bot, strlen(binstr)); + HIDTryUnpack(&packed); + free(binstr); + return PM3_SUCCESS; +} int CmdWiegandList(const char *Cmd) { CLIParserContext *ctx; @@ -116,16 +157,18 @@ int CmdWiegandDecode(const char *Cmd) { CLIParserContext *ctx; CLIParserInit(&ctx, "wiegand decode", "Decode raw hex or binary to wiegand format", - "wiegand decode --raw 2006f623ae" + "wiegand decode --raw 2006F623AE\n" + "wiegand decode --new 06BD88EB80 -> 4..8 bytes, new padded format " ); void *argtable[] = { arg_param_begin, arg_str0("r", "raw", "", "raw hex to be decoded"), arg_str0("b", "bin", "", "binary string to be decoded"), + arg_str0("n", "new", "", "new padded pacs as raw hex to be decoded"), arg_param_end }; - CLIExecWithReturn(ctx, Cmd, argtable, true); + CLIExecWithReturn(ctx, Cmd, argtable, false); int hlen = 0; char hex[40] = {0}; CLIParamStrToBuf(arg_get_str(ctx, 1), (uint8_t *)hex, sizeof(hex), &hlen); @@ -133,6 +176,11 @@ int CmdWiegandDecode(const char *Cmd) { int blen = 0; uint8_t binarr[100] = {0x00}; int res = CLIParamBinToBuf(arg_get_str(ctx, 2), binarr, sizeof(binarr), &blen); + + int plen = 0; + uint8_t phex[8] = {0}; + res = CLIParamHexToBuf(arg_get_str(ctx, 3), phex, sizeof(phex), &plen); + CLIParserFree(ctx); if (res) { @@ -155,12 +203,20 @@ int CmdWiegandDecode(const char *Cmd) { return PM3_EINVARG; } PrintAndLogEx(INFO, "Input bin len... %d", blen); + + } else if (plen) { + + return wiegand_new_pacs(phex, plen); + } else { PrintAndLogEx(ERR, "Empty input"); return PM3_EINVARG; } - decode_wiegand(top, mid, bot, blen); + PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "Wiegand decode"); + wiegand_message_t packed = initialize_message_object(top, mid, bot, blen); + HIDTryUnpack(&packed); return PM3_SUCCESS; } diff --git a/doc/commands.json b/doc/commands.json index cc7ce0ca8..530cad30a 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -13167,15 +13167,17 @@ "command": "wiegand decode", "description": "Decode raw hex or binary to wiegand format", "notes": [ - "wiegand decode --raw 2006f623ae" + "wiegand decode --raw 2006f623ae", + "wiegand decode --new 04801EEF8DC0 -> 4..8 bytes, new padded format" ], "offline": true, "options": [ "-h, --help This help", "-r, --raw raw hex to be decoded", - "-b, --bin binary string to be decoded" + "-b, --bin binary string to be decoded", + "-n, --new new padded pacs as raw hex to be decoded" ], - "usage": "wiegand decode [-h] [-r ] [-b ]" + "usage": "wiegand decode [-h] [-r ] [-b ] [-n ]" }, "wiegand encode": { "command": "wiegand encode", @@ -13212,6 +13214,6 @@ "metadata": { "commands_extracted": 759, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-02-22T17:26:54" + "extracted_on": "2025-02-23T21:55:16" } } diff --git a/tools/pm3_tests.sh b/tools/pm3_tests.sh index d5a73fb62..d0952ec36 100755 --- a/tools/pm3_tests.sh +++ b/tools/pm3_tests.sh @@ -445,6 +445,8 @@ while true; do if ! CheckExecute "nfc decode test - vcard" "$CLIENTBIN -c 'nfc decode -d d20ca3746578742f782d7643617264424547494e3a56434152440a56455253494f4e3a332e300a4e3a43687269733b4963656d616e3b3b3b0a464e3a476f7468656e627572670a5245563a323032312d30362d32345432303a31353a30385a0a6974656d322e582d4142444154453b747970653d707265663a323032302d30362d32340a4954454d322e582d41424c4142454c3a5f24213c416e6e69766572736172793e21245f0a454e443a56434152440a'" "END:VCARD"; then break; fi if ! CheckExecute "nfc decode test - apple wallet" "$CLIENTBIN -c 'nfc decode -d 031AD10116550077616C6C65743A2F2F61637469766174652F6E6663FE'" "activate/nfc"; then break; fi if ! CheckExecute "nfc decode test - signature" "$CLIENTBIN -c 'nfc decode -d 03FF010194113870696C65742E65653A656B616172743A3266195F26063132303832325904202020205F28033233335F2701316E1B5A13333038363439303039303030323636343030355304EBF2CE704103000000AC536967010200803A2448FCA7D354A654A81BD021150D1A152D1DF4D7A55D2B771F12F094EAB6E5E10F2617A2F8DAD4FD38AFF8EA39B71C19BD42618CDA86EE7E144636C8E0E7CFC4096E19C3680E09C78A0CDBC05DA2D698E551D5D709717655E56FE3676880B897D2C70DF5F06ECE07C71435255144F8EE41AF110E7B180DA0E6C22FB8FDEF61800025687474703A2F2F70696C65742E65652F6372742F33303836343930302D303030312E637274FE'" "30864900-0001.crt"; then break; fi + if ! CheckExecute "wiegand decode test - raw" "$CLIENTBIN -c 'wiegand decode --raw 2006F623AE'" "FC: 123 CN: 4567 parity \( ok \)"; then break; fi + if ! CheckExecute "wiegand decode test - new" "$CLIENTBIN -c 'wiegand decode --new 06BD88EB80'" "FC: 123 CN: 4567 parity \( ok \)"; then break; fi echo -e "\n${C_BLUE}Testing LF:${C_NC}" if ! CheckExecute "lf hitag2 test" "$CLIENTBIN -c 'lf hitag test'" "Tests \( ok"; then break; fi From c498d38b82b095aa216ddcd06d95d86c66f7d12f Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Sun, 23 Feb 2025 23:13:08 +0100 Subject: [PATCH 071/105] the wiegand formats unpack functions now clears the struct after sanity checking is done. Also adapted testing wiegand formats which might just have a preamble bit but not a sentinel bit like iclass credentials --- CHANGELOG.md | 1 + client/src/wiegand_formats.c | 220 +++++++++++++++++++++-------------- 2 files changed, 131 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3af0004e..ef10de0c7 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 wiegand format unpack functions to clear struct later (@iceman1001) - Changed `wiegand decode` - now accepts new padding format (@iceman1001) - Changed `mem spiffs tree` - ID is now shown in decimal (@iceman1001) - Added sample wiegand format 56bit (@iceman1001) diff --git a/client/src/wiegand_formats.c b/client/src/wiegand_formats.c index d3ecacc12..17447ac3a 100644 --- a/client/src/wiegand_formats.c +++ b/client/src/wiegand_formats.c @@ -19,6 +19,19 @@ #include #include "commonutil.h" +static bool step_parity_check(wiegand_message_t *packed, int start, int length, bool even_parity) { + bool parity = even_parity; + for (int i = start; i < start + length; i += 2) { + // Extract 2 bits + bool bit1 = get_bit_by_position(packed, i); + bool bit2 = get_bit_by_position(packed, i + 1); + + // Calculate parity for these 2 bits + parity ^= (bit1 ^ bit2); + } + return parity; +} + static bool Pack_Defcon32(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); @@ -63,10 +76,10 @@ static bool Pack_Defcon32(int format_idx, wiegand_card_t *card, wiegand_message_ } static bool Unpack_Defcon32(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); - if (packed->Length != 42) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 1, 16); card->IssueLevel = get_linear_field(packed, 17, 4); card->CardNumber = get_linear_field(packed, 21, 20); @@ -95,9 +108,10 @@ static bool Pack_H10301(int format_idx, wiegand_card_t *card, wiegand_message_t } static bool Unpack_H10301(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 26) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->CardNumber = (packed->Bot >> 1) & 0xFFFF; card->FacilityCode = (packed->Bot >> 17) & 0xFF; card->ParityValid = @@ -130,10 +144,10 @@ static bool Pack_ind26(int format_idx, wiegand_card_t *card, wiegand_message_t * } static bool Unpack_ind26(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); - if (packed->Length != 26) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 1, 12); card->CardNumber = get_linear_field(packed, 13, 12); @@ -157,10 +171,11 @@ static bool Pack_Tecom27(int format_idx, wiegand_card_t *card, wiegand_message_t } static bool Unpack_Tecom27(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 27) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->CardNumber = get_nonlinear_field(packed, 16, (uint8_t[]) {0, 1, 13, 12, 9, 26, 20, 16, 17, 21, 25, 7, 8, 11, 4, 5}); card->FacilityCode = get_nonlinear_field(packed, 11, (uint8_t[]) {15, 19, 24, 23, 22, 18, 6, 10, 14, 3, 2}); return true; @@ -183,10 +198,10 @@ static bool Pack_ind27(int format_idx, wiegand_card_t *card, wiegand_message_t * } static bool Unpack_ind27(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); - if (packed->Length != 27) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 0, 13); card->CardNumber = get_linear_field(packed, 13, 14); return true; @@ -206,10 +221,10 @@ static bool Pack_indasc27(int format_idx, wiegand_card_t *card, wiegand_message_ } static bool Unpack_indasc27(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); - if (packed->Length != 27) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_nonlinear_field(packed, 13, (uint8_t[]) {9, 4, 6, 5, 0, 7, 19, 8, 10, 16, 24, 12, 22}); card->CardNumber = get_nonlinear_field(packed, 14, (uint8_t[]) {26, 1, 3, 15, 14, 17, 20, 13, 25, 2, 18, 21, 11, 23}); return true; @@ -238,10 +253,11 @@ static bool Pack_2804W(int format_idx, wiegand_card_t *card, wiegand_message_t * } static bool Unpack_2804W(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 28) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 4, 8); card->CardNumber = get_linear_field(packed, 12, 15); card->ParityValid = @@ -268,10 +284,10 @@ static bool Pack_ind29(int format_idx, wiegand_card_t *card, wiegand_message_t * } static bool Unpack_ind29(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); - if (packed->Length != 29) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 0, 13); card->CardNumber = get_linear_field(packed, 13, 16); return true; @@ -297,10 +313,10 @@ static bool Pack_ATSW30(int format_idx, wiegand_card_t *card, wiegand_message_t } static bool Unpack_ATSW30(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); - if (packed->Length != 30) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 1, 12); card->CardNumber = get_linear_field(packed, 13, 16); card->ParityValid = @@ -324,9 +340,11 @@ static bool Pack_ADT31(int format_idx, wiegand_card_t *card, wiegand_message_t * } static bool Unpack_ADT31(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 31) return false; // Wrong length? Stop here. + + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 1, 4); card->CardNumber = get_linear_field(packed, 5, 23); return true; @@ -348,10 +366,11 @@ static bool Pack_hcp32(int format_idx, wiegand_card_t *card, wiegand_message_t * } static bool Unpack_hcp32(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 32) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->CardNumber = get_linear_field(packed, 1, 24); return true; } @@ -373,10 +392,11 @@ static bool Pack_hpp32(int format_idx, wiegand_card_t *card, wiegand_message_t * } static bool Unpack_hpp32(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 32) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 1, 12); card->CardNumber = get_linear_field(packed, 13, 29); return true; @@ -399,10 +419,11 @@ static bool Pack_wie32(int format_idx, wiegand_card_t *card, wiegand_message_t * } static bool Unpack_wie32(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 32) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 4, 12); card->CardNumber = get_linear_field(packed, 16, 16); return true; @@ -426,11 +447,12 @@ static bool Pack_Kastle(int format_idx, wiegand_card_t *card, wiegand_message_t } static bool Unpack_Kastle(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 32) return false; // Wrong length? Stop here. if (get_bit_by_position(packed, 1) != 1) return false; // Always 1 in this format + memset(card, 0, sizeof(wiegand_card_t)); + card->IssueLevel = get_linear_field(packed, 2, 5); card->FacilityCode = get_linear_field(packed, 7, 8); card->CardNumber = get_linear_field(packed, 15, 16); @@ -454,9 +476,10 @@ static bool Pack_Kantech(int format_idx, wiegand_card_t *card, wiegand_message_t } static bool Unpack_Kantech(wiegand_message_t *packed, wiegand_card_t *card) { + if (packed->Length != 32) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); - if (packed->Length != 32) return false; // Wrong length? Stop here. card->FacilityCode = get_linear_field(packed, 7, 8); card->CardNumber = get_linear_field(packed, 15, 16); return true; @@ -478,10 +501,11 @@ static bool Pack_D10202(int format_idx, wiegand_card_t *card, wiegand_message_t } static bool Unpack_D10202(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 33) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->CardNumber = get_linear_field(packed, 8, 24); card->FacilityCode = get_linear_field(packed, 1, 7); card->ParityValid = @@ -507,10 +531,11 @@ static bool Pack_H10306(int format_idx, wiegand_card_t *card, wiegand_message_t } static bool Unpack_H10306(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 34) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 1, 16); card->CardNumber = get_linear_field(packed, 17, 16); @@ -543,10 +568,11 @@ static bool Pack_N10002(int format_idx, wiegand_card_t *card, wiegand_message_t } static bool Unpack_N10002(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 34) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 1, 16); card->CardNumber = get_linear_field(packed, 17, 16); @@ -575,10 +601,11 @@ static bool Pack_C1k35s(int format_idx, wiegand_card_t *card, wiegand_message_t } static bool Unpack_C1k35s(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 35) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->CardNumber = (packed->Bot >> 1) & 0x000FFFFF; card->FacilityCode = ((packed->Mid & 1) << 11) | ((packed->Bot >> 21)); card->ParityValid = @@ -620,13 +647,14 @@ static bool Pack_H10320(int format_idx, wiegand_card_t *card, wiegand_message_t } static bool Unpack_H10320(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 37) return false; // Wrong length? Stop here. if (get_bit_by_position(packed, 0) != 1) { return false; } + memset(card, 0, sizeof(wiegand_card_t)); + // This card is BCD-encoded rather than binary. Get the 4-bit groups independently. for (uint32_t idx = 0; idx < 8; idx++) { uint64_t val = get_linear_field(packed, idx * 4, 4); @@ -663,10 +691,10 @@ static bool Pack_S12906(int format_idx, wiegand_card_t *card, wiegand_message_t } static bool Unpack_S12906(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); - if (packed->Length != 36) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 1, 8); card->IssueLevel = get_linear_field(packed, 9, 2); card->CardNumber = get_linear_field(packed, 11, 24); @@ -696,10 +724,11 @@ static bool Pack_Sie36(int format_idx, wiegand_card_t *card, wiegand_message_t * } static bool Unpack_Sie36(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 36) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 1, 18); card->CardNumber = get_linear_field(packed, 19, 16); card->ParityValid = @@ -728,12 +757,11 @@ static bool Pack_C15001(int format_idx, wiegand_card_t *card, wiegand_message_t } static bool Unpack_C15001(wiegand_message_t *packed, wiegand_card_t *card) { + + if (packed->Length != 36) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); - - if (packed->Length != 36) - return false; // Wrong length? Stop here. - card->OEM = get_linear_field(packed, 1, 10); card->FacilityCode = get_linear_field(packed, 11, 8); card->CardNumber = get_linear_field(packed, 19, 16); @@ -758,10 +786,10 @@ static bool Pack_H10302(int format_idx, wiegand_card_t *card, wiegand_message_t } static bool Unpack_H10302(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); - if (packed->Length != 37) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->CardNumber = get_linear_field(packed, 1, 35); card->ParityValid = (get_bit_by_position(packed, 0) == evenparity32(get_linear_field(packed, 1, 18))) && @@ -785,10 +813,11 @@ static bool Pack_P10004(int format_idx, wiegand_card_t *card, wiegand_message_t } static bool Unpack_P10004(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 37) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 1, 13); card->CardNumber = get_linear_field(packed, 14, 18); // unknown parity scheme @@ -813,10 +842,11 @@ static bool Pack_H10304(int format_idx, wiegand_card_t *card, wiegand_message_t } static bool Unpack_H10304(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 37) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 1, 16); card->CardNumber = get_linear_field(packed, 17, 19); card->ParityValid = @@ -860,11 +890,12 @@ static bool Pack_HGeneric37(int format_idx, wiegand_card_t *card, wiegand_messag } static bool Unpack_HGeneric37(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 37) return false; // Wrong length? Stop here. if (get_bit_by_position(packed, 36) != 1) return false; // Always 1 in this format + memset(card, 0, sizeof(wiegand_card_t)); + card->CardNumber = get_linear_field(packed, 4, 32); card->ParityValid = (get_bit_by_position(packed, 0) == evenparity32(get_nonlinear_field(packed, 8, (uint8_t[]) {4, 8, 12, 16, 20, 24, 28, 32}))) && @@ -899,13 +930,13 @@ static bool Pack_H800002(int format_idx, wiegand_card_t *card, } static bool Unpack_H800002(wiegand_message_t *packed, wiegand_card_t *card) { - int even_parity = 0; - memset(card, 0, sizeof(wiegand_card_t)); - if (packed->Length != 46) { return false; // Wrong length? Stop here. } + int even_parity = 0; + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 1, 14); card->CardNumber = get_linear_field(packed, 15, 30); even_parity = evenparity32((packed->Bot >> 1) ^ (packed->Mid & 0x1fff)); @@ -933,10 +964,10 @@ static bool Pack_MDI37(int format_idx, wiegand_card_t *card, wiegand_message_t * } static bool Unpack_MDI37(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); - if (packed->Length != 37) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 3, 4);; card->CardNumber = get_linear_field(packed, 7, 29); @@ -970,10 +1001,10 @@ static bool Pack_P10001(int format_idx, wiegand_card_t *card, wiegand_message_t static bool Unpack_P10001(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); - if (packed->Length != 40) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->CardNumber = get_linear_field(packed, 16, 16); card->FacilityCode = get_linear_field(packed, 4, 12); card->ParityValid = ( @@ -1006,10 +1037,10 @@ static bool Pack_C1k48s(int format_idx, wiegand_card_t *card, wiegand_message_t } static bool Unpack_C1k48s(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); - if (packed->Length != 48) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->CardNumber = (packed->Bot >> 1) & 0x007FFFFF; card->FacilityCode = ((packed->Mid & 0x00003FFF) << 8) | ((packed->Bot >> 24)); card->ParityValid = @@ -1034,20 +1065,21 @@ static bool Pack_CasiRusco40(int format_idx, wiegand_card_t *card, wiegand_messa } static bool Unpack_CasiRusco40(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 40) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->CardNumber = get_linear_field(packed, 1, 38); return true; } static bool Pack_Optus(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { - memset(packed, 0, sizeof(wiegand_message_t)); - if (!validate_card_limit(format_idx, card)) return false; + memset(packed, 0, sizeof(wiegand_message_t)); + packed->Length = 34; // Set number of bits set_linear_field(packed, card->CardNumber, 1, 16); set_linear_field(packed, card->FacilityCode, 22, 11); @@ -1058,10 +1090,11 @@ static bool Pack_Optus(int format_idx, wiegand_card_t *card, wiegand_message_t * } static bool Unpack_Optus(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 34) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->CardNumber = get_linear_field(packed, 1, 16); card->FacilityCode = get_linear_field(packed, 22, 11); return true; @@ -1084,10 +1117,11 @@ static bool Pack_Smartpass(int format_idx, wiegand_card_t *card, wiegand_message } static bool Unpack_Smartpass(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 34) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 1, 13); card->IssueLevel = get_linear_field(packed, 14, 3); card->CardNumber = get_linear_field(packed, 17, 16); @@ -1118,10 +1152,11 @@ static bool Pack_bqt34(int format_idx, wiegand_card_t *card, wiegand_message_t * } static bool Unpack_bqt34(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 34) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 1, 8); card->CardNumber = get_linear_field(packed, 9, 24); @@ -1156,10 +1191,11 @@ static bool Pack_bqt38(int format_idx, wiegand_card_t *card, wiegand_message_t * } static bool Unpack_bqt38(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 38) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 24, 13); card->CardNumber = get_linear_field(packed, 1, 19); card->IssueLevel = get_linear_field(packed, 20, 4); @@ -1195,10 +1231,11 @@ static bool Pack_iscs38(int format_idx, wiegand_card_t *card, wiegand_message_t } static bool Unpack_iscs38(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 38) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 5, 10); card->CardNumber = get_linear_field(packed, 15, 22); card->OEM = get_linear_field(packed, 1, 4); @@ -1233,10 +1270,11 @@ static bool Pack_pw39(int format_idx, wiegand_card_t *card, wiegand_message_t *p } static bool Unpack_pw39(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 39) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 1, 17); card->CardNumber = get_linear_field(packed, 18, 20); @@ -1270,10 +1308,12 @@ static bool Pack_bc40(int format_idx, wiegand_card_t *card, wiegand_message_t *p } static bool Unpack_bc40(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 40) return false; // Wrong length? Stop here. + memset(card, 0, sizeof(wiegand_card_t)); + + card->OEM = get_linear_field(packed, 0, 7); card->FacilityCode = get_linear_field(packed, 7, 12); card->CardNumber = get_linear_field(packed, 19, 19); @@ -1283,19 +1323,6 @@ static bool Unpack_bc40(wiegand_message_t *packed, wiegand_card_t *card) { return true; } -static bool step_parity_check(wiegand_message_t *packed, int start, int length, bool even_parity) { - bool parity = even_parity; - for (int i = start; i < start + length; i += 2) { - // Extract 2 bits - bool bit1 = get_bit_by_position(packed, i); - bool bit2 = get_bit_by_position(packed, i + 1); - - // Calculate parity for these 2 bits - parity ^= (bit1 ^ bit2); - } - return parity; -} - static bool Pack_Avig56(int format_idx, wiegand_card_t *card, wiegand_message_t *packed, bool preamble) { memset(packed, 0, sizeof(wiegand_message_t)); packed->Length = 56; @@ -1318,10 +1345,11 @@ static bool Pack_Avig56(int format_idx, wiegand_card_t *card, wiegand_message_t } static bool Unpack_Avig56(wiegand_message_t *packed, wiegand_card_t *card) { - memset(card, 0, sizeof(wiegand_card_t)); if (packed->Length != 56) return false; + memset(card, 0, sizeof(wiegand_card_t)); + card->FacilityCode = get_linear_field(packed, 1, 20); card->CardNumber = get_linear_field(packed, 21, 34); @@ -1471,7 +1499,6 @@ static const cardformat_t FormatTable[] = { {NULL, NULL, NULL, NULL, 0, {0, 0, 0, 0, 0, 0, 0, 0, 0}} // Must null terminate array }; - void HIDListFormats(void) { if (FormatTable[0].Name == NULL) return; @@ -1565,31 +1592,33 @@ void HIDPackTryAll(wiegand_card_t *card, bool preamble) { } bool HIDTryUnpack(wiegand_message_t *packed) { - if (FormatTable[0].Name == NULL) + if (FormatTable[0].Name == NULL) { return false; + } - int i = 0; wiegand_card_t card; memset(&card, 0, sizeof(wiegand_card_t)); uint8_t found_cnt = 0, found_invalid_par = 0; + int i = 0; while (FormatTable[i].Name) { if (FormatTable[i].Unpack(packed, &card)) { found_cnt++; hid_print_card(&card, FormatTable[i]); // if fields has parity AND card parity is false - if (FormatTable[i].Fields.hasParity && (card.ParityValid == false)) + if (FormatTable[i].Fields.hasParity && (card.ParityValid == false)) { found_invalid_par++; + } } ++i; } if (found_cnt) { - PrintAndLogEx(INFO, "found " _YELLOW_("%u") " matching " _YELLOW_("%d bit") " format%s" + PrintAndLogEx(INFO, "found " _YELLOW_("%u") " matching " _YELLOW_("%d-bit") " format%s" , found_cnt , packed->Length - , (found_cnt) ? "s" : "" + , (found_cnt > 1) ? "s" : "" ); } @@ -1612,26 +1641,37 @@ void HIDUnpack(int idx, wiegand_message_t *packed) { // decode wiegand format using HIDTryUnpack // return true if at least one valid matching formats found bool decode_wiegand(uint32_t top, uint32_t mid, uint32_t bot, int n) { - bool decode_result; + bool res = false; if (top == 0 && mid == 0 && bot == 0) { - decode_result = false; - } else if ((n > 0) || ((mid & 0xFFFFFFC0) > 0)) { // if n > 0 or there's more than 38 bits - wiegand_message_t packed = initialize_message_object(top, mid, bot, n); - decode_result = HIDTryUnpack(&packed); - } else { // n <= 0 and 39-64 bits are all 0, try two possible bitlens - wiegand_message_t packed1 = initialize_message_object(top, mid, bot, n); // 26-37 bits - wiegand_message_t packed2 = initialize_message_object(top, mid, bot, 38); // 38 bits - bool packed1_result = HIDTryUnpack(&packed1); - bool packed2_result = HIDTryUnpack(&packed2); - decode_result = (packed1_result || packed2_result); + return res; } - if (decode_result == false) { + if (n || ((mid & 0xFFFFFFC0) > 0)) { // if n > 0 or there's more than 38 bits + wiegand_message_t packed = initialize_message_object(top, mid, bot, n); + res = HIDTryUnpack(&packed); + + } else { // n <= 0 and 39-64 bits are all 0, try two possible bitlens + + wiegand_message_t packed = initialize_message_object(top, mid, bot, n); // 26-37 bits + res = HIDTryUnpack(&packed); + + n = packed.Length - 1; + PrintAndLogEx(INFO, "Trying without a preamble bit..."); + packed = initialize_message_object(top, mid, bot, n); // iclass has only a preamble bit. + res |= HIDTryUnpack(&packed); + + if (res == false) { + packed = initialize_message_object(top, mid, bot, 38); // 38 bits + res |= HIDTryUnpack(&packed); + } + } + + if (res == false) { PrintAndLogEx(DEBUG, "DEBUG: Error - " _RED_("HID no values found")); } - return decode_result; + return res; } int HIDDumpPACSBits(const uint8_t *const data, const uint8_t length, bool verbose) { From 482deecebf133016393d3a2167dcc81313ed98bd Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Mon, 24 Feb 2025 08:55:17 +0100 Subject: [PATCH 072/105] hf mf gload did not handle the large dump files yet. Shold need to look if more commands is missing this support. --- CHANGELOG.md | 1 + client/src/cmdhfmf.c | 52 ++++++++++++++++++++++++++------------------ 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef10de0c7..311259996 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 mf gload` - now handles 1k ev1 sized dumps (@iceman1001) - Changed wiegand format unpack functions to clear struct later (@iceman1001) - Changed `wiegand decode` - now accepts new padding format (@iceman1001) - Changed `mem spiffs tree` - ID is now shown in decimal (@iceman1001) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 8aab08019..1b1291bb1 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -5490,7 +5490,7 @@ static int CmdHF14AMfCLoad(const char *Cmd) { return PM3_EFILE; } - PrintAndLogEx(INFO, "Copying to magic gen1a card"); + PrintAndLogEx(INFO, "Copying to magic gen1a MIFARE Classic " _GREEN_("%s"), s); PrintAndLogEx(INFO, "." NOLF); int blockno = 0; @@ -5538,7 +5538,11 @@ static int CmdHF14AMfCLoad(const char *Cmd) { return PM3_EFILE; } - PrintAndLogEx(SUCCESS, "Card loaded " _YELLOW_("%d") " blocks from file", block_cnt); + PrintAndLogEx(SUCCESS, + "Card loaded " _YELLOW_("%d") " blocks from %s" + , block_cnt + , (fill_from_emulator ? "emulator memory" : "file") + ); PrintAndLogEx(INFO, "Done!"); return PM3_SUCCESS; } @@ -8270,23 +8274,24 @@ static int CmdHF14AGen4Load(const char *cmd) { CLIExecWithReturn(ctx, cmd, argtable, false); bool m0 = arg_get_lit(ctx, 1); bool m1 = arg_get_lit(ctx, 2); - bool m2 = arg_get_lit(ctx, 3); - bool m4 = arg_get_lit(ctx, 4); + bool m1ev1 = arg_get_lit(ctx, 3); + bool m2 = arg_get_lit(ctx, 4); + bool m4 = arg_get_lit(ctx, 5); int pwd_len = 0; uint8_t pwd[4] = {0}; - CLIGetHexWithReturn(ctx, 5, pwd, &pwd_len); + CLIGetHexWithReturn(ctx, 6, pwd, &pwd_len); - bool verbose = arg_get_lit(ctx, 6); + bool verbose = arg_get_lit(ctx, 7); int fnlen = 0; char filename[FILE_PATH_SIZE] = {0}; - CLIParamStrToBuf(arg_get_str(ctx, 7), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); + CLIParamStrToBuf(arg_get_str(ctx, 8), (uint8_t *)filename, FILE_PATH_SIZE, &fnlen); - bool fill_from_emulator = arg_get_lit(ctx, 8); + bool fill_from_emulator = arg_get_lit(ctx, 9); - int start = arg_get_int_def(ctx, 9, 0); - int end = arg_get_int_def(ctx, 10, -1); + int start = arg_get_int_def(ctx, 10, 0); + int end = arg_get_int_def(ctx, 11, -1); CLIParserFree(ctx); @@ -8296,14 +8301,14 @@ static int CmdHF14AGen4Load(const char *cmd) { return PM3_EINVARG; } - if ((m0 + m1 + m2 + m4) > 1) { + if ((m0 + m1 + m2 + m4 + m1ev1) > 1) { PrintAndLogEx(WARNING, "Only specify one MIFARE Type"); return PM3_EINVARG; - } else if ((m0 + m1 + m2 + m4) == 0) { + } else if ((m0 + m1 + m2 + m4 + m1ev1) == 0) { m1 = true; } - char s[6]; + char s[8]; memset(s, 0, sizeof(s)); uint16_t block_cnt = MIFARE_1K_MAXBLOCK; if (m0) { @@ -8312,6 +8317,9 @@ static int CmdHF14AGen4Load(const char *cmd) { } else if (m1) { block_cnt = MIFARE_1K_MAXBLOCK; strncpy(s, "1K", 3); + } else if (m1ev1) { + block_cnt = MIFARE_1K_EV1_MAXBLOCK; + strncpy(s, "1K Ev1", 7); } else if (m2) { block_cnt = MIFARE_2K_MAXBLOCK; strncpy(s, "2K", 3); @@ -8390,16 +8398,13 @@ static int CmdHF14AGen4Load(const char *cmd) { } if (verbose) { - if (fnlen != 0) { - PrintAndLogEx(INFO, "File: " _YELLOW_("%s"), filename); - PrintAndLogEx(INFO, "File size %zu bytes, file blocks %d (0x%x)", bytes_read, block_cnt, block_cnt); - } else { + if (fnlen == 0) { PrintAndLogEx(INFO, "Read %d blocks from emulator memory", block_cnt); } } PrintAndLogEx(INFO, "Copying to magic gen4 GTU MIFARE Classic " _GREEN_("%s"), s); - PrintAndLogEx(INFO, "Starting block: %d. Ending block: %d.", start, end); + PrintAndLogEx(INFO, "Block... %d - %d", start, end); // copy to card for (uint16_t blockno = start; blockno <= end; blockno++) { @@ -8427,10 +8432,15 @@ static int CmdHF14AGen4Load(const char *cmd) { } PrintAndLogEx(NORMAL, "\n"); - if (data != NULL) free(data); + if (data != NULL) { + free(data); + } - PrintAndLogEx(SUCCESS, "Card loaded " _YELLOW_("%d") " blocks from %s", end - start + 1, - (fill_from_emulator ? "emulator memory" : "file")); + PrintAndLogEx(SUCCESS, + "Card loaded " _YELLOW_("%d") " blocks from %s" + , end - start + 1 + , (fill_from_emulator ? "emulator memory" : "file") + ); PrintAndLogEx(INFO, "Done!"); return PM3_SUCCESS; } From 3b97acfefe22c976718ed1c214d514ce889f9ac8 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Mon, 24 Feb 2025 08:55:50 +0100 Subject: [PATCH 073/105] this define is nasty. Must be reworked! --- armsrc/hitagS.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/armsrc/hitagS.c b/armsrc/hitagS.c index 924240ea7..4c4e3d493 100644 --- a/armsrc/hitagS.c +++ b/armsrc/hitagS.c @@ -74,7 +74,7 @@ static uint8_t pwdh0, pwdl0, pwdl1; // password bytes static uint8_t rnd[] = {0x85, 0x44, 0x12, 0x74}; // random number static uint16_t timestamp_high = 0; // Timer Counter 2 overflow count, ~47min -#define TIMESTAMP (AT91C_BASE_TC2->TC_SR &AT91C_TC_COVFS ? timestamp_high += 1 : 0, ((timestamp_high << 16) + AT91C_BASE_TC2->TC_CV) / T0) +#define TIMESTAMP ( (AT91C_BASE_TC2->TC_SR & AT91C_TC_COVFS) ? timestamp_high += 1 : 0, ((timestamp_high << 16) + AT91C_BASE_TC2->TC_CV) / T0) //#define SENDBIT_TEST @@ -777,7 +777,9 @@ void hts_simulate(bool tag_mem_supplied, const uint8_t *data, bool ledcontrol) { if (ledcontrol) LED_B_ON(); // Capture reader cmd start timestamp - if (start_time == 0) start_time = TIMESTAMP - HITAG_T_LOW; + if (start_time == 0) { + start_time = TIMESTAMP - HITAG_T_LOW; + } // Capture reader frame if (rb >= HITAG_T_STOP) { @@ -865,9 +867,6 @@ static void hts_receive_frame(uint8_t *rx, size_t sizeofrx, size_t *rxlen, uint3 uint32_t rb_i = 0, h2 = 0, h3 = 0, h4 = 0; uint8_t edges[160] = {0}; - // Dbprintf("TC0_CV:%i TC1_CV:%i TC1_RB:%i TIMESTAMP:%u", AT91C_BASE_TC0->TC_CV, AT91C_BASE_TC1->TC_CV, - // AT91C_BASE_TC1->TC_RB, TIMESTAMP); - // Receive tag frame, watch for at most T0*HITAG_T_PROG_MAX periods while (AT91C_BASE_TC0->TC_CV < (T0 * HITAG_T_PROG_MAX)) { From d2e8066cbf353ccc2fc391ea8fbba95484f0e1d8 Mon Sep 17 00:00:00 2001 From: Donny <107092000+Donny-Guo@users.noreply.github.com> Date: Thu, 27 Feb 2025 11:29:33 -0800 Subject: [PATCH 074/105] Revise decode_wiegand function --- client/src/cmdwiegand.c | 6 ++---- client/src/wiegand_formats.c | 16 ++++---------- client/src/wiegand_formatutils.c | 36 ++++++++------------------------ 3 files changed, 15 insertions(+), 43 deletions(-) diff --git a/client/src/cmdwiegand.c b/client/src/cmdwiegand.c index 404754715..82d6ce14c 100644 --- a/client/src/cmdwiegand.c +++ b/client/src/cmdwiegand.c @@ -69,8 +69,7 @@ static int wiegand_new_pacs(uint8_t *padded_pacs, uint8_t plen) { PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "------------------------- " _CYAN_("SIO - Wiegand") " ---------------------------"); - wiegand_message_t packed = initialize_message_object(top, mid, bot, strlen(binstr)); - HIDTryUnpack(&packed); + decode_wiegand(top, mid, bot, strlen(binstr)); free(binstr); return PM3_SUCCESS; } @@ -215,8 +214,7 @@ int CmdWiegandDecode(const char *Cmd) { PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "Wiegand decode"); - wiegand_message_t packed = initialize_message_object(top, mid, bot, blen); - HIDTryUnpack(&packed); + decode_wiegand(top, mid, bot, blen); return PM3_SUCCESS; } diff --git a/client/src/wiegand_formats.c b/client/src/wiegand_formats.c index 17447ac3a..b538f6fdc 100644 --- a/client/src/wiegand_formats.c +++ b/client/src/wiegand_formats.c @@ -1647,24 +1647,16 @@ bool decode_wiegand(uint32_t top, uint32_t mid, uint32_t bot, int n) { return res; } - if (n || ((mid & 0xFFFFFFC0) > 0)) { // if n > 0 or there's more than 38 bits + if (n > 0) { wiegand_message_t packed = initialize_message_object(top, mid, bot, n); res = HIDTryUnpack(&packed); - - } else { // n <= 0 and 39-64 bits are all 0, try two possible bitlens - + } else { wiegand_message_t packed = initialize_message_object(top, mid, bot, n); // 26-37 bits res = HIDTryUnpack(&packed); - n = packed.Length - 1; - PrintAndLogEx(INFO, "Trying without a preamble bit..."); - packed = initialize_message_object(top, mid, bot, n); // iclass has only a preamble bit. + PrintAndLogEx(INFO, "Trying with a preamble bit..."); + packed.Length += 1; res |= HIDTryUnpack(&packed); - - if (res == false) { - packed = initialize_message_object(top, mid, bot, 38); // 38 bits - res |= HIDTryUnpack(&packed); - } } if (res == false) { diff --git a/client/src/wiegand_formatutils.c b/client/src/wiegand_formatutils.c index 0a17f654d..211207e13 100644 --- a/client/src/wiegand_formatutils.c +++ b/client/src/wiegand_formatutils.c @@ -132,8 +132,6 @@ static uint8_t get_length_from_header(wiegand_message_t *data) { /** * detect if message has "preamble" / "sentinel bit" * Right now we just calculate the highest bit set - * 38 bits format is handled by directly setting n=38 in initialize_message_object() - * since it's hard to distinguish 38 bits with formats with preamble bit (26-36 bits) * * (from http://www.proxmark.org/forum/viewtopic.php?pid=5368#p5368) * 0000 0010 0000 0000 01xx xxxx xxxx xxxx xxxx xxxx xxxx 26-bit @@ -148,7 +146,6 @@ static uint8_t get_length_from_header(wiegand_message_t *data) { * 0000 0010 1xxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx 35-bit * 0000 0011 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx 36-bit * 0000 000x xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx 37-bit - * 0000 00xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx 38-bit */ uint8_t len = 0; uint32_t hfmt = 0; // for calculating card length @@ -156,26 +153,15 @@ static uint8_t get_length_from_header(wiegand_message_t *data) { if ((data->Top & 0x000FFFFF) > 0) { // > 64 bits hfmt = data->Top & 0x000FFFFF; len = 64; - } else if (data->Mid > 0) { - // detect HID format b38 set - if (data->Mid & 0xFFFFFFC0) { // 39-64 bits - hfmt = data->Mid; - len = 31; // remove leading 1 (preamble) in 39-64 bits format - } else { // detect card format 26-37 bits using "preamble" / "sentinel bit" - PrintAndLogEx(DEBUG, "hid preamble detected"); - - // if bit 38 is set: => 26-36 bits - if (((data->Mid >> 5) & 1) == 1) { - hfmt = (((data->Mid & 31) << 12) | (data->Bot >> 26)); //get bits 27-37 to check for format len bit - len = 19; - } else { // if bit 38 is not set => 37 bits - hfmt = 0; - len = 37; - } - } - } else { - hfmt = data->Bot; - len = 0; + } else if (data->Mid & 0xFFFFFFC0) { // handle 38bit and above format + hfmt = data->Mid; + len = 31; // remove leading 1 (preamble) in 38-64 bits format + } else if (((data->Mid >> 5) & 1) == 1) { // bit 38 is set => 26-36bit format + hfmt = (((data->Mid & 31) << 12) | (data->Bot >> 26)); // get bits 27-37 to check for format len bit + len = 19; + } else { // if bit 38 is not set => 37bit format + hfmt = 0; + len = 37; } while (hfmt > 0) { @@ -183,10 +169,6 @@ static uint8_t get_length_from_header(wiegand_message_t *data) { len++; } - // everything less than 26 bits found, assume 26 bits - if (len < 26) - len = 26; - return len; } From 8c23ebca2e2bc2b8d24e2c5bdd8622050bdb958e Mon Sep 17 00:00:00 2001 From: Donny <107092000+Donny-Guo@users.noreply.github.com> Date: Thu, 27 Feb 2025 14:40:29 -0800 Subject: [PATCH 075/105] Fix bug --- client/src/wiegand_formatutils.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/wiegand_formatutils.c b/client/src/wiegand_formatutils.c index 211207e13..9dfda9d0f 100644 --- a/client/src/wiegand_formatutils.c +++ b/client/src/wiegand_formatutils.c @@ -157,8 +157,8 @@ static uint8_t get_length_from_header(wiegand_message_t *data) { hfmt = data->Mid; len = 31; // remove leading 1 (preamble) in 38-64 bits format } else if (((data->Mid >> 5) & 1) == 1) { // bit 38 is set => 26-36bit format - hfmt = (((data->Mid & 31) << 12) | (data->Bot >> 26)); // get bits 27-37 to check for format len bit - len = 19; + hfmt = (((data->Mid & 31) << 6) | (data->Bot >> 26)); // get bits 27-37 to check for format len bit + len = 25; } else { // if bit 38 is not set => 37bit format hfmt = 0; len = 37; From 5e018ea3b39abe953eee931991f91beca63e79e2 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Sun, 2 Mar 2025 16:20:13 +0100 Subject: [PATCH 076/105] fm11rf08s_recovery.py: fix it given the changes in the client and add some docstring --- client/pyscripts/fm11rf08s_recovery.py | 182 +++++++++++++++++-------- 1 file changed, 125 insertions(+), 57 deletions(-) diff --git a/client/pyscripts/fm11rf08s_recovery.py b/client/pyscripts/fm11rf08s_recovery.py index 19407ec12..cbb7246d5 100755 --- a/client/pyscripts/fm11rf08s_recovery.py +++ b/client/pyscripts/fm11rf08s_recovery.py @@ -1,17 +1,18 @@ #!/usr/bin/env python3 +""" +Combine several attacks to recover all FM11RF08S keys. -# Combine several attacks to recover all FM11RF08S keys -# -# Conditions: -# * Presence of the backdoor with known key -# -# Duration strongly depends on some key being reused and where. -# Examples: -# * 32 random keys: ~20 min -# * 16 random keys with keyA==keyB in each sector: ~30 min -# * 24 random keys, some reused across sectors: <1 min -# -# Doegox, 2024, cf https://eprint.iacr.org/2024/1275 for more info +Conditions: +* Presence of the backdoor with known key + +Duration strongly depends on some key being reused and where. +Examples: +* 32 random keys: ~20 min +* 16 random keys with keyA==keyB in each sector: ~30 min +* 24 random keys, some reused across sectors: <1 min + +Doegox, 2024, cf https://eprint.iacr.org/2024/1275 for more info +""" import os import sys @@ -19,6 +20,7 @@ import time import subprocess import argparse import json +import re import pm3 from pm3_resources import find_tool, find_dict @@ -28,6 +30,7 @@ try: from colors import color except ModuleNotFoundError: def color(s, fg=None): + """Return the string as such, without color.""" _ = fg return str(s) @@ -51,7 +54,45 @@ staticnested_2x1nt_path = find_tool("staticnested_2x1nt_rf08s") staticnested_2x1nt1key_path = find_tool("staticnested_2x1nt_rf08s_1key") -def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debug=False, supply_chain=False, quiet=True, keyset=False): +def match_key(line): + """ + Extract a 12-character hexadecimal key from a given string. + + Args: + line (str): The input string to search for the hexadecimal key. + + Returns: + str or None: The 12-character hexadecimal key in uppercase if found, otherwise None. + """ + match = re.search(r'([0-9a-fA-F]{12})', line) + if match: + return match.group(1).upper() + else: + return None + + +def recovery(init_check=False, final_check=False, keep=False, no_oob=False, + debug=False, supply_chain=False, quiet=True, keyset=[]): + """ + Perform recovery operation for FM11RF08S cards. + + Args: + init_check (bool): If True, check for default keys initially. + final_check (bool): If True, perform a final check and dump keys. + keep (bool): If True, keep the generated dictionaries after processing. + no_oob (bool): If True, do not include out-of-bounds sectors. + debug (bool): If True, print debug information. + supply_chain (bool): If True, use supply-chain attack data. + quiet (bool): If True, suppress output messages. + keyset (list): A list of key pairs to use for the recovery process. + + Returns: + dict: A dictionary containing the following keys: + - 'keyfile': Path to the generated binary key file. + - 'found_keys': List of found keys for each sector. + - 'dump_file': Path to the generated dump file. + - 'data': List of data blocks for each sector. + """ def show(s='', prompt="[" + color("=", fg="yellow") + "] ", **kwargs): if not quiet: s = f"{prompt}" + f"\n{prompt}".join(s.split('\n')) @@ -82,10 +123,10 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu found_keys = [["", ""] for _ in range(NUM_SECTORS + NUM_EXTRA_SECTORS)] - if keyset != False: - n = min(len(found_keys),len(keyset)) + if len(keyset) > 0: + n = min(len(found_keys), len(keyset)) show(f"{n} Key pairs supplied: ") - for i in range(0, n): + for i in range(n): found_keys[i] = keyset[i] show(f" Sector {i:2d} : A = {found_keys[i][0]:12s} B = {found_keys[i][1]:12s}") @@ -111,8 +152,9 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu for line in p.grabbed_output.split('\n'): if "Wrong" in line or "error" in line: break - if "Saved" in line: - nonces_with_data = line[line.index("`"):].strip("`") + matched = "Saved to json file " + if matched in line: + nonces_with_data = line[line.index(matched)+len(matched):] if nonces_with_data != "": break @@ -146,8 +188,8 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu data[blk] = dict_nwd["blocks"][f"{blk}"] show("Generating first dump file") - dumpfile = f"{save_path}hf-mf-{uid:08X}-dump.bin" - with (open(dumpfile, "wb")) as f: + dump_file = f"{save_path}hf-mf-{uid:08X}-dump.bin" + with (open(dump_file, "wb")) as f: for sec in range(NUM_SECTORS): for b in range(4): d = data[(sec * 4) + b] @@ -160,7 +202,7 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu kb = "FFFFFFFFFFFF" d = ka + d[12:20] + kb f.write(bytes.fromhex(d)) - show(f"Data has been dumped to `{dumpfile}`") + show(f"Data has been dumped to `{dump_file}`") elapsed_time1 = time.time() - start_time minutes = int(elapsed_time1 // 60) @@ -240,8 +282,9 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu result = subprocess.run(cmd, capture_output=True, text=True).stdout keys_def_set = set() for line in result.split('\n'): - if "MATCH:" in line: - keys_def_set.add(line[12:]) + matched = match_key(line) + if matched is not None: + keys_def_set.add(matched) keys_set.difference_update(keys_def_set) else: # Prioritize default keys @@ -285,8 +328,9 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu result = subprocess.run(cmd, capture_output=True, text=True).stdout keys_def_set = set() for line in result.split('\n'): - if "MATCH:" in line: - keys_def_set.add(line[12:]) + matched = match_key(line) + if matched is not None: + keys_def_set.add(matched) keys_set.difference_update(keys_def_set) else: # Prioritize default keys @@ -419,8 +463,9 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu for line in p.grabbed_output.split('\n'): if "aborted via keyboard" in line: abort = True - if "found:" in line: - found_keys[sec][key_type] = line[30:].strip() + matched = match_key(line) + if matched is not None: + found_keys[sec][key_type] = matched show_key(real_sec, key_type, found_keys[sec][key_type]) if nt[sec][0] == nt[sec][1] and found_keys[sec][key_type ^ 1] == "": found_keys[sec][key_type ^ 1] = found_keys[sec][key_type] @@ -445,8 +490,9 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu for line in p.grabbed_output.split('\n'): if "aborted via keyboard" in line: abort = True - if "found:" in line: - found_keys[sec][key_type] = line[30:].strip() + matched = match_key(line) + if matched is not None: + found_keys[sec][key_type] = matched show_key(real_sec, key_type, found_keys[sec][key_type]) if abort: break @@ -466,11 +512,12 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu for line in p.grabbed_output.split('\n'): if "aborted via keyboard" in line: abort = True - if "found:" in line: - found_keys[sec][0] = line[30:].strip() - found_keys[sec][1] = line[30:].strip() - show_key(real_sec, 0, found_keys[sec][key_type]) - show_key(real_sec, 1, found_keys[sec][key_type]) + matched = match_key(line) + if matched is not None: + found_keys[sec][0] = matched + found_keys[sec][1] = matched + show_key(real_sec, 0, found_keys[sec][0]) + show_key(real_sec, 1, found_keys[sec][1]) if abort: break @@ -494,8 +541,9 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu result = subprocess.run(cmd, capture_output=True, text=True).stdout keys = set() for line in result.split('\n'): - if "MATCH:" in line: - keys.add(line[12:]) + matched = match_key(line) + if matched is not None: + keys.add(matched) if len(keys) > 1: kt = ['a', 'b'][key_type_target] cmd = f"hf mf fchk --blk {real_sec * 4} -{kt} --no-default" @@ -507,8 +555,9 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu for line in p.grabbed_output.split('\n'): if "aborted via keyboard" in line: abort = True - if "found:" in line: - found_keys[sec][key_type_target] = line[30:].strip() + matched = match_key(line) + if matched is not None: + found_keys[sec][key_type_target] = matched elif len(keys) == 1: found_keys[sec][key_type_target] = keys.pop() if found_keys[sec][key_type_target] != "": @@ -530,7 +579,10 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu cmd = f"hf mf fchk -f keys_{uid:08x}.dic --no-default --dump" if debug: print(cmd) - p.console(cmd, capture=False, quiet=False) + p.console(cmd, capture=True, quiet=False) + for line in p.grabbed_output.split('\n'): + if "Found keys have been dumped to" in line: + keyfile = line[line.index("`"):].strip("`") else: show() show(color("found keys:", fg="green"), prompt=plus) @@ -569,22 +621,22 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu if unknown: show(" --[ " + color("FFFFFFFFFFFF", fg="yellow") + " ]-- has been inserted for unknown keys", prompt="[" + color("=", fg="yellow") + "]") - show("Generating final dump file", prompt=plus) - dumpfile = f"{save_path}hf-mf-{uid:08X}-dump.bin" - with (open(dumpfile, "wb")) as f: - for sec in range(NUM_SECTORS): - for b in range(4): - d = data[(sec * 4) + b] - if b == 3: - ka = found_keys[sec][0] - kb = found_keys[sec][1] - if ka == "": - ka = "FFFFFFFFFFFF" - if kb == "": - kb = "FFFFFFFFFFFF" - d = ka + d[12:20] + kb - f.write(bytes.fromhex(d)) - show("Data has been dumped to `" + color(dumpfile, fg="yellow")+"`", prompt=plus) + show("Generating final dump file", prompt=plus) + dump_file = f"{save_path}hf-mf-{uid:08X}-dump.bin" + with (open(dump_file, "wb")) as f: + for sec in range(NUM_SECTORS): + for b in range(4): + d = data[(sec * 4) + b] + if b == 3: + ka = found_keys[sec][0] + kb = found_keys[sec][1] + if ka == "": + ka = "FFFFFFFFFFFF" + if kb == "": + kb = "FFFFFFFFFFFF" + d = ka + d[12:20] + kb + f.write(bytes.fromhex(d)) + show("Data has been dumped to `" + color(dump_file, fg="yellow")+"`", prompt=plus) # Remove generated dictionaries after processing if not keep: @@ -610,11 +662,27 @@ def recovery(init_check=False, final_check=False, keep=False, no_oob=False, debu seconds = int(elapsed_time % 60) show("---- TOTAL: " + color(f"{minutes:2}", fg="yellow") + " minutes " + color(f"{seconds:2}", fg="yellow") + " seconds -----------") - - return {'keyfile': keyfile, 'found_keys': found_keys, 'dumpfile': dumpfile, 'data': data} + return {'keyfile': keyfile, 'found_keys': found_keys, 'dump_file': dump_file, 'data': data} def main(): + """ + Parse command-line arguments and initiate the recovery process. + + Command-line arguments: + -x, --init-check: Run an initial fchk for default keys. + -y, --final-check: Run a final fchk with the found keys. + -n, --no-oob: Do not save out of bounds keys. + -k, --keep: Keep generated dictionaries after processing. + -d, --debug: Enable debug mode. + -s, --supply-chain: Enable supply-chain mode. Look for hf-mf-XXXXXXXX-default_nonces.json. + + The supply-chain mode json can be produced from the json saved by + "hf mf isen --collect_fm11rf08s --key A396EFA4E24F" on a wiped card, then processed with + jq '{Created: .Created, FileType: "fm11rf08s_default_nonces", nt: .nt | del(.["32"]) | map_values(.a)}'. + + This function calls the recovery function with the parsed arguments. + """ parser = argparse.ArgumentParser(description='A script combining staticnested* tools ' 'to recover all keys from a FM11RF08S card.') parser.add_argument('-x', '--init-check', action='store_true', help='Run an initial fchk for default keys') From beeec2385cad58c3833f0b2ae9f7becae4586336 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Sun, 2 Mar 2025 16:41:14 +0100 Subject: [PATCH 077/105] fm11rf08s_full pip8 style --- client/pyscripts/fm11rf08s_full.py | 188 ++++++++++++++++------------- 1 file changed, 101 insertions(+), 87 deletions(-) diff --git a/client/pyscripts/fm11rf08s_full.py b/client/pyscripts/fm11rf08s_full.py index 6f3dbc93e..714d1acaf 100644 --- a/client/pyscripts/fm11rf08s_full.py +++ b/client/pyscripts/fm11rf08s_full.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +"""This script recovers Fudan FM11RF08S cards, including functionalities for Bambu tags decoding.""" # ------------------------------------------------------------------------------ # Imports @@ -43,17 +44,18 @@ try: from colors import color except ModuleNotFoundError: def color(s, fg=None): + """Return the string as such, without color.""" _ = fg return str(s) def initlog(): - """Print and Log: init globals + """Print and Log: init globals. -globals: -- logbuffer (W) -- logfile (W) -""" + globals: + - logbuffer (W) + - logfile (W) + """ global logbuffer global logfile logbuffer = '' @@ -61,12 +63,12 @@ globals: def startlog(uid, dpath, append=False): - """Print and Log: set logfile and flush logbuffer + """Print and Log: set logfile and flush logbuffer. -globals: -- logbuffer (RW) -- logfile (RW) -""" + globals: + - logbuffer (RW) + - logfile (RW) + """ global logfile global logbuffer @@ -81,13 +83,12 @@ globals: def lprint(s='', end='\n', flush=False, prompt="[" + color("=", fg="yellow") + "] ", log=True): - """Print and Log - -globals: -- logbuffer (RW) -- logfile (R) -""" + """Print and Log. + globals: + - logbuffer (RW) + - logfile (R) + """ s = f"{prompt}" + f"\n{prompt}".join(s.split('\n')) print(s, end=end, flush=flush) @@ -102,11 +103,11 @@ globals: def main(): - """== MAIN == + """== MAIN ==. -globals: -- p (W) -""" + globals: + - p (W) + """ global p p = pm3.pm3() # console interface initlog() @@ -143,7 +144,7 @@ globals: else: # FIXME: recovery() is only for RF08S. TODO for the other ones with a "darknested" attack keyfile = recoverKeys(uid=uid, kdf=[["Bambu v1", kdfBambu1]]) - if keyfile == False: + if keyfile is False: lprint("Script failed - aborting") return key = loadKeys(keyfile) @@ -181,11 +182,11 @@ globals: def getPrefs(): - """Get PM3 preferences + """Get PM3 preferences. -globals: -- p (R) -""" + globals: + - p (R) + """ p.console("prefs show --json") prefs = json.loads(p.grabbed_output) dpath = prefs['file.default.dumppath'] + os.path.sep @@ -193,7 +194,7 @@ globals: def checkVer(): - """Assert python version""" + """Assert python version.""" required_version = (3, 8) if sys.version_info < required_version: print(f"Python version: {sys.version}") @@ -203,7 +204,7 @@ def checkVer(): def parseCli(): - """Parse the CLi arguments""" + """Parse the CLi arguments.""" parser = argparse.ArgumentParser(description='Full recovery of Fudan FM11RF08S cards.') parser.add_argument('-n', '--nokeys', action='store_true', help='extract data even if keys are missing') @@ -222,15 +223,15 @@ def parseCli(): def getBackdoorKey(): - """Find backdoor key -[=] # | sector 00 / 0x00 | ascii -[=] ----+-------------------------------------------------+----------------- -[=] 0 | 5C B4 9C A6 D2 08 04 00 04 59 92 25 BF 5F 70 90 | \\........Y.%._p. + r"""Find backdoor key. -globals: -- p (R) -""" + [=] # | sector 00 / 0x00 | ascii + [=] ----+-------------------------------------------------+----------------- + [=] 0 | 5C B4 9C A6 D2 08 04 00 04 59 92 25 BF 5F 70 90 | \........Y.%._p. + globals: + - p (R) + """ # FM11RF08S FM11RF08 FM11RF32 dklist = ["A396EFA4E24F", "A31667A8CEC1", "518b3354E760"] @@ -259,14 +260,14 @@ globals: def getUIDfromBlock0(blk0): - """Extract UID from block 0""" + """Extract UID from block 0.""" uids = blk0[0:11] # UID string : "11 22 33 44" uid = bytes.fromhex(uids.replace(' ', '')) # UID (bytes) : 11223344 return uid def decodeBlock0(blk0): - """Extract data from block 0""" + """Extract data from block 0.""" lprint() lprint(" UID BCC ++---- RF08* ID -----++") lprint(" ! ! SAK !! !!") @@ -346,7 +347,7 @@ def decodeBlock0(blk0): def fudanValidate(blk0, live=False): - """Fudan validation""" + """Fudan validation.""" url = "https://rfid.fm-uivs.com/nfcTools/api/M1KeyRest" hdr = "Content-Type: application/text; charset=utf-8" post = f"{blk0.replace(' ', '')}" @@ -387,10 +388,10 @@ def fudanValidate(blk0, live=False): def loadKeys(keyfile): - """Load keys from file + """Load keys from file. -If keys cannot be loaded AND --recover is specified, then run key recovery -""" + If keys cannot be loaded AND --recover is specified, then run key recovery + """ key = [[b'' for _ in range(2)] for _ in range(17)] # create a fresh array lprint("\nLoad keys from file... " + color(f"{keyfile}", fg="yellow")) @@ -408,15 +409,15 @@ If keys cannot be loaded AND --recover is specified, then run key recovery def recoverKeys(uid, kdf=[[]]): - """Run key recovery script""" + """Run key recovery script.""" badrk = 0 # 'bad recovered key' count (ie. not recovered) - keys = False - lprint(f"\nTrying KDFs:"); + keys = [] + lprint("\nTrying KDFs:") for fn in kdf: lprint(f" {fn[0]:s}", end='') keys = fn[1](uid) - if keys != False: + if len(keys) > 0: lprint(" .. Success", prompt='') break lprint(" .. Fail", prompt='') @@ -427,7 +428,7 @@ def recoverKeys(uid, kdf=[[]]): r = recovery(quiet=False, keyset=keys) lprint('`-._,-\'"`-._,-"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,-\'"`-._,') - if r == False: + if r is False: return False keyfile = r['keyfile'] @@ -453,14 +454,28 @@ def recoverKeys(uid, kdf=[[]]): lprint("", prompt='') return keyfile + def kdfBambu1(uid): + """Derive keys from a given UID using the Bambu HKDF algorithm and validates the card data. + + This function generates two keys (keyA and keyB) using the Bambu HKDF algorithm with a predefined salt and context. + It then attempts to read block 13 from sector 3 of the card using keyA. If successful, it decodes the data + and checks if it matches a specific date format. If the data is valid, it returns a list of derived keys. + + Args: + uid (bytes): The UID of the card. + + Returns: + list: A list of derived keys if the card data is valid. + bool: False if any step in the process fails. + """ from Cryptodome.Protocol.KDF import HKDF - from Cryptodome.Hash import SHA256 + from Cryptodome.Hash import SHA256 # Generate all keys try: # extracted from Bambu firmware - salt = bytes([0x9a,0x75,0x9c,0xf2,0xc4,0xf7,0xca,0xff,0x22,0x2c,0xb9,0x76,0x9b,0x41,0xbc,0x96]) + salt = bytes([0x9a, 0x75, 0x9c, 0xf2, 0xc4, 0xf7, 0xca, 0xff, 0x22, 0x2c, 0xb9, 0x76, 0x9b, 0x41, 0xbc, 0x96]) keyA = HKDF(uid, 6, salt, SHA256, 16, context=b"RFID-A\0") keyB = HKDF(uid, 6, salt, SHA256, 16, context=b"RFID-B\0") except Exception as e: @@ -469,7 +484,7 @@ def kdfBambu1(uid): # --- Grab block 13 (in sector 3) --- cmd = f"hf mf rdbl -c 0 --key {keyA[3].hex()} --blk 12" - #lprint(f" `{cmd}`", flush=True, log=False, end='') + # lprint(f" `{cmd}`", flush=True, log=False, end='') for retry in range(5): p.console(cmd) @@ -502,13 +517,13 @@ def kdfBambu1(uid): return keys + def verifyKeys(key): - """Verify keys - -globals: -- p (R) -""" + """Verify keys. + globals: + - p (R) + """ badk = 0 mad = False @@ -563,16 +578,15 @@ globals: def readBlocks(bdkey, fast=False): + r"""Read all block data - INCLUDING advanced verification blocks. + + [=] # | sector 00 / 0x00 | ascii + [=] ----+-------------------------------------------------+----------------- + [=] 0 | 5C B4 9C A6 D2 08 04 00 04 59 92 25 BF 5F 70 90 | \........Y.%._p. + + globals: + - p (R) """ -Read all block data - INCLUDING advanced verification blocks - -[=] # | sector 00 / 0x00 | ascii -[=] ----+-------------------------------------------------+----------------- -[=] 0 | 5C B4 9C A6 D2 08 04 00 04 59 92 25 BF 5F 70 90 | \\........Y.%._p. - -globals: -- p (R) -""" data = [] blkn = list(range(0, 63 + 1)) + list(range(128, 135 + 1)) @@ -634,9 +648,10 @@ globals: def patchKeys(data, key): - """Patch keys in to data - 3 | 00 00 00 00 00 00 87 87 87 69 00 00 00 00 00 00 | .........i...... -""" + """Patch keys in to data. + + 3 | 00 00 00 00 00 00 87 87 87 69 00 00 00 00 00 00 | .........i...... + """ lprint("\nPatching keys in to data") for sec in range(0, 16 + 1): @@ -662,7 +677,7 @@ def patchKeys(data, key): def dumpData(data, blkn): - """Dump data""" + """Dump data.""" lprint() lprint("===========") lprint(" Card Data") @@ -708,14 +723,14 @@ def detectBambu(data): def dumpBambu(data): - """Dump bambu details + """Dump bambu details. -https://github.com/Bambu-Research-Group/RFID-Tag-Guide/blob/main/README.md + https://github.com/Bambu-Research-Group/RFID-Tag-Guide/blob/main/README.md - 6 18 30 42 53 - | | | | | - 3 | 00 00 00 00 00 00 87 87 87 69 00 00 00 00 00 00 | .........i...... -""" + 6 18 30 42 53 + | | | | | + 3 | 00 00 00 00 00 00 87 87 87 69 00 00 00 00 00 00 | .........i...... + """ try: lprint() lprint("===========") @@ -833,14 +848,14 @@ https://github.com/Bambu-Research-Group/RFID-Tag-Guide/blob/main/README.md # The Access bits on both (used) Sectors is the same: 78 77 88 -# Let's reorganise that according to the official spec Fig 9. +# Let's reorganize that according to the official spec Fig 9. # Access C1 C2 C3 # ========== =========== # 78 77 88 --> 78 87 87 # ab cd ef --> cb fa ed # The second nybble of each byte is the inverse of the first nybble. -# It is there to trap tranmission errors, so we can just ignore it/them. +# It is there to trap transmission errors, so we can just ignore it/them. # So our Access Control value is : {c, f, e} == {7, 8, 8} @@ -903,13 +918,13 @@ https://github.com/Bambu-Research-Group/RFID-Tag-Guide/blob/main/README.md # IF YOU PLAN TO CHANGE ACCESS BITS, RTFM, THERE IS MUCH TO CONSIDER ! # ============================================================================== def dumpAcl(data): - """Dump ACL + """Dump ACL. - 6 18 24 27 30 33 42 53 - | | | | | | | | - 3 | 00 00 00 00 00 00 87 87 87 69 00 00 00 00 00 00 | .........i...... - ab cd ef -""" + 6 18 24 27 30 33 42 53 + | | | | | | | | + 3 | 00 00 00 00 00 00 87 87 87 69 00 00 00 00 00 00 | .........i...... + ab cd ef + """ aclkh = [] # key header aclk = [""] * 8 # key lookup aclkx = [] # key output @@ -1010,10 +1025,10 @@ def dumpAcl(data): def diskDump(data, uid, dpath): - """Full Dump""" + """Full Dump.""" dump18 = f'{dpath}hf-mf-{uid.hex().upper()}-dump18.bin' - lprint(f'\nDump card data to file... ' + color(dump18, fg='yellow')) + lprint('\nDump card data to file... ' + color(dump18, fg='yellow')) bad = False try: @@ -1037,12 +1052,11 @@ def diskDump(data, uid, dpath): def dumpMad(dump18): - """Dump MAD - -globals: -- p (R) -""" + """Dump MAD. + globals: + - p (R) + """ lprint() lprint("====================================") lprint(" MiFare Application Directory (MAD)") From af3a16b25c126b1420bd5ba76a834af67685e528 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Sun, 2 Mar 2025 16:45:01 +0100 Subject: [PATCH 078/105] text and style --- client/src/cmdhfmf.c | 20 ++++++++++---------- client/src/cmdwiegand.c | 3 ++- client/src/wiegand_formats.c | 21 +++++++++++++-------- client/src/wiegand_formatutils.c | 7 +++++-- client/src/wiegand_formatutils.h | 1 + doc/commands.json | 6 +++--- 6 files changed, 34 insertions(+), 24 deletions(-) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 1b1291bb1..5becf435b 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -5538,11 +5538,11 @@ static int CmdHF14AMfCLoad(const char *Cmd) { return PM3_EFILE; } - PrintAndLogEx(SUCCESS, - "Card loaded " _YELLOW_("%d") " blocks from %s" - , block_cnt - , (fill_from_emulator ? "emulator memory" : "file") - ); + PrintAndLogEx(SUCCESS, + "Card loaded " _YELLOW_("%d") " blocks from %s" + , block_cnt + , (fill_from_emulator ? "emulator memory" : "file") + ); PrintAndLogEx(INFO, "Done!"); return PM3_SUCCESS; } @@ -8436,11 +8436,11 @@ static int CmdHF14AGen4Load(const char *cmd) { free(data); } - PrintAndLogEx(SUCCESS, - "Card loaded " _YELLOW_("%d") " blocks from %s" - , end - start + 1 - , (fill_from_emulator ? "emulator memory" : "file") - ); + PrintAndLogEx(SUCCESS, + "Card loaded " _YELLOW_("%d") " blocks from %s" + , end - start + 1 + , (fill_from_emulator ? "emulator memory" : "file") + ); PrintAndLogEx(INFO, "Done!"); return PM3_SUCCESS; } diff --git a/client/src/cmdwiegand.c b/client/src/cmdwiegand.c index 82d6ce14c..e6ae43629 100644 --- a/client/src/cmdwiegand.c +++ b/client/src/cmdwiegand.c @@ -213,7 +213,8 @@ int CmdWiegandDecode(const char *Cmd) { } PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(INFO, "Wiegand decode"); + PrintAndLogEx(INFO, "------------------------- " _CYAN_("Wiegand") " ---------------------------"); + decode_wiegand(top, mid, bot, blen); return PM3_SUCCESS; } diff --git a/client/src/wiegand_formats.c b/client/src/wiegand_formats.c index b538f6fdc..05e6f0d7c 100644 --- a/client/src/wiegand_formats.c +++ b/client/src/wiegand_formats.c @@ -1676,17 +1676,18 @@ int HIDDumpPACSBits(const uint8_t *const data, const uint8_t length, bool verbos bytes_2_binstr(binstr, data + 1, n); - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(SUCCESS, "PACS......... " _GREEN_("%s"), sprint_hex_inrow(data, length)); - PrintAndLogEx(SUCCESS, "padded bin... " _GREEN_("%s") " ( %zu )", binstr, strlen(binstr)); +// PrintAndLogEx(NORMAL, ""); + PrintAndLogEx(INFO, "------------------------- " _CYAN_("Wiegand") " ---------------------------"); + PrintAndLogEx(SUCCESS, "PACS............. " _GREEN_("%s"), sprint_hex_inrow(data, length)); + PrintAndLogEx(DEBUG, "padded bin....... " _GREEN_("%s") " ( %zu )", binstr, strlen(binstr)); binstr[strlen(binstr) - pad] = '\0'; - PrintAndLogEx(SUCCESS, "bin.......... " _GREEN_("%s") " ( %zu )", binstr, strlen(binstr)); + PrintAndLogEx(DEBUG, "bin.............. " _GREEN_("%s") " ( %zu )", binstr, strlen(binstr)); size_t hexlen = 0; uint8_t hex[16] = {0}; binstr_2_bytes(hex, &hexlen, binstr); - PrintAndLogEx(SUCCESS, "hex.......... " _GREEN_("%s"), sprint_hex_inrow(hex, hexlen)); + PrintAndLogEx(SUCCESS, "hex.............. " _GREEN_("%s"), sprint_hex_inrow(hex, hexlen)); uint32_t top = 0, mid = 0, bot = 0; if (binstring_to_u96(&top, &mid, &bot, binstr) != strlen(binstr)) { @@ -1696,14 +1697,16 @@ int HIDDumpPACSBits(const uint8_t *const data, const uint8_t length, bool verbos } PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(INFO, "Wiegand decode"); wiegand_message_t packed = initialize_message_object(top, mid, bot, strlen(binstr)); HIDTryUnpack(&packed); - PrintAndLogEx(NORMAL, ""); - if (strlen(binstr) >= 26 && verbose) { + + // SEOS + // iCLASS Legacy SE + // iCLASS Legacy SR + // iCLASS Legacy PrintAndLogEx(INFO, "Clone to " _YELLOW_("iCLASS Legacy")); PrintAndLogEx(SUCCESS, " hf iclass encode --ki 0 --bin %s", binstr); @@ -1714,6 +1717,8 @@ int HIDDumpPACSBits(const uint8_t *const data, const uint8_t length, bool verbos PrintAndLogEx(SUCCESS, " lf hid clone -w H10301 --bin %s", binstr); PrintAndLogEx(NORMAL, ""); + // MIFARE DESFire + // MIFARE Classic char mfcbin[28] = {0}; mfcbin[0] = '1'; diff --git a/client/src/wiegand_formatutils.c b/client/src/wiegand_formatutils.c index 9dfda9d0f..d6d5758e6 100644 --- a/client/src/wiegand_formatutils.c +++ b/client/src/wiegand_formatutils.c @@ -128,7 +128,7 @@ bool set_nonlinear_field(wiegand_message_t *data, uint64_t value, uint8_t numBit return result; } -static uint8_t get_length_from_header(wiegand_message_t *data) { +uint8_t get_length_from_header(wiegand_message_t *data) { /** * detect if message has "preamble" / "sentinel bit" * Right now we just calculate the highest bit set @@ -146,6 +146,7 @@ static uint8_t get_length_from_header(wiegand_message_t *data) { * 0000 0010 1xxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx 35-bit * 0000 0011 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx 36-bit * 0000 000x xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx 37-bit + * 0000 00xx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx 38-bit */ uint8_t len = 0; uint32_t hfmt = 0; // for calculating card length @@ -188,13 +189,15 @@ wiegand_message_t initialize_message_object(uint32_t top, uint32_t mid, uint32_t bool add_HID_header(wiegand_message_t *data) { // Invalid value - if (data->Length > 84 || data->Length == 0) + if (data->Length > 84 || data->Length == 0) { return false; + } if (data->Length == 48) { data->Mid |= 1U << (data->Length - 32); // Example leading 1: start bit return true; } + if (data->Length >= 64) { data->Top |= 0x09e00000; // Extended-length header data->Top |= 1U << (data->Length - 64); // leading 1: start bit diff --git a/client/src/wiegand_formatutils.h b/client/src/wiegand_formatutils.h index 01a413032..05a36329b 100644 --- a/client/src/wiegand_formatutils.h +++ b/client/src/wiegand_formatutils.h @@ -52,6 +52,7 @@ bool set_nonlinear_field(wiegand_message_t *data, uint64_t value, uint8_t numBit wiegand_message_t initialize_message_object(uint32_t top, uint32_t mid, uint32_t bot, int n); +uint8_t get_length_from_header(wiegand_message_t *data); bool add_HID_header(wiegand_message_t *data); #endif diff --git a/doc/commands.json b/doc/commands.json index 530cad30a..812792b26 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -13167,8 +13167,8 @@ "command": "wiegand decode", "description": "Decode raw hex or binary to wiegand format", "notes": [ - "wiegand decode --raw 2006f623ae", - "wiegand decode --new 04801EEF8DC0 -> 4..8 bytes, new padded format" + "wiegand decode --raw 2006F623AE", + "wiegand decode --new 06BD88EB80 -> 4..8 bytes, new padded format" ], "offline": true, "options": [ @@ -13214,6 +13214,6 @@ "metadata": { "commands_extracted": 759, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-02-23T21:55:16" + "extracted_on": "2025-03-02T15:43:45" } } From d5a1248862d9f753dcd6ecdac4d8c125cef642fc Mon Sep 17 00:00:00 2001 From: Jean-Michel Picod Date: Wed, 5 Mar 2025 17:25:45 +0100 Subject: [PATCH 079/105] Fix wiegand InnerRange56 --- client/src/wiegand_formats.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/wiegand_formats.c b/client/src/wiegand_formats.c index 05e6f0d7c..d41ceaabc 100644 --- a/client/src/wiegand_formats.c +++ b/client/src/wiegand_formats.c @@ -1367,8 +1367,8 @@ static bool Pack_IR56(int format_idx, wiegand_card_t *card, wiegand_message_t *p if (!validate_card_limit(format_idx, card)) return false; - set_linear_field(packed, card->FacilityCode, 1, 24); - set_linear_field(packed, card->CardNumber, 25, 32); + packed->Bot = card->CardNumber; + packed->Mid = card->FacilityCode; if (preamble) return add_HID_header(packed); @@ -1381,8 +1381,8 @@ static bool Unpack_IR56(wiegand_message_t *packed, wiegand_card_t *card) { if (packed->Length != 56) return false; - card->FacilityCode = get_linear_field(packed, 1, 24); - card->CardNumber = get_linear_field(packed, 25, 32); + card->FacilityCode = packed->Mid; + card->CardNumber = packed->Bot; return true; } // --------------------------------------------------------------------------------------------------- From dbbb20a5106da2c67e9bea85b3cc58fba58bb93b Mon Sep 17 00:00:00 2001 From: leecher1337 Date: Sat, 8 Mar 2025 21:51:10 +0100 Subject: [PATCH 080/105] ISO15693 code cleanup: Use ISO15693 command defines for commands, not hardcoded values. --- armsrc/iso15693.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index 390766099..72be68eb9 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -2767,10 +2767,10 @@ void LockPassSlixIso15693(uint32_t pass_id, uint32_t password) { LED_A_ON(); uint8_t cmd_inventory[] = {ISO15693_REQ_DATARATE_HIGH | ISO15693_REQ_INVENTORY | ISO15693_REQINV_SLOT1, 0x01, 0x00, 0x00, 0x00 }; - uint8_t cmd_get_rnd[] = {ISO15693_REQ_DATARATE_HIGH, 0xB2, 0x04, 0x00, 0x00 }; - uint8_t cmd_set_pass[] = {ISO15693_REQ_DATARATE_HIGH, 0xB3, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - //uint8_t cmd_write_pass[] = {ISO15693_REQ_DATARATE_HIGH | ISO15693_REQ_ADDRESS, 0xB4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; - uint8_t cmd_lock_pass[] = {ISO15693_REQ_DATARATE_HIGH | ISO15693_REQ_ADDRESS, 0xB5, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00 }; + uint8_t cmd_get_rnd[] = {ISO15693_REQ_DATARATE_HIGH, ISO15693_GET_RANDOM_NUMBER, 0x04, 0x00, 0x00 }; + uint8_t cmd_set_pass[] = {ISO15693_REQ_DATARATE_HIGH, ISO15693_SET_PASSWORD, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + //uint8_t cmd_write_pass[] = {ISO15693_REQ_DATARATE_HIGH | ISO15693_REQ_ADDRESS, ISO15693_WRITE_PASSWORD, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + uint8_t cmd_lock_pass[] = {ISO15693_REQ_DATARATE_HIGH | ISO15693_REQ_ADDRESS, ISO15693_LOCK_PASSWORD, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00 }; uint16_t crc; uint16_t recvlen = 0; uint8_t recvbuf[ISO15693_MAX_RESPONSE_LENGTH]; @@ -3054,7 +3054,7 @@ static uint32_t set_privacy_15693_Slix(uint32_t start_time, uint32_t *eof_time, } // 0x04, == NXP from manufacture id list. - uint8_t c[] = { ISO15_REQ_DATARATE_HIGH, 0xBA, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + uint8_t c[] = { ISO15_REQ_DATARATE_HIGH, ISO15693_ENABLE_PRIVACY, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; init_password_15693_Slix(&c[3], password, rnd); AddCrc15(c, 7); @@ -3088,7 +3088,7 @@ static uint32_t disable_eas_15693_Slix(uint32_t start_time, uint32_t *eof_time, } // 0x04, == NXP from manufacture id list. - uint8_t c[] = { ISO15_REQ_DATARATE_HIGH, 0xA3, 0x04, 0x00, 0x00}; + uint8_t c[] = { ISO15_REQ_DATARATE_HIGH, ISO15693_RESET_EAS, 0x04, 0x00, 0x00}; AddCrc15(c, 3); start_time = *eof_time + DELAY_ISO15693_VICC_TO_VCD_READER; @@ -3119,7 +3119,7 @@ static uint32_t enable_eas_15693_Slix(uint32_t start_time, uint32_t *eof_time, c } } // 0x04, == NXP from manufacture id list. - uint8_t c[] = { ISO15_REQ_DATARATE_HIGH, 0xA2, 0x04, 0x00, 0x00}; + uint8_t c[] = { ISO15_REQ_DATARATE_HIGH, ISO15693_SET_EAS, 0x04, 0x00, 0x00}; //init_password_15693_Slix(&c[3], password, rnd); AddCrc15(c, 3); From 0e2a02bdf0c0e281590a3191edf2b0b37be2fc50 Mon Sep 17 00:00:00 2001 From: leecher1337 Date: Sun, 9 Mar 2025 11:54:51 +0100 Subject: [PATCH 081/105] Implement new command hf 15 slixprotectpage to do ISO15693_PROTECT_PAGE on slix tags --- armsrc/appmain.c | 11 ++++ armsrc/iso15693.c | 72 +++++++++++++++++++++--- armsrc/iso15693.h | 1 + client/src/cmdhf15.c | 98 +++++++++++++++++++++++++++++++++ client/src/pm3line_vocabulary.h | 1 + include/pm3_cmd.h | 2 +- 6 files changed, 176 insertions(+), 9 deletions(-) diff --git a/armsrc/appmain.c b/armsrc/appmain.c index 9140c03c3..a32024223 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -1467,6 +1467,17 @@ static void PacketReceived(PacketCommandNG *packet) { WritePasswordSlixIso15693(payload->old_pwd, payload->new_pwd, payload->pwd_id); break; } + case CMD_HF_ISO15693_SLIX_PROTECT_PAGE: { + struct p { + uint8_t read_pwd[4]; + uint8_t write_pwd[4]; + uint8_t divide_ptr; + uint8_t prot_status; + } PACKED; + struct p *payload = (struct p *) packet->data.asBytes; + ProtectPageSlixIso15693(payload->read_pwd, payload->write_pwd, payload->divide_ptr, payload->prot_status); + break; + } case CMD_HF_ISO15693_SLIX_DISABLE_PRIVACY: { struct p { uint8_t pwd[4]; diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index 72be68eb9..c07c1bc3c 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -3020,14 +3020,7 @@ static uint32_t disable_privacy_15693_Slix(uint32_t start_time, uint32_t *eof_ti return PM3_SUCCESS; } -static uint32_t set_pass_15693_Slix(uint32_t start_time, uint32_t *eof_time, uint8_t pass_id, const uint8_t *password, const uint8_t *uid) { - - - uint8_t rnd[2]; - if (get_rnd_15693_Slix(start_time, eof_time, rnd) == false) { - return PM3_ETIMEOUT; - } - +static uint32_t set_pass_15693_SlixRnd(uint32_t start_time, uint32_t *eof_time, uint8_t pass_id, const uint8_t *password, const uint8_t *uid, uint8_t *rnd) { // 0x04, == NXP from manufacture id list. uint8_t c[] = { (ISO15_REQ_DATARATE_HIGH | ISO15_REQ_ADDRESS), ISO15693_SET_PASSWORD, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, pass_id, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; @@ -3047,6 +3040,18 @@ static uint32_t set_pass_15693_Slix(uint32_t start_time, uint32_t *eof_time, uin return PM3_SUCCESS; } +static uint32_t set_pass_15693_Slix(uint32_t start_time, uint32_t *eof_time, uint8_t pass_id, const uint8_t *password, const uint8_t *uid) { + + + uint8_t rnd[2]; + if (get_rnd_15693_Slix(start_time, eof_time, rnd) == false) { + return PM3_ETIMEOUT; + } + + return set_pass_15693_SlixRnd(start_time, eof_time, pass_id, password, uid, rnd); +} + + static uint32_t set_privacy_15693_Slix(uint32_t start_time, uint32_t *eof_time, const uint8_t *password) { uint8_t rnd[2]; if (get_rnd_15693_Slix(start_time, eof_time, rnd) == false) { @@ -3154,6 +3159,26 @@ static uint32_t write_password_15693_Slix(uint32_t start_time, uint32_t *eof_tim return PM3_SUCCESS; } +static uint32_t protect_page_15693_Slix(uint32_t start_time, uint32_t *eof_time, uint8_t divide_ptr, uint8_t prot_status, const uint8_t *uid) { + + uint8_t protect_cmd[] = { (ISO15_REQ_DATARATE_HIGH | ISO15_REQ_ADDRESS), ISO15693_PROTECT_PAGE, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, divide_ptr, prot_status, 0x00, 0x00}; + + memcpy(&protect_cmd[3], uid, 8); + + AddCrc15(protect_cmd, 13); + + start_time = *eof_time + DELAY_ISO15693_VICC_TO_VCD_READER; + uint8_t recvbuf[ISO15693_MAX_RESPONSE_LENGTH]; + uint16_t recvlen = 0; + + int res_wrp = SendDataTag(protect_cmd, sizeof(protect_cmd), false, true, recvbuf, sizeof(recvbuf), start_time, ISO15693_READER_TIMEOUT_WRITE, eof_time, &recvlen); + if (res_wrp != PM3_SUCCESS && recvlen != 3) { + return PM3_EWRONGANSWER; + } + + return PM3_SUCCESS; +} + static uint32_t pass_protect_EASAFI_15693_Slix(uint32_t start_time, uint32_t *eof_time, bool set_option_flag, const uint8_t *password) { uint8_t flags; @@ -3254,6 +3279,37 @@ void WritePasswordSlixIso15693(const uint8_t *old_password, const uint8_t *new_p } +void ProtectPageSlixIso15693(const uint8_t *read_password, const uint8_t *write_password, uint8_t divide_ptr, uint8_t prot_status) { + LED_D_ON(); + Iso15693InitReader(); + StartCountSspClk(); + uint32_t start_time = 0, eof_time = 0; + int res = PM3_SUCCESS; + + uint8_t uid[8], rnd[2]; + get_uid_slix(start_time, &eof_time, uid); + + if (get_rnd_15693_Slix(start_time, &eof_time, rnd) == false) { + reply_ng(CMD_HF_ISO15693_SLIX_PROTECT_PAGE, PM3_ETIMEOUT, NULL, 0); + switch_off(); + return; + } + + if (read_password) + res = set_pass_15693_SlixRnd(start_time, &eof_time, 0x01, read_password, uid, rnd); + + if (res == PM3_SUCCESS && write_password) + res = set_pass_15693_SlixRnd(start_time, &eof_time, 0x02, write_password, uid, rnd); + + if (res == PM3_SUCCESS) + res = protect_page_15693_Slix(start_time, &eof_time, divide_ptr, prot_status, uid); + + reply_ng(CMD_HF_ISO15693_SLIX_PROTECT_PAGE, res, NULL, 0); + + switch_off(); + +} + void DisablePrivacySlixIso15693(const uint8_t *password) { LED_D_ON(); Iso15693InitReader(); diff --git a/armsrc/iso15693.h b/armsrc/iso15693.h index 81bc2d1a0..a4f633252 100644 --- a/armsrc/iso15693.h +++ b/armsrc/iso15693.h @@ -68,4 +68,5 @@ void EnableEAS_AFISlixIso15693(const uint8_t *password, bool usepwd); void PassProtextEASSlixIso15693(const uint8_t *password); void PassProtectAFISlixIso15693(const uint8_t *password); void WriteAFIIso15693(const uint8_t *password, bool use_pwd, uint8_t *uid, bool use_uid, uint8_t afi); +void ProtectPageSlixIso15693(const uint8_t *read_password, const uint8_t *write_password, uint8_t divide_ptr, uint8_t prot_status); #endif diff --git a/client/src/cmdhf15.c b/client/src/cmdhf15.c index c50f6b5e0..5d5c9fccb 100644 --- a/client/src/cmdhf15.c +++ b/client/src/cmdhf15.c @@ -826,6 +826,8 @@ static int NxpSysInfo(uint8_t *uid) { PrintAndLogEx(INFO, ""); PrintAndLogEx(INFO, _CYAN_(" Password protection configuration")); + PrintAndLogEx(INFO, " Page prot. ptr. " _YELLOW_("%d"), d[1]); + PrintAndLogEx(INFO, " Page L read.... %s" , (d[2] & 0x01) ? _RED_("password") : _GREEN_("no password") ); @@ -3203,6 +3205,101 @@ static int CmdHF15SlixWritePassword(const char *Cmd) { return resp.status; } +static int CmdHF15SlixProtectPage(const char *Cmd) { + CLIParserContext *ctx; + CLIParserInit(&ctx, "hf 15 slixprotectpage", + "Defines protection pointer address of user mem and access cond. for pages", + "hf 15 slixprotectpage -w deadbeef -p 3 -h 3"); + + void *argtable[] = { + arg_param_begin, + arg_str0("r", "readpw", "", "read password, 4 hex bytes"), + arg_str0("w", "writepw", "", "write password, 4 hex bytes"), + arg_int0("p", "ptr", "", "protection pointer page (0-78), if 0 entire user mem"), + arg_int1("l", "lo", "", "page protection flags of lo page (0-None, 1-ReadPR, 2-WritePR)"), + arg_int1("i", "hi", "", "page protection flags of hi page (0-None, 1-ReadPR, 2-WritePR)"), + arg_param_end + }; + + CLIExecWithReturn(ctx, Cmd, argtable, false); + + struct p { + uint8_t read_pwd[4]; + uint8_t write_pwd[4]; + uint8_t divide_ptr; + uint8_t prot_status; + } PACKED payload = {0}; + int pwdlen = 0; + + CLIGetHexWithReturn(ctx, 1, payload.read_pwd, &pwdlen); + + if (pwdlen > 0 && pwdlen != 4) { + PrintAndLogEx(WARNING, "read password must be 4 hex bytes if provided"); + CLIParserFree(ctx); + return PM3_ESOFT; + } + + CLIGetHexWithReturn(ctx, 2, payload.write_pwd, &pwdlen); + + if (pwdlen > 0 && pwdlen != 4) { + PrintAndLogEx(WARNING, "write password must be 4 hex bytes if provided"); + CLIParserFree(ctx); + return PM3_ESOFT; + } + + payload.divide_ptr = (uint8_t)arg_get_int_def(ctx, 3, 0); + if (payload.divide_ptr > 78) { + PrintAndLogEx(WARNING, "protection pointer page is invalid (is %d but should be <=78).", payload.divide_ptr); + CLIParserFree(ctx); + return PM3_ESOFT; + } + + pwdlen = arg_get_int_def(ctx, 4, 0); + if (pwdlen > 3) { + PrintAndLogEx(WARNING, "page protection flags must be between 0 and 3"); + CLIParserFree(ctx); + return PM3_ESOFT; + } + payload.prot_status = (uint8_t)pwdlen; + + pwdlen = arg_get_int_def(ctx, 5, 0); + if (pwdlen > 3) { + PrintAndLogEx(WARNING, "page protection flags must be between 0 and 3"); + CLIParserFree(ctx); + return PM3_ESOFT; + } + payload.prot_status |= (uint8_t)pwdlen<<4; + + PrintAndLogEx(INFO, "Trying to set page protection pointer to " _YELLOW_("%d"), payload.divide_ptr); + PrintAndLogEx(INFO, _YELLOW_("LO") " page access %s%s", (payload.prot_status & 0x01)?_RED_("R"):_GREEN_("r"), (payload.prot_status & 0x02)?_RED_("W"):_GREEN_("w")); + PrintAndLogEx(INFO, _YELLOW_("HI") " page access %s%s", (payload.prot_status & 0x10)?_RED_("R"):_GREEN_("r"), (payload.prot_status & 0x20)?_RED_("W"):_GREEN_("w")); + + PacketResponseNG resp; + clearCommandBuffer(); + SendCommandNG(CMD_HF_ISO15693_SLIX_PROTECT_PAGE, (uint8_t *)&payload, sizeof(payload)); + if (WaitForResponseTimeout(CMD_HF_ISO15693_SLIX_PROTECT_PAGE, &resp, 2000) == false) { + PrintAndLogEx(WARNING, "timeout while waiting for reply"); + DropField(); + return PM3_ESOFT; + } + + switch (resp.status) { + case PM3_ETIMEOUT: { + PrintAndLogEx(WARNING, "no tag found"); + break; + } + case PM3_EWRONGANSWER: { + PrintAndLogEx(WARNING, "Protection flags were not accepted, locked? ( " _RED_("fail") " )"); + break; + } + case PM3_SUCCESS: { + PrintAndLogEx(SUCCESS, "Page protection written ( " _GREEN_("ok") " ) "); + break; + } + } + return resp.status; +} + static int CmdHF15AFIPassProtect(const char *Cmd) { CLIParserContext *ctx; @@ -3513,6 +3610,7 @@ static command_t CommandTable[] = { {"slixeasenable", CmdHF15SlixEASEnable, IfPm3Iso15693, "Enable EAS mode on SLIX ISO-15693 tag"}, {"slixprivacydisable", CmdHF15SlixDisable, IfPm3Iso15693, "Disable privacy mode on SLIX ISO-15693 tag"}, {"slixprivacyenable", CmdHF15SlixEnable, IfPm3Iso15693, "Enable privacy mode on SLIX ISO-15693 tag"}, + {"slixprotectpage", CmdHF15SlixProtectPage, IfPm3Iso15693, "Protect pages on SLIX ISO-15693 tag"}, {"passprotectafi", CmdHF15AFIPassProtect, IfPm3Iso15693, "Password protect AFI - Cannot be undone"}, {"passprotecteas", CmdHF15EASPassProtect, IfPm3Iso15693, "Password protect EAS - Cannot be undone"}, {"-----------", CmdHF15Help, IfPm3Iso15693, "-------------------------- " _CYAN_("afi") " ------------------------"}, diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index 048a92ec8..2447b924f 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -202,6 +202,7 @@ const static vocabulary_t vocabulary[] = { { 0, "hf 15 slixeasenable" }, { 0, "hf 15 slixprivacydisable" }, { 0, "hf 15 slixprivacyenable" }, + { 0, "hf 15 slixprotectpage" }, { 0, "hf 15 passprotectafi" }, { 0, "hf 15 passprotecteas" }, { 0, "hf 15 findafi" }, diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index a592fb5c0..b93d2b1d6 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -1,4 +1,3 @@ -//----------------------------------------------------------------------------- // Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. // // This program is free software: you can redistribute it and/or modify @@ -575,6 +574,7 @@ typedef struct { #define CMD_HF_ISO15693_SLIX_PASS_PROTECT_AFI 0x0863 #define CMD_HF_ISO15693_SLIX_PASS_PROTECT_EAS 0x0864 #define CMD_HF_ISO15693_SLIX_WRITE_PWD 0x0865 +#define CMD_HF_ISO15693_SLIX_PROTECT_PAGE 0x0868 #define CMD_HF_ISO15693_WRITE_AFI 0x0866 #define CMD_HF_TEXKOM_SIMULATE 0x0320 #define CMD_HF_ISO15693_EML_CLEAR 0x0330 From ce115598b69ad9c0219c3f21a67e8b1103ece93a Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Mon, 10 Mar 2025 16:03:45 +0100 Subject: [PATCH 082/105] missing input parameter causing client to crash --- CHANGELOG.md | 1 + client/src/cmdhfmf.c | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 311259996..1dbbbbc5f 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 `hf mf gload` - missing parameter (@iceman1001) - Changed `hf mf gload` - now handles 1k ev1 sized dumps (@iceman1001) - Changed wiegand format unpack functions to clear struct later (@iceman1001) - Changed `wiegand decode` - now accepts new padding format (@iceman1001) diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 5becf435b..3a9afca3e 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -8260,6 +8260,7 @@ static int CmdHF14AGen4Load(const char *cmd) { arg_param_begin, arg_lit0(NULL, "mini", "MIFARE Classic Mini / S20"), arg_lit0(NULL, "1k", "MIFARE Classic 1k / S50 (def)"), + arg_lit0(NULL, "1k+", "MIFARE Classic Ev1 1k / S50"), arg_lit0(NULL, "2k", "MIFARE Classic/Plus 2k"), arg_lit0(NULL, "4k", "MIFARE Classic 4k / S70"), arg_str0("p", "pwd", "", "password 4bytes"), From 970b38803b891f19051f80e3108e467e90d2b484 Mon Sep 17 00:00:00 2001 From: leecher1337 Date: Mon, 10 Mar 2025 22:50:54 +0100 Subject: [PATCH 083/105] Updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dbbbbc5f..d33c5de4c 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] +- Added `hf 15 slixprotectpage` command - Fixed `hf mf gload` - missing parameter (@iceman1001) - Changed `hf mf gload` - now handles 1k ev1 sized dumps (@iceman1001) - Changed wiegand format unpack functions to clear struct later (@iceman1001) From cfa1bb3a0f6b1ad5ef427af60298311ed069908d Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Mon, 10 Mar 2025 16:42:22 -0700 Subject: [PATCH 084/105] Correct oid_hex for hf seos adf and hf seos pacs --- client/src/cmdhfseos.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/cmdhfseos.c b/client/src/cmdhfseos.c index 27285629c..a416b24da 100644 --- a/client/src/cmdhfseos.c +++ b/client/src/cmdhfseos.c @@ -1381,7 +1381,7 @@ static int CmdHfSeosPACS(const char *Cmd) { uint8_t get_data[] = {0x5c, 0x02, 0xff, 0x00}; int oid_len = 0; - uint8_t oid_hex[256] = {0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x02, 0x01, 0x18, 0x01, 0x01, 0x02}; + uint8_t oid_hex[256] = {0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x02, 0x01, 0x18, 0x01, 0x01, 0x02, 0x02}; CLIGetHexWithReturn(ctx, 1, oid_hex, &oid_len); int key_index = arg_get_int_def(ctx, 2, 0); @@ -1390,7 +1390,7 @@ static int CmdHfSeosPACS(const char *Cmd) { // Fall back to default OID if (oid_len == 0) { - oid_len = 16; + oid_len = 17; } // convert OID hex to literal string @@ -1440,7 +1440,7 @@ static int CmdHfSeosADF(const char *Cmd) { CLIGetHexWithReturn(ctx, 1, get_data, &get_data_len); int oid_len = 0; - uint8_t oid_hex[256] = {0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x02, 0x01, 0x18, 0x01, 0x01, 0x02}; + uint8_t oid_hex[256] = {0x2B, 0x06, 0x01, 0x04, 0x01, 0x81, 0xE4, 0x38, 0x01, 0x01, 0x02, 0x01, 0x18, 0x01, 0x01, 0x02, 0x02}; CLIGetHexWithReturn(ctx, 2, oid_hex, &oid_len); int key_index = arg_get_int_def(ctx, 3, 0); @@ -1453,7 +1453,7 @@ static int CmdHfSeosADF(const char *Cmd) { // Catching when the OID value is not supplied if (oid_len == 0) { - oid_len = 16; + oid_len = 17; } // convert OID hex to literal string From fd0311b1390eb376ceee421ab0eba24b295cf58d Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Tue, 11 Mar 2025 09:21:01 +0100 Subject: [PATCH 085/105] style --- armsrc/iso15693.c | 2 +- client/src/cmdhf15.c | 6 +++--- client/src/pm3line_vocabulary.h | 1 - doc/commands.json | 5 +++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/armsrc/iso15693.c b/armsrc/iso15693.c index c07c1bc3c..df4f06230 100644 --- a/armsrc/iso15693.c +++ b/armsrc/iso15693.c @@ -3301,7 +3301,7 @@ void ProtectPageSlixIso15693(const uint8_t *read_password, const uint8_t *write_ if (res == PM3_SUCCESS && write_password) res = set_pass_15693_SlixRnd(start_time, &eof_time, 0x02, write_password, uid, rnd); - if (res == PM3_SUCCESS) + if (res == PM3_SUCCESS) res = protect_page_15693_Slix(start_time, &eof_time, divide_ptr, prot_status, uid); reply_ng(CMD_HF_ISO15693_SLIX_PROTECT_PAGE, res, NULL, 0); diff --git a/client/src/cmdhf15.c b/client/src/cmdhf15.c index 5d5c9fccb..fa5fb266a 100644 --- a/client/src/cmdhf15.c +++ b/client/src/cmdhf15.c @@ -3268,11 +3268,11 @@ static int CmdHF15SlixProtectPage(const char *Cmd) { CLIParserFree(ctx); return PM3_ESOFT; } - payload.prot_status |= (uint8_t)pwdlen<<4; + payload.prot_status |= (uint8_t)pwdlen << 4; PrintAndLogEx(INFO, "Trying to set page protection pointer to " _YELLOW_("%d"), payload.divide_ptr); - PrintAndLogEx(INFO, _YELLOW_("LO") " page access %s%s", (payload.prot_status & 0x01)?_RED_("R"):_GREEN_("r"), (payload.prot_status & 0x02)?_RED_("W"):_GREEN_("w")); - PrintAndLogEx(INFO, _YELLOW_("HI") " page access %s%s", (payload.prot_status & 0x10)?_RED_("R"):_GREEN_("r"), (payload.prot_status & 0x20)?_RED_("W"):_GREEN_("w")); + PrintAndLogEx(INFO, _YELLOW_("LO") " page access %s%s", (payload.prot_status & 0x01) ? _RED_("R") : _GREEN_("r"), (payload.prot_status & 0x02) ? _RED_("W") : _GREEN_("w")); + PrintAndLogEx(INFO, _YELLOW_("HI") " page access %s%s", (payload.prot_status & 0x10) ? _RED_("R") : _GREEN_("r"), (payload.prot_status & 0x20) ? _RED_("W") : _GREEN_("w")); PacketResponseNG resp; clearCommandBuffer(); diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index 2447b924f..048a92ec8 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -202,7 +202,6 @@ const static vocabulary_t vocabulary[] = { { 0, "hf 15 slixeasenable" }, { 0, "hf 15 slixprivacydisable" }, { 0, "hf 15 slixprivacyenable" }, - { 0, "hf 15 slixprotectpage" }, { 0, "hf 15 passprotectafi" }, { 0, "hf 15 passprotecteas" }, { 0, "hf 15 findafi" }, diff --git a/doc/commands.json b/doc/commands.json index 812792b26..4c349414b 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -5023,6 +5023,7 @@ "-h, --help This help", "--mini MIFARE Classic Mini / S20", "--1k MIFARE Classic 1k / S50 (def)", + "--1k+ MIFARE Classic Ev1 1k / S50", "--2k MIFARE Classic/Plus 2k", "--4k MIFARE Classic 4k / S70", "-p, --pwd password 4bytes", @@ -5032,7 +5033,7 @@ "--start index of block to start writing (default 0)", "--end index of block to end writing (default last block)" ], - "usage": "hf mf gload [-hv] [--mini] [--1k] [--2k] [--4k] [-p ] [-f ] [--emu] [--start ] [--end ]" + "usage": "hf mf gload [-hv] [--mini] [--1k] [--1k+] [--2k] [--4k] [-p ] [-f ] [--emu] [--start ] [--end ]" }, "hf mf gsave": { "command": "hf mf gsave", @@ -13214,6 +13215,6 @@ "metadata": { "commands_extracted": 759, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-03-02T15:43:45" + "extracted_on": "2025-03-11T08:20:44" } } From 57df87c6f3a42854641ce098c0cf4759fcf30303 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Tue, 11 Mar 2025 14:04:11 +0100 Subject: [PATCH 086/105] added identification of textcom, thanks @en4rab --- CHANGELOG.md | 1 + client/src/cmdlft55xx.c | 4 ++++ client/src/cmdlft55xx.h | 1 + 3 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d33c5de4c..c3b7a1305 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] +- Added texecom identification, thanks @en4rab ! (@iceman1001) - Added `hf 15 slixprotectpage` command - Fixed `hf mf gload` - missing parameter (@iceman1001) - Changed `hf mf gload` - now handles 1k ev1 sized dumps (@iceman1001) diff --git a/client/src/cmdlft55xx.c b/client/src/cmdlft55xx.c index 4f5f6c3ca..d30892156 100644 --- a/client/src/cmdlft55xx.c +++ b/client/src/cmdlft55xx.c @@ -1550,6 +1550,7 @@ bool testKnownConfigBlock(uint32_t block0) { case T55X7_NEXWATCH_CONFIG_BLOCK: case T55X7_JABLOTRON_CONFIG_BLOCK: case T55X7_PYRONIX_CONFIG_BLOCK: + case T55X7_TEXECOM_CONFIG_BLOCK: return true; } return false; @@ -2298,6 +2299,9 @@ static void printT5x7KnownBlock0(uint32_t b0) { case T55X7_PYRONIX_CONFIG_BLOCK: snprintf(s + strlen(s), sizeof(s) - strlen(s), "Pyronix "); break; + case T55X7_TEXECOM_CONFIG_BLOCK: + snprintf(s + strlen(s), sizeof(s) - strlen(s), "Telecom "); + break; default: break; } diff --git a/client/src/cmdlft55xx.h b/client/src/cmdlft55xx.h index c3b8c31df..9160050a5 100644 --- a/client/src/cmdlft55xx.h +++ b/client/src/cmdlft55xx.h @@ -43,6 +43,7 @@ #define T55X7_SECURAKEY_CONFIG_BLOCK 0x000C8060 // ASK, Manchester, data rate 40, 3 data blocks #define T55X7_UNK_CONFIG_BLOCK 0x000880FA // ASK, Manchester, data rate 32, 7 data blocks STT, Inverse ... #define T55X7_PYRONIX_CONFIG_BLOCK 0x00088C40 // ASK, Manchester, data rate 32, 2 data blocks +#define T55X7_TEXECOM_CONFIG_BLOCK 0x001C8020 // ASK, Manchester, data rate 128, 1 data block // FDXB requires data inversion and BiPhase 57 is simply BiPhase 50 inverted, so we can either do it using the modulation scheme or the inversion flag // we've done both below to prove that it works either way, and the modulation value for BiPhase 50 in the Atmel data sheet of binary "10001" (17) is a typo, From e76ab097624be08be83ab47178aff9daf0c5154c Mon Sep 17 00:00:00 2001 From: Iceman Date: Tue, 11 Mar 2025 14:20:48 +0100 Subject: [PATCH 087/105] Update Windows-Installation-Instructions.md Signed-off-by: Iceman --- .../Windows-Installation-Instructions.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/md/Installation_Instructions/Windows-Installation-Instructions.md b/doc/md/Installation_Instructions/Windows-Installation-Instructions.md index e56979bf5..89cf966ea 100644 --- a/doc/md/Installation_Instructions/Windows-Installation-Instructions.md +++ b/doc/md/Installation_Instructions/Windows-Installation-Instructions.md @@ -6,6 +6,7 @@ ## Table of Contents - [Windows Installation instructions](#windows-installation-instructions) - [Table of Contents](#table-of-contents) + - [Installing on WSL 2](#installing-on-wsl-2) - [Installing dev-environment with ProxSpace](#installing-dev-environment-with-proxspace) - [Video Installation guide](#video-installation-guide) - [Driver Installation ( Windows 7 )](#driver-installation--windows-7-) @@ -25,6 +26,8 @@ - [Done!](#done-1) + + There are three ways to install, build and use Proxmark3 on Windows: * Using Gator96100 **ProxSpace**, a package to assist in your Windows installation of MinGW @@ -34,6 +37,9 @@ There are three ways to install, build and use Proxmark3 on Windows: We have listed three ways to use these two setups (dev environment vs pre-compiled binaries) --- +## Installing on WSL 2 +^[Top](#top) +Installing on WSL 2 use this [installation readme](Windows-WSL2-Installation-Instructions.md). ## Installing dev-environment with ProxSpace ^[Top](#top) From 2f1b8eb8f2705a3b0d691eca759fbd93191ace7b Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Tue, 11 Mar 2025 14:23:33 +0100 Subject: [PATCH 088/105] spelling --- client/src/cmdlft55xx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/cmdlft55xx.c b/client/src/cmdlft55xx.c index d30892156..33f3cdf7c 100644 --- a/client/src/cmdlft55xx.c +++ b/client/src/cmdlft55xx.c @@ -2300,7 +2300,7 @@ static void printT5x7KnownBlock0(uint32_t b0) { snprintf(s + strlen(s), sizeof(s) - strlen(s), "Pyronix "); break; case T55X7_TEXECOM_CONFIG_BLOCK: - snprintf(s + strlen(s), sizeof(s) - strlen(s), "Telecom "); + snprintf(s + strlen(s), sizeof(s) - strlen(s), "Texecom "); break; default: break; From b66e2e03fdb490a865003c7f7337bfcb332f36c2 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Wed, 12 Mar 2025 08:48:20 +0100 Subject: [PATCH 089/105] Added make commands to regenerate commands documentation files and autocompletion data independently of make style --- CHANGELOG.md | 1 + Makefile | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3b7a1305..60ba2958d 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] +- Added `make commands` to regenerate commands documentation files and autocompletion data independently of `make style` (@doegox) - Added texecom identification, thanks @en4rab ! (@iceman1001) - Added `hf 15 slixprotectpage` command - Fixed `hf mf gload` - missing parameter (@iceman1001) diff --git a/Makefile b/Makefile index 960fc7557..8d9ba781c 100644 --- a/Makefile +++ b/Makefile @@ -204,6 +204,7 @@ help: @echo "+ fpga_compress - Make tools/fpga_compress" @echo @echo "+ style - Apply some automated source code formatting rules" + @echo "+ commands - Regenerate commands documentation files and autocompletion data @echo "+ check - Run offline tests. Set CHECKARGS to pass arguments to the test script" @echo "+ .../check - Run offline tests against specific target. See above." @echo "+ miscchecks - Detect various encoding issues in source code" @@ -303,7 +304,7 @@ endif # easy printing of MAKE VARIABLES print-%: ; @echo $* = $($*) -style: +style: commands # Make sure astyle is installed @command -v astyle >/dev/null || ( echo "Please install 'astyle' package first" ; exit 1 ) # Remove spaces & tabs at EOL, add LF at EOF if needed on *.c, *.h, *.cpp. *.lua, *.py, *.pl, Makefile, *.v, pm3 @@ -317,13 +318,14 @@ style: --keep-one-line-blocks --max-continuation-indent=60 \ --style=google --pad-oper --unpad-paren --pad-header \ --align-pointer=name {} \; + +commands: # Update commands.md [ -x client/proxmark3 ] && client/proxmark3 -m | tr -d '\r' > doc/commands.md # Make sure python3 is installed @command -v python3 >/dev/null || ( echo "Please install 'python3' package first" ; exit 1 ) # Update commands.json, patch port in case it was run under Windows [ -x client/proxmark3 ] && client/proxmark3 --fulltext | sed 's#com[0-9]#/dev/ttyACM0#'|python3 client/pyscripts/pm3_help2json.py - - | tr -d '\r' > doc/commands.json - # Update the readline autocomplete autogenerated code [ -x client/proxmark3 ] && client/proxmark3 --fulltext | python3 client/pyscripts/pm3_help2list.py - - | tr -d '\r' > client/src/pm3line_vocabulary.h From 2137284a938b872bc1cbbe305a665c678f8a4ab7 Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 12 Mar 2025 16:41:06 +0100 Subject: [PATCH 090/105] style\n Some improvements to `trace list -t seos` annotations. --- CHANGELOG.md | 1 + client/deps/cliparser/cliparser.c | 41 +++++-- client/src/cmdhf14a.c | 25 ++-- client/src/cmdhficlass.c | 15 +-- client/src/cmdhflist.c | 150 ++++++++++++++++++++---- client/src/cmdhflist.h | 3 +- client/src/cmdhflto.c | 2 +- client/src/cmdhfmf.c | 3 +- client/src/cmdlfhid.c | 3 + client/src/cmdtrace.c | 10 +- client/src/fileutils.c | 2 +- client/src/mifare/mad.c | 24 +++- client/src/pm3line_vocabulary.h | 1 + client/src/util.c | 189 +++++++++++++++++++++--------- doc/commands.json | 21 +++- doc/commands.md | 1 + include/pm3_cmd.h | 1 + 17 files changed, 366 insertions(+), 126 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60ba2958d..a50827e96 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 `trace list -t seos` - improved annotation (@iceman1001) - Added `make commands` to regenerate commands documentation files and autocompletion data independently of `make style` (@doegox) - Added texecom identification, thanks @en4rab ! (@iceman1001) - Added `hf 15 slixprotectpage` command diff --git a/client/deps/cliparser/cliparser.c b/client/deps/cliparser/cliparser.c index 0ea0bd33b..acefe7105 100644 --- a/client/deps/cliparser/cliparser.c +++ b/client/deps/cliparser/cliparser.c @@ -180,8 +180,10 @@ int CLIParserParseStringEx(CLIParserContext *ctx, const char *str, void *vargtab // parse params for (int i = 0; i < len; i++) { + switch (state) { - case PS_FIRST: // first char + case PS_FIRST: { // first char + if (!clueData || str[i] == '-') { // first char before space is '-' - next element - option OR not "clueData" for not-option fields state = PS_OPTION; @@ -193,24 +195,32 @@ int CLIParserParseStringEx(CLIParserContext *ctx, const char *str, void *vargtab } } spaceptr = NULL; - case PS_ARGUMENT: - if (state == PS_FIRST) + } + case PS_ARGUMENT: { + + if (state == PS_FIRST) { state = PS_ARGUMENT; - if (str[i] == '"') { + } + + if (str[i] == '"' || str[i] == '\'') { state = PS_QUOTE; break; } + if (isSpace(str[i])) { spaceptr = bufptr; state = PS_FIRST; } + *bufptr = str[i]; bufptr++; break; - case PS_OPTION: + } + + case PS_OPTION: { + if (isSpace(str[i])) { state = PS_FIRST; - *bufptr = 0x00; bufptr++; argv[argc++] = bufptr; @@ -220,17 +230,22 @@ int CLIParserParseStringEx(CLIParserContext *ctx, const char *str, void *vargtab *bufptr = str[i]; bufptr++; break; - case PS_QUOTE: - if (str[i] == '"') { + } + case PS_QUOTE: { + + if (str[i] == '"' || str[i] == '\'') { *bufptr++ = 0x00; state = PS_FIRST; } else { - if (isSpace(str[i]) == false) { + +// if (isSpace(str[i]) == false) { *bufptr++ = str[i]; - } +// } } break; } + } + if (bufptr > bufptrend) { PrintAndLogEx(ERR, "ERROR: Line too long\n"); fflush(stdout); @@ -296,8 +311,9 @@ int CLIParamBinToBuf(struct arg_str *argstr, uint8_t *data, int maxdatalen, int int CLIParamStrToBuf(struct arg_str *argstr, uint8_t *data, int maxdatalen, int *datalen) { *datalen = 0; - if (!argstr->count) + if (!argstr->count) { return 0; + } uint8_t tmpstr[MAX_INPUT_ARG_LENGTH + 1] = {0}; int ibuf = 0; @@ -319,8 +335,9 @@ int CLIParamStrToBuf(struct arg_str *argstr, uint8_t *data, int maxdatalen, int ibuf = MIN(ibuf, (sizeof(tmpstr) / 2)); tmpstr[ibuf] = 0; - if (ibuf == 0) + if (ibuf == 0) { return 0; + } if (ibuf > maxdatalen) { PrintAndLogEx(ERR, "Parameter error: string too long (%i chars), expected MAX %i chars\n", ibuf, maxdatalen); diff --git a/client/src/cmdhf14a.c b/client/src/cmdhf14a.c index 30b45c68c..eab7dfce8 100644 --- a/client/src/cmdhf14a.c +++ b/client/src/cmdhf14a.c @@ -1207,28 +1207,35 @@ static int CmdExchangeAPDU(bool chainingin, const uint8_t *datain, int datainlen } uint16_t cmdc = 0; - if (chainingin) + if (chainingin) { cmdc = ISO14A_SEND_CHAINING; + } // "Command APDU" length should be 5+255+1, but javacard's APDU buffer might be smaller - 133 bytes // https://stackoverflow.com/questions/32994936/safe-max-java-card-apdu-data-command-and-respond-size // here length PM3_CMD_DATA_SIZE=512 // timeout must be authomatically set by "get ATS" - if (datain) + if (datain) { SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_APDU | ISO14A_NO_DISCONNECT | cmdc, (datainlen & 0x1FF), 0, datain, datainlen & 0x1FF); - else + } else { SendCommandMIX(CMD_HF_ISO14443A_READER, ISO14A_APDU | ISO14A_NO_DISCONNECT | cmdc, 0, 0, NULL, 0); + } PacketResponseNG resp; - if (WaitForResponseTimeout(CMD_ACK, &resp, timeout)) { + if (WaitForResponseTimeout(CMD_ACK, &resp, timeout) == false) { + PrintAndLogEx(DEBUG, "ERR: APDU: Reply timeout"); + return PM3_EAPDU_FAIL; + } + const uint8_t *recv = resp.data.asBytes; int iLen = resp.oldarg[0]; uint8_t res = resp.oldarg[1]; int dlen = iLen - 2; - if (dlen < 0) + if (dlen < 0) { dlen = 0; + } *dataoutlen += dlen; if (maxdataoutlen && *dataoutlen > maxdataoutlen) { @@ -1243,7 +1250,7 @@ static int CmdExchangeAPDU(bool chainingin, const uint8_t *datain, int datainlen return PM3_SUCCESS; } - if (!iLen) { + if (iLen == 0) { PrintAndLogEx(DEBUG, "ERR: APDU: No APDU response"); return PM3_EAPDU_FAIL; } @@ -1272,10 +1279,6 @@ static int CmdExchangeAPDU(bool chainingin, const uint8_t *datain, int datainlen PrintAndLogEx(DEBUG, "ERR: APDU: ISO 14443A CRC error"); return PM3_EAPDU_FAIL; } - } else { - PrintAndLogEx(DEBUG, "ERR: APDU: Reply timeout"); - return PM3_EAPDU_FAIL; - } return PM3_SUCCESS; } @@ -2779,7 +2782,7 @@ int infoHF14A(bool verbose, bool do_nack_test, bool do_aid_search) { if ((isMagic & MAGIC_FLAG_GEN_2) == MAGIC_FLAG_GEN_2) { PrintAndLogEx(HINT, "Hint: use `" _YELLOW_("hf mf") "` commands"); } else { - PrintAndLogEx(HINT, "Hint: try " _YELLOW_("`hf mf`") " commands"); + PrintAndLogEx(HINT, "Hint: try " _YELLOW_("`hf mf info`")); } } diff --git a/client/src/cmdhficlass.c b/client/src/cmdhficlass.c index 70dc6b1ab..aa73c7e1c 100644 --- a/client/src/cmdhficlass.c +++ b/client/src/cmdhficlass.c @@ -1414,7 +1414,7 @@ static int iclass_decode_credentials_new_pacs(uint8_t *d) { uint8_t pad = d[offset]; - PrintAndLogEx(INFO, "%u , %u", offset, pad); + PrintAndLogEx(DEBUG, "%u , %u", offset, pad); char *binstr = (char *)calloc((PICOPASS_BLOCK_SIZE * 8) + 1, sizeof(uint8_t)); if (binstr == NULL) { @@ -1424,17 +1424,16 @@ static int iclass_decode_credentials_new_pacs(uint8_t *d) { uint8_t n = PICOPASS_BLOCK_SIZE - offset - 2; bytes_2_binstr(binstr, d + offset + 2, n); - PrintAndLogEx(NORMAL, ""); - PrintAndLogEx(SUCCESS, "PACS......... " _GREEN_("%s"), sprint_hex_inrow(d + offset + 2, n)); - PrintAndLogEx(SUCCESS, "padded bin... " _GREEN_("%s") " ( %zu )", binstr, strlen(binstr)); + PrintAndLogEx(DEBUG, "PACS......... " _GREEN_("%s"), sprint_hex_inrow(d + offset + 2, n)); + PrintAndLogEx(DEBUG, "padded bin... " _GREEN_("%s") " ( %zu )", binstr, strlen(binstr)); binstr[strlen(binstr) - pad] = '\0'; - PrintAndLogEx(SUCCESS, "bin.......... " _GREEN_("%s") " ( %zu )", binstr, strlen(binstr)); + PrintAndLogEx(DEBUG, "bin.......... " _GREEN_("%s") " ( %zu )", binstr, strlen(binstr)); size_t hexlen = 0; uint8_t hex[16] = {0}; binstr_2_bytes(hex, &hexlen, binstr); - PrintAndLogEx(SUCCESS, "hex.......... " _GREEN_("%s"), sprint_hex_inrow(hex, hexlen)); + PrintAndLogEx(DEBUG, "hex.......... " _GREEN_("%s"), sprint_hex_inrow(hex, hexlen)); uint32_t top = 0, mid = 0, bot = 0; if (binstring_to_u96(&top, &mid, &bot, binstr) != strlen(binstr)) { @@ -2852,8 +2851,9 @@ static int CmdHFiClass_ReadBlock(const char *Cmd) { return PM3_SUCCESS; bool use_sc = IsCardHelperPresent(verbose); - if (use_sc == false) + if (use_sc == false) { return PM3_SUCCESS; + } // crypto helper available. PrintAndLogEx(INFO, "----------------------------- " _CYAN_("Cardhelper") " -----------------------------"); @@ -3234,6 +3234,7 @@ void print_iclass_sio(uint8_t *iclass_dump, size_t dump_len, bool verbose) { size_t sio_length; detect_credential(iclass_dump, dump_len, &is_legacy, &is_se, &is_sr, &sio_start, &sio_length); + // sanity checks if (sio_start == NULL) { return; } diff --git a/client/src/cmdhflist.c b/client/src/cmdhflist.c index fdd189568..5b0bb7636 100644 --- a/client/src/cmdhflist.c +++ b/client/src/cmdhflist.c @@ -70,8 +70,14 @@ static uint8_t *gs_mfuc_key = NULL; */ uint8_t iso14443A_CRC_check(bool isResponse, uint8_t *d, uint8_t n) { - if (n < 3) return 2; - if (isResponse && (n == 5)) return 2; + if (n < 3) { + return 2; + } + + if (isResponse && (n == 5)) { + return 2; + } + if (d[1] == 0x50 && d[0] >= ISO14443A_CMD_ANTICOLL_OR_SELECT && d[0] <= ISO14443A_CMD_ANTICOLL_OR_SELECT_3) { @@ -80,6 +86,25 @@ uint8_t iso14443A_CRC_check(bool isResponse, uint8_t *d, uint8_t n) { return check_crc(CRC_14443_A, d, n); } +uint8_t seos_CRC_check(bool isResponse, uint8_t *d, uint8_t n) { + if (n < 3) { + return 2; + } + + // 5 bytes response Card busy 0xFA have crc, the rest is most likely 14a anticollision + if ((n == 5) && (d[0] != 0xFA)) { + return 2; + } + + if (d[1] == 0x50 && + d[0] >= ISO14443A_CMD_ANTICOLL_OR_SELECT && + d[0] <= ISO14443A_CMD_ANTICOLL_OR_SELECT_3) { + return 2; + } + return check_crc(CRC_14443_A, d, n); +} + + uint8_t mifare_CRC_check(bool isResponse, uint8_t *data, uint8_t len) { switch (MifareAuthState) { case masNone: @@ -423,14 +448,14 @@ int applyIso14443a(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize, bool i snprintf(exp, size, "?"); break; } - case NTAG_I2C_FASTWRITE: + case NTAG_I2C_FASTWRITE: { if (cmdsize == 69) snprintf(exp, size, "FAST WRITE (" _MAGENTA_("%d-%d") ")", cmd[1], cmd[2]); else snprintf(exp, size, "?"); break; - - default: + } + default: { if ((cmd[0] & 0xF0) == 0xD0 && (cmdsize == 4 || cmdsize == 5)) { snprintf(exp, size, "PPS - CID=%x", cmd[0] & 0x0F) ; } else if ((cmd[0] & 0xF0) == 0x60 && (cmdsize == 4)) { @@ -440,7 +465,10 @@ int applyIso14443a(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize, bool i return PM3_ESOFT; } } + } + } else { + if (gs_mfuc_state == 1) { if ((cmd[0] == 0xAF) && (cmdsize == 11)) { // register RndB @@ -450,6 +478,7 @@ int applyIso14443a(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize, bool i gs_mfuc_state = 0; } } + if (gs_mfuc_state == 3) { if ((cmd[0] == 0x00) && (cmdsize == 11)) { // register RndA' @@ -1748,49 +1777,115 @@ void annotateCryptoRF(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize) { } } -void annotateSeos(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize) { +void annotateSeos(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize, bool isResponse) { + + if (cmd[0] == 0xFA && cmdsize == 5) { + snprintf(exp, size, (isResponse) ? "BUSY" : "DONE?"); + return; + } // it's basically a ISO14443a tag, so try annotation from there if (applyIso14443a(exp, size, cmd, cmdsize, false) != PM3_SUCCESS) { int pos = 0; switch (cmd[0]) { - case 2: - case 3: - pos = 2; - break; case 0: + case 2: + case 3: { pos = 1; break; - default: + } + default: { pos = 2; break; } + } - if (memcmp(cmd + pos, "\x00\xa4\x04\x00\x0a", 5) == 0) { - snprintf(exp, size, "SELECT AID"); + if (memcmp(cmd + pos, "\x00\xA4\x04\x00", 4) == 0) { + uint8_t n = cmd[pos + 4]; + snprintf(exp, size, "SELECT AID " _WHITE_("%s"), sprint_hex_inrow(cmd + pos + 4 + 1, n)); + return; + } + + if (memcmp(cmd + pos, "\x80\xA5\x00\x00", 4) == 0) { + snprintf(exp, size, "SELECT GDF"); + return; } if (memcmp(cmd + pos, "\x80\xA5\x04\x00", 4) == 0) { - snprintf(exp, size, "SELECT ADF / OID"); + uint8_t n = cmd[pos + 4 + 2]; + snprintf(exp, size, "SELECT OID " _WHITE_("%s"), sprint_hex_inrow(cmd + pos + 4 + 2 + 1, n)); + return; } - if (memcmp(cmd + pos, "\x00\x87\x00\x01\x04\x7c\x02\x81\x00", 9) == 0) { - snprintf(exp, size, "GET CHALLENGE"); + if (memcmp(cmd + pos, "\x80\xA5\x07", 3) == 0) { + uint8_t ks = cmd[pos + 3]; + snprintf(exp, size, "SELECT GDF " _WHITE_("(") " key " _MAGENTA_("%02X") " )", ks); + return; } - if (memcmp(cmd + pos, "\x00\x87\x00\x01\x2c", 5) == 0) { - snprintf(exp, size, "MUTUAL AUTHENTICATION"); + if (memcmp(cmd + pos, "\x00\x87\x00", 3) == 0) { + uint8_t ks = cmd[pos + 3]; + if (memcmp(cmd + pos + 3 + 1, "\x04\x7c\x02\x81\x00", 5) == 0) { + snprintf(exp, size, "GET CHALLENGE " _WHITE_("(") " key " _MAGENTA_("%02X") " )", ks); + } + return; } - if (memcmp(cmd + pos, "\x0c\xcb\x3f\xff", 4) == 0) { + if (memcmp(cmd + pos, "\x00\x87\x00", 3) == 0) { + uint8_t ks = cmd[pos + 3]; + if (memcmp(cmd + pos + 3 + 1, "\x2C\x7C\x2A\x82\x28", 5) == 0) { + snprintf(exp, size, "MUTUAL AUTHENTICATION " _WHITE_("(") " key " _MAGENTA_("%02X") " )", ks); + } + return; + } + + if (memcmp(cmd + pos, "\x0C\xCB\x3F\xFF", 4) == 0) { snprintf(exp, size, "GET DATA"); + return; } - // apply ISO7816 annotations? -// if (annotateIso7816(exp, size, cmd, cmdsize) == 0) { -// } - // apply SEOS annotations? + if (memcmp(cmd + pos, "\x0C\xDB\x3F\xFF", 4) == 0) { + snprintf(exp, size, "UPDATE DATA"); + return; + } + + if (memcmp(cmd + pos, "\x0C\xED\x06\x00", 4) == 0) { + snprintf(exp, size, "DELETE DATA"); + return; + } + + if (memcmp(cmd + pos, "\x0C\x41\x0C\x03", 4) == 0) { + snprintf(exp, size, "CREATE ADF"); + return; + } + + if (isResponse) { + + if (memcmp(cmd + pos, "\xCD\x02", 2) == 0) { + + uint8_t ea = cmd[pos + 2]; + uint8_t ha = cmd[pos + 3]; + + char eas[10] = {0}; + if (ea == SEOS_ENCRYPTION_2K3DES) { + strcat(eas, "2K3DES"); + } else if (ea == SEOS_ENCRYPTION_3K3DES) { + strcat(eas, "3K3DES"); + } else if (ea == SEOS_ENCRYPTION_AES) { + strcat(eas, "AES"); + } + + char has[10] = {0}; + if (ha == SEOS_HASHING_SHA1) { + strcat(has, "SHA1"); + } else if (ha == SEOS_HASHING_SHA256) { + strcat(has, "SHA256"); + } + snprintf(exp, size, "%s / %s", eas, has); + return; + } + } } } @@ -1830,11 +1925,13 @@ void annotateLegic(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize) { uint16_t address = (cmd[2] << 7) | cmd[1] >> 1; - if (cmdBit == LEGIC_READ) + if (cmdBit == LEGIC_READ) { snprintf(exp, size, "READ Byte(%d)", address); + } - if (cmdBit == LEGIC_WRITE) + if (cmdBit == LEGIC_WRITE) { snprintf(exp, size, "WRITE Byte(%d)", address); + } break; } case 21: { @@ -1854,8 +1951,9 @@ void annotateLegic(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize) { break; } case 12: - default: + default: { break; + } } } diff --git a/client/src/cmdhflist.h b/client/src/cmdhflist.h index 92f54e4af..98bf27318 100644 --- a/client/src/cmdhflist.h +++ b/client/src/cmdhflist.h @@ -46,6 +46,7 @@ uint8_t felica_CRC_check(uint8_t *d, uint8_t n); uint8_t mifare_CRC_check(bool isResponse, uint8_t *data, uint8_t len); uint8_t iso15693_CRC_check(uint8_t *d, uint8_t n); uint8_t iclass_CRC_check(bool isResponse, uint8_t *d, uint8_t n); +uint8_t seos_CRC_check(bool isResponse, uint8_t *d, uint8_t n); int applyIso14443a(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize, bool is_response); @@ -68,7 +69,7 @@ void annotateMifare(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize, void annotateLTO(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize); void annotateCryptoRF(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize); -void annotateSeos(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize); +void annotateSeos(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize, bool isResponse); bool DecodeMifareData(uint8_t *cmd, uint8_t cmdsize, uint8_t *parity, bool isResponse, uint8_t *mfData, size_t *mfDataLen, const uint64_t *dicKeys, uint32_t dicKeysCount); bool NTParityChk(AuthData_t *ad, uint32_t ntx); diff --git a/client/src/cmdhflto.c b/client/src/cmdhflto.c index 6ffe115c0..7542114b1 100644 --- a/client/src/cmdhflto.c +++ b/client/src/cmdhflto.c @@ -682,7 +682,7 @@ static int CmdHfLTOWriteBlock(const char *Cmd) { int res = wrblLTO(blk, block_data, true); if (res == PM3_SUCCESS) - PrintAndLogEx(HINT, "Try use 'hf lto rdbl' for verification"); + PrintAndLogEx(HINT, "Try `" _YELLOW_("hf lto rdbl") "` to verify"); return res; } diff --git a/client/src/cmdhfmf.c b/client/src/cmdhfmf.c index 3a9afca3e..c2d20cb4c 100644 --- a/client/src/cmdhfmf.c +++ b/client/src/cmdhfmf.c @@ -8267,7 +8267,7 @@ static int CmdHF14AGen4Load(const char *cmd) { arg_lit0("v", "verbose", "verbose output"), arg_str0("f", "file", "", "Specify a filename for dump file"), arg_lit0(NULL, "emu", "from emulator memory"), - arg_int0(NULL, "start", "", "index of block to start writing (default 0)"), + arg_int0(NULL, "start", "", "index of block to start writing (def 0)"), arg_int0(NULL, "end", "", "index of block to end writing (default last block)"), arg_param_end }; @@ -10234,7 +10234,6 @@ static command_t CommandTable[] = { {"gdmparsecfg", CmdHF14AGen4_GDM_ParseCfg, AlwaysAvailable, "Parse config block to card"}, {"gdmsetblk", CmdHF14AGen4_GDM_SetBlk, IfPm3Iso14443a, "Write block to card"}, {"-----------", CmdHelp, IfPm3Iso14443a, "----------------------- " _CYAN_("ndef") " -----------------------"}, -// {"ice", CmdHF14AMfice, IfPm3Iso14443a, "collect MIFARE Classic nonces to file"}, {"ndefformat", CmdHFMFNDEFFormat, IfPm3Iso14443a, "Format MIFARE Classic Tag as NFC Tag"}, {"ndefread", CmdHFMFNDEFRead, IfPm3Iso14443a, "Read and print NDEF records from card"}, {"ndefwrite", CmdHFMFNDEFWrite, IfPm3Iso14443a, "Write NDEF records to card"}, diff --git a/client/src/cmdlfhid.c b/client/src/cmdlfhid.c index bb1946414..6b651dccc 100644 --- a/client/src/cmdlfhid.c +++ b/client/src/cmdlfhid.c @@ -575,11 +575,13 @@ static int CmdHIDBrute(const char *Cmd) { PrintAndLogEx(INFO, "Facility code.... %u", card_hi.FacilityCode); PrintAndLogEx(INFO, "Card number...... %" PRIu64, card_hi.CardNumber); PrintAndLogEx(INFO, "Delay............ " _YELLOW_("%d"), delay); + if (strcmp(field, "fc") == 0) { PrintAndLogEx(INFO, "Field............ " _YELLOW_("fc")); } else if (strcmp(field, "cn") == 0) { PrintAndLogEx(INFO, "Field............ " _YELLOW_("cn")); } + switch (direction) { case 0: PrintAndLogEx(INFO, "Direction........ " _YELLOW_("both")); @@ -594,6 +596,7 @@ static int CmdHIDBrute(const char *Cmd) { break; } } + PrintAndLogEx(NORMAL, ""); PrintAndLogEx(INFO, "Started bruteforcing HID Prox reader"); PrintAndLogEx(INFO, "Press " _GREEN_("pm3 button") " or " _GREEN_("") " to abort simulation"); diff --git a/client/src/cmdtrace.c b/client/src/cmdtrace.c index 113c89f7f..07ffc3377 100644 --- a/client/src/cmdtrace.c +++ b/client/src/cmdtrace.c @@ -561,9 +561,11 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr case ISO_14443A: case MFDES: case LTO: - case SEOS: crcStatus = iso14443A_CRC_check(hdr->isResponse, frame, data_len); break; + case SEOS: + crcStatus = seos_CRC_check(hdr->isResponse, frame, data_len); + break; case ISO_7816_4: crcStatus = iso14443A_CRC_check(hdr->isResponse, frame, data_len) == 1 ? 3 : 0; crcStatus = iso14443B_CRC_check(frame, data_len) == 1 ? 4 : crcStatus; @@ -803,6 +805,9 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr case ICLASS: annotateIclass(explanation, sizeof(explanation), frame, data_len, hdr->isResponse); break; + case SEOS: + annotateSeos(explanation, sizeof(explanation), frame, data_len, hdr->isResponse); + break; default: break; } @@ -839,9 +844,6 @@ static uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *tr case PROTO_CRYPTORF: annotateCryptoRF(explanation, sizeof(explanation), frame, data_len); break; - case SEOS: - annotateSeos(explanation, sizeof(explanation), frame, data_len); - break; case PROTO_FMCOS20: annotateFMCOS20(explanation, sizeof(explanation), frame, data_len); break; diff --git a/client/src/fileutils.c b/client/src/fileutils.c index 73b992fd2..2ba24d2e4 100644 --- a/client/src/fileutils.c +++ b/client/src/fileutils.c @@ -2412,7 +2412,6 @@ int loadFileDICTIONARY_safe_ex(const char *preferredName, const char *suffix, vo // larger keys than expected is skipped if (strlen(line) > keylen) { - PrintAndLogEx(INFO, "larger %zu - %s", strlen(line), line); continue; } @@ -3085,6 +3084,7 @@ int pm3_load_dump(const char *fn, void **pdump, size_t *dumplen, size_t maxdumpl PrintAndLogEx(WARNING, "fail, cannot allocate memory"); return PM3_EMALLOC; } + res = loadFileJSON(fn, *pdump, maxdumplen, dumplen, NULL); if (res == PM3_SUCCESS) { return res; diff --git a/client/src/mifare/mad.c b/client/src/mifare/mad.c index 10335994f..a3365bb04 100644 --- a/client/src/mifare/mad.c +++ b/client/src/mifare/mad.c @@ -355,8 +355,16 @@ int MAD1DecodeAndPrint(uint8_t *sector, bool swapmad, bool verbose, bool *haveMA aid ); } else { - char fmt[60]; - snprintf(fmt, sizeof(fmt), (ibs == i) ? _MAGENTA_(" %02d [%04X]%s") : " %02d [" _GREEN_("%04X") "]%s", i, aid, "%s"); + char fmt[80]; + snprintf(fmt + , sizeof(fmt) + , (ibs == i) ? + _MAGENTA_(" %02d [%04X] %s") : + " %02d [" _GREEN_("%04X") "] %s" + , i + , aid + , "%s" + ); print_aid_description(mad_known_aids, aid, fmt, verbose); prev_aid = aid; } @@ -408,8 +416,16 @@ int MAD2DecodeAndPrint(uint8_t *sector, bool swapmad, bool verbose) { aid ); } else { - char fmt[60]; - snprintf(fmt, sizeof(fmt), (ibs == i) ? _MAGENTA_(" %02d [%04X]%s") : " %02d [" _GREEN_("%04X") "]%s", i + 16, aid, "%s"); + char fmt[80]; + snprintf(fmt + , sizeof(fmt) + , (ibs == i) ? + _MAGENTA_(" %02d [%04X] %s") : + " %02d [" _GREEN_("%04X") "] %s" + , i + 16 + , aid + , "%s" + ); print_aid_description(mad_known_aids, aid, fmt, verbose); prev_aid = aid; } diff --git a/client/src/pm3line_vocabulary.h b/client/src/pm3line_vocabulary.h index 048a92ec8..2447b924f 100644 --- a/client/src/pm3line_vocabulary.h +++ b/client/src/pm3line_vocabulary.h @@ -202,6 +202,7 @@ const static vocabulary_t vocabulary[] = { { 0, "hf 15 slixeasenable" }, { 0, "hf 15 slixprivacydisable" }, { 0, "hf 15 slixprivacyenable" }, + { 0, "hf 15 slixprotectpage" }, { 0, "hf 15 passprotectafi" }, { 0, "hf 15 passprotecteas" }, { 0, "hf 15 findafi" }, diff --git a/client/src/util.c b/client/src/util.c index 29ed40eb2..5db38bf86 100644 --- a/client/src/util.c +++ b/client/src/util.c @@ -152,10 +152,10 @@ void FillFileNameByUID(char *filenamePrefix, const uint8_t *uid, const char *ext int len = strlen(filenamePrefix); - for (int j = 0; j < uidlen; j++) { + for (int i = 0; i < uidlen; i++) { // This is technically not the safest option, but there is no way to make this work without changing the function signature // Possibly todo for future PR, but given UID lenghts are defined by program and not variable, should not be an issue - snprintf(filenamePrefix + len + j * 2, 3, "%02X", uid[j]); + snprintf(filenamePrefix + len + i * 2, 3, "%02X", uid[i]); } strcat(filenamePrefix, ext); @@ -196,7 +196,7 @@ bool CheckStringIsHEXValue(const char *value) { } for (size_t i = 0; i < strlen(value); i++) { - if (!isxdigit(value[i])) { + if (isxdigit(value[i]) == 0) { return false; } } @@ -220,11 +220,13 @@ void ascii_to_buffer(uint8_t *buf, const uint8_t *hex_data, const size_t hex_len } size_t m = (min_str_len > i) ? min_str_len : 0; - if (m > hex_max_len) + if (m > hex_max_len) { m = hex_max_len; + } - for (; i < m; i++, tmp++) + for (; i < m; i++, tmp++) { *tmp = ' '; + } // remove last space *tmp = '\0'; @@ -234,8 +236,9 @@ void hex_to_buffer(uint8_t *buf, const uint8_t *hex_data, const size_t hex_len, const size_t min_str_len, const size_t spaces_between, bool uppercase) { // sanity check - if (buf == NULL || hex_len < 1) + if (buf == NULL || hex_len < 1) { return; + } // 1. hex string length. // 2. byte array to be converted to string @@ -252,18 +255,22 @@ void hex_to_buffer(uint8_t *buf, const uint8_t *hex_data, const size_t hex_len, *(tmp++) = b2s((hex_data[i] >> 4), uppercase); *(tmp++) = b2s(hex_data[i], uppercase); - for (size_t j = 0; j < spaces_between; j++) + for (size_t j = 0; j < spaces_between; j++) { *(tmp++) = ' '; + } } i *= (2 + spaces_between); size_t m = (min_str_len > i) ? min_str_len : 0; - if (m > hex_max_len) - m = hex_max_len; - while (m--) + if (m > hex_max_len) { + m = hex_max_len; + } + + while (m--) { *(tmp++) = ' '; + } // remove last space *tmp = '\0'; @@ -274,9 +281,9 @@ void hex_to_buffer(uint8_t *buf, const uint8_t *hex_data, const size_t hex_len, void print_hex(const uint8_t *data, const size_t len) { if (data == NULL || len == 0) return; - for (size_t i = 0; i < len; i++) + for (size_t i = 0; i < len; i++) { PrintAndLogEx(NORMAL, "%02x " NOLF, data[i]); - + } PrintAndLogEx(NORMAL, ""); } @@ -621,10 +628,13 @@ char *sprint_breakdown_bin(color_t color, const char *bs, int width, int padn, i } int hex_to_bytes(const char *hexValue, uint8_t *bytesValue, size_t maxBytesValueLen) { + char buf[4] = {0}; int indx = 0; int bytesValueLen = 0; + while (hexValue[indx]) { + if (hexValue[indx] == '\t' || hexValue[indx] == ' ') { indx++; continue; @@ -684,6 +694,7 @@ void bytes_to_bytebits(const void *src, const size_t srclen, void *dest) { uint32_t i = srclen * 8; size_t j = srclen; + while (j--) { uint8_t b = s[j]; d[--i] = (b >> 0) & 1; @@ -740,20 +751,33 @@ int param_getptr(const char *line, int *bg, int *en, int paramnum) { *en = 0; // skip spaces - while (line[*bg] == ' ' || line[*bg] == '\t')(*bg)++; + while (line[*bg] == ' ' || line[*bg] == '\t') { + (*bg)++; + } + if (*bg >= len) { return 1; } for (i = 0; i < paramnum; i++) { - while (line[*bg] != ' ' && line[*bg] != '\t' && line[*bg] != '\0')(*bg)++; - while (line[*bg] == ' ' || line[*bg] == '\t')(*bg)++; - if (line[*bg] == '\0') return 1; + while (line[*bg] != ' ' && line[*bg] != '\t' && line[*bg] != '\0') { + (*bg)++; + } + + while (line[*bg] == ' ' || line[*bg] == '\t') { + (*bg)++; + } + + if (line[*bg] == '\0') { + return 1; + } } *en = *bg; - while (line[*en] != ' ' && line[*en] != '\t' && line[*en] != '\0')(*en)++; + while (line[*en] != ' ' && line[*en] != '\t' && line[*en] != '\0') { + (*en)++; + } (*en)--; @@ -763,7 +787,9 @@ int param_getptr(const char *line, int *bg, int *en, int paramnum) { int param_getlength(const char *line, int paramnum) { int bg, en; - if (param_getptr(line, &bg, &en, paramnum)) return 0; + if (param_getptr(line, &bg, &en, paramnum)) { + return 0; + } return en - bg + 1; } @@ -775,10 +801,13 @@ char param_getchar(const char *line, int paramnum) { char param_getchar_indx(const char *line, int indx, int paramnum) { int bg, en; - if (param_getptr(line, &bg, &en, paramnum)) return 0x00; + if (param_getptr(line, &bg, &en, paramnum)) { + return 0; + } - if (bg + indx > en) + if (bg + indx > en) { return '\0'; + } return line[bg + indx]; } @@ -795,7 +824,9 @@ uint8_t param_get8(const char *line, int paramnum) { */ uint8_t param_getdec(const char *line, int paramnum, uint8_t *destination) { uint8_t val = param_get8ex(line, paramnum, 255, 10); - if ((int8_t) val == -1) return 1; + if ((int8_t) val == -1) { + return 1; + } (*destination) = val; return 0; } @@ -808,49 +839,56 @@ uint8_t param_getdec(const char *line, int paramnum, uint8_t *destination) { uint8_t param_isdec(const char *line, int paramnum) { int bg, en; //TODO, check more thorougly - if (!param_getptr(line, &bg, &en, paramnum)) return 1; + if (!param_getptr(line, &bg, &en, paramnum)) { + return 1; + } // return strtoul(&line[bg], NULL, 10) & 0xff; - return 0; } uint8_t param_get8ex(const char *line, int paramnum, int deflt, int base) { int bg, en; - if (!param_getptr(line, &bg, &en, paramnum)) + if (param_getptr(line, &bg, &en, paramnum) == 0) { return strtoul(&line[bg], NULL, base) & 0xff; - else + } else { return deflt; + } } uint32_t param_get32ex(const char *line, int paramnum, int deflt, int base) { int bg, en; - if (!param_getptr(line, &bg, &en, paramnum)) + if (param_getptr(line, &bg, &en, paramnum) == 0) { return strtoul(&line[bg], NULL, base); - else + } else { return deflt; + } } uint64_t param_get64ex(const char *line, int paramnum, int deflt, int base) { int bg, en; - if (!param_getptr(line, &bg, &en, paramnum)) + if (param_getptr(line, &bg, &en, paramnum) == 0) { return strtoull(&line[bg], NULL, base); - else + } else { return deflt; + } } float param_getfloat(const char *line, int paramnum, float deflt) { int bg, en; - if (!param_getptr(line, &bg, &en, paramnum)) + if (param_getptr(line, &bg, &en, paramnum) == 0) { return strtof(&line[bg], NULL); - else + } else { return deflt; + } } int param_gethex_ex(const char *line, int paramnum, uint8_t *data, int *hexcnt) { int bg, en, i; uint32_t temp; - if (param_getptr(line, &bg, &en, paramnum)) return 1; + if (param_getptr(line, &bg, &en, paramnum)) { + return 1; + } *hexcnt = en - bg + 1; @@ -860,7 +898,9 @@ int param_gethex_ex(const char *line, int paramnum, uint8_t *data, int *hexcnt) } for (i = 0; i < *hexcnt; i += 2) { - if (!(isxdigit(line[bg + i]) && isxdigit(line[bg + i + 1]))) return 1; + if (!(isxdigit(line[bg + i]) && isxdigit(line[bg + i + 1]))) { + return 1; + } sscanf((char[]) {line[bg + i], line[bg + i + 1], 0}, "%X", &temp); data[i / 2] = temp & 0xff; @@ -873,14 +913,16 @@ int param_gethex_to_eol(const char *line, int paramnum, uint8_t *data, int maxda int bg, en; - if (param_getptr(line, &bg, &en, paramnum)) + if (param_getptr(line, &bg, &en, paramnum)) { return 1; + } *datalen = 0; char buf[5] = {0}; int indx = bg; while (line[indx]) { + if (line[indx] == '\t' || line[indx] == ' ') { indx++; continue; @@ -910,9 +952,10 @@ int param_gethex_to_eol(const char *line, int paramnum, uint8_t *data, int maxda indx++; } - if (strlen(buf) > 0) + if (strlen(buf) > 0) { //error when not completed hex bytes return 3; + } return 0; } @@ -927,6 +970,7 @@ int param_getbin_to_eol(const char *line, int paramnum, uint8_t *data, int maxda char buf[5] = {0}; int indx = bg; while (line[indx]) { + if (line[indx] == '\t' || line[indx] == ' ') { indx++; continue; @@ -992,11 +1036,14 @@ int hextobinarray_n(char *target, char *source, int sourcelen) { char *start = source; // process 4 bits (1 hex digit) at a time while (sourcelen--) { + char x = *(source++); + // capitalize if (x >= 'a' && x <= 'f') { x -= 32; } + // convert to numeric value if (x >= '0' && x <= '9') { x -= '0'; @@ -1006,6 +1053,7 @@ int hextobinarray_n(char *target, char *source, int sourcelen) { PrintAndLogEx(INFO, "(hextobinarray) discovered unknown character %c %d at idx %d of %s", x, x, (int16_t)(source - start), start); return 0; } + // output for (i = 0 ; i < 4 ; ++i, ++count) { *(target++) = (x >> (3 - i)) & 1; @@ -1053,15 +1101,20 @@ int binarray_2_hex(char *target, const size_t targetlen, const char *source, siz uint32_t t = 0; // written target chars uint32_t r = 0; // consumed bits uint8_t w = 0; // wrong bits separator printed + for (size_t s = 0 ; s < srclen; s++) { + if ((source[s] == 0) || (source[s] == 1)) { w = 0; x += (source[s] << (3 - i)); i++; + if (i == 4) { + if (t >= targetlen - 2) { return r; } + snprintf(target + t, targetlen - t, "%X", x); t++; r += 4; @@ -1069,10 +1122,13 @@ int binarray_2_hex(char *target, const size_t targetlen, const char *source, siz i = 0; } } else { + if (i > 0) { + if (t >= targetlen - 5) { return r; } + snprintf(target + t, targetlen - t, "%X[%i]", x, i); t += 4; r += i; @@ -1080,13 +1136,17 @@ int binarray_2_hex(char *target, const size_t targetlen, const char *source, siz i = 0; w = 1; } + if (w == 0) { + if (t >= targetlen - 2) { return r; } + snprintf(target + t, targetlen - t, " "); t++; } + r++; } } @@ -1107,9 +1167,9 @@ int binstr_2_binarray(uint8_t *target, char *source, int length) { while (length--) { char x = *(source++); // convert from binary value - if (x >= '0' && x <= '1') + if (x >= '0' && x <= '1') { x -= '0'; - else { + } else { PrintAndLogEx(WARNING, "(binstring2binarray) discovered unknown character %c %d at idx %d of %s", x, x, (int16_t)(source - start), start); return 0; } @@ -1165,8 +1225,9 @@ void hex_xor_token(uint8_t *d, const uint8_t *x, int dn, int xn) { // return parity bit required to match type uint8_t GetParity(const uint8_t *bits, uint8_t type, int length) { int x; - for (x = 0 ; length > 0 ; --length) + for (x = 0 ; length > 0 ; --length) { x += bits[length - 1]; + } x %= 2; return x ^ type; } @@ -1190,24 +1251,30 @@ void wiegand_add_parity_swapped(uint8_t *target, const uint8_t *source, uint8_t // Pack a bitarray into a uint32_t. uint32_t PackBits(uint8_t start, uint8_t len, const uint8_t *bits) { - if (len > 32) return 0; + if (len > 32) { + return 0; + } int i = start; int j = len - 1; uint32_t tmp = 0; - for (; j >= 0; --j, ++i) + for (; j >= 0; --j, ++i) { tmp |= bits[i] << j; + } return tmp; } uint64_t HornerScheme(uint64_t num, uint64_t divider, uint64_t factor) { + uint64_t remaind = 0, quotient = 0, result = 0; remaind = num % divider; quotient = num / divider; - if (!(quotient == 0 && remaind == 0)) + + if (!(quotient == 0 && remaind == 0)) { result += HornerScheme(quotient, divider, factor) * factor + remaind; + } return result; } @@ -1228,25 +1295,28 @@ int detect_num_CPUs(void) { return sysinfo.dwNumberOfProcessors; #else int count = sysconf(_SC_NPROCESSORS_ONLN); - if (count <= 0) + if (count <= 0) { count = 1; + } return count; #endif } void str_lower(char *s) { - for (size_t i = 0; i < strlen(s); i++) + for (size_t i = 0; i < strlen(s); i++) { s[i] = tolower(s[i]); } +} void str_upper(char *s) { strn_upper(s, strlen(s)); } void strn_upper(char *s, size_t n) { - for (size_t i = 0; i < n; i++) + for (size_t i = 0; i < n; i++) { s[i] = toupper(s[i]); } +} // check for prefix in string bool str_startswith(const char *s, const char *pre) { return strncmp(pre, s, strlen(pre)) == 0; @@ -1265,8 +1335,9 @@ bool str_endswith(const char *s, const char *suffix) { // Replace unprintable characters with a dot in char buffer void clean_ascii(unsigned char *buf, size_t len) { for (size_t i = 0; i < len; i++) { - if (!isprint(buf[i])) + if (isprint(buf[i]) == 0) { buf[i] = '.'; + } } } @@ -1279,11 +1350,11 @@ void str_cleanrn(char *buf, size_t len) { // replace char in buffer void str_creplace(char *buf, size_t len, char from, char to) { for (size_t i = 0; i < len; i++) { - if (buf[i] == from) + if (buf[i] == from) { buf[i] = to; } } - +} char *str_dup(const char *src) { return str_ndup(src, strlen(src)); @@ -1365,8 +1436,9 @@ int binstring_to_u96(uint32_t *hi2, uint32_t *hi, uint32_t *lo, const char *str) for (;;) { int res = sscanf(&str[i], "%1u", &n); - if ((res != 1) || (n > 1)) + if ((res != 1) || (n > 1)) { break; + } *hi2 = (*hi2 << 1) | (*hi >> 31); *hi = (*hi << 1) | (*lo >> 31); @@ -1388,8 +1460,9 @@ int binarray_to_u96(uint32_t *hi2, uint32_t *hi, uint32_t *lo, const uint8_t *ar int i = 0; for (; i < arrlen; i++) { uint8_t n = arr[i]; - if (n > 1) + if (n > 1) { break; + } *hi2 = (*hi2 << 1) | (*hi >> 31); *hi = (*hi << 1) | (*lo >> 31); @@ -1445,17 +1518,20 @@ int byte_strstr(const uint8_t *src, size_t srclen, const uint8_t *pattern, size_ for (size_t i = 0; i < max; i++) { // compare only first byte - if (src[i] != pattern[0]) + if (src[i] != pattern[0]) { continue; + } // try to match rest of the pattern for (int j = plen - 1; j >= 1; j--) { - if (src[i + j] != pattern[j]) + if (src[i + j] != pattern[j]) { break; + } - if (j == 1) + if (j == 1) { return i; + } } } return -1; @@ -1467,17 +1543,20 @@ int byte_strstr(const uint8_t *src, size_t srclen, const uint8_t *pattern, size_ int byte_strrstr(const uint8_t *src, size_t srclen, const uint8_t *pattern, size_t plen) { for (int i = srclen - plen; i >= 0; i--) { // compare only first byte - if (src[i] != pattern[0]) + if (src[i] != pattern[0]) { continue; + } // try to match rest of the pattern for (int j = plen - 1; j >= 1; j--) { - if (src[i + j] != pattern[j]) + if (src[i + j] != pattern[j]) { break; + } - if (j == 1) + if (j == 1) { return i; + } } } return -1; diff --git a/doc/commands.json b/doc/commands.json index 4c349414b..8e00c0031 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -2064,6 +2064,23 @@ ], "usage": "hf 15 slixprivacyenable [-h] -p " }, + "hf 15 slixprotectpage": { + "command": "hf 15 slixprotectpage", + "description": "Defines protection pointer address of user mem and access cond. for pages", + "notes": [ + "hf 15 slixprotectpage -w deadbeef -p 3 -h 3" + ], + "offline": false, + "options": [ + "-h, --help This help", + "-r, --readpw read password, 4 hex bytes", + "-w, --writepw write password, 4 hex bytes", + "-p, --ptr protection pointer page (0-78), if 0 entire user mem", + "-l, --lo page protection flags of lo page (0-None, 1-ReadPR, 2-WritePR)", + "-i, --hi page protection flags of hi page (0-None, 1-ReadPR, 2-WritePR)" + ], + "usage": "hf 15 slixprotectpage [-h] [-r ] [-w ] [-p ] -l -i " + }, "hf 15 slixwritepwd": { "command": "hf 15 slixwritepwd", "description": "Write a password on a SLIX family ISO-15693 tag.nSome tags do not support all different password types.", @@ -13213,8 +13230,8 @@ } }, "metadata": { - "commands_extracted": 759, + "commands_extracted": 760, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-03-11T08:20:44" + "extracted_on": "2025-03-12T08:23:41" } } diff --git a/doc/commands.md b/doc/commands.md index 032f7dd6d..960e40a2b 100644 --- a/doc/commands.md +++ b/doc/commands.md @@ -261,6 +261,7 @@ Check column "offline" for their availability. |`hf 15 slixeasenable `|N |`Enable EAS mode on SLIX ISO-15693 tag` |`hf 15 slixprivacydisable`|N |`Disable privacy mode on SLIX ISO-15693 tag` |`hf 15 slixprivacyenable`|N |`Enable privacy mode on SLIX ISO-15693 tag` +|`hf 15 slixprotectpage `|N |`Protect pages on SLIX ISO-15693 tag` |`hf 15 passprotectafi `|N |`Password protect AFI - Cannot be undone` |`hf 15 passprotecteas `|N |`Password protect EAS - Cannot be undone` |`hf 15 findafi `|N |`Brute force AFI of an ISO-15693 tag` diff --git a/include/pm3_cmd.h b/include/pm3_cmd.h index b93d2b1d6..a71e09a1e 100644 --- a/include/pm3_cmd.h +++ b/include/pm3_cmd.h @@ -1,3 +1,4 @@ +//----------------------------------------------------------------------------- // Copyright (C) Proxmark3 contributors. See AUTHORS.md for details. // // This program is free software: you can redistribute it and/or modify From 06a1627a95c27e01ff5c795c4d75c501a11ad8da Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 12 Mar 2025 16:48:30 +0100 Subject: [PATCH 091/105] style --- client/deps/cliparser/cliparser.c | 4 +- client/src/cmdhf14a.c | 78 +++++++++++++++---------------- client/src/cmdhflist.c | 4 +- client/src/util.c | 6 +-- doc/commands.json | 2 +- 5 files changed, 47 insertions(+), 47 deletions(-) diff --git a/client/deps/cliparser/cliparser.c b/client/deps/cliparser/cliparser.c index acefe7105..64de03871 100644 --- a/client/deps/cliparser/cliparser.c +++ b/client/deps/cliparser/cliparser.c @@ -239,11 +239,11 @@ int CLIParserParseStringEx(CLIParserContext *ctx, const char *str, void *vargtab } else { // if (isSpace(str[i]) == false) { - *bufptr++ = str[i]; + *bufptr++ = str[i]; // } } break; - } + } } if (bufptr > bufptrend) { diff --git a/client/src/cmdhf14a.c b/client/src/cmdhf14a.c index eab7dfce8..7bd292489 100644 --- a/client/src/cmdhf14a.c +++ b/client/src/cmdhf14a.c @@ -1228,57 +1228,57 @@ static int CmdExchangeAPDU(bool chainingin, const uint8_t *datain, int datainlen return PM3_EAPDU_FAIL; } - const uint8_t *recv = resp.data.asBytes; - int iLen = resp.oldarg[0]; - uint8_t res = resp.oldarg[1]; + const uint8_t *recv = resp.data.asBytes; + int iLen = resp.oldarg[0]; + uint8_t res = resp.oldarg[1]; - int dlen = iLen - 2; + int dlen = iLen - 2; if (dlen < 0) { - dlen = 0; + dlen = 0; } - *dataoutlen += dlen; + *dataoutlen += dlen; - if (maxdataoutlen && *dataoutlen > maxdataoutlen) { - PrintAndLogEx(DEBUG, "ERR: APDU: Buffer too small(%d), needs %d bytes", *dataoutlen, maxdataoutlen); - return PM3_EAPDU_FAIL; - } + if (maxdataoutlen && *dataoutlen > maxdataoutlen) { + PrintAndLogEx(DEBUG, "ERR: APDU: Buffer too small(%d), needs %d bytes", *dataoutlen, maxdataoutlen); + return PM3_EAPDU_FAIL; + } - // I-block ACK - if ((res & 0xF2) == 0xA2) { - *dataoutlen = 0; - *chainingout = true; - return PM3_SUCCESS; - } + // I-block ACK + if ((res & 0xF2) == 0xA2) { + *dataoutlen = 0; + *chainingout = true; + return PM3_SUCCESS; + } if (iLen == 0) { - PrintAndLogEx(DEBUG, "ERR: APDU: No APDU response"); - return PM3_EAPDU_FAIL; - } + PrintAndLogEx(DEBUG, "ERR: APDU: No APDU response"); + return PM3_EAPDU_FAIL; + } - // check apdu length - if (iLen < 2 && iLen >= 0) { - PrintAndLogEx(DEBUG, "ERR: APDU: Small APDU response, len %d", iLen); - return PM3_EAPDU_FAIL; - } + // check apdu length + if (iLen < 2 && iLen >= 0) { + PrintAndLogEx(DEBUG, "ERR: APDU: Small APDU response, len %d", iLen); + return PM3_EAPDU_FAIL; + } - // check block TODO - if (iLen == -2) { - PrintAndLogEx(DEBUG, "ERR: APDU: Block type mismatch"); - return PM3_EAPDU_FAIL; - } + // check block TODO + if (iLen == -2) { + PrintAndLogEx(DEBUG, "ERR: APDU: Block type mismatch"); + return PM3_EAPDU_FAIL; + } - memcpy(dataout, recv, dlen); + memcpy(dataout, recv, dlen); - // chaining - if ((res & 0x10) != 0) { - *chainingout = true; - } + // chaining + if ((res & 0x10) != 0) { + *chainingout = true; + } - // CRC Check - if (iLen == -1) { - PrintAndLogEx(DEBUG, "ERR: APDU: ISO 14443A CRC error"); - return PM3_EAPDU_FAIL; - } + // CRC Check + if (iLen == -1) { + PrintAndLogEx(DEBUG, "ERR: APDU: ISO 14443A CRC error"); + return PM3_EAPDU_FAIL; + } return PM3_SUCCESS; } diff --git a/client/src/cmdhflist.c b/client/src/cmdhflist.c index 5b0bb7636..4e04819bf 100644 --- a/client/src/cmdhflist.c +++ b/client/src/cmdhflist.c @@ -464,7 +464,7 @@ int applyIso14443a(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize, bool i } else { return PM3_ESOFT; } - } + } } } else { @@ -1798,7 +1798,7 @@ void annotateSeos(char *exp, size_t size, uint8_t *cmd, uint8_t cmdsize, bool is default: { pos = 2; break; - } + } } if (memcmp(cmd + pos, "\x00\xA4\x04\x00", 4) == 0) { diff --git a/client/src/util.c b/client/src/util.c index 5db38bf86..f01067534 100644 --- a/client/src/util.c +++ b/client/src/util.c @@ -1305,7 +1305,7 @@ int detect_num_CPUs(void) { void str_lower(char *s) { for (size_t i = 0; i < strlen(s); i++) { s[i] = tolower(s[i]); -} + } } void str_upper(char *s) { @@ -1315,7 +1315,7 @@ void str_upper(char *s) { void strn_upper(char *s, size_t n) { for (size_t i = 0; i < n; i++) { s[i] = toupper(s[i]); -} + } } // check for prefix in string bool str_startswith(const char *s, const char *pre) { @@ -1352,9 +1352,9 @@ void str_creplace(char *buf, size_t len, char from, char to) { for (size_t i = 0; i < len; i++) { if (buf[i] == from) { buf[i] = to; + } } } -} char *str_dup(const char *src) { return str_ndup(src, strlen(src)); diff --git a/doc/commands.json b/doc/commands.json index 8e00c0031..04d547610 100644 --- a/doc/commands.json +++ b/doc/commands.json @@ -13232,6 +13232,6 @@ "metadata": { "commands_extracted": 760, "extracted_by": "PM3Help2JSON v1.00", - "extracted_on": "2025-03-12T08:23:41" + "extracted_on": "2025-03-12T15:46:33" } } From 3a8dc89dca3294b0ed9c2458c0755c5262045e49 Mon Sep 17 00:00:00 2001 From: tinooo <34310283+tinooo@users.noreply.github.com> Date: Mon, 10 Feb 2025 22:41:01 +0200 Subject: [PATCH 092/105] [PCF7930] Refactoring & bugfix in READING firs commit of a few to come. First renames of variables, added a few comments to improve clarity. Fixed types (int -> unitx_t , const, ...) - not all. still some to come Fixed 2 conditions that did not work properly. Here some explanation: Imagine dest[i-1] = 255 and dest[i] = 0. THis would mean a clear falling edge. However, this code would not work, since dest[i] > lmax is not true. This condition only works if I have at least 1 sample between lmax and 255. Same for the other way around. --- armsrc/pcf7931.c | 156 ++++++++++++++++++++++++++++++++--------------- armsrc/pcf7931.h | 7 +++ 2 files changed, 115 insertions(+), 48 deletions(-) diff --git a/armsrc/pcf7931.c b/armsrc/pcf7931.c index 2d56943af..67391d226 100644 --- a/armsrc/pcf7931.c +++ b/armsrc/pcf7931.c @@ -37,84 +37,147 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { uint8_t *dest = BigBuf_get_addr(); int g_GraphTraceLen = BigBuf_max_traceLen(); + // limit g_GraphTraceLen to a little more than 2 data frames. + // To make sure a complete dataframe is in the dataset. if (g_GraphTraceLen > 18000) { g_GraphTraceLen = 18000; } - int i = 2, j, lastval, bitidx, half_switch; - int clock = 64; - int tolerance = clock / 8; + int i = 2, j, bitPos; + uint8_t half_switch; + + uint16_t bitPosLastEdge; + uint16_t bitPosCurrentEdge; + uint8_t lastClockDuration; // used to store the duration of the last "clock", for decoding. + // clock may not be the correct term, maybe bit is better. + // The duration between two edges is meant + const uint8_t clock = 64; + const uint8_t tolerance = clock / 8; + const uint8_t _16T0 = clock/4; + const uint8_t _32T0 = clock/2; + const uint8_t _64T0 = clock; + int pmc, block_done; - int lc, warnings = 0; + int warnings = 0; size_t num_blocks = 0; - int lmin = 64, lmax = 192; - uint8_t dir; + // int lmin = 64, lmax = 192; // used for some thresholds to identify high/low + uint8_t threshold = 30; // threshold to filter out noise, from an actual slope. + EdgeType expectedNextEdge = UNDEFINED; // direction in which the next slope is expected should go. + BigBuf_Clear_keep_EM(); LFSetupFPGAForADC(LF_DIVISOR_125, true); DoAcquisition_default(0, true, ledcontrol); - /* Find first local max/min */ - if (dest[1] > dest[0]) { - while (i < g_GraphTraceLen) { - if (!(dest[i] > dest[i - 1]) && dest[i] > lmax) { - break; - } - i++; + // /* Find first local max/min */ + // if (dest[1] > dest[0]) { + // while (i < g_GraphTraceLen) { + // // Todo: dont think that this condition is correct. same issue as below. + // if (!(dest[i] > dest[i - 1]) && dest[i] > lmax) { + // break; + // } + // i++; + // } + // dir = 0; + // } else { + // while (i < g_GraphTraceLen) { + // // Todo: dont think that this condition is correct. same issue as below. + // if (!(dest[i] < dest[i - 1]) && dest[i] < lmin) { + // break; + // } + // i++; + // } + // dir = 1; + // } + + i = 1; + while (i < g_GraphTraceLen && expectedNextEdge==UNDEFINED) { + // find falling edge + if ((dest[i] + threshold) < dest[i-1]) { + expectedNextEdge = RISING; // current edge is falling, so next has to be rising + + // find rising edge + } else if ((dest[i] - threshold) > dest[i-1]){ + expectedNextEdge = FALLING; // current edge is rising, so next has to be falling } - dir = 0; - } else { - while (i < g_GraphTraceLen) { - if (!(dest[i] < dest[i - 1]) && dest[i] < lmin) { - break; - } - i++; - } - dir = 1; + + i++; } - lastval = i++; + bitPosLastEdge = i++; half_switch = 0; pmc = 0; block_done = 0; - for (bitidx = 0; i < g_GraphTraceLen; i++) { + for (bitPos = 0; i < g_GraphTraceLen; i++) { - if ((dest[i - 1] > dest[i] && dir == 1 && dest[i] > lmax) || (dest[i - 1] < dest[i] && dir == 0 && dest[i] < lmin)) { - lc = i - lastval; - lastval = i; + // Todo: This condition is not working properly. It is failing, in case the samples are falling/rising RAPIDLY. + // Imagine dest[i-1] = 255 and dest[i] = 0. THis would mean a clear falling edge. + // However, this code would not work, since dest[i] > lmax is not true. + // This condition only works if I have at least 1 sample between lmax and 255. + // Same for the other way around. + // if ((dest[i - 1] > dest[i] && dir == 1 && dest[i] > lmax) || (dest[i - 1] < dest[i] && dir == 0 && dest[i] < lmin)) { + + + if (bitPos%4 == 0){ + //Dbprintf("dest[%d]: %d",i, dest[i]); + } + + // condition is searching for the next slope, in the expected diretion. + if ( ((dest[i] + threshold) < dest[i-1] && expectedNextEdge == FALLING ) || + ((dest[i] - threshold) > dest[i-1] && expectedNextEdge == RISING )) { + + expectedNextEdge = (expectedNextEdge == FALLING) ? RISING : FALLING; //toggle the next expected edge + //okay, next falling/rising edge found + bitPosCurrentEdge = i; - // Switch depending on lc length: + lastClockDuration = bitPosCurrentEdge - bitPosLastEdge; + bitPosLastEdge = i; + + // Switch depending on lastClockDuration length: // Tolerance is 1/8 of clock rate (arbitrary) - if (ABS(lc - clock / 4) < tolerance) { - // 16T0 - if ((i - pmc) == lc) { // 16T0 was previous one + + // 16T0 + if (ABS(lastClockDuration - _16T0) < tolerance) { + if ((i - pmc) == lastClockDuration) { // 16T0 was previous one // It's a PMC + Dbprintf(_GREEN_("PMC 16T0 FOUND:") " at i: %d", i); i += (128 + 127 + 16 + 32 + 33 + 16) - 1; - lastval = i; + bitPosLastEdge = i; pmc = 0; block_done = 1; } else { pmc = i; } - } else if (ABS(lc - clock / 2) < tolerance) { - // 32TO - if ((i - pmc) == lc) { // 16T0 was previous one + + // 32TO + } else if (ABS(lastClockDuration - _32T0) < tolerance) { + if ((i - pmc) == lastClockDuration) { // 16T0 was previous one // It's a PMC ! + Dbprintf(_GREEN_("PMC 32T0 FOUND:") " at i: %d", i); i += (128 + 127 + 16 + 32 + 33) - 1; - lastval = i; + bitPosLastEdge = i; pmc = 0; block_done = 1; + + // if no pmc, then its a normal bit. Check if its the second time, the edge changed + // if yes, then the bit is 0 } else if (half_switch == 1) { - bits[bitidx++] = 0; + bits[bitPos++] = 0; + // reset the edge counter to 0 half_switch = 0; + + // so it is the first time the edge changed. No bit value will be set here, bit if the + // edge changes again, it will be. see case above. } else half_switch++; - } else if (ABS(lc - clock) < tolerance) { - // 64TO - bits[bitidx++] = 1; + + // 64T0 + } else if (ABS(lastClockDuration - _64T0) < tolerance) { + bits[bitPos++] = 1; + + // Error } else { - // Error if (++warnings > 10) { if (g_dbglevel >= DBG_EXTENDED) { @@ -126,7 +189,7 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { } if (block_done == 1) { - if (bitidx == 128) { + if (bitPos == 128) { for (j = 0; j < 16; ++j) { blocks[num_blocks][j] = 128 * bits[j * 8 + 7] + @@ -141,18 +204,15 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { } num_blocks++; } - bitidx = 0; + bitPos = 0; block_done = 0; half_switch = 0; } - if (i < g_GraphTraceLen) { - dir = (dest[i - 1] > dest[i]) ? 0 : 1; - } } - if (bitidx == 255) { - bitidx = 0; + if (bitPos == 255) { + bitPos = 0; } if (num_blocks == 4) { diff --git a/armsrc/pcf7931.h b/armsrc/pcf7931.h index 3be9ea5be..a3e3a032d 100644 --- a/armsrc/pcf7931.h +++ b/armsrc/pcf7931.h @@ -18,6 +18,13 @@ #include "common.h" + +typedef enum{ + UNDEFINED, + FALLING, + RISING +} EdgeType; + size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol); bool IsBlock0PCF7931(uint8_t *block); bool IsBlock1PCF7931(const uint8_t *block); From 9bfd55ebe0d5e8e53fce3bd2d561fa558a2047b1 Mon Sep 17 00:00:00 2001 From: tinooo <34310283+tinooo@users.noreply.github.com> Date: Wed, 12 Feb 2025 08:42:02 +0200 Subject: [PATCH 093/105] [PCF7931] draft of working & refacored DemodPCF7931() demod function now seems to work basically. Not all error cases are handled I guess. Also still debug prints, since I've to figure out the rest. Also unclear, why limit the buffer size to 1-2 blocks only? --- armsrc/pcf7931.c | 154 ++++++++++++++++++++++++----------------------- 1 file changed, 78 insertions(+), 76 deletions(-) diff --git a/armsrc/pcf7931.c b/armsrc/pcf7931.c index 67391d226..3e4444ed9 100644 --- a/armsrc/pcf7931.c +++ b/armsrc/pcf7931.c @@ -39,133 +39,118 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { int g_GraphTraceLen = BigBuf_max_traceLen(); // limit g_GraphTraceLen to a little more than 2 data frames. // To make sure a complete dataframe is in the dataset. - if (g_GraphTraceLen > 18000) { - g_GraphTraceLen = 18000; - } + // 1 Frame is 16 Byte -> 128byte. at a T0 of 64 -> 8129 Samples per frame. + // + PMC -> 384T0 --> 8576 samples required for one block + // to make sure that one complete block is definitely being sampled, we need 2 times that + // which is ~17.xxx samples. round up. and clamp to this value. + + // TODO: Doublecheck why this is being limited? + g_GraphTraceLen = (g_GraphTraceLen > 18000) ? 18000 : g_GraphTraceLen; - int i = 2, j, bitPos; + uint8_t j; uint8_t half_switch; - uint16_t bitPosLastEdge; - uint16_t bitPosCurrentEdge; - uint8_t lastClockDuration; // used to store the duration of the last "clock", for decoding. - // clock may not be the correct term, maybe bit is better. - // The duration between two edges is meant + uint8_t bitPos; // max 128 bit in one block. if more, then there is an error and PMC was not found. + + uint16_t sample; // to keep track of the current sample that is being analyzed + uint16_t samplePosLastEdge; + uint16_t samplePosCurrentEdge; + uint8_t lastClockDuration; // used to store the duration of the last "clock", for decoding. clock may not be the correct term, maybe bit is better. The duration between two edges is meant + uint8_t beforeLastClockDuration; // store the clock duration of the cycle before the last Clock duration. Basically clockduration -2 + + const uint8_t clock = 64; const uint8_t tolerance = clock / 8; const uint8_t _16T0 = clock/4; const uint8_t _32T0 = clock/2; const uint8_t _64T0 = clock; - int pmc, block_done; + int block_done; int warnings = 0; size_t num_blocks = 0; // int lmin = 64, lmax = 192; // used for some thresholds to identify high/low - uint8_t threshold = 30; // threshold to filter out noise, from an actual slope. - EdgeType expectedNextEdge = UNDEFINED; // direction in which the next slope is expected should go. + uint8_t threshold = 30; // threshold to filter out noise, from an actual edge. + EdgeType expectedNextEdge = UNDEFINED; // direction in which the next edge is expected should go. BigBuf_Clear_keep_EM(); LFSetupFPGAForADC(LF_DIVISOR_125, true); DoAcquisition_default(0, true, ledcontrol); - // /* Find first local max/min */ - // if (dest[1] > dest[0]) { - // while (i < g_GraphTraceLen) { - // // Todo: dont think that this condition is correct. same issue as below. - // if (!(dest[i] > dest[i - 1]) && dest[i] > lmax) { - // break; - // } - // i++; - // } - // dir = 0; - // } else { - // while (i < g_GraphTraceLen) { - // // Todo: dont think that this condition is correct. same issue as below. - // if (!(dest[i] < dest[i - 1]) && dest[i] < lmin) { - // break; - // } - // i++; - // } - // dir = 1; - // } - - i = 1; - while (i < g_GraphTraceLen && expectedNextEdge==UNDEFINED) { + sample = 1; + while (sample < g_GraphTraceLen && expectedNextEdge==UNDEFINED) { // find falling edge - if ((dest[i] + threshold) < dest[i-1]) { + if ((dest[sample] + threshold) < dest[sample-1]) { expectedNextEdge = RISING; // current edge is falling, so next has to be rising // find rising edge - } else if ((dest[i] - threshold) > dest[i-1]){ + } else if ((dest[sample] - threshold) > dest[sample-1]){ expectedNextEdge = FALLING; // current edge is rising, so next has to be falling } - i++; + sample++; } - bitPosLastEdge = i++; + samplePosLastEdge = sample++; half_switch = 0; - pmc = 0; block_done = 0; + bitPos = 0; + lastClockDuration=0; - for (bitPos = 0; i < g_GraphTraceLen; i++) { + // dont reset sample here. we've already found the last edge. continue from here + for ( ; sample < g_GraphTraceLen; sample++) { - // Todo: This condition is not working properly. It is failing, in case the samples are falling/rising RAPIDLY. - // Imagine dest[i-1] = 255 and dest[i] = 0. THis would mean a clear falling edge. - // However, this code would not work, since dest[i] > lmax is not true. - // This condition only works if I have at least 1 sample between lmax and 255. - // Same for the other way around. - // if ((dest[i - 1] > dest[i] && dir == 1 && dest[i] > lmax) || (dest[i - 1] < dest[i] && dir == 0 && dest[i] < lmin)) { - - - if (bitPos%4 == 0){ - //Dbprintf("dest[%d]: %d",i, dest[i]); + if (sample%4 == 0){ + // Dbprintf("dest[%d]: %d, bitPos: %d",sample, dest[sample], bitPos); } - // condition is searching for the next slope, in the expected diretion. - if ( ((dest[i] + threshold) < dest[i-1] && expectedNextEdge == FALLING ) || - ((dest[i] - threshold) > dest[i-1] && expectedNextEdge == RISING )) { - - expectedNextEdge = (expectedNextEdge == FALLING) ? RISING : FALLING; //toggle the next expected edge + // condition is searching for the next edge, in the expected diretion. + if ( ((dest[sample] + threshold) < dest[sample-1] && expectedNextEdge == FALLING ) || + ((dest[sample] - threshold) > dest[sample-1] && expectedNextEdge == RISING )) { //okay, next falling/rising edge found - bitPosCurrentEdge = i; - lastClockDuration = bitPosCurrentEdge - bitPosLastEdge; - bitPosLastEdge = i; + expectedNextEdge = (expectedNextEdge == FALLING) ? RISING : FALLING; //toggle the next expected edge + samplePosCurrentEdge = sample; + beforeLastClockDuration = lastClockDuration; // save the previous clock duration for PMC recognition + lastClockDuration = samplePosCurrentEdge - samplePosLastEdge; + samplePosLastEdge = sample; // Switch depending on lastClockDuration length: // Tolerance is 1/8 of clock rate (arbitrary) - // 16T0 if (ABS(lastClockDuration - _16T0) < tolerance) { - if ((i - pmc) == lastClockDuration) { // 16T0 was previous one + + //tollerance is missing for PMC!! TODO + // if the clock before was 16, it is indicating a PMC - check this + if (ABS(beforeLastClockDuration - _16T0) < tolerance) { // It's a PMC - Dbprintf(_GREEN_("PMC 16T0 FOUND:") " at i: %d", i); - i += (128 + 127 + 16 + 32 + 33 + 16) - 1; - bitPosLastEdge = i; - pmc = 0; + Dbprintf(_GREEN_("PMC 16T0 FOUND:") " bitPos: %d, sample: %d", bitPos, sample); + sample += (128 + 127 + 16 + 32 + 33 + 16) - 1; // move to the sample after PMC + samplePosLastEdge = sample; block_done = 1; - } else { - pmc = i; + // TODO: Not sure if sample need to set expected next edge? + } // 32TO } else if (ABS(lastClockDuration - _32T0) < tolerance) { - if ((i - pmc) == lastClockDuration) { // 16T0 was previous one + // if the clock before was 16, it is indicating a PMC - check this + if (ABS(beforeLastClockDuration - _16T0) < tolerance) { // It's a PMC ! - Dbprintf(_GREEN_("PMC 32T0 FOUND:") " at i: %d", i); - i += (128 + 127 + 16 + 32 + 33) - 1; - bitPosLastEdge = i; - pmc = 0; + Dbprintf(_GREEN_("PMC 32T0 FOUND:") " bitPos: %d, sample: %d", bitPos, sample); + sample += (128 + 127 + 16 + 32 + 33) - 1; // move to the sample after PMC + samplePosLastEdge = sample; block_done = 1; + // TODO: Not sure if sample need to set expected next edge? + // if no pmc, then its a normal bit. Check if its the second time, the edge changed // if yes, then the bit is 0 } else if (half_switch == 1) { - bits[bitPos++] = 0; + bits[bitPos] = 0; // reset the edge counter to 0 half_switch = 0; + bitPos++; // so it is the first time the edge changed. No bit value will be set here, bit if the // edge changes again, it will be. see case above. @@ -174,21 +159,28 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { // 64T0 } else if (ABS(lastClockDuration - _64T0) < tolerance) { - bits[bitPos++] = 1; + // this means, bit here is 1 + bits[bitPos] = 1; + bitPos++; // Error } else { + Dbprintf(_RED_("ELSE error case") " bitPos: %d, sample: %d", bitPos, sample); if (++warnings > 10) { if (g_dbglevel >= DBG_EXTENDED) { Dbprintf("Error: too many detection errors, aborting"); - } + } return 0; } } if (block_done == 1) { + Dbprintf(_YELLOW_("Block Done") " bitPos: %d, sample: %d", bitPos, sample); + // check if it is a complete block. If bitpos <128, it means that we did not receive + // a complete block. E.g. at the first start of a transmission. + // only save if a complete block is being received. if (bitPos == 128) { for (j = 0; j < 16; ++j) { blocks[num_blocks][j] = @@ -204,6 +196,7 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { } num_blocks++; } + // now start over for the next block / first complete block. bitPos = 0; block_done = 0; half_switch = 0; @@ -211,11 +204,16 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { } - if (bitPos == 255) { + // one block only holds 16byte (=128 bit) and then comes the PMC. so if more bit are found than 129, there must be an issue and PMC has not been identfied... + // TODO: not sure what to do in such case... + if (bitPos >= 129) { + Dbprintf(_RED_("PMC should have been found...") " bitPos: %d, sample: %d", bitPos, sample); bitPos = 0; } + // Todo: No idea, why blocks 4 is checked.. if (num_blocks == 4) { + Dbprintf(_RED_("we should never get here!!!") " at sample: %d", sample); break; } } @@ -264,6 +262,9 @@ bool IsBlock1PCF7931(const uint8_t *block) { } void ReadPCF7931(bool ledcontrol) { + + Dbprintf("ReadPCF7931()=========="); + int found_blocks = 0; // successfully read blocks int max_blocks = 8; // readable blocks uint8_t memory_blocks[8][17]; // PCF content @@ -283,6 +284,7 @@ void ReadPCF7931(bool ledcontrol) { int i = 0, j = 0; do { + Dbprintf("ReadPCF7931() -- DO LOOP =========="); i = 0; memset(tmp_blocks, 0, 4 * 16 * sizeof(uint8_t)); From 2da713eba9e5fdd7419488f6dc783181fd7b40a3 Mon Sep 17 00:00:00 2001 From: tinooo <34310283+tinooo@users.noreply.github.com> Date: Fri, 14 Feb 2025 14:40:00 +0100 Subject: [PATCH 094/105] [PCF7931] draft continue with refactoring ReadPCF7931() still not done with DemodPCF7931(). But now including changes in ReadPCF7931(). They work tightly together. Trying to resolve some issues and bugs. Basically it seems to work and my results are consistent. However, they still deviate from what I get if I do analyze the signal using lf read and data commands. still some issues somewhere. --- armsrc/pcf7931.c | 313 +++++++++++++++++++++++------------------------ armsrc/pcf7931.h | 1 - 2 files changed, 152 insertions(+), 162 deletions(-) diff --git a/armsrc/pcf7931.c b/armsrc/pcf7931.c index 3e4444ed9..9007b14ff 100644 --- a/armsrc/pcf7931.c +++ b/armsrc/pcf7931.c @@ -30,6 +30,8 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { + const uint8_t DECIMATION = 4; + // 2021 iceman, memor uint8_t bits[256] = {0x00}; uint8_t blocks[8][16]; @@ -45,114 +47,102 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { // which is ~17.xxx samples. round up. and clamp to this value. // TODO: Doublecheck why this is being limited? - g_GraphTraceLen = (g_GraphTraceLen > 18000) ? 18000 : g_GraphTraceLen; +// g_GraphTraceLen = (g_GraphTraceLen > 18000) ? 18000 : g_GraphTraceLen; + + BigBuf_Clear_keep_EM(); + LFSetupFPGAForADC(LF_DIVISOR_125, true); + // DoAcquisition_default(0, true, ledcontrol); + // sample with decimation of 2 --> This means double the values can be sampled. + // this is needed to get a complete frame in the buffer (64 * 8 * 16 * 8 + 8*PMC(~380)) = ~68.000 samples. Buffer is only 41.xxx + // with decimation 2, buffer will be twice as big. + DoAcquisition(DECIMATION, 8, 0, 0, false, 0, 0, 0, ledcontrol); + uint8_t j; uint8_t half_switch; uint8_t bitPos; // max 128 bit in one block. if more, then there is an error and PMC was not found. - uint16_t sample; // to keep track of the current sample that is being analyzed - uint16_t samplePosLastEdge; - uint16_t samplePosCurrentEdge; + uint32_t sample; // to keep track of the current sample that is being analyzed + uint32_t samplePosLastEdge; + uint32_t samplePosCurrentEdge; uint8_t lastClockDuration; // used to store the duration of the last "clock", for decoding. clock may not be the correct term, maybe bit is better. The duration between two edges is meant uint8_t beforeLastClockDuration; // store the clock duration of the cycle before the last Clock duration. Basically clockduration -2 - - const uint8_t clock = 64; + + const uint8_t clock = 64/DECIMATION; // this actually is 64, but since samples are decimated by 2, clock is also /2 const uint8_t tolerance = clock / 8; const uint8_t _16T0 = clock/4; const uint8_t _32T0 = clock/2; const uint8_t _64T0 = clock; - int block_done; - int warnings = 0; + const uint16_t pmc16T0Len = (128 + 127 + 16 + 32 + 33 + 16) * clock/64; // calculating the two possible pmc lengths, based on the clock. -4 at the end is to make sure not to increment too far + const uint16_t pmc32T0Len = (128 + 127 + 16 + 32 + 33 ) * clock/64; + + uint8_t block_done; size_t num_blocks = 0; - // int lmin = 64, lmax = 192; // used for some thresholds to identify high/low - uint8_t threshold = 30; // threshold to filter out noise, from an actual edge. - EdgeType expectedNextEdge = UNDEFINED; // direction in which the next edge is expected should go. + uint8_t threshold = 50; // threshold to filter out noise, from an actual edge. + EdgeType expectedNextEdge = FALLING; // direction in which the next edge is expected should go. - - BigBuf_Clear_keep_EM(); - LFSetupFPGAForADC(LF_DIVISOR_125, true); - DoAcquisition_default(0, true, ledcontrol); - - sample = 1; - while (sample < g_GraphTraceLen && expectedNextEdge==UNDEFINED) { - // find falling edge - if ((dest[sample] + threshold) < dest[sample-1]) { - expectedNextEdge = RISING; // current edge is falling, so next has to be rising - - // find rising edge - } else if ((dest[sample] - threshold) > dest[sample-1]){ - expectedNextEdge = FALLING; // current edge is rising, so next has to be falling - } - - sample++; - } - - samplePosLastEdge = sample++; half_switch = 0; + samplePosLastEdge = 0; block_done = 0; bitPos = 0; lastClockDuration=0; - // dont reset sample here. we've already found the last edge. continue from here - for ( ; sample < g_GraphTraceLen; sample++) { - - if (sample%4 == 0){ - // Dbprintf("dest[%d]: %d, bitPos: %d",sample, dest[sample], bitPos); - } - + for (sample = 1 ; sample < g_GraphTraceLen; sample++) { // condition is searching for the next edge, in the expected diretion. if ( ((dest[sample] + threshold) < dest[sample-1] && expectedNextEdge == FALLING ) || ((dest[sample] - threshold) > dest[sample-1] && expectedNextEdge == RISING )) { //okay, next falling/rising edge found - + + + expectedNextEdge = (expectedNextEdge == FALLING) ? RISING : FALLING; //toggle the next expected edge samplePosCurrentEdge = sample; beforeLastClockDuration = lastClockDuration; // save the previous clock duration for PMC recognition lastClockDuration = samplePosCurrentEdge - samplePosLastEdge; samplePosLastEdge = sample; + // Dbprintf("%d, %d, edge found, len: %d, nextEdge: %d", sample, dest[sample], lastClockDuration*DECIMATION, expectedNextEdge); + // Switch depending on lastClockDuration length: - // Tolerance is 1/8 of clock rate (arbitrary) // 16T0 if (ABS(lastClockDuration - _16T0) < tolerance) { - //tollerance is missing for PMC!! TODO - // if the clock before was 16, it is indicating a PMC - check this + // if the clock before also was 16T0, it is a PMC! if (ABS(beforeLastClockDuration - _16T0) < tolerance) { // It's a PMC Dbprintf(_GREEN_("PMC 16T0 FOUND:") " bitPos: %d, sample: %d", bitPos, sample); - sample += (128 + 127 + 16 + 32 + 33 + 16) - 1; // move to the sample after PMC + sample += pmc16T0Len; // move to the sample after PMC + + expectedNextEdge = FALLING; samplePosLastEdge = sample; block_done = 1; - // TODO: Not sure if sample need to set expected next edge? - } // 32TO } else if (ABS(lastClockDuration - _32T0) < tolerance) { - // if the clock before was 16, it is indicating a PMC - check this + // if the clock before also was 16T0, it is a PMC! if (ABS(beforeLastClockDuration - _16T0) < tolerance) { // It's a PMC ! Dbprintf(_GREEN_("PMC 32T0 FOUND:") " bitPos: %d, sample: %d", bitPos, sample); - sample += (128 + 127 + 16 + 32 + 33) - 1; // move to the sample after PMC + + sample += pmc32T0Len; // move to the sample after PMC + + expectedNextEdge = FALLING; samplePosLastEdge = sample; block_done = 1; - // TODO: Not sure if sample need to set expected next edge? - - // if no pmc, then its a normal bit. Check if its the second time, the edge changed - // if yes, then the bit is 0 + // if no pmc, then its a normal bit. + // Check if its the second time, the edge changed if yes, then the bit is 0 } else if (half_switch == 1) { bits[bitPos] = 0; // reset the edge counter to 0 half_switch = 0; bitPos++; - // so it is the first time the edge changed. No bit value will be set here, bit if the + // if it is the first time the edge changed. No bit value will be set here, bit if the // edge changes again, it will be. see case above. } else half_switch++; @@ -165,19 +155,17 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { // Error } else { - Dbprintf(_RED_("ELSE error case") " bitPos: %d, sample: %d", bitPos, sample); - if (++warnings > 10) { + // some Error. maybe check tolerances. + // likeley to happen in the first block. + Dbprintf(_RED_("ERROR in demodulation") " Length last clock: %d - check threshold/tolerance/signal. Toss block", lastClockDuration*DECIMATION); - if (g_dbglevel >= DBG_EXTENDED) { - Dbprintf("Error: too many detection errors, aborting"); - } - - return 0; - } + // Toss this block. + block_done = 1; } if (block_done == 1) { - Dbprintf(_YELLOW_("Block Done") " bitPos: %d, sample: %d", bitPos, sample); + // Dbprintf(_YELLOW_("Block Done") " bitPos: %d, sample: %d", bitPos, sample); + // check if it is a complete block. If bitpos <128, it means that we did not receive // a complete block. E.g. at the first start of a transmission. // only save if a complete block is being received. @@ -202,6 +190,8 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { half_switch = 0; } + }else { + // Dbprintf("%d, %d", sample, dest[sample]); } // one block only holds 16byte (=128 bit) and then comes the PMC. so if more bit are found than 129, there must be an issue and PMC has not been identfied... @@ -211,12 +201,8 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { bitPos = 0; } - // Todo: No idea, why blocks 4 is checked.. - if (num_blocks == 4) { - Dbprintf(_RED_("we should never get here!!!") " at sample: %d", sample); - break; - } - } + } + memcpy(outBlocks, blocks, 16 * num_blocks); return num_blocks; } @@ -265,23 +251,29 @@ void ReadPCF7931(bool ledcontrol) { Dbprintf("ReadPCF7931()=========="); + uint8_t maxBlocks = 8; // readable blocks + int found_blocks = 0; // successfully read blocks - int max_blocks = 8; // readable blocks - uint8_t memory_blocks[8][17]; // PCF content - uint8_t single_blocks[8][17]; // PFC blocks with unknown position + + // TODO: Why 17 byte len? 16 should be good. + uint8_t memory_blocks[maxBlocks][17]; // PCF content + uint8_t single_blocks[maxBlocks][17]; // PFC blocks with unknown position + uint8_t tmp_blocks[4][16]; // temporary read buffer + int single_blocks_cnt = 0; size_t n; // transmitted blocks - uint8_t tmp_blocks[4][16]; // temporary read buffer - - uint8_t found_0_1 = 0; // flag: blocks 0 and 1 were found + + //uint8_t found_0_1 = 0; // flag: blocks 0 and 1 were found int errors = 0; // error counter int tries = 0; // tries counter + // reuse lenghts and consts to properly clear memset(memory_blocks, 0, 8 * 17 * sizeof(uint8_t)); memset(single_blocks, 0, 8 * 17 * sizeof(uint8_t)); - int i = 0, j = 0; + int i = 0; + //j = 0; do { Dbprintf("ReadPCF7931() -- DO LOOP =========="); @@ -294,15 +286,13 @@ void ReadPCF7931(bool ledcontrol) { // exit if no block is received if (errors >= 10 && found_blocks == 0 && single_blocks_cnt == 0) { - - if (g_dbglevel >= DBG_INFO) - Dbprintf("[!!] Error, no tag or bad tag"); - + Dbprintf("[!!] Error, no tag or bad tag"); return; } - // exit if too many errors during reading - if (tries > 50 && (2 * errors > tries)) { + // exit if too many tries without finding the first block + if (tries > 10) { + Dbprintf("End after 10 tries"); if (g_dbglevel >= DBG_INFO) { Dbprintf("[!!] Error reading the tag, only partial content"); } @@ -310,93 +300,94 @@ void ReadPCF7931(bool ledcontrol) { goto end; } - // our logic breaks if we don't get at least two blocks - if (n < 2) { - // skip if all 0s block or no blocks - if (n == 0 || !memcmp(tmp_blocks[0], "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16)) - continue; + // // our logic breaks if we don't get at least two blocks + // if (n < 2) { + // // skip if all 0s block or no blocks + // if (n == 0 || !memcmp(tmp_blocks[0], "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16)) + // continue; - // add block to single blocks list - if (single_blocks_cnt < max_blocks) { - for (i = 0; i < single_blocks_cnt; ++i) { - if (!memcmp(single_blocks[i], tmp_blocks[0], 16)) { - j = 1; - break; - } - } - if (j != 1) { - memcpy(single_blocks[single_blocks_cnt], tmp_blocks[0], 16); - print_result("got single block", single_blocks[single_blocks_cnt], 16); - single_blocks_cnt++; - } - j = 0; - } - ++tries; - continue; - } + // // add block to single blocks list + // if (single_blocks_cnt < maxBlocks) { + // for (i = 0; i < single_blocks_cnt; ++i) { + // if (!memcmp(single_blocks[i], tmp_blocks[0], 16)) { + // j = 1; + // break; + // } + // } + // if (j != 1) { + // memcpy(single_blocks[single_blocks_cnt], tmp_blocks[0], 16); + // print_result("got single block", single_blocks[single_blocks_cnt], 16); + // single_blocks_cnt++; + // } + // j = 0; + // } + // ++tries; + // continue; + // } - if (g_dbglevel >= DBG_EXTENDED) - Dbprintf("(dbg) got %d blocks (%d/%d found) (%d tries, %d errors)", n, found_blocks, (max_blocks == 0 ? found_blocks : max_blocks), tries, errors); + // Dbprintf("(dbg) got %d blocks (%d/%d found) (%d tries, %d errors)", n, found_blocks, (maxBlocks == 0 ? found_blocks : maxBlocks), tries, errors); + // if (g_dbglevel >= DBG_EXTENDED) + // Dbprintf("(dbg) got %d blocks (%d/%d found) (%d tries, %d errors)", n, found_blocks, (maxBlocks == 0 ? found_blocks : maxBlocks), tries, errors); for (i = 0; i < n; ++i) { print_result("got consecutive blocks", tmp_blocks[i], 16); } - i = 0; - if (!found_0_1) { - while (i < n - 1) { - if (IsBlock0PCF7931(tmp_blocks[i]) && IsBlock1PCF7931(tmp_blocks[i + 1])) { - found_0_1 = 1; - memcpy(memory_blocks[0], tmp_blocks[i], 16); - memcpy(memory_blocks[1], tmp_blocks[i + 1], 16); - memory_blocks[0][ALLOC] = memory_blocks[1][ALLOC] = 1; - // block 1 tells how many blocks are going to be sent - max_blocks = MAX((memory_blocks[1][14] & 0x7f), memory_blocks[1][15]) + 1; - found_blocks = 2; + // i = 0; + // if (!found_0_1) { + // while (i < n - 1) { + // if (IsBlock0PCF7931(tmp_blocks[i]) && IsBlock1PCF7931(tmp_blocks[i + 1])) { + // found_0_1 = 1; + // memcpy(memory_blocks[0], tmp_blocks[i], 16); + // memcpy(memory_blocks[1], tmp_blocks[i + 1], 16); + // memory_blocks[0][ALLOC] = memory_blocks[1][ALLOC] = 1; + // // block 1 tells how many blocks are going to be sent + // maxBlocks = MAX((memory_blocks[1][14] & 0x7f), memory_blocks[1][15]) + 1; + // found_blocks = 2; - Dbprintf("Found blocks 0 and 1. PCF is transmitting %d blocks.", max_blocks); + // Dbprintf("Found blocks 0 and 1. PCF is transmitting %d blocks.", maxBlocks); - // handle the following blocks - for (j = i + 2; j < n; ++j) { - memcpy(memory_blocks[found_blocks], tmp_blocks[j], 16); - memory_blocks[found_blocks][ALLOC] = 1; - ++found_blocks; - } - break; - } - ++i; - } - } else { - // Trying to re-order blocks - // Look for identical block in memory blocks - while (i < n - 1) { - // skip all zeroes blocks - if (memcmp(tmp_blocks[i], "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16)) { - for (j = 1; j < max_blocks - 1; ++j) { - if (!memcmp(tmp_blocks[i], memory_blocks[j], 16) && !memory_blocks[j + 1][ALLOC]) { - memcpy(memory_blocks[j + 1], tmp_blocks[i + 1], 16); - memory_blocks[j + 1][ALLOC] = 1; - if (++found_blocks >= max_blocks) goto end; - } - } - } - if (memcmp(tmp_blocks[i + 1], "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16)) { - for (j = 0; j < max_blocks; ++j) { - if (!memcmp(tmp_blocks[i + 1], memory_blocks[j], 16) && !memory_blocks[(j == 0 ? max_blocks : j) - 1][ALLOC]) { - if (j == 0) { - memcpy(memory_blocks[max_blocks - 1], tmp_blocks[i], 16); - memory_blocks[max_blocks - 1][ALLOC] = 1; - } else { - memcpy(memory_blocks[j - 1], tmp_blocks[i], 16); - memory_blocks[j - 1][ALLOC] = 1; - } - if (++found_blocks >= max_blocks) goto end; - } - } - } - ++i; - } - } + // // handle the following blocks + // for (j = i + 2; j < n; ++j) { + // memcpy(memory_blocks[found_blocks], tmp_blocks[j], 16); + // memory_blocks[found_blocks][ALLOC] = 1; + // ++found_blocks; + // } + // break; + // } + // ++i; + // } + // } else { + // // Trying to re-order blocks + // // Look for identical block in memory blocks + // while (i < n - 1) { + // // skip all zeroes blocks + // if (memcmp(tmp_blocks[i], "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16)) { + // for (j = 1; j < maxBlocks - 1; ++j) { + // if (!memcmp(tmp_blocks[i], memory_blocks[j], 16) && !memory_blocks[j + 1][ALLOC]) { + // memcpy(memory_blocks[j + 1], tmp_blocks[i + 1], 16); + // memory_blocks[j + 1][ALLOC] = 1; + // if (++found_blocks >= maxBlocks) goto end; + // } + // } + // } + // if (memcmp(tmp_blocks[i + 1], "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16)) { + // for (j = 0; j < maxBlocks; ++j) { + // if (!memcmp(tmp_blocks[i + 1], memory_blocks[j], 16) && !memory_blocks[(j == 0 ? maxBlocks : j) - 1][ALLOC]) { + // if (j == 0) { + // memcpy(memory_blocks[maxBlocks - 1], tmp_blocks[i], 16); + // memory_blocks[maxBlocks - 1][ALLOC] = 1; + // } else { + // memcpy(memory_blocks[j - 1], tmp_blocks[i], 16); + // memory_blocks[j - 1][ALLOC] = 1; + // } + // if (++found_blocks >= maxBlocks) goto end; + // } + // } + // } + // ++i; + // } + // } ++tries; if (BUTTON_PRESS()) { if (g_dbglevel >= DBG_EXTENDED) @@ -404,13 +395,13 @@ void ReadPCF7931(bool ledcontrol) { goto end; } - } while (found_blocks < max_blocks); + } while (found_blocks < maxBlocks); end: Dbprintf("-----------------------------------------"); Dbprintf("Memory content:"); Dbprintf("-----------------------------------------"); - for (i = 0; i < max_blocks; ++i) { + for (i = 0; i < maxBlocks; ++i) { if (memory_blocks[i][ALLOC]) print_result("Block", memory_blocks[i], 16); else @@ -418,7 +409,7 @@ end: } Dbprintf("-----------------------------------------"); - if (found_blocks < max_blocks) { + if (found_blocks < maxBlocks) { Dbprintf("-----------------------------------------"); Dbprintf("Blocks with unknown position:"); Dbprintf("-----------------------------------------"); diff --git a/armsrc/pcf7931.h b/armsrc/pcf7931.h index a3e3a032d..352997678 100644 --- a/armsrc/pcf7931.h +++ b/armsrc/pcf7931.h @@ -20,7 +20,6 @@ typedef enum{ - UNDEFINED, FALLING, RISING } EdgeType; From 3939e28640a822468b31478fa7712e6531bb27e9 Mon Sep 17 00:00:00 2001 From: tinooo <34310283+tinooo@users.noreply.github.com> Date: Tue, 4 Mar 2025 10:30:59 +0100 Subject: [PATCH 095/105] [PCF7931] Starting refactor of write procedure added comments --- armsrc/pcf7931.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/armsrc/pcf7931.c b/armsrc/pcf7931.c index 9007b14ff..843d48ced 100644 --- a/armsrc/pcf7931.c +++ b/armsrc/pcf7931.c @@ -645,8 +645,8 @@ bool AddPatternPCF7931(uint32_t a, uint32_t b, uint32_t c, uint32_t *tab) { uint32_t u = 0; for (u = 0; tab[u] != 0; u += 3) {} //we put the cursor at the last value of the array - tab[u] = (u == 0) ? a : a + tab[u - 1]; - tab[u + 1] = b + tab[u]; + tab[u] = (u == 0) ? a : a + tab[u - 1]; // if it is the first value of the array, nothing needs to be added. + tab[u + 1] = b + tab[u]; // otherwise always add up the values, because later on it is compared to a counter tab[u + 2] = c + tab[u + 1]; return true; From f6600ec96213f903cfc81c9551b302aeb2e17c0c Mon Sep 17 00:00:00 2001 From: tinooo <34310283+tinooo@users.noreply.github.com> Date: Tue, 4 Mar 2025 10:35:30 +0100 Subject: [PATCH 096/105] [PCF7931] Refactor removed early returns unneccessary returns. --- armsrc/pcf7931.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/armsrc/pcf7931.c b/armsrc/pcf7931.c index 843d48ced..3e5a89075 100644 --- a/armsrc/pcf7931.c +++ b/armsrc/pcf7931.c @@ -591,9 +591,9 @@ bool AddBytePCF7931(uint8_t byte, uint32_t *tab, int32_t l, int32_t p) { uint32_t u; for (u = 0; u < 8; ++u) { if (byte & (1 << u)) { //bit is 1 - if (AddBitPCF7931(1, tab, l, p) == 1) return true; + AddBitPCF7931(1, tab, l, p); } else { //bit is 0 - if (AddBitPCF7931(0, tab, l, p) == 1) return true; + AddBitPCF7931(0, tab, l, p); } } @@ -620,9 +620,8 @@ bool AddBitPCF7931(bool b, uint32_t *tab, int32_t l, int32_t p) { tab[u + 1] = 6 * T0_PCF + tab[u] + l; tab[u + 2] = 88 * T0_PCF + tab[u + 1] - l - p; - return false; + } else { //add a bit 0 - if (u == 0) tab[u] = 98 * T0_PCF + p; else @@ -630,7 +629,7 @@ bool AddBitPCF7931(bool b, uint32_t *tab, int32_t l, int32_t p) { tab[u + 1] = 6 * T0_PCF + tab[u] + l; tab[u + 2] = 24 * T0_PCF + tab[u + 1] - l - p; - return false; + } return true; } From ea96a3b0c923e9ed294b98de83932cd02993f3ef Mon Sep 17 00:00:00 2001 From: tinooo <34310283+tinooo@users.noreply.github.com> Date: Tue, 4 Mar 2025 13:26:15 +0100 Subject: [PATCH 097/105] [PCF7931] refactor write function rename some variables for more clear reading changed data type to meaningfull size --- armsrc/pcf7931.c | 127 ++++++++++++++++++++++++++--------------------- armsrc/pcf7931.h | 6 +-- 2 files changed, 73 insertions(+), 60 deletions(-) diff --git a/armsrc/pcf7931.c b/armsrc/pcf7931.c index 3e5a89075..a49dfa327 100644 --- a/armsrc/pcf7931.c +++ b/armsrc/pcf7931.c @@ -29,15 +29,10 @@ #define ALLOC 16 size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { - const uint8_t DECIMATION = 4; - - // 2021 iceman, memor uint8_t bits[256] = {0x00}; uint8_t blocks[8][16]; - uint8_t *dest = BigBuf_get_addr(); - int g_GraphTraceLen = BigBuf_max_traceLen(); // limit g_GraphTraceLen to a little more than 2 data frames. // To make sure a complete dataframe is in the dataset. @@ -51,18 +46,12 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { BigBuf_Clear_keep_EM(); LFSetupFPGAForADC(LF_DIVISOR_125, true); - // DoAcquisition_default(0, true, ledcontrol); - // sample with decimation of 2 --> This means double the values can be sampled. - // this is needed to get a complete frame in the buffer (64 * 8 * 16 * 8 + 8*PMC(~380)) = ~68.000 samples. Buffer is only 41.xxx - // with decimation 2, buffer will be twice as big. DoAcquisition(DECIMATION, 8, 0, 0, false, 0, 0, 0, ledcontrol); - uint8_t j; uint8_t half_switch; + uint8_t bitPos; - uint8_t bitPos; // max 128 bit in one block. if more, then there is an error and PMC was not found. - uint32_t sample; // to keep track of the current sample that is being analyzed uint32_t samplePosLastEdge; uint32_t samplePosCurrentEdge; @@ -421,7 +410,13 @@ end: reply_mix(CMD_ACK, 0, 0, 0, 0, 0); } -static void RealWritePCF7931(uint8_t *pass, uint16_t init_delay, int32_t l, int32_t p, uint8_t address, uint8_t byte, uint8_t data, bool ledcontrol) { +static void RealWritePCF7931( + uint8_t *pass, + uint16_t init_delay, + int8_t offsetPulseWidth, int8_t offsetPulsePosition, + uint8_t address, uint8_t byte, uint8_t data, + bool ledcontrol){ + uint32_t tab[1024] = {0}; // data times frame uint32_t u = 0; uint8_t parity = 0; @@ -429,28 +424,36 @@ static void RealWritePCF7931(uint8_t *pass, uint16_t init_delay, int32_t l, int3 //BUILD OF THE DATA FRAME //alimentation of the tag (time for initializing) + // ToDo: This could be optimized/automated. e.g. Read one cycle, find PMC and calculate time. + // I dont understand, why 8192/2 AddPatternPCF7931(init_delay, 0, 8192 / 2 * T0_PCF, tab); + + // why "... + 70"? Why not "... + x * T0"? + // I think he just added 70 to be somewhere in The PMC window, which is 32T0 (=32*8 = 256) + // 3*T0 = PMC width + // 29*T0 = rest of PMC window (total 32T0 = 3+29) + // after the PMC, it directly goes to the password indication bit. AddPatternPCF7931(8192 / 2 * T0_PCF + 319 * T0_PCF + 70, 3 * T0_PCF, 29 * T0_PCF, tab); //password indication bit - AddBitPCF7931(1, tab, l, p); + AddBitPCF7931(1, tab, offsetPulseWidth, offsetPulsePosition); //password (on 56 bits) - AddBytePCF7931(pass[0], tab, l, p); - AddBytePCF7931(pass[1], tab, l, p); - AddBytePCF7931(pass[2], tab, l, p); - AddBytePCF7931(pass[3], tab, l, p); - AddBytePCF7931(pass[4], tab, l, p); - AddBytePCF7931(pass[5], tab, l, p); - AddBytePCF7931(pass[6], tab, l, p); - //programming mode (0 or 1) - AddBitPCF7931(0, tab, l, p); + AddBytePCF7931(pass[0], tab, offsetPulseWidth, offsetPulsePosition); + AddBytePCF7931(pass[1], tab, offsetPulseWidth, offsetPulsePosition); + AddBytePCF7931(pass[2], tab, offsetPulseWidth, offsetPulsePosition); + AddBytePCF7931(pass[3], tab, offsetPulseWidth, offsetPulsePosition); + AddBytePCF7931(pass[4], tab, offsetPulseWidth, offsetPulsePosition); + AddBytePCF7931(pass[5], tab, offsetPulseWidth, offsetPulsePosition); + AddBytePCF7931(pass[6], tab, offsetPulseWidth, offsetPulsePosition); + //programming mode (0 or 1) -> 0 = byte wise; 1 = block wise programming + AddBitPCF7931(0, tab, offsetPulseWidth, offsetPulsePosition); //block address on 6 bits for (u = 0; u < 6; ++u) { if (address & (1 << u)) { // bit 1 ++parity; - AddBitPCF7931(1, tab, l, p); + AddBitPCF7931(1, tab, offsetPulseWidth, offsetPulsePosition); } else { // bit 0 - AddBitPCF7931(0, tab, l, p); + AddBitPCF7931(0, tab, offsetPulseWidth, offsetPulsePosition); } } @@ -458,28 +461,29 @@ static void RealWritePCF7931(uint8_t *pass, uint16_t init_delay, int32_t l, int3 for (u = 0; u < 4; ++u) { if (byte & (1 << u)) { // bit 1 parity++; - AddBitPCF7931(1, tab, l, p); + AddBitPCF7931(1, tab, offsetPulseWidth, offsetPulsePosition); } else // bit 0 - AddBitPCF7931(0, tab, l, p); + AddBitPCF7931(0, tab, offsetPulseWidth, offsetPulsePosition); } //data on 8 bits for (u = 0; u < 8; u++) { if (data & (1 << u)) { // bit 1 parity++; - AddBitPCF7931(1, tab, l, p); + AddBitPCF7931(1, tab, offsetPulseWidth, offsetPulsePosition); } else //bit 0 - AddBitPCF7931(0, tab, l, p); + AddBitPCF7931(0, tab, offsetPulseWidth, offsetPulsePosition); } //parity bit if ((parity % 2) == 0) - AddBitPCF7931(0, tab, l, p); //even parity + AddBitPCF7931(0, tab, offsetPulseWidth, offsetPulsePosition); //even parity else - AddBitPCF7931(1, tab, l, p);//odd parity + AddBitPCF7931(1, tab, offsetPulseWidth, offsetPulsePosition);//odd parity - //time access memory - AddPatternPCF7931(5120 + 2680, 0, 0, tab); + // time access memory (640T0) + // Not sure why 335*T0, but should not matter. Since programming should be finished at that point + AddPatternPCF7931((640 + 335)* T0_PCF, 0, 0, tab); //conversion of the scale time for (u = 0; u < 500; ++u) @@ -500,14 +504,19 @@ static void RealWritePCF7931(uint8_t *pass, uint16_t init_delay, int32_t l, int3 /* Write on a byte of a PCF7931 tag * @param address : address of the block to write - @param byte : address of the byte to write - @param data : data to write + * @param byte : address of the byte to write + * @param data : data to write */ -void WritePCF7931(uint8_t pass1, uint8_t pass2, uint8_t pass3, uint8_t pass4, uint8_t pass5, uint8_t pass6, uint8_t pass7, uint16_t init_delay, int32_t l, int32_t p, uint8_t address, uint8_t byte, uint8_t data, bool ledcontrol) { +void WritePCF7931( + uint8_t pass1, uint8_t pass2, uint8_t pass3, uint8_t pass4, uint8_t pass5, uint8_t pass6, uint8_t pass7, + uint16_t init_delay, + int8_t offsetPulseWidth, int8_t offsetPulsePosition, + uint8_t address, uint8_t byte, uint8_t data, + bool ledcontrol) { if (g_dbglevel >= DBG_INFO) { Dbprintf("Initialization delay : %d us", init_delay); - Dbprintf("Offsets : %d us on the low pulses width, %d us on the low pulses positions", l, p); + Dbprintf("Offsets : %d us on the low pulses width, %d us on the low pulses positions", offsetPulseWidth, offsetPulsePosition); } Dbprintf("Password (LSB first on each byte): %02x %02x %02x %02x %02x %02x %02x", pass1, pass2, pass3, pass4, pass5, pass6, pass7); @@ -517,11 +526,11 @@ void WritePCF7931(uint8_t pass1, uint8_t pass2, uint8_t pass3, uint8_t pass4, ui uint8_t password[7] = {pass1, pass2, pass3, pass4, pass5, pass6, pass7}; - RealWritePCF7931(password, init_delay, l, p, address, byte, data, ledcontrol); + RealWritePCF7931(password, init_delay, offsetPulseWidth, offsetPulsePosition, address, byte, data, ledcontrol); } -/* Send a trame to a PCF7931 tags +/* Send a frame to a PCF7931 tags * @param tab : array of the data frame */ @@ -581,32 +590,36 @@ void SendCmdPCF7931(const uint32_t *tab, bool ledcontrol) { } -/* Add a byte for building the data frame of PCF7931 tags +/* Add a byte for building the data frame of PCF7931 tags. + * See Datasheet of PCF7931 diagramm on page 8. This explains pulse widht & positioning + * Normally, no offset should be required. * @param b : byte to add * @param tab : array of the data frame - * @param l : offset on low pulse width - * @param p : offset on low pulse positioning + * @param offsetPulseWidth : offset on low pulse width in µs (default pulse widht is 6T0) + * @param offsetPulsePosition : offset on low pulse positioning in µs */ -bool AddBytePCF7931(uint8_t byte, uint32_t *tab, int32_t l, int32_t p) { +bool AddBytePCF7931(uint8_t byte, uint32_t *tab, int8_t offsetPulseWidth, int8_t offsetPulsePosition) { uint32_t u; for (u = 0; u < 8; ++u) { if (byte & (1 << u)) { //bit is 1 - AddBitPCF7931(1, tab, l, p); + AddBitPCF7931(1, tab, offsetPulseWidth, offsetPulsePosition); } else { //bit is 0 - AddBitPCF7931(0, tab, l, p); + AddBitPCF7931(0, tab, offsetPulseWidth, offsetPulsePosition); } } return false; } -/* Add a bits for building the data frame of PCF7931 tags +/* Add a bits for building the data frame of PCF7931 tags. + * See Datasheet of PCF7931 diagramm on page 8. This explains pulse widht & positioning + * Normally, no offset should be required. * @param b : bit to add * @param tab : array of the data frame - * @param l : offset on low pulse width - * @param p : offset on low pulse positioning + * @param offsetPulseWidth : offset on low pulse width in µs (default pulse widht is 6T0) + * @param offsetPulsePosition : offset on low pulse positioning in µs */ -bool AddBitPCF7931(bool b, uint32_t *tab, int32_t l, int32_t p) { +bool AddBitPCF7931(bool b, uint32_t *tab, int8_t offsetPulseWidth, int8_t offsetPulsePosition) { uint8_t u = 0; //we put the cursor at the last value of the array @@ -614,21 +627,21 @@ bool AddBitPCF7931(bool b, uint32_t *tab, int32_t l, int32_t p) { if (b == 1) { //add a bit 1 if (u == 0) - tab[u] = 34 * T0_PCF + p; + tab[u] = 34 * T0_PCF + offsetPulsePosition; else - tab[u] = 34 * T0_PCF + tab[u - 1] + p; + tab[u] = 34 * T0_PCF + tab[u - 1] + offsetPulsePosition; - tab[u + 1] = 6 * T0_PCF + tab[u] + l; - tab[u + 2] = 88 * T0_PCF + tab[u + 1] - l - p; + tab[u + 1] = 6 * T0_PCF + tab[u] + offsetPulseWidth; + tab[u + 2] = 88 * T0_PCF + tab[u + 1] - offsetPulseWidth - offsetPulsePosition; } else { //add a bit 0 if (u == 0) - tab[u] = 98 * T0_PCF + p; + tab[u] = 98 * T0_PCF + offsetPulsePosition; else - tab[u] = 98 * T0_PCF + tab[u - 1] + p; + tab[u] = 98 * T0_PCF + tab[u - 1] + offsetPulsePosition; - tab[u + 1] = 6 * T0_PCF + tab[u] + l; - tab[u + 2] = 24 * T0_PCF + tab[u + 1] - l - p; + tab[u + 1] = 6 * T0_PCF + tab[u] + offsetPulseWidth; + tab[u + 2] = 24 * T0_PCF + tab[u + 1] - offsetPulseWidth - offsetPulsePosition; } return true; diff --git a/armsrc/pcf7931.h b/armsrc/pcf7931.h index 352997678..78496c193 100644 --- a/armsrc/pcf7931.h +++ b/armsrc/pcf7931.h @@ -29,9 +29,9 @@ bool IsBlock0PCF7931(uint8_t *block); bool IsBlock1PCF7931(const uint8_t *block); void ReadPCF7931(bool ledcontrol); void SendCmdPCF7931(const uint32_t *tab, bool ledcontrol); -bool AddBytePCF7931(uint8_t byte, uint32_t *tab, int32_t l, int32_t p); -bool AddBitPCF7931(bool b, uint32_t *tab, int32_t l, int32_t p); +bool AddBytePCF7931(uint8_t byte, uint32_t *tab, int8_t offsetPulseWidth, int8_t offsetPulsePosition); +bool AddBitPCF7931(bool b, uint32_t *tab, int8_t offsetPulseWidth, int8_t offsetPulsePosition); bool AddPatternPCF7931(uint32_t a, uint32_t b, uint32_t c, uint32_t *tab); -void WritePCF7931(uint8_t pass1, uint8_t pass2, uint8_t pass3, uint8_t pass4, uint8_t pass5, uint8_t pass6, uint8_t pass7, uint16_t init_delay, int32_t l, int32_t p, uint8_t address, uint8_t byte, uint8_t data, bool ledcontrol); +void WritePCF7931(uint8_t pass1, uint8_t pass2, uint8_t pass3, uint8_t pass4, uint8_t pass5, uint8_t pass6, uint8_t pass7, uint16_t init_delay, int8_t offsetPulseWidth, int8_t offsetPulsePosition, uint8_t address, uint8_t byte, uint8_t data, bool ledcontrol); #endif From 8723037e683cb752489283fea3432a92ab79040d Mon Sep 17 00:00:00 2001 From: tinooo <34310283+tinooo@users.noreply.github.com> Date: Tue, 4 Mar 2025 15:56:38 +0100 Subject: [PATCH 098/105] [PCF7931] refactor SendCmdPCF7931 first steps in understading and optimizing this function. replace != with < - if we don't poll fast enough, it is possible that the condition != is missed. --- armsrc/pcf7931.c | 8 ++++---- client/src/cmdlfpcf7931.c | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/armsrc/pcf7931.c b/armsrc/pcf7931.c index a49dfa327..d2d5ab9f6 100644 --- a/armsrc/pcf7931.c +++ b/armsrc/pcf7931.c @@ -555,7 +555,7 @@ void SendCmdPCF7931(const uint32_t *tab, bool ledcontrol) { AT91C_BASE_PMC->PMC_PCER |= (0x1 << AT91C_ID_TC0); AT91C_BASE_TCB->TCB_BMR = AT91C_TCB_TC0XC0S_NONE | AT91C_TCB_TC1XC1S_TIOA0 | AT91C_TCB_TC2XC2S_NONE; AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKDIS; // timer disable - AT91C_BASE_TC0->TC_CMR = AT91C_TC_CLKS_TIMER_DIV3_CLOCK; // clock at 48/32 MHz + AT91C_BASE_TC0->TC_CMR = AT91C_TC_CLKS_TIMER_DIV3_CLOCK; // clock at 48/32 MHz (48Mhz clock, 32 = prescaler (div3)) AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKEN; // Assert a sync signal. This sets all timers to 0 on next active clock edge @@ -565,19 +565,19 @@ void SendCmdPCF7931(const uint32_t *tab, bool ledcontrol) { for (u = 0; tab[u] != 0; u += 3) { // modulate antenna HIGH(GPIO_SSC_DOUT); - while (tempo != tab[u]) { + while (tempo < tab[u]) { tempo = AT91C_BASE_TC0->TC_CV; } // stop modulating antenna LOW(GPIO_SSC_DOUT); - while (tempo != tab[u + 1]) { + while (tempo < tab[u + 1]) { tempo = AT91C_BASE_TC0->TC_CV; } // modulate antenna HIGH(GPIO_SSC_DOUT); - while (tempo != tab[u + 2]) { + while (tempo < tab[u + 2]) { tempo = AT91C_BASE_TC0->TC_CV; } } diff --git a/client/src/cmdlfpcf7931.c b/client/src/cmdlfpcf7931.c index 53bc19f03..8b7e73def 100644 --- a/client/src/cmdlfpcf7931.c +++ b/client/src/cmdlfpcf7931.c @@ -105,8 +105,8 @@ static int CmdLFPCF7931Config(const char *Cmd) { arg_lit0("r", "reset", "Reset configuration to default values"), arg_str0("p", "pwd", "", "Password, 7bytes, LSB-order"), arg_u64_0("d", "delay", "", "Tag initialization delay (in us)"), - arg_int0(NULL, "lw", "", "offset, low pulses width (in us)"), - arg_int0(NULL, "lp", "", "offset, low pulses position (in us)"), + arg_int0(NULL, "lw", "", "offset, low pulses width (in us), optional!"), + arg_int0(NULL, "lp", "", "offset, low pulses position (in us), optional!"), arg_param_end }; CLIExecWithReturn(ctx, Cmd, argtable, true); From d3a87ead613f27f67f71dd1036c82d7e97dc961d Mon Sep 17 00:00:00 2001 From: tinooo <34310283+tinooo@users.noreply.github.com> Date: Tue, 4 Mar 2025 16:09:21 +0100 Subject: [PATCH 099/105] [PCF7930] refactor move code move "remapping" of dataframes to the actual send function, where the timer is located --- armsrc/pcf7931.c | 31 +++++++++++++++---------------- armsrc/pcf7931.h | 2 +- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/armsrc/pcf7931.c b/armsrc/pcf7931.c index d2d5ab9f6..fbd1b2b10 100644 --- a/armsrc/pcf7931.c +++ b/armsrc/pcf7931.c @@ -420,7 +420,6 @@ static void RealWritePCF7931( uint32_t tab[1024] = {0}; // data times frame uint32_t u = 0; uint8_t parity = 0; - bool comp = 0; //BUILD OF THE DATA FRAME //alimentation of the tag (time for initializing) @@ -485,20 +484,6 @@ static void RealWritePCF7931( // Not sure why 335*T0, but should not matter. Since programming should be finished at that point AddPatternPCF7931((640 + 335)* T0_PCF, 0, 0, tab); - //conversion of the scale time - for (u = 0; u < 500; ++u) - tab[u] = (tab[u] * 3) / 2; - - //compensation of the counter reload - while (!comp) { - comp = 1; - for (u = 0; tab[u] != 0; ++u) - if (tab[u] > 0xFFFF) { - tab[u] -= 0xFFFF; - comp = 0; - } - } - SendCmdPCF7931(tab, ledcontrol); } @@ -534,7 +519,7 @@ void WritePCF7931( * @param tab : array of the data frame */ -void SendCmdPCF7931(const uint32_t *tab, bool ledcontrol) { +void SendCmdPCF7931(uint32_t *tab, bool ledcontrol) { uint16_t u = 0, tempo = 0; if (g_dbglevel >= DBG_INFO) { @@ -546,6 +531,20 @@ void SendCmdPCF7931(const uint32_t *tab, bool ledcontrol) { FpgaWriteConfWord(FPGA_MAJOR_MODE_LF_PASSTHRU); if (ledcontrol) LED_A_ON(); + + // rescale the values to match the time of the timer below. + for (u = 0; u < 500; ++u) { + tab[u] = (tab[u] * 3) / 2; + } + + // compensation for the counter overflow + // only one overflow should be possible. + for (u = 0; tab[u] != 0; ++u) + if (tab[u] > 0xFFFF) { + tab[u] -= 0xFFFF; + break; + } + // steal this pin from the SSP and use it to control the modulation AT91C_BASE_PIOA->PIO_PER = GPIO_SSC_DOUT; diff --git a/armsrc/pcf7931.h b/armsrc/pcf7931.h index 78496c193..314fb7e3c 100644 --- a/armsrc/pcf7931.h +++ b/armsrc/pcf7931.h @@ -28,7 +28,7 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol); bool IsBlock0PCF7931(uint8_t *block); bool IsBlock1PCF7931(const uint8_t *block); void ReadPCF7931(bool ledcontrol); -void SendCmdPCF7931(const uint32_t *tab, bool ledcontrol); +void SendCmdPCF7931(uint32_t *tab, bool ledcontrol); bool AddBytePCF7931(uint8_t byte, uint32_t *tab, int8_t offsetPulseWidth, int8_t offsetPulsePosition); bool AddBitPCF7931(bool b, uint32_t *tab, int8_t offsetPulseWidth, int8_t offsetPulsePosition); bool AddPatternPCF7931(uint32_t a, uint32_t b, uint32_t c, uint32_t *tab); From 23ddf69f70676d38aaf38652427a4f465cc60654 Mon Sep 17 00:00:00 2001 From: tinooo <34310283+tinooo@users.noreply.github.com> Date: Wed, 12 Mar 2025 16:37:46 +0100 Subject: [PATCH 100/105] [PCF7931] added IIR filter different tags seem to behave differently. an old tag from the car had way worse signal. Therefore filtering made it way better. Still not ideal. --- armsrc/pcf7931.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/armsrc/pcf7931.c b/armsrc/pcf7931.c index fbd1b2b10..c3f5ffab4 100644 --- a/armsrc/pcf7931.c +++ b/armsrc/pcf7931.c @@ -28,6 +28,13 @@ #define T0_PCF 8 //period for the pcf7931 in us #define ALLOC 16 +// IIR filter consts +#define IIR_CONST1 0.1f +#define IIR_CONST2 0.9f + +// theshold for recognition of positive/negative slope +#define THRESHOLD 80 + size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { const uint8_t DECIMATION = 4; uint8_t bits[256] = {0x00}; @@ -70,7 +77,6 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { uint8_t block_done; size_t num_blocks = 0; - uint8_t threshold = 50; // threshold to filter out noise, from an actual edge. EdgeType expectedNextEdge = FALLING; // direction in which the next edge is expected should go. half_switch = 0; @@ -78,15 +84,16 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { block_done = 0; bitPos = 0; lastClockDuration=0; - - for (sample = 1 ; sample < g_GraphTraceLen; sample++) { + + for (sample = 1 ; sample < g_GraphTraceLen-4; sample++) { // condition is searching for the next edge, in the expected diretion. - if ( ((dest[sample] + threshold) < dest[sample-1] && expectedNextEdge == FALLING ) || - ((dest[sample] - threshold) > dest[sample-1] && expectedNextEdge == RISING )) { + //todo: without flouz + dest[sample] = (uint8_t)(dest[sample-1] * IIR_CONST1 + dest[sample] * IIR_CONST2); // apply IIR filter + + if ( ((dest[sample] + THRESHOLD) < dest[sample-1] && expectedNextEdge == FALLING ) || + ((dest[sample] - THRESHOLD) > dest[sample-1] && expectedNextEdge == RISING )) { //okay, next falling/rising edge found - - - + expectedNextEdge = (expectedNextEdge == FALLING) ? RISING : FALLING; //toggle the next expected edge samplePosCurrentEdge = sample; beforeLastClockDuration = lastClockDuration; // save the previous clock duration for PMC recognition @@ -146,7 +153,10 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { } else { // some Error. maybe check tolerances. // likeley to happen in the first block. - Dbprintf(_RED_("ERROR in demodulation") " Length last clock: %d - check threshold/tolerance/signal. Toss block", lastClockDuration*DECIMATION); + + // In an Ideal world, this can be enabled. However, if only bad antenna field, this print will flood the output + // and one might miss some "good" frames. + //Dbprintf(_RED_("ERROR in demodulation") " Length last clock: %d - check threshold/tolerance/signal. Toss block", lastClockDuration*DECIMATION); // Toss this block. block_done = 1; From 0b2b2384578e326f751a366f29ae96af2785b8f5 Mon Sep 17 00:00:00 2001 From: tinooo <34310283+tinooo@users.noreply.github.com> Date: Wed, 12 Mar 2025 17:11:28 +0100 Subject: [PATCH 101/105] [PCF7931] getting things ready for PR since this is somekind of work in progress, I'm still going for a PR. This commit is reworking some comments and making the code stable (at least as good es or better as before). Also made als const as #define --- armsrc/pcf7931.c | 59 ++++++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/armsrc/pcf7931.c b/armsrc/pcf7931.c index c3f5ffab4..a2c2121e4 100644 --- a/armsrc/pcf7931.c +++ b/armsrc/pcf7931.c @@ -32,15 +32,28 @@ #define IIR_CONST1 0.1f #define IIR_CONST2 0.9f +// used to decimate samples. this allows DoAcquisition to sample for a longer duration. +// Decimation of 4 makes sure that all blocks can be sampled at once! +#define DECIMATION 4 + +#define CLOCK (64/DECIMATION) // this actually is 64, but since samples are decimated by 2, CLOCK is also /2 +#define TOLERANCE (CLOCK / 8) +#define _16T0 (CLOCK/4) +#define _32T0 (CLOCK/2) +#define _64T0 (CLOCK) + +// calculating the two possible pmc lengths, based on the clock. -4 at the end is to make sure not to increment too far +#define PMC_16T0_LEN ((128 + 127 + 16 + 32 + 33 + 16) * CLOCK/64); +#define PMC_32T0_LEN ((128 + 127 + 16 + 32 + 33 ) * CLOCK/64); + // theshold for recognition of positive/negative slope #define THRESHOLD 80 size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { - const uint8_t DECIMATION = 4; uint8_t bits[256] = {0x00}; uint8_t blocks[8][16]; uint8_t *dest = BigBuf_get_addr(); - int g_GraphTraceLen = BigBuf_max_traceLen(); + uint16_t g_GraphTraceLen = BigBuf_max_traceLen(); // limit g_GraphTraceLen to a little more than 2 data frames. // To make sure a complete dataframe is in the dataset. // 1 Frame is 16 Byte -> 128byte. at a T0 of 64 -> 8129 Samples per frame. @@ -48,8 +61,8 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { // to make sure that one complete block is definitely being sampled, we need 2 times that // which is ~17.xxx samples. round up. and clamp to this value. - // TODO: Doublecheck why this is being limited? -// g_GraphTraceLen = (g_GraphTraceLen > 18000) ? 18000 : g_GraphTraceLen; + // TODO: Doublecheck why this is being limited? - seems not to be needed. + // g_GraphTraceLen = (g_GraphTraceLen > 18000) ? 18000 : g_GraphTraceLen; BigBuf_Clear_keep_EM(); LFSetupFPGAForADC(LF_DIVISOR_125, true); @@ -66,15 +79,6 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { uint8_t beforeLastClockDuration; // store the clock duration of the cycle before the last Clock duration. Basically clockduration -2 - const uint8_t clock = 64/DECIMATION; // this actually is 64, but since samples are decimated by 2, clock is also /2 - const uint8_t tolerance = clock / 8; - const uint8_t _16T0 = clock/4; - const uint8_t _32T0 = clock/2; - const uint8_t _64T0 = clock; - - const uint16_t pmc16T0Len = (128 + 127 + 16 + 32 + 33 + 16) * clock/64; // calculating the two possible pmc lengths, based on the clock. -4 at the end is to make sure not to increment too far - const uint16_t pmc32T0Len = (128 + 127 + 16 + 32 + 33 ) * clock/64; - uint8_t block_done; size_t num_blocks = 0; EdgeType expectedNextEdge = FALLING; // direction in which the next edge is expected should go. @@ -104,13 +108,13 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { // Switch depending on lastClockDuration length: // 16T0 - if (ABS(lastClockDuration - _16T0) < tolerance) { + if (ABS(lastClockDuration - _16T0) < TOLERANCE) { // if the clock before also was 16T0, it is a PMC! - if (ABS(beforeLastClockDuration - _16T0) < tolerance) { + if (ABS(beforeLastClockDuration - _16T0) < TOLERANCE) { // It's a PMC Dbprintf(_GREEN_("PMC 16T0 FOUND:") " bitPos: %d, sample: %d", bitPos, sample); - sample += pmc16T0Len; // move to the sample after PMC + sample += PMC_16T0_LEN; // move to the sample after PMC expectedNextEdge = FALLING; samplePosLastEdge = sample; @@ -118,13 +122,13 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { } // 32TO - } else if (ABS(lastClockDuration - _32T0) < tolerance) { + } else if (ABS(lastClockDuration - _32T0) < TOLERANCE) { // if the clock before also was 16T0, it is a PMC! - if (ABS(beforeLastClockDuration - _16T0) < tolerance) { + if (ABS(beforeLastClockDuration - _16T0) < TOLERANCE) { // It's a PMC ! Dbprintf(_GREEN_("PMC 32T0 FOUND:") " bitPos: %d, sample: %d", bitPos, sample); - sample += pmc32T0Len; // move to the sample after PMC + sample += PMC_32T0_LEN; // move to the sample after PMC expectedNextEdge = FALLING; samplePosLastEdge = sample; @@ -144,7 +148,7 @@ size_t DemodPCF7931(uint8_t **outBlocks, bool ledcontrol) { half_switch++; // 64T0 - } else if (ABS(lastClockDuration - _64T0) < tolerance) { + } else if (ABS(lastClockDuration - _64T0) < TOLERANCE) { // this means, bit here is 1 bits[bitPos] = 1; bitPos++; @@ -248,10 +252,7 @@ bool IsBlock1PCF7931(const uint8_t *block) { void ReadPCF7931(bool ledcontrol) { - Dbprintf("ReadPCF7931()=========="); - uint8_t maxBlocks = 8; // readable blocks - int found_blocks = 0; // successfully read blocks // TODO: Why 17 byte len? 16 should be good. @@ -275,7 +276,7 @@ void ReadPCF7931(bool ledcontrol) { //j = 0; do { - Dbprintf("ReadPCF7931() -- DO LOOP =========="); + Dbprintf("ReadPCF7931() -- Reading Loop =========="); i = 0; memset(tmp_blocks, 0, 4 * 16 * sizeof(uint8_t)); @@ -299,6 +300,9 @@ void ReadPCF7931(bool ledcontrol) { goto end; } + // This part was not working properly. + // So currently the blocks are not being sorted, but at least printed. + // // our logic breaks if we don't get at least two blocks // if (n < 2) { // // skip if all 0s block or no blocks @@ -328,8 +332,9 @@ void ReadPCF7931(bool ledcontrol) { // if (g_dbglevel >= DBG_EXTENDED) // Dbprintf("(dbg) got %d blocks (%d/%d found) (%d tries, %d errors)", n, found_blocks, (maxBlocks == 0 ? found_blocks : maxBlocks), tries, errors); + // print blocks that have been found for (i = 0; i < n; ++i) { - print_result("got consecutive blocks", tmp_blocks[i], 16); + print_result("Block found: ", tmp_blocks[i], 16); } // i = 0; @@ -396,7 +401,9 @@ void ReadPCF7931(bool ledcontrol) { } } while (found_blocks < maxBlocks); + end: +/* Dbprintf("-----------------------------------------"); Dbprintf("Memory content:"); Dbprintf("-----------------------------------------"); @@ -417,6 +424,8 @@ end: Dbprintf("-----------------------------------------"); } +*/ + reply_mix(CMD_ACK, 0, 0, 0, 0, 0); } From aa35a8a1a6eca2cd3c0afe3bbb0505ff45d305bc Mon Sep 17 00:00:00 2001 From: iceman1001 Date: Wed, 12 Mar 2025 17:40:07 +0100 Subject: [PATCH 102/105] missing defines --- client/src/cmdhflist.c | 1 + client/src/cmdhfseos.h | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/client/src/cmdhflist.c b/client/src/cmdhflist.c index 4e04819bf..d23a02507 100644 --- a/client/src/cmdhflist.c +++ b/client/src/cmdhflist.c @@ -31,6 +31,7 @@ #include "protocols.h" #include "cmdhficlass.h" #include "mifare/mifaredefault.h" // mifare consts +#include "cmdhfseos.h" enum MifareAuthSeq { masNone, diff --git a/client/src/cmdhfseos.h b/client/src/cmdhfseos.h index f1dfe99a0..46ce3d32e 100644 --- a/client/src/cmdhfseos.h +++ b/client/src/cmdhfseos.h @@ -21,6 +21,12 @@ #include "common.h" +#define SEOS_ENCRYPTION_2K3DES 0x02 +#define SEOS_ENCRYPTION_3K3DES 0x03 +#define SEOS_ENCRYPTION_AES 0x09 + +#define SEOS_HASHING_SHA1 0x06 +#define SEOS_HASHING_SHA256 0x07 int infoSeos(bool verbose); int CmdHFSeos(const char *Cmd); int seos_kdf(bool encryption, uint8_t *masterKey, uint8_t keyslot, From e9ef11f81273e84da95d2a824f6310b86eb6d7ed Mon Sep 17 00:00:00 2001 From: tinooo <34310283+tinooo@users.noreply.github.com> Date: Fri, 14 Mar 2025 09:01:06 +0100 Subject: [PATCH 103/105] [PCF7931] Added infos to CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a50827e96..7510597a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ 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] +- Improved `pcf7931` generic readability of the code. Unified datatypes and added documentation/explainations (@tinooo) +- Improved `lf pcf7931` read code - fixed some checks for more stability (@tinooo) - Changed `trace list -t seos` - improved annotation (@iceman1001) - Added `make commands` to regenerate commands documentation files and autocompletion data independently of `make style` (@doegox) - Added texecom identification, thanks @en4rab ! (@iceman1001) From 939f5cb11fc53f470b1360f9d1e22b65cedb5ff0 Mon Sep 17 00:00:00 2001 From: tinooo <34310283+tinooo@users.noreply.github.com> Date: Fri, 14 Mar 2025 09:05:53 +0100 Subject: [PATCH 104/105] [PCF7931] Added type cast to compare equally sized types the github pipeline was stressed about comparing an uint16_t to an uint32_t. --- armsrc/pcf7931.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/armsrc/pcf7931.c b/armsrc/pcf7931.c index a2c2121e4..c1f2e3f7f 100644 --- a/armsrc/pcf7931.c +++ b/armsrc/pcf7931.c @@ -583,19 +583,19 @@ void SendCmdPCF7931(uint32_t *tab, bool ledcontrol) { for (u = 0; tab[u] != 0; u += 3) { // modulate antenna HIGH(GPIO_SSC_DOUT); - while (tempo < tab[u]) { + while ((uint32_t)tempo < tab[u]) { tempo = AT91C_BASE_TC0->TC_CV; } // stop modulating antenna LOW(GPIO_SSC_DOUT); - while (tempo < tab[u + 1]) { + while ((uint32_t)tempo < tab[u + 1]) { tempo = AT91C_BASE_TC0->TC_CV; } // modulate antenna HIGH(GPIO_SSC_DOUT); - while (tempo < tab[u + 2]) { + while ((uint32_t)tempo < tab[u + 2]) { tempo = AT91C_BASE_TC0->TC_CV; } } From 0908ff212688cc0cc184ddf52349245216e6c77b Mon Sep 17 00:00:00 2001 From: Lucifer Voeltner Date: Sat, 15 Mar 2025 09:02:17 +0700 Subject: [PATCH 105/105] `hf_mfu_uscuid.py` - A helper script for interacting with USCUID-UL --- client/pyscripts/hf_mfu_uscuid.py | 198 ++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 client/pyscripts/hf_mfu_uscuid.py diff --git a/client/pyscripts/hf_mfu_uscuid.py b/client/pyscripts/hf_mfu_uscuid.py new file mode 100644 index 000000000..a477c1b16 --- /dev/null +++ b/client/pyscripts/hf_mfu_uscuid.py @@ -0,0 +1,198 @@ +### Crappy helper script for USCUID-UL, v0.2.4.2 +## Written and tested by Eltrick +# It is recommended that you are able to backdoor read main blocks +# in case changing from one type to another messes up keys/pwd +# unless you know what you're doing. + +## For the uninitiated, the keys are stored in the following locations +## per the corresponding datasheets +# UL11 - PWD - page 18d +# UL21 - PWD - page 39d +# UL-C - KEY - pages 44d to 47d +# NTAG 213 - PWD - page 43d +# NTAG 215 - PWD - page 133d +# NTAG 216 - PWD - page 229d + +import argparse +import pm3 + +try: + # pip install ansicolors + from colors import color +except ModuleNotFoundError: + def color(s, fg=None): + _ = fg + return str(s) + +HEX_DIGITS = "0123456789ABCDEF" +MEMORY_CONFIG = { "C3": "UL11", "3C": "UL21", "00": "UL-C", "A5": "NTAG 213", "5A": "NTAG 215", "AA": "NTAG 216", "55": "Unknown IC with 238 pages" } +KNOWN_CONFIGS = ["C30004030101000B03", "3C0004030101000E03", "000000000000000000", "A50004040201000F03", "5A0004040201001103", "AA0004040201001303"] + +parser = argparse.ArgumentParser(description='A script to help with raw USCUID-UL commands. Out of everything until -s, only one functionality can be used at a time, prioritised in order listed below.') +parser.add_argument('-r', '--read', action='store_true', help='Read and parse config from card') +parser.add_argument('-t', '--type', help='Type to change to: 1-UL11; 2-UL21; 3-UL-C; 4-NTAG213; 5-NTAG215; 6-NTAG216') +parser.add_argument('-c', '--cfg', help='Config to write') +parser.add_argument('-p', '--parse', help='Config to parse') +parser.add_argument('-b', '--bdr', help='Page num to read with backdoor') +parser.add_argument('-w', '--wbd', help='First page num to write with backdoor') +parser.add_argument('-u', '--uid', help='New UID to write') +parser.add_argument('-d', '--data', help='Page data to write if using -w, multiple of 4 bytes') +parser.add_argument('-s', '--sig', help='Signature to write with backdoor') +parser.add_argument('--gen1a', action='store_true', help='Use gen1a (40/43) magic wakeup') +parser.add_argument('--gdm', action='store_true', help='Use gdm alt (20/23) magic wakeup') + +args = parser.parse_args() +card_config = args.read +ul_type = args.type +config = args.cfg +parse = args.parse +backdoor_block = args.bdr +write_backdoor = args.wbd +data = args.data +signature = args.sig +gen1a = args.gen1a +alt = args.gdm +uid = args.uid + +field_on = False +p = pm3.pm3() + +ERROR = "[" + color("-", "red") + "] " +SUCCESS = "[" + color("+", "green") + "] " + +def verify_config(config: str) -> bool: + if len(config) != 32: + print(ERROR + "Configuration data must be 16 bytes.") + return False + if set(config) > set(HEX_DIGITS): + print(ERROR + "Configuration data must be in hex.") + return False + return True + +def parse_config(config: str): + print(SUCCESS + "" + config) + cfg_magic_wup = config[0:4] + cfg_wup_style = config[4:6] + cfg_regular_available = config[6:8] + cfg_auth_type = config[8:10] + cfg_cuid = config[12:14] + cfg_memory_config = config[14:16] + + log_magic_wup = "Magic wakeup " + ("en" if cfg_magic_wup != "8500" else "dis") + "abled" + (" with config access" if cfg_magic_wup == "7AFF" else "") + log_wup_style = "Magic wakeup style " + ("Gen1a 40(7)/43" if cfg_wup_style == "00" else ("GDM 20(7)/23" if cfg_wup_style == "85" else "unknown")) + log_regular_available = "Config " + ("" if cfg_regular_available == "A0" else "un") + "available in regular mode" + log_auth_type = "Auth type " + ("1B - PWD" if cfg_auth_type == "00" else "1A - 3DES") + log_cuid = "CUID " + ("dis" if cfg_cuid == "A0" else "en") + "abled" + log_memory_config = "Maximum memory configuration: " + (MEMORY_CONFIG[cfg_memory_config] if cfg_memory_config in MEMORY_CONFIG.keys() else "unknown") + + print(SUCCESS + "^^^^............................ " + log_magic_wup) + print(SUCCESS + "....^^.......................... " + log_wup_style) + print(SUCCESS + "......^^........................ " + log_regular_available) + print(SUCCESS + "........^^...................... " + log_auth_type) + print(SUCCESS + "..........^^.................... unknown") + print(SUCCESS + "............^^.................. " + log_cuid) + print(SUCCESS + "..............^^................ " + log_memory_config) + print(SUCCESS + "................^^^^^^^^^^^^^^^^ version info") + +def try_auth_magic(enforced = False): + if enforced and not (gen1a | alt): + print(ERROR + "Magic wakeup required. Please select one.") + exit() + if gen1a ^ alt: + p.console("hf 14a raw -akb 7 " + ("40" if gen1a else "20")) + p.console("hf 14a raw -k " + ("43" if gen1a else "23")) + +def write_config(config: str): + try_auth_magic() + for i in range(4): + p.console("hf 14a raw -" + ("s" if (i == 0 and not (gen1a or alt)) else "") + ("k" if i != 3 else "") + "c" + f" E2{i:02x}" + config[8*i:8*i+8], False, False) + +def grab_config() -> str: + try_auth_magic() + p.console("hf 14a raw -c" + ("s" if not (gen1a or alt) else "") + " E050") + return p.grabbed_output.split("\n")[-2][4:-9].replace(" ", "") + +if gen1a and alt: + print(ERROR + "Please only choose one magic wakeup type.") + exit() + +if card_config: + config_grab = grab_config() + if not verify_config(config_grab): + print(ERROR + "Failed to grab config data from card.") + exit() + parse_config(config_grab) + +elif ul_type != None: + ul_type_num = int(ul_type) - 1 + if ul_type_num < 0 or ul_type_num >= len(KNOWN_CONFIGS): + print(ERROR + "Type specified is non-existent.") + exit() + old_config = grab_config() + new_config = old_config[0:8] + ("0A" if ul_type_num == 2 else "00") + old_config[10:14] + KNOWN_CONFIGS[ul_type_num] + write_config(new_config) + +elif config != None: + config = config.upper() + if not verify_config(config): + exit() + write_config(config) + +elif parse != None: + parse = parse.upper() + if not verify_config(parse): + exit() + parse_config(parse) + +elif backdoor_block != None: + block = int(backdoor_block) + try_auth_magic(True) + p.console(f"hf 14a raw -c 30{block:02x}") + print(p.grabbed_output.split("\n")[-2][4:-9].replace(" ", "")) + +elif write_backdoor != None: + write_backdoor_num = int(write_backdoor) + if data == None: + print(ERROR + "Specify data to write to the block.") + exit() + if len(data) % 8 != 0: + print(ERROR + "Data must be a multiple of 4 bytes.") + exit() + + try_auth_magic(True) + for i in range(len(data) // 8): + p.console("hf 14a raw -" + ("k" if i != (len(data) // 8 - 1) else "") + f"c A2{(write_backdoor_num + i):02x}{data[8*i:8*i+8]}", False, False) + +elif uid != None: + if len(uid) != 14: + print(ERROR + "UID must be 7 bytes.") + exit() + try_auth_magic() + p.console(f"hf 14a raw -kc" + ("s" if not (gen1a or alt) else "") + " 3002") + block_2 = p.grabbed_output.split("\n")[-2][4:-9].replace(" ", "")[:8] + uid_bytes = [int(uid[2*x:2*x+2], 16) for x in range(7)] + + bcc_0 = 0x88 ^ uid_bytes[0] ^ uid_bytes[1] ^ uid_bytes[2] + new_block_0 = "" + for i in range(3): + new_block_0 += f"{uid_bytes[i]:02x}" + new_block_0 += f"{bcc_0:02x}" + + bcc_1 = uid_bytes[3] ^ uid_bytes[4] ^ uid_bytes[5] ^ uid_bytes[6] + new_block_1 = uid[6:] + new_block_2 = f"{bcc_1:02x}" + block_2[2:] + p.console("hf 14a raw -kc A200" + new_block_0, False, False) + p.console("hf 14a raw -kc A201" + new_block_1, False, False) + p.console("hf 14a raw -c A202" + new_block_2, False, False) + +elif signature != None: + if len(signature) != 64: + print(ERROR + "Signature must be 32 bytes.") + exit() + try_auth_magic(True) + signature_pages = [signature[8*x:8*x+8] for x in range(8)] + for i in range(8, 16): + p.console("hf 14a raw -c" + ("k" if i != 15 else "") + f" A2F{i:01x}{signature_pages[i - 8]}", False, False) + +# Always try to HALT +p.console("hf 14a raw -c 5000")