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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 bjG=yEHDCbUG7c`z7#SGi!=d85=zaqL`?4CG diff --git a/fpga/fpga_pm3_felica.bit b/fpga/fpga_pm3_felica.bit index f79babab68f3c97bee1693154350734905216534..b8f8e80cc20bd4fab9dcb9b29b0158e300ec32a1 100644 GIT binary patch literal 42176 zcmZSJl!c|0I6XxU}yl*oCXXE zAR5MJG+=OG2w+f2W)Ko!zztX$z;Z;WQD6eA#6>{NAx4c5E+atVF!zAixG+?WFfMtJ zI17kixG%uiz)-*-!obeJcz~gRL56{ifw6%hr9m=*$-@4~0g$2tte=>!G}tOEIl=si z;Y@?E0^bSdGKMql#teKPmOJV#!Ss%yyFu##_YNgKhVB-n1$=v$ z6j%&EYC2foFic|5Vc=k3YG7z!P;lTDV9H@y$+EzK%Y-S35p2!@(KQO44tf_jHZr|Y z>SoYt;9kJQ)6}h?6u`BCsemN`WDbMqQ-+%ib`N+qOm1O0;bF9aufaG^;Dv_~1K$Sb z02Z)04;Xha{A0B@;6I`Kli|OEeS!QJ=05^|8|)A87clQ&0Gs0=(8+My!6<-dgYrv; z-wbva_%|@WX*kJYH;I1(^92?KkX!t7&I7|POxld(NW^oU^*+v$e<&_ZLsJELjXvP0;3j3YlGqgj!LF$ z3|l7XKH!dE`^VMVrMrN;g6Rar91jkThRX~F2Y3va&oFFY&}rZ|WImH{SkQ(;-hlZM zB;h#7?qK-Cu%AW!fbs{1|19zj{0G?oF#Kn-ci{WLJb?}DuNACE7!EWTEZ}KiR$=(e zVC2Bx!2E{cCxeZG&PL`}kofWtoWax1sOZ2^pd`Vtg+Z}Fx^NjaxmDzzk>M{%RdGE89WP^e?jc>VEDlB zM_@mL{sHX|41YNGGx8r`e&Fzffscv5fq4=GIQ?)4JYhJ=VCTp`fmwy&B!iIy&kSZ2 zhMx|04*V0C4?*l|U~y&WWYBZqp1`ES(8!?Vz#+k;%COTxFMxXj(*p)@Mweh!W$0wk zci_IjWx~|UpnO2%B1jE~o*VZBrelz};9wD9Xl2lK;0|B{F%%s*0<>5JHZtfqa0@jZ z0v96;J9rb=PB7Rp@Ha3kFq~j8V&FNztibSt!A^_6iTNEfINoQ-eqs8{Waq#y!2E>a zC&NCFQ@=3$WwHk`p0R<`ss!s2rh}kpngCW~=fHn~`3ciW20I6y3CwJe__7dqz|p~= z#K7Ia^nhUpgFc9HfT4pykAdp|6CcFy94uEDS{V`@xC_LtFl+@mUcl)KLu*@ZfhVWiO|GB=-cSFAcp6`3_MRnAjk3u|Q-g!$}pR4E_nsPZ@qP z)FtrGWv#fBe=_6+?E%pVxeFxWHjA8O+l<`kN;zEPffuVsxM}eDx%L$Y(9Jno=4uC`$xEYvE!Sa^^Ll2`W1GfiLfWj7# z-wT-jFf3=$J+mCKH&FYeg(;29I{K8 zelS@v@G~ecVEMsd=fJ;#@dd*VCOZcH4a|oi`OAUz6T{yY`z7u#nC%?SPB2Vy`2j8% z?NxYAFoOzF1_p)|f)g0F3g|X)CveFyv~nmqoCsjL!_Y3Edx)!0=oKWK85pL3qN#~H zfk}p8Dx+f3iU6iB4DAfM2RIy<4nXo(g2+*YjR$l#a4RQ6~ix(cP21DV0gt~ z*Wtf``3cMK2D=0NEzA!f`HNBD55qeSb5Qd8pm2`CegXdl45_{e>JdXF|2M-JHWkx=?z1-fL;st z2BsGd77R)Z9GjXRK;nXfL4%>0LE!_M-Ibl z2D1bH8`$44bTimB^iNZgMEYi1yD%>PGih3 zAo=S6<3EPu4fYRsD!BhK+;1>mz<+?*fZ+~<@dMsZ%mu>WGP8jJRBh=caBN^&!=TN; zwSc>WX&J-I7VU#vf0!0P!l#4v1w#{q-T@8+K9GA98n_vf8WnL ziV0l|dX5}hnAS9OD=2jcZ(v%ZxROEZ0M`bl4Ul+u5IqMf#~L_xGM{2N)L`_0e}nU4 zh7(Li2Y8X{uYI6Wf+7C{_b-Nj4fYTC8+WV=t3dL)QYc8{9jX^B8V7m>=Ny&|CzG z3sC*FxkKTCL<0j$!@>m`2HZ?c91TqhY6mzhn0CPOml%V6hhmDv2_`ef-U*2c(w%e`0eF{ScO$*g9}9aB(m-F)Y5qVZtN?G3NloCWa{u>I*bZa0xNAG$U^ zX=2ucfwzG^)je}Za%{Bk-^SEe*!bS{#wXz z!oiAx?|||Kh8_iDX8r@r9~l0CYJ3K6E=c~;VAWt~b5Wkav4Ba7p^HUf1NQ=<7Yw@? z^cuJyFndAbLW9+ZscFHA3tStRdKj7+7JT3~U^=6)nL+OWHvz;uCS3q!&IjszwKNd8g~Im6VNs#SaB(m+GN>?c891F_*vzot00#pXG=DMh<}mDJuxsFFU~XV| z!C-WNe}nQHhTjaJ`iq@`5!^mpAbE%350gCu{|9bxGoGOjl*Vcp><{oCV15Bfs|Q&B zG5u{YX5cyCY{c-V!Q6rO2eS>sSpnl0JinR^Ao;67;0;43gMtHZ2a^)Rjuxc^E(2wC zht87C*Es$l`d?>Qj2RRdIv9i)1a>s29pE%z@(^fLQ8~e}0jdAR(A=ZsqA_WLlEMxK zrAOKuloTC08I+E2!24egqHh>}HW*28tT9$$_`qP6;M>TZ!*ue1*+CuyZg~G|57S?U zy$AGHIR6p&JHgI@?*{WVf#0C|YXhSH1&a3~CO!pbRhC~&RtJ1GI%_c( zlD|%{>L2L*p!9-cKa;-0ZU%)d9DA8_1b#Ctdcg6G$p{h`4_I?RC3Yj109QB1=7J1I zjwwt$3=K(2Cpb))EFk$yfl-d3yB3MLMSIUXE3 z45t|sj&KPw_o#jrvhZ*b2|@CggX|uLe+>3V_&+k%ID8keKfwQy`OCC_ z0`>>^KQLc_v=3LXf@7J1XM(c>!-)n12fhwx9i}&|MrV39Bl=%^n0gr&HgG5~Niej6 z3hF{89)?zig=aVlnJz)n`~ik}4BI;tlQ<%p!Wh~*6ce~2n4}n{HYhwm?th(OXyj3N zz+s@o$I#rUaDl^M5ii4J2L(|5bqSKcK=s!@2IGb`4}`0l&M_EI;9J0LWpqzqky6hB zCV2nr0mBbQ8v*_XP}bwv$Hd>L{IJ1+!G=M<0jd6CI>}&^z$YoF!tipTjf2lF<|7Oz z1tw|m%!1cnEGbN#3{4IkGo)BS9kB$B8A{I_RxVhTz#+p7tG`%T7#bOvBD5w5sWPl& z(DUG!!1RQ*n_-oM#uP;T#p2K?py0wGz_f&+nL)>aLs01q!$N@-XE=nIoM81=BEty> z11r9+W(9^940>8VEXgUl_hFu-DN(~%Knoc%sa!_jEc*11C(AS|nf#V4?7bIV3u=+VQ3oJOGVZn5UVKalq zg^Uesd<@MT3L88uoZ$7B07DA{xIK13Vh@vs0!IOpEMp79!U-HHOi1@!40p!z{UFk{sqiW7(O$Y&Cs8~e1_>agSCVH2IeM6{$gPL zQ&8mKC}5ItXjRd5&`4m8 zacEJnWY8$!g4QVv3=>#%85Eg36xc!pTAB12v@SHsGPFD7HgFU$Il$_#Hw;}0iwD>ocnp}c zAaUBj@POf`fE|av1osMuUkY{~d8a79Vfeuy6~Mm*ss3U*#{g;O#`b&YK!$s+U*95f{3_BF` zB37s{=`wU2&{E*o#)MRVH8UtMa44{~Fid99YhYyH;^A7rq`|;pzyzf$} zIIj`-&0u$cX92Sg!zm^!MxG7K43PHW0@*)I-@&yNLja^A1Jz&unM@h@KOpzN{x_I| zh7FAv?lf!$MVuHzzlJgc{{v=t{q>GvcY{)b#|$GShR!CXhaMA{)-bGpq146wf(t%= zc!ps&gIWUz0}~6w1O_dJ2qj2c9W)N01Zf{05M9H#TS4grM@NzZLq~%a3&#ScH4Lj6 zlsvc_m<%9s;UM~+;diGA15XFDM!QMcxn0 z{}}$Wn;+o6!TitRH-p^+{sY|b{?}TD-w9?8Jd>E;1l(}219eN^Fq~#E3gA;r4AW)2*S5cQW2!)6B^21XsGLkyEVRBlK#D7|6mW=v?{ zQb4M|9JWu;Pmru+y5-P5L0d&LlIb2(dxPo&jyFQ^`iq6(GqX(xuR_xihRX~l3_J|X zXBZAM7zpq&FvIJwEe!uzY!C1~V7?*npTYJZ-vQ@8paD_`ejh~rb%NpW5`zZ54rT?0 z0}KWNJPVlh7)~EBI>6Jy4DWx*uYW1CBxLtu#kzXfJ=s<*+KJwh65M8|22=f zy+QFI#~rq93QHT5A9DO)l2d4JNKD|+;DYzR&M-D^P`Jur;?&Qe%%E_P%aDnWp_xG| zf(>nb7=%;iZ7l0iFrXs-Rwg1D^mhtpCO0*3j9YQ=&16>4`*R zlTwn#1SWNcoeX*o95b5W^_PxAr-+^tsM-Q0trHrc!A6i8u1QGs7egbc+7e(o!qCjH z$iZVG7n{RI4INO82F+hPcoP^v(?I=G1}{)2v<;HK7Kk2EI_P8&z%zlF zh2@}=RR-S_ZZ(j55_%=zpSB8o0Y4?L!S#ONM<1ls9msFj+D5F(_{8P+b*nC@_F zSI|GeQGi^3eQz*K;K^W)Vz>fgR489#_`hK91HKBx_~9CcUko-5{07V&94{Gc9Mm^5 zKVhiOGPuESfT+LzG5iPBUq6`Tn2tAWT)_83`Jco8PUC+pFPIr%_17GR?F@<+G#tER z7}^vRLC&}X5;?+E1nUzqFic?B%AnZ5mC9tJFvmlgfvbS&3gdPL#Zw*yZ1Dcq0fvnZ z3NyGEocI|gG$=S}Y+-r=>Zjb`LaM)d9e#3bYUGn(;&-^rV6cJj3-cR={|v?-dNy!F z>&ga(Ck#J$>?XMGP=3Skn!&CGG*<$eG&#V(0a1VbWjGHSc{`zO$8oM{^8vmO&7gLa z(gFSeME@&|VO4{k0`~?jeTHsfr3YLal*<)XGbkP4p23XN|LPFYO9VBI96K457I17} z(qn99SapDV2crL_!myx0$APwv&pg$@S2LmdrHIvgDeN(@~qxHKkoHYhFN z*unGwlD|%{o@Hy|(d*z~0yPUc6c$J3PBq#v@ciPo=Qw|0lLK!Tw>rbc11b$Xpm{5BK7GKr z2ULGG@C$I?Vfw&e>%{+q`J2-p2I~fWkO#qIo(=+Q7)}WoP2g#1f68- zi(QKj$Qxrf;FdO5~G5Hlm!!O zLj$N$p~l3^(8#FM&|$y?o&VavD91E!g7OpYN+vs&mPVG8&PXOXr}hcC3pkMGzjPQT zF&H%PDKPP{9Ael2s=wAa9u~0a&_SC2+QaaUVgEti2mEs!{s`GS@E17$adERf zu|wenhe?+ns5jccVZemc|2lWTa01T*W=p2NMVlA!K47*`I_I#tAqQ#x>jBdb2|FhJ z2IdDGKN##-_#2oXG5kmX%`Pz_jUR%iz2ng}~!1S&k?^Is_p3mp~( zaLi#+<>&;>e+g)*F*Uk?M(h#uUn&fp4oU$WQeg|*zf)h?g0y=%o%nMGuFtXxnU{+)~!LW&ezX37- z^{nCl0{a*Ix0wHdI$|080m|PxzB1W5Fr$ng9t6$9&S6$-IJ?5wq2~g#3By??ThIvJ zF-Uw_2s~=&Sg=Sz<0Y5UBG5z_2dHthiXjg)5q1uezdTs4ENEo_P1Uin9BO1xaF77i zUyUp|po!)~ko+Yf+QQJPpy<#Mpu_?mFyYvsq|&gGLC+C1_Xo*e3q+qVoooOP&#E(= z++gIOBf+fNa8f`I zz;lFMp$RlU$lu8P@WhD(GX|apX4w1}qXttKgVF-t2TGa@T@1<-xF0b6Wa;bJJe6Ys z6MX*5kD-}CM=f4&(N`m?Yhd3W_`G5xQ8o3LY?lAN= zs6OH-V1mwnt>Ca@I@VyifG2_}iskr%jSu)Mn7=U{X)t`CgH(Tk<`ir|<;Kc}&kTAE z{1ceJFnwaMazM;~F|dAPINo3kst4^E{&(0X@cvLXVmQ)Z`T#Uf1c@)u{1<3=mIE}m zwVgxP!NY;ckYO2vq6bGI6Ld}rH2>8H>NDPGl2F(#pyR@Mkx9Uzok5X7q5wAkr6BTv zsewa*fkT9gn_;(uP5`G)lM2H^2L(_+1wQ`;8b>lXrn7`um*M|Jy9>PAn3WVxOx^%$ zkVEYP4Z!_musguNfEhG6V<*4|8mRwu06gjc05U$$DDZ~i9D{uWPe8MT!g&VcNBkF@ zKQNp>VBDbh0XF~Dz*51mhC#o9;{uZfQ#XT>2KO4KXDp2_W}F-w5c6Lu4Bem>;{qlf zg>D9=7VZwFM-1IgN)22af?)GsEDQ__8T1@96qq}CL$EztfK zgXmj^AH4>dJW|Xm6+eXRdE^f&8!((T0O6U_=cG_ANQ;NyoZ+a~Bg z;L2dT#jvGC_X$U)QxLuOEMQ0UzZ$kPDmrL52uZMPVN^VzlfVQXfobqfU_|8-VibAZAT4ud9rh7B#?A_W|$2RIeD;Qg-^4F4K7Z{f*lwq*Fl zu&IG}0rLu`Qw*CLc%C5oUk??2DA<9<4<8)(!Dz?e&%k_u=>vle1OI_$c>VRP0W@X> zUbb=ifQ<{!EM~QalMF@~924OEFNU0koeVl29CMjd7&;sD5;$fssWEgi7!_~`z~{eM zRTy`I=2)jVser~99XKv9nSe^a0Imy&{#Og*Mo|6|kOB=h>VoQb&_a%d4qAb*@eNS_ z3)Dm8X<$+Wb;PuIy1JDXo%m^_#di+T|9ZpnmBrqHKY;m*7Rd4X6NEt>7Apr{DMbGZ zBx3BqbAeff=_G@B2G1>Kvxbukj1ze7A@{#_FqkoLEMNu=swfF?EMd|B^~k1ZbTGlk z507x{WY7T3cBwElGN=S_2r{uUG&(GB;4p!&-w0g->WwCHOmIqN0M*@~xj)cC7e@}n z{FlfQhMx)+4mvW->aUpIi|tt&#;++=K*Nii(x~9&LIv9a33EuSPh&1Qec!( z+0LMQfIETd4#O5s-2)s2O)?Daii!t10$}6w8XR&C-$AQ2DwyvzeCe=J;IUxlV>tZ4 z-~o>XHy0#-amcP^_|0T@pnn7N8-^bWb`AO<26*0K0&M=vLG&BL{|4J7JU5yDDf|H~ z4EO;WHL9B3_1ri48Vh^DhIeG zFiAk#Eh|LVf`-=ucnp~DF#KwOjwAhN*mi(tLo?Ja(EL|5i=7kyLQwq$TJp4k`3=W! z1}jis23CIs{BZ#FzjzLC+c2D2V9&_&gZV$G7jS_81AP3D!H2P%K}UeAhgpZA^MsCp zJZQn*ZU(&y?mLL_!vw}|2E7FxGnjN3x)}5paBOmV!?2r0?*aEDCIv`&$ibk%(#)X4 zz}3K{!vtnP{H1e%TLG587&sEZ%Qko#K*NR%b_^V#xcCpMg(omGFhS02qfge=L|7WlTjSaqF2lr(Tuzq1W+hF*B=K-@21E|~8U%_n0aJI?#K|j3z)gbV; zp|eQ|w0=X0VMT*hf^>(F5<^Fe(gSXI|Eq)bLBe8&MWC4x27!f;^@l7Bix?CRcpI=i zg0v402rgh))ueQQV-J@S!wN~QE8Gi&G#FMpD1lb9!24g18y-6tJ6b%>*YNr0i9LDPXF zKv@Ko(GGAIAl7fRGqxU3{=iwoRK(DAK(V09hbf3*TZ8Tc?h3^E!^6yt2Ngh5nZ6Cp z4Vou3ESPv1HV5b&;6_@1xSQb^!-fUAFO)0UP8~2>z@@{T;qa@$uE86*{{>zP!PmgO zK;Vahy#V_e=8p`296&W1V-Tc$$RY4#*+~hb3p@hSpzfqmg3knIwT_=!b^&PX4;i%_ zxTZ9*FkECPDd3qXq&{J1hh72S1m*?s^&1M^97zEjGqh9~dRst)X-y^!dl}R~YZQ_C zUz`jXj#7ebYz%W*)EzVfm_!7(GUR>Qp@ZoC)%}FUS$TV;(K-MQ9 z`d^aY8vcXkiEeRSVff8p>%ezG_z4TBBIBEYnE(31aFo&3f#(jh3Da2ydr(F8grSxJ zyb=uF|2n_{T5ZU|;iLdzG&ntA*acaU2T0 z5nw~~zm!%o=s9#u-~uhe)C&L&qH=&%YoPSM99}l+Bp^WYa?e$Mby!p?(z67!n|&{|Ml)b$&VMhrSl+@STDc5FP2%nKSo zoA|djnw~gZD@#4kl?Uk;#XQApz}e} zg6WjQ=2HsOybT!O{Vy4Yo(4q*P_?Dd!k~LVqrmBkz&-|52Wh1KmlVVCMneUi3NAT@ zXAQOqJQd8-6uvbWCh#Nmzt*t)X4vPzzrp(q$1et(1N;q)FB?8HBCkJWVEx2!W`dz2 z&ktrhmOm5B6+mM<4Cfk+pYZ=ctUsK?&|;v-z;S>{CSfasZi93I+l_#B0nG;P0;K+z zLW_ek3uh^p48t}CRW|NGrXLDx85BWkpz9A6M9zYTbT~PfI2krG=rD1sFdbo7ut39s z8)^MvJHr8o4F`BFxcMDEGnh@_w_rZP^eO?g-~qAz@CnNg7P~2WTczJJ{PeJ!z~96C zvf;La-2~+E!zT=98;n_a{(zP@Fc=@;KQP&f!I#0lfgj%gYGg@o>SWMT;5fph$IyMi zETp@qIh}#ygkAwRQvXYhp+iBbiDSbob%D+!N{6^-Flj7U*`T)(qz2Mf=U`!B=wQ%d zuxMavVeDj3ao}FSl)|t8w9?4{Hon2Yo5FB{Nr{1HgR%;!9An^b5LRJ&0m@B?^@p5a z7`}jeTgoL&{~7EV`VTPQV7LkvfzN-jeqcD$z^%aJ!EMCwM?qPE=LEA6!#Jx*+{82F48xTA+pWObjbJ zR1`QQm=;0nFVSTR-5g3T91Ga=7nwg0DY3#BhSaz=FqCSy`dr z#12qLgM;Z2gIa@+1os43{k4VRuY&zT{Rd1R82&JUi-I4Z8m*cCB69^~e%V1_NyCo< zBNv{H%nA-C1dM!mCNRHX01deDOoI2n7}XdyHYk1JSu3RN($T;KYQ}@wy(SGD(-8At zjt(0f6dE`MxI8!vl^7a0G?-i(CNd~ENUAWw`(Hu~+dw@RPo}a1+glVDNMtgJF-&by zP2$Xe_rEw87(O!?Fz^a8c^vovYH2DoA5pl;XyBl$fZYH3&S2}{f6#r-rQ=Mt4g3kr zR~X(h*gNoLAo^d=7*=}NUE-Hu@L@RAU~)ii12Z4PX-1<1%nK0fH|8+3GwL4jb`TO_ zxz4C~zymap*w3H|YQbJ&0q+Yqz|f|+yjAxB_YH=B672`nA9#V*5rbCLb7vsxuX7B| z3v@1U+c@>GOm2kqzgU_<fq=8R?S%cvagHnS(QvLOa z;fH{n5Pu6(qQMUaI|cp@?uSf280-Z64t)dTjXw;(8SD=* zTwrEn1dnxF2)tl`3>!Lu>PltMu;BxS4GdNc91V#6*A<3#7HtQv3rwJe9EuKH2H^Tj z4>Y&M1Xmt)0fqo8U2kr@MZy0tmXd(B%1fMgU46rKZ6Jc&)I>}@N zsTKbSHSHGkgRU_!F3)D1ho^v;!tREU})%G!}yw|%Rzqw_W~vhg$#t#{r6BaySHefj0u$V#T zfV2UVAH!ybgcsZfi1{xLh86}@25uLziHt2=x&qQam|_^VFHk+iUC;y{Ka^uQ-eCBE zKZEHW$I}Me2iy_NaSY!V7=i{8;p^vDu)JWfV&I=3e1PK@X!=!vdkw?y1UpdM7`lEQ zwEpl+gS`TOg>n(Y`3d%*g^0fd{+}{_!2f~)zJ7itOS^*Z5pG8&nWh#2T@mg=rW*|V z1vHOvCon! znFeDH9uH=l1w5dI97z4Irwtv9N(VSLfcuO}Q#v}Bv>8@0XeDqV^}miPtYUa@fCJ>z zE|8lSF!2a3Vo+j`K$`zr;?TvQbbwRgX>`En zhO(Z+DF!149S&|?hEokj1wEj7s{&Bt7&QO&&%yqJ{to6pO#c|{Tlfz!|6%w8@hQCj z1)8e`r%VvT9<)!2hv6i%5z78A9fnPyxB&TU6NBCs?hR7f4xkmS+(`Ye9EJ@bH4~V4 zK#Heus|oo82r;NIa3l4<=wTI=egZ&Zy56m?T{}}jN*grDvX?V_H?ZW>Nv3}zX!%r5Y zhddjYb(l_p)}3-N>j*S5F*WdSK(4>Q>%$9}Y#7=Y6dSnkk#SHe^5G zs9=&~XzS1f?f;Tv>Tl6ZMDBl`Vc6NA@gM^5uPW3X=Ff55H5bnL+H1^h_$*8_$h3T+Jh4cZSFelVyp__r`0RQSQb#=wlUe&ZQv zbk~tz05t8zV3f%-gZVAPO9rC?$4&<&P)~{lG|5%KF@@L00UVQe=| zpi+XX8?;WwNK0n{^Q$E<98l-KzBT*=Z8f^V{EgvngMEVk1yI?t0CoS@7lxmVMvgo) zm{l20G8hH$1Td>G9TnK_$TNW%-v4^QvSYy_4UK*!rIwB+Wep9`AUkNHnWKRTssFW= zBh!f^ph={uS3?n0ZGk#s??5>Y*8dV*!mtt4-7a7f;pkOTbl{l7qyh?`1da(z$Kdl{ z3_laB5_l#utFbgPY;y1s;Z{*P*=Q4?gVg_8!tql9v}}X>33yPKe*$Qap#s|cmjkG_ z;(_dsGiLB<;AUVr!C=OK(*JVU1zLXyT6%VhNx6ad5tA0%Ee2tf{a=0zn;Tpna9Dtr z9X2R5aM(0)x6Ec>n1Nh>$uMkpP`$uW!X(Qu-65et@&c2H!h8co2X3VPmmI^shV2V9 zesI|_oom<#>VL7hTyNNj(*Ih)@Qc9;lvCb-_DMM~Z(x4W@Vf!E|MiXGY=eCQ{|#m{ zP@FE{@nAM%=!dMRhu2?o16mby9k>ITL>xfOu8U7@cQcnXs4SBw*(Ul!;T3$0bC4BtPHC`D<6>hUn>|+gGSX> zaA`ICX0V&Uqb01u^j={PsLqAj1sXpDE#c!|$_(D?+R{H|@k@nh2C)Um{jWEmwapDY z3d|M^{}_y&c|I^(Gn{AG-N1v?|H@<72b%wS!eq&?k3rd?V+j+h#(IWTppiHD{x2SOl6GG!i(F z`d>NV`U{+XelQq0@HB9%Fq~$za?nAlzrdwBgZ}|eP(Q`qf&T#W503w!`U`3P>jP+* zSCQR=*`VQ!g0X_`H)flLI}PS4ydF#iu>RM>4Z9fh7Wyq}QfBBpsKmkXjmdUF6NeH9 zM-x*2i(!+7P6PJ@CI+X52^wCUW=uQ>CW6X&hDGrG%gYRQbL3s%Zg5iL>}b$n@Yuk_ z!_cUp62U#A3A%sTLG&5J4+lF{{@u#T3I`jl6nHmq>j>O9V0K}C2V(#77KZ-|?3wsp zFh5}U$so?czk&G=!~X_r1(^qk_47*@UV(yT3bP8s2^J%zegkG5p;rP{0X!}6{uiqX z!)`~W1P%@*mWGZ7B?ayeLiS0k4`>CfSc};I<-o9kK}CVX;1~l-LxVyCw;3BB%Yu^{ z3>-a(^@n0CTMnqd;ds#}&M>t@@d0;*o}9zd2F)!TFX8*YI2asWa+q}K2nsU<9AsE< zL*9T{hv9*P)d3zxZp8XSfxixxC;1ODe_?nlV1I%CBl91HzfATA_#YzbuLq1j6>I`n zHh{L2f+qO3H0vsyYA{>C)4+|G{{oHAAL2-011*cvKcG>>#M98vVA{a-fIA3MjvZi_ zrLetC_klzVXxnmwE@<<$977v}`T;H%#QMXt44VUWu1HugomJR;Nbe%Y1}@P07PS*y z8cjiv_TdZB-44Gl*tO_9asI*ZZ;@RK&jMx*hI0|-Gx**h=D!{={9pmC)^L8nvV#M} zUa#%3ID{y&m4X#+8yAVp{xp8cmtZ3fv-PIVc6-Q;J`7b>1l^$ zqh0{VTqYH!&I3vTpm6}`_#vwr!^Vz9;Gr`HONP7vtqEN$44ns*95@6K^It3u8wC_V zB1afDHsl~J*Y7pQAy?9hLK`47WSCLrV&3>lUO4I3&Zat1MlENJCWJ;WWL1gpP9moaQ) z0oBw^piN#14x9l@AskZ~lwG(25cStnhMxkP68NFRhK>;T955^3pQa35zo7x@f3e!7 z@Xrx`vf=XyJ5V2O3BykTD+k^Q&0di44Gn>XOeYwOIQSbTKVUlHXvfIgz`TIrM1m0m z57PQW4URqr9d23$Q1 z%?(Ny7z{YNAoD2;82Jp^SQZ}OE;w{fpp8M-fun?pgQ34e@c>r^lLlnWY6XWF185$E zzk>Z9XG?>91MdUoX)MPWHY)H0GOvQoe=TA7!Dx4Yzk&G)!!Hdx2hf5CrjMYG*d}Ip z|LYsmnFjkO{5PEKI6(7XygxwmDWDaJNb3)`I2;ksJ-~fYNXB6cgFXZ22PR2|Zww0? zxC+^zb5aZpa~P&L=w9Fq;EG{b%Ani8<-ioe&;nY4>c=@Fl`jL%V561%cTpx{!m4vLqYEZb4QaJ!wv?O0~`WvdJLTm zi(J5~Bfw)j94ssx4WO1wu|JA`@cYwP=NrmADgIPn* z1ZD=%f@%jI2Kf3zNl5+03tBdRhrt@OHUqk%1F8S@kKs;(y#db&<|3Cr3icd)Cz$OR z{x;Yu^!!l%-~jIL91wWN(5;|%f@2etPQ#7~dSAF_Fo6aD^%6kq4z0 z%ry*u8SFvp^x*xk9)?>CMh*O+<0*bKY;NLT(X0d7<>%nDQ5in}rQ_7uvM32Wa=W`j zseyY7lMchK13Cvdb|B`zJQya9Cu~DNDw6>XHvx~wPE(0d0 zIiUStpixLvwHCrbfVf2n|0YaHOdz_f*7Cx?<3XpM(ZC#O;X$3&(!NLk0hV#3fapyx z6Jdz{7wEJNMsCpluN@3Z4H^y12@HD}^cc9{{VxyJCrpjt89F5vhP@1mjvN=5L>RU* zXeDq6FkOO-&r67|5$I&pbMW54q{pzGNeR?PvtiiFr02jjf$13}F2MW0K(h=Jm{k;h zDr^dnnA@bn@biL&Ll3C`1u^ralJc2JS2-ONOo!dJDMqKnpp1lxOfhVLAm) z@(dcRPD~RU6au&{nAi_AJLn$Z0?+j?NI1YD!E_1|rwbSr7+O0OL9@4Fpd&0CxE*I(KbOIbpFPdhK&z=JeW^0f%71T>mRuC5@325uQ9UXG0{3J%;NO&knO963xJ3QRX3X?}(10)~?ZHhJ*NFsp(F zC=B=~aBDD~aIg#DoxuDGQXV!iJOzywI`L0wewy$LH0`yJ`6Cn-t(J=5DutUviC3Vh zMah7B1!DYAMWDMw!GU9jkOMZt zgHi%_4}%s%cZgmh*B+*~5B3LWU*Px=WB>{04%Pz-jSLD6-k@X&T0F^N$aH4HW)1K< zA|putIv~2jq+3C00rw0cHHOs+dI?+`nBFjKX3zqSOhVTmI*2}GIOSmUgTLc2Gt+Ah zn;;%g-wLuX8?pYdf$4vv{Q-Ul<{t|8K@}Ou&A%DI>m17XzX^>d*D5X=`}+)qn-orC#F9P`x%rEa91$H&)@J6=upsk zz|F&Ul7W#y2ejP=w9!)G0Jou%6(qhC7{xqV8g)~+BiMd1v^VG{aDpm@?GqHAa91$Z zK*~%H4i={-51Rx049rKH4l?LC@ES0KPH!+cz;D0~?|`~`^q*As@5EOrlhW-xOwoNn0kfM*A@9>Zw^vj;re7Q_2rEds5Kx(*x#ToRyV zy`UveG7QTFYz}dhFd0G0u>%bAKu-OTV8LY1(Ac1?&}qfQ$I!k&^$mvwlLDlS@(?-C z)cimvfx}3MpJ8ExLIbA(6A!~`@IooX`ompK=N#;}@H}CbWH`6Lc*5ER+!_q0Sj-xD z7BC_9e;ND`uw&$JV1A(VL&c7XuLV?pf!e(cNb_F|Cl`Q63!iYD1Q(v*4yKU<|3vuu zLzWcKu%ScuB(^6EHz5Z}Jz?0%VCKj@g$q`HF|sf;GA22APXNt-G3X_5gBUwOr{W;* zUv_Go02(b6dcrVKM8`o|fa!?CMg{bXkpi+2TUhk*fI34YJR-n1A~zj ze^)bn|JS#M{|j^-`7a265%{~v_5f(ve8W!*JJ9J&Nd2#~pz}9C{pFJk<_CBL!0RQ9 z9C$7;t3k>`@cB`o^EVop9x#EYgBzG0I_zKoZ3IK?Up~SBS~kxu0rr=g19;36RQYNO zFa<&KmxO35XtiMi_XeXU3@aOwJV5I=SU~GHIFa@*KVv${XjBZ^($#R1!_2`4TsMLH zgUX2g%MCvl>~rAXqx=?B&V#o1J^?fM1ej+*%0uw^Q4U57{7vi+n!pR`o0tZIzr2*;2aBEq|Ayv+44)Y+8u&LcziIgGV0VLG0nz{Z z&Tt+yet47lI?MSU!v#DynE!#+nLqH2P=>8P6r97*5}|v5yU=+C$YD*qp!NLiOyCn; zRzdO?1H&|i76)Ay?m#Edu~Et`905#sKx6nvI0~3pAo)u{;9ns8hT$`V-GP44LVEDoN1*eg&M+81;Xfd3p>UePc!kad=RXXmK?^w^ zFhlq7HnOC!bT}$GaGNl7fZgMk6b^73Fs)!{W>IM1P!Kx9&IgA^04ltKF{An~~=sC;{A3uEW(V?gW8byE5(cQqbfuo5@1#}RH0mnq91(0y= zU_B$yn6!w6OMvOXf@TJV77xQF7ls`S2`n58T=4P3H4GgNN(o#u*d8);aV!FDBT{wP zk+4XCV@4Bn{RV^R9flJPn=14`@paO{0OYR}hK~$J#vDC}@xwg~KOO8DcrP%&^Y}Z# zu7Up|^Ec4^7o$GX{^d1}Zv%`3_$RPGWO(Ud)S$D8`EA2b&_Q;si19rh?7nZb08VPAvl1CI#U`d$wX9)^Vs8yNUO+cO`7mb)7;A5j2r zWNtu=AA**N+PCn7;_I6LXlan}ABS>KjfOP;^@rgF%Yp+epyj5g7;IX2H!vSzIK)tV zfad^W|MFY`P;ZN)KksEKY;IBp^$_ndEbq`?$yven zPo=*>evjao!t_L>6VfbXVd!LB zo-&t4mMbU)||lWuNH=%3N{J+vxJ{5_}O6N#4p4Al;QVD z8wb4!%<%I!7BIYUFk;|oVP{}C;b6ze+XFh#%)yAkrv%u3|$9|8yFrqaY5=G4OVA`)ek^RgOV7%8X0r~W?FFZDl{K(xuLM32|j+vz|iWT ze1M~hOMs!BL)VF;z{!T8pF`n*hY4c*P>A7e!^Q_XznFv=jyKpU@Mbv6NgQw3x}{t{rA z0h)R~#8J?6fuWy4(-AZV;;@H7^#W%KZ2f$O$O(o9rv)!GY#0tIEN)P6kYI2-;V^+y zCxuf3aeh<}!)Xtr2RdJrUof2BVgYLZyl42`VAkN<02@D?!0?jc185b>CgvC54FdWT zn4dK4Zm?niH54J~r$OKy%ef^62Y5a(TQHn=us*_jfw_hup1~jiG+qZgKPn-k(?JQe z9h-}xxkc#!#|$nNhRqC00UaHP_45i$T_80ZoRpZlnY0dY&S27UnCcImX3``6RyBHE2G#Fsx8x6b(31M?N8?+pA5?4W_fJ)rq7#P}iOJ%%R@#vc49m~9yTG8i-P{9rZ$&0s!| z|G^Bezutk?A1d%oSfnGd88&Vla-GpIYM!=(Sixro6Q+!15?0m&Dj{mY@@7KChYMx4LF(8#dLkzMuw8qx=tS)kY;$%=d#Lzuq!3>Udi1mjn7+!T)@8N&L{DR>egYkkjE1E&;JPkMSJZXlHA3kLI z;b_Oe-Y@|)?PUepzx;sh1%n-nKFayMr#Xx~`X?zpmFQdmKG*RL2PnRHCLsD>t_%wu zlq$GqF{Ltec4&dBq^F=BS%B6QCs_ZBRfVCGL&?E=gVGU&m5j>{a7)(u!9$LK7f%He+%>B1utG~vC?T_ zKFDyv(LjKwg&E%eI>PjkalZreE#YTP_ZQe_f(rbG+6DF*i26%{^$Ej41|!Fw0On@~ z-~>5^8FW%Hcts-o{HO;EHyF$qIU1B6Fmy1OGjOb8Ucdxek*IM1ssF_Zs$o4O*sd@v zWl(gG0Ii?z1u{2;F-ew3?u?NXA-{ta1Fz62AdQ76E+`VxXEbep+A9n31lJ19OhH-{+GfJ1#oW* zw0_>0fv1HVbiOh8pkvtnFIG*4T?~sFyg{|{D#pc)JRrs{2Bii34-oTTo(!8E6mE3e z2>CEDGbkx+)&_a$E48K4fu?_Cf z@k0jI|DfL1Q_zt<4CkAS6~Oh^U(gD-6UgUpv^yxWa0fBn;b3P_WaB6}bcbOpgR%pc z18hwoX#T6+LGg%p0NVwI77kSft^%eT3R@TynK*91>MsS6GtA8l3Ig05O`s!-mkDrL zFr8x9z@Q+&C5t$}cMa%toe%y3%x@S@H<*3V`vKbIwZQ0#j;bwxP9R7sq)9W2X2E$_5Na7&e0{vL6ipn2Z}_KEUU{SbUk9 z9To|BcPvt1T*07pk?R8!D0~zebT@D2Q+>dzyTUV+sL5g;0YT2 zhR%P1cZq_Q(ef}XW?A8&Wx#fZVIzZ*BWDAm|CPgVn#qWPyFr;jpn=K2foB8r3x*G% z`fCDw{!8`_$9D#M&~e|Op=ommP{W?(4a0s0z5r(U`MviT&O%n#{A0Ltz+Qp(1iO*L zl?Fo#u=u@7L|kC223v)CbH-_ za4Rq|G)!R7;ow$4KL4=01-#qq1=A`9y$iJc zgTYRbUw}!C;U))P2Hy&#{ugL^=mP%-=08lJ?Y+DYn5#1WPTA}LI-L@dzZ?WsfX)F1 zt=2zGCcvb-q>|U^(Yp`A@^9JesUeIcJ(8$sOhBplM4BQ8p3>AJT zv~lRS3M0;+Z#ch+=>i09LzwDzX&?9R)7y_{O}LcR|b8Mo8N&Ka_~=J zUe@q+fqe#V0IdHd!Fq-1FpC*z**s_~1o-^JCroEq>>YBD&OZcITS}n(4mn7QyMeiZ zv4aJ4elKGE{1XQ7RvH1OCkzW26&$$*n9eXZGN^zmGWhz9B@CS`N)Fr`gr2Z9GAKE5 zPheK=#x0s99$H<*7h$W5>Zt)KtI z;NQT!kO#H?V$?mr;h=QGqs>D12zMdVg@kqmn;=q4k#vLt}%^0dEDSLjp|<;O(LZ z7@8SX8fL=#U!e8#CqPlq!1MytcK~g*KEUvbVON9C1Z8;t>kH#Q7F*D1y%#{`A&BvT z;X6Zh1K$OB{l)r^;m-uaCp@6O%%28(9-qU^e;U4n4t0M4J3p#H;2pzG1tkv#(01${ z4SGBr%}nn(W`a)9UjREls)O|m!zR$$cmoDdRjbp{p}=$|a3e#`0ci#J{$gGa4cwg12g9Xq+b9!zxOwT!37;qU-*ZEodeGl(84W7yAS*em@h!)D<3fK z;rOetKM7PFGyG$cchJ88x<7@%{sa31X88Qq8phuNb{Ay!IKOKAAy9XKe@pWlhLfB| z7x)(-#t(Hs$5bB3TEX;!amN9@3w#@x{;+r^Djo1!z`P04k9)xC!?2@4=K_ZT(^=43 z8V+s-rZWtJpmmiBi2YxnW%K$=xIoihTMpp=boi21KKprf-7 z@N80k!E~U(NCLb-1vDj6at?iYyjH_k9@=FmCBZN_whp}|Ap61T}DP~<9rHY6gCA0Duu zz_&yh)c@K9ntpx3@QGp51pWso>kozO7+Ar_MlskiFt;#+50V1ip8_90d;*?f;n~1^ zgym!dc;mwphMxzFKnWYR{*WPsVP}I*0M|_BHw-Hsz!#}JVc6MZr18T9h93@g z0z3^$k3iQ|SoQZUezf2RgOLC~QvVAyerOAt?RpA2|1g7p7BlFyc`LO3*Ix$EDf*zL z^(>$z5X@!*e;JHG6$jG#ja>|S3>uL8Q}h_P7ceP;=D!wjH!z{BKjeW;d+|6(3$V3- z209$2L1$ip*I7x3g3jO20Z)4|fR23R7GzTa?HvHEctJY9_vb-72c2o4=|vB_1pXP^ zO9VbLYy!ClQGbDU#6RNSp!@{HIKjUGbgsw&u;UT^FNPlupTNf+D*gbiDQy9bA2Qep z=pe1%&|ujGT8^@a=|#)Fg?bI%OBpQ~`Wo~=bEEL{qkI@PGbl{p;9xq)u<3x#4Q>k- z*M_DOItMuo5c|Jm7`8VkgO(v(VA#f>+rV8S#KzFhsrvx={3tnwzYU6@kt~g?#?uPX@bF{B6xIKoxWsX#R_-8dQqH?@s~k|N7csxB$Ej7JRqG4dzb_ z=NoMw==|u0j~~tfoo#r5BY`={_Gw2`S_<+>^V%Yd#K~IMolMlm2P&v=QbcP93@^c`af4GL>cZWfMP6O!p1rECq z{td!!7*2E8U*P!wt1Ch4=Rv#kK+7o^emckq_-|l-3%Yyg5V)p>^}j$X9~^iNfY!<~ z*n99iVE)%|9<;U@Y5jZ-$Ib@50Imk5cL_TW=ou+&P&&iV9iR_t;ltO@>wuQtC2)Y! z>dp>52960#FBG~#wcZA#{a*~A^KG~km=1uhC1T_Nm6^>T52CD}{|P!jYJoE7`X|u- zO63*xQ1Mhg?b<80ssV0S>i0G)pb>wmHSV>rTL3~FZ7fF>@3c#nbhwKdp3 z@PEV%KmYI@L&pIn&?ujZKzDHnV`w4EY3^Up^qZMyQJg-2YMldHEEF0h0<-H;di@?hXcM{RLWoc=Ci1sGpL^ zaGJ@kfVYj=1k}1X$iIjA0KEQU_$#m0QVK9|E%^0_!|)Q*E)tD0^nWh z4?!C!PVg^aUZ!wb!0rM69&Y&h4IYNg0wDJ&yLm-lUtr&B0^_>5+Lb?LpLE z9~eNZHJCxi_S!h`8#1>zfO}*r%#$GNdmUugu>4}MJHo$#`3dMaVh8>W&QF}cJ3AII z2SCopUcvefbRE?J{td=YIKa_8iTMl1X-06X@)cw~zzM-Qpws3-O};Y>?JT+nxdSF$ zZvx#t#9hGj3DQnzV1S$!#a+R4jiJ3Y_n~)%(lKa9jOhW=_#tTS&w%M1$nT%HO`47f zfYYG`^7vuj1EU811lPJ!+f0yPv*vD!8AKWIjrKhN|5RKzuaro9+I=N~$Q1`_QU z_&X5mH=cn`kp0@G8jnXI5Q0NLRCzd-w!7dUi-mTfdLf$J~O>Im@p8}R*K z8K7l{pl;XWMJE{StoZwyA1weKM8@CM4Bx+egyAn6xK#hv06w;Nmhu;-pP)(#ets`# z{o!8*BT!%FDQG~zfoFpA6EK5+0@C^o&@C09{?{u|1DJth0n-5%(1{$}4`BT-4_45! zdGI<1h*LQPK=(

N;`XZu&+U>fg5ywR2PHZ1dauufez69DM;sU_&IDop#VBr=nW{1{opiWI>0mm zwEL9}x&GS7p?H8hLFf)cJA=*y&H|x7poNGBxC_|e`9e}fsm{%{V%b^(ZcQra1G8Mq6Wt~j)UDpvUVLkWgC44XiAtbmsOw!`i$TgIpf z%9YUlUkM^-7&amKU5ML&=>TZXg@K!c3A+Aphv*uH-v>5<-24wzj$PoN!2E#YC1@9R zYctaRug?K?2mBWZzhML4bqLz82kl@&*UvWy{A>8fu=xQ01K~Fu=Ry6i6U<+j&VtV0 zc)$of!?uAXi(%IRCQ#q%4@)Ms`1wq;QN3$$!Q2ef|v2}47J4tV_`WN28ttH)!8hH-l1xH`4xP(5gJp@EQ|n@=gb|y_W@aAZG)&0Tb-}DA6^b zz1z*)4NVFR9UOWL+=5*^49!YP4cr@$&hG`)RulS{a4RvLaeM=rqG7&|o#9 z|HT4Yj>4}Xe8}S;qkRhh2jM>q|5-o_9uW1{8n8M1OPLiJPC3{y@NZ$(aR4oJL7d-v zf>j6Po<{B!Ov(;j40;UQo0xP2x>=MOIgs`*gW88W2e@UL_&`Yuw2*_3p_xfx5(m=$ zWzg6==-NsqIfm8--6z~XnBO)Wgg z`{(ozkV}-?)L;8%gwV%;jn@c=zL>n z{pBGDIv-o-0EYpS3_~-6BB;qH!_dy5h;)80X#Q(EXxmo>XxU+d!UB#8COeh+4T&HI z{QgRCoF3pX1YIT2q|m^j%f#0-TR;i4@CNDp-d_xM4LU2hHJDB<*xaD=fLnv%R>S57 z9pwGXpz8t|_#430mOW@V<^jVCCOZ%Vt^WnSzY=ueBhQ3p9f8vfMj1Rv_gAJsx)lOK zYD}FBNr4;_lt8ETE-K(aoF4_Qzw{iqFK~&p^s*=ha0qaLH#>tSgHY-(9nk%iqM(De zK=WTLPK^Q!Tr>n=2{) zKqq#?*Uy92Z-8&u1YMf~I-iXrfJsCEG)k<2*8c)^OF>>{c;LWsi9=)p=tN(%{mYON zTZQ8#qfr3Qlx|hfK0D9}A;|qNMsU3hZW}xJL)IvQMUeYnp!RbEvjV9863}U4R%kc@ znm|YHe+htY`{Kx8(hz84(QAaB>!{p-e10z^e{t9`@h~(o>426Z_i{9YHZr63zZi5M za0E1gdd#{9JT6F0VQ6<$2Bl|s{Uyiny}{-Ij|DRu!?6Y%&~+#CU>lj?=Wl?^gai5$ zxF3K9F&g+MIHw4JZhq89>3{uium-i~?HSHB7(dW?!E6OOd1(O;%K1^?^B4=51X$WY z`OASx0F;nG?t#`Rp#Il((1|w&Od+5#&nAupCP{@B4#flxr1N_r`HS0t$pO^(-@w7p z1v&r|e5V`I`Muzi3>R=`fQ}FP!n2`Shv9UCL4yv`{gvSA_yGSF=1zuR4EsPs7E4%u zg9h_KyKLb7um7NGD}z~$;TVH4=-8+~pb2XSh>G`TKbbLYzfcrZP zN1a#`8Y2|KI7FLZjAqt^17MK`n7^DDK#OQm0|4YgCJqL88gpVvXp~TJ;}C2@FiaXH z72G@o;eJnGjbIF82yGArF~qbW#0SO`!tVoPNhzO)x$pA{L zpou*J4i+Xx5F-G@U}^AF5b%I5ZDwFN>c$+{a3#T1f)~Otm*73d6xetNBm%SRs2gix zV@yK0gy=CSL+}_oR0QTPS2yOuhC2x$?NA2mF;<9(>;XYY{&K}INA#GeVS`MfyM*Wg zK}h~$faNcEwsL7=Ola`{#ZCjHP0PT*#+cX;ksv0)+J?;VNDz|{ZGc$C;Of9yz!<|2 z&LDaK$`ClfTEO4~7J=I3>co)HAfdnpN}eDF11Q0QVuy``u>t00Cl-Z94oK1hF<>AcRNMiPhm?h(ZVlD+AOn24=>910e>YJgm-82BR}$ zP(z4B126Ie-;e=XNHKvy z4K!>ADs!QBakwN2N;J4B3U+`PEN+Z~9WDui5-o0uf(Ub50|Y}@LK#IJzzk7FRtHCz z2-Fw`4n^=GFrZ6RK@3raRu4rd4v}UNg;tmn1};XX28RU#8Z5|+0}LP$xEdbjV1_FP zObvK_zzkIbP9NrA))tTmBHkH`7|`kh_992H2qJtWJdOz%w)7+_Ll``X$^s4tgbZ4G z6ybK+|M&lY`Tv*ye=!+wHN-Gfn~ z8tXd0`;)gndHWN@m}LH>_osD*$1&lJPu^6*)i5#@ILr}HXW=}E%s2xQf%}~WR2_qA zVQ{?+WB7m=a5Wg^2CM*KU_rz?w)zxQ!*Vdf)o`#BG0HT!D+r#zV8HESz-0~tBa%7j z^)j@AP7r{r(PHsx6k6c2LJ*Sy4IhpJJPg4MA_rIv7=17pkf~Zw&cmpep%t_PBV<`F z1IGaeaEDs~bl@c@x1cbgX%$N4gqL64@&x&^)g%pY>)#36KDg20y780AtXj44@d+W7a+Sp zB{ph(3M$OqI0T{Pp#vkfdKp?%L*pWV5nH_sGY9M(1_xIMmHkp9FUaL0I@Ox-k_t%t=s};5>%RI0F)astLeWFS8al`haTuLlC4rui#lPE=C8;Yy}sA803IGe?fB`RE=u_iv=SegL8wx0}KW< z3Ae}OLg zMAXZmRrufv8o~e@>5o#9$ z17>v$7lErGhJjff!$fM~+A*tRn8;ta8c=l%ua{97a5YRV3JWjBEZPOP|P56fn9--1;l`!Gr+;X(82-Hb)dx{k%c3HNnk?@i{cqjI78jT zz+ePw3n?%{>QfE{0S*Qy1%^g&xI*(F12nvtm>4EB=qRx8GJtv+EQ%bUz=f8F4A2G) z69dBo1`}|jyrF@CgW(7`kRf~a85jhhoogfpSRO&x*a$c@G%$b;wm>ivpd$SIaD@jX zAxx$QMh6B4$p&zP2OUBn%}7IVQOJn$e`f_%FCeJ z1)R79lv-pKI)HLF+#CiL7SJ#bDE=839RwUf13gyIfgZS`76wBG4kso7hZYva1|BCi z0frXP`In&F4O3$Q%gY`tY>W+{VJHa}F>HCcgF%S}bORkr4>*l6D0FZz!c!_Jxe0*s z8wZCI149D`DA6)0fCe#`K~iALI2bq@lng*Qmt{r+n*vJ@lM=%Wa9(Cc&dZ?tz#147 zSZ09o_XH*dh8d9U& z=m7E#2a6sfPeY)BfCOy74eDx89@H>o0Oe(Y77;}bo+LIAg%%S<7LW)`jQ|6?1)~or zFJE9cVB`@fW{`NmuD}SokxT|DFDpRuazcxOA`6cLlR!fYXn(c?6U;rJybKyC01sKP zK*Glf96pG$4wRQw48S=zp+QA~g$JCeK_s3j+fKJTEgq z^O68G>oW)lzzPG1QX3nA0BFud8OOH817Vkgu_?Z^v? zQ6G51_LxyJEnsX3X+iF$d-DGW&rTt>yl24#hY1_lht4BSQr zMke|SM*0fI3@HqphNf1AMph<<45-#AkrYa~d!xAoCdw7#tV^7*vuOgajCH1C|D`91&_1n7}G=5fF2TQ6q%Q z2#`3;Js>tN3{@kHOCBW70wNgh3ote?Brr%YurojzYz&Ok7*ZP~7}zZAj~oCgI>7pe z;Y`DB1s;!JBZeal#tQr=n2i}q8|)A8{$e&@C;+Kx5O~7W-Jq1fu!BjDp}Rrp0rw6j zy@uHxRt*||m=#zIKx#Tzk1(ue&|;8aU}9isVo+eqpgCJl$(3|dW`8<-4O5m;+eA<~(5RVYtVzUt#?V?oST?PuK_WA87uM7*4+gL~4gzZ& z4l$T9^le$3%kbO5=n~Hs=BEt5nT!N@HgF5DD1hub!K%a1)u8l<1LWon3`znV3`{Q= zb~7k3aC~4gVsHSddBEz!(AcEXz^%aK#IUhxfkOw6(*cIf3@Sn#MobnA0U$LBjB;#U z9f~QO5lnKP+a@S3kf><5$FRRaHIbvDi34Je2L}(sA%-mn_>Gx;6+UtpH0T&GpJ4dR zV8Wndz%B$yI1aLV82&NXHt-!}{=o2$UDkpB0P`1z{|weH{2!Pvuz~%xg7pom`&i(VSe3kYJt%X{w!3hQ{2A&3H1%@3Ab`1Ot%!VF60*tJ57I5n^gX4XM z?6-!m47Lvb6P%wg{A93m;Ge+!gkdj(eFon|=4Wi+v?{^+?Z8iloesPgn7448Wti;1 zb3s#u;Ut5x1J4C!Hb{I~2s}El1C;6-oD>*3zzik^h7N{B3>pngd=S5LupVjH$)J(I zDZtdi(8!?Rz$L)M0%9ECFkoVW@svG zFh61XIl;z(Ux4`(B!6iLEMhppXr$2B#{7`uhl8C&e+%w!D7MC z)nL5H;|(eF&1j z99aJ`oIPZpzNY5UnpLnlfldYiLVuca~RqgbPsR@C|yk0;-M?T zUC{K0p`SsqgQI{+3lh!@3}7`5oCQo*7`8L$9^!Ujy5rE!kjT(cz{CK_UkM^l7@8e) z0y%7$jx%gLptGUdM(I3PGpHak;NpP9`wG!D45tO`4)E>~e(Uf%!0rqG7Uq8n{0xgO z^a#N0YG8QA@LRy{h5th4Ck($B>@M&(Fu!T|Eno-o-~&khVifqpaGt^b0RM&N9}NE( z>>cD=|#g%20aDt1x#lcni&=;aO_}GfW!p{0~Vw43PY_K=ua1e+F9y{sYV( z82&JU3J~T$4F4G{8TdaizkuYg1FYW|jyIS-=&ew;W4PaF{($!cvqi%_hRq5*pOim9 z;;TX65kp6lk^)BulM+KmgOY+r2a|QeY6T?+uANK|AmQA>dI04222KGc1%{mrdIvZ= zm^dcvz`7^^^2lEvEH_RZP z+AHvHVEzFKLkEEs48I(V4)JYZUczva!7hMz1G5IhYA34(z75P1Ao=SAt6sxK1_e;H zr6It@q0qpgp=7}j%%G&i@dHtR`7vx_X*j@Tp>&2}af3pF2ZIs^!$JnF1zd(qJ7D=s zh+*piWd*JiOu-B-7Zf*fL@;qROl?@Gz)`^jufI4LK57^+a4|HWV0t95p+!fbn}gvc zgH;2c0rMnC{({tB_6K}FGEZUnDqxw&SIGQh!(WDe7XFXTNcGnb1`~!JP`Ubn!Hj`_ z0`m!l(+mcUJQj%hYXZY|M#Y011xzw5+Zh!Pa0f8mXy|9y%D_>?Bm@cP0}Qhm_H^hg zX?h3=G3;qbRp72*`X$hQKzV^i6%)MvI>WHJLE{03VG}Q?%D%vD>hyx4nL$B_!+=Q$ z67MfWS1|l)u-U+?!>z%vi@|IK|4Zf{OnnW?n|Ky*!|SgP4u1}a3-KRu{?PD;!VuQr{0@33PHyJhr@Xlg> z%5agvz=2Fw_16N17YcR^ zyba6`9DW4Y34r2M-~@va1Aim)DM#`isG=fqMnh zDFJZ%P=nR4VKIZw6AdG#GaQ>6bRKY8fXl-L4%`+@ry%)j0iz8=E2zFGV7ddU3l4A= zG~MZFS5OV$E>L;_iPIGvatzNJY!~oEFkJ)HUki9MnEy2$|6rKFU%~telD{})TNr*Y z*c{-S!2E#WH-lXR?*itx8-6S7bKu{=46VNySpPBnZ`hom^Mcuq=}d!hf=&dtox=YH z;|DxHm>D4P1**TkNGP&#By!0xw6H8Zz){E~&ga(HwS(**q!j7#{9D32ZJ34{}kpo48IwyocK2|L-Pfw z{`$jU&!F>?*@WT$0{a8}ADC@G^_K(B2Id2>`YWAb2ZK%m2ZNK6!44KZ1#XEh9f58Z zO$UyROb=l7my*Kn2E7E1Elf%byBYKfI6Ihl7`hpf4sdK>dH{(F4i*N6O$<7W90E)Y z92*#PKz$h=hGqt>0~!WQ(EP=~o51js!H$7v0k;Y$E*v-;lx-M}GZ-E4+2DMD8I&y; z7Vv&x_yVfG9GC+*K#cwa;QCA6f&T*Y3wChcIl%gZ;b?>L1KuCZb_~ZGWFPQXI9myv zZ?HeW^8l&;#nRod?Eu3DCLV^)7Ci;-8BAIXs~PkTdaP%H_rKmSbTTYx;4}zg;b>ye zIl#SuNr$Bw)aF+>qy($KmN9f6P;1cG!lc5`!KBB)-N2*+GUot?0<8b#Ao`EtM3a#N z?;d6iPz8OUe*-fE<7o!_13U|u1z`2p8ixN2_D=jCnSU_-Ww2-PzaagA^*@8{0saTf zi2fJD$si*J-VMwO46hiBj_@pCR$(~JVDx|=WDdB0ae|dkp}9eeiDO5Tl0rv>k_rc_ zkP6Fc2IYg?4-oa23qv!5(gV$wu3Ux14GIT144Jqani&=x;8sBNzvP+v8#JGAoM4J; z=v|Pkz;S~~ieY(!;z#ZbME{GG<1m9!gDwNJ3)3Tp4F_}-n0XRTGZ=tcgGl|a{|xqx z`VP!n8vZEQv+y5ez6bK@A^r!*{jbjq297)p!5jjISriWN>|j=4_{~svfPaB<7Nou5 zAvlL&JA}a{r5Ql?%s3CKHBU z0c8)4YfN7lIze?DQvZw1sgptZh(;ijj03oi3t;-f)F_~k!ZE1{(f?X>g275aX9=?+ z1E^T$X<+`qaKd4el}RK7-$d8{Ypq%+SHIWwAy_lM+J*qY|iH_kp3KK~Dg={yNgs$e`%K z9pGfb(c7Zzz!4$zg|U%A$AufzX9VYE3DKntoeXLY8W*@s1i=2f!1P6-lR*zu$HD8b zCkzLbi~{s0aI-NSWDo$k=P0Ow2kJb+>#r7$-_Y*lBv8LHe}eE6P)E#xzX8$zTF`KU z!B{}&0P_ciH{i}SvmwI?hE1S`G_3!{Xra)xU{M3_0;U%nT`YPn-3yg75>7EFPvUN1 zg7v>xeL?x_fW`)9#+ZNUZYe=T5?0rhSeI2@QRFtjk{GH{;| zDq!GmQ9Qu?4c7l!!6COHc*dQQGg?WNrG`Z!$KBB|4TvS zi9@r48mI>iD&7@9#UT5{-3$s08YuPG=?0?%JR6vGKy@5FaR)1-NhTslx8Gz~n1tkZL1|}7T z)eK6E%p2hSFBS&S$N+pSuEE2Ai38N`JXB7;bJp=y*=RXX88SFsAYhPIYvw(WX$o1E6 zNN#$_@Z*48178#K8-~{mRtI<{AdMe#Y-CVq@ZP`%8iG@E(AdCqf}x+m>`I526Vmu0 zSd9Xt70$pR!vq>B)i}T*(1aL2WZ2%InBeWfbca6}^dUpx*U zTnu{s6`W2md}Oc@;1OXy>hM^=;)sp_H&XrejY0k>{{!|P4F3eg8`uvh|6%wqXnTbJ zhBH$C>o=&)59(L`1|@6*W)+6t3PuNbwlTxU59cs!hYX8>+N%l&KpioL&6$b^xIT0V zLFyFH_#tStkh_BEI>Xim#RnV}O=1Gu8x$XSRPZ7CUmTkQbPjTxFr8=E%%Jdr!-mV3 zVKbw`fi6oXc>T44;oAiH2L43m9}fQ*HqYRB=qxGli)mAX&I@LE|LXz64+lMkesH&| z(T+){MC?xRC;6kduc5*0zx+f4d8dLaGKy{o0$3&#@L(piU1GfMd3&X|+6$cIhE*6$X z1%(5gf?V+WD}mt$C^v!nxjPu_w0OD>D=h+*&pdt2@cQcs!(T>wP;2lD!&lIl%mt<| zD}GwqIPl*>)L#&P@l0R_dk5q%6ONM%h7LUU5cSssh8+xxz(X4xpz%YE16&FWKR^SC z{Yc}740{=r9Ufd@GGSQD02=%hQsIDfLcQSQhf5fCvM9T9++ea{SgWAyz;S^|6*PA0 zps|Gsss8$yVC29fz|F$+5L}-MgZjA}9C(oXU!b-zs78CjaI+CSZ1|MnBe;U*hL0aE zfQ8Qk@K7+%0cJ&p7Ys%WJWcTNLsn3iT%nlGhoJt~ z*9Joceh;Qg3||{e5_CM6?G)ZO7$)d&AnLC*EFT&7F@m~^48IxVK-KXZf!_h(fnTKh z>(2zkCp{6&LJUVh_16z(JBITIj1zc%a3hT$ZZS|iz!4xUz|h8^*aXU73fBb`T{H?2 z_17GRt&GYSq^>XtIw(D>me1tSYS9cKQ90}qS@cqcI1GQ0;xoB;!(|Hbf&$&R6QA@f>>UkWxH ze8S9cM50;j0{EviBaI*aXE0{qdBAMLa27Ot-N4Mla2h0X1yO(HFo0@Ljs<+WOx+BN zKy{N2Q#XT262}+B_@R!#E&-(j92=Q*96A`38YDI_=?FA4DkXr%7@_?y7aoRPEJ~m@ zi4H>-gF*ubsIqHjP;t!L>lDb28gI*#>18n^81j`AA1_p(L+yYEJ49yHGN}Lck>oI6B zAdMd~bu=g`ac^jP!?2n`fuUmqlL~0m5fo6+@k1BU9}Fi>Y);_cz`Taxbb}daiiDZr zG=q%?&z@%Z_~9OgKTLLv{69h6Nddb9{0Er7gZe2NpdJ}yd_F*61;Ysjs{kHQSNb)B zRR_-&W*vst40acIkj4-B8ahgp7&taCod-24Ch$Vt(~z+N(f{&d=wMnPA;G|O=D=zJ z4F-({E*6Hx96AmhOtA684UGH(Z4HVF9TA|pqyvgiIDMMz7}^{3H8~=fI3VLB9vqw; z9SQ~qbOf2tFg#|kaN<#6=3#iuWCQ9~B8?yZWw2-A{{ZqAs62eae24MBfNe)V()`!U zro#-I8g!O{hX{-qcqR#}2>fQSYv5^Me#KA#>Uf_J>Mtfap6w0!3pg^EKEV24XBZkzDkN}#hIDo^D1e$G zJp!8GA{ubp23OgL^82A~Q;q}+MhLemIpwSF)fuF&%p;?9Dr+^)(z(?+XIe}+$ zo-piGP)g{S#-zfrbAgTn$0U^gm*Of1jtNXp7sc_-8qPVfo2qi&B4mTkw~`D1m1J^B0D{48{jQJq(7w3`QvPUk@2} zEL39PZD4x9umMzUDKLR1-Lx2Z8DRC72kQ}rjT{OA9s+Dfz?CnD027PgMy4bO4&?De zft`X%4xAI3p0I3WP;df`uWIaM$a3JEh^W7wGW_(|kiapA863_9;12Ic2BTD-8L;ug z48|o2KN;+v@Xus^!tqmKpG*HF=4TE+IqVYnC6M}Gpy_D=ofc4>GHg=dYh+en_`zz& z;M0Jpzbsh#7?fH#7BKx**r%Y}$nl2Bf?;2SMFZCZME}c&sf%Gj0*3+9nTE|C3J0|e zx;Pj%JLnwXuz<~fEnpO4Xk$doLg9m&O%u)>Bn`|HOWFYFV1q{C!?L7E56odO;4%`jQF9d!w*nq}B;PuxphVKo= zPk1VYj2zB47%$-Y&0H05zQO(h{}1K^ko>hma00`22h%gW1VGL5;7}HePyo%EaXT;_faEU)kpqpJ4=lOCVaw#f z(9xg(Dh4@0h35fohNcsc{Ix=KA;Yf(ql^`x{?}vp3hoF9?5dQ+^7Yx4{%ntB_Vh7s)`oQpq!@z;(1M|OzzYLolcrI{jD4YlN zzdpjo4;xsL8_bSqd2noCddRSgK}kWQK}mz5`+(K~?o~+RhYY(Llo~iTFg*n=6;R;V zz@)*@t)O&(dqNP>_~8x)jRP45P6t3~m4QQm3Dp160L_RU5(E`d43P0daKG{i!v|1y z01e=PDl+~F%m*Oj^9y7@F#Kl(4|QIE_P>;WfEb_xA6|d`WB9LNeSoKc*@odxgRuh7 z31%aPvklvy@SH^Mf9<}cr_c+U|LSJYI>fP|sfVFk!Q>zp1JeRXICrp~0Z$Te83ge# z%w|&Z;80@XSQ+IF(a5XSAb*g{{8#h4XUGxpZZ>H)4 zdIy-T7%npy?a-OWtiyQnfY|}w8O#i@@xwg~{~2sFct3PMYxn~y=O1x@VEV_f{{Y`X zVV$@XZZY=4#R#2t4fUxOzc734GIigjlww$ zs~pW9c`V?9^}iVX7$!UFcrY3;u`?`aP&lAz!^ObR*zn>2_W~~X{FfZV_6eFxIJ}#r z9Qr3HYH)cl#W5_KsQQ5ux&OuRnL&qv*NBTn;W3L*1Mh@p9){B#76aQmZKO5{E%p|y77(vr9dJ~w{ znqD#(CFD#%j31^m>^!9BHe*JU3QMPhQc%APlRCr02Bi?aIZO)>^Itmy^c*-Yag{jq zG9@}lU1~5rm3NBoNTuqSijUBu% z7S`w7GE1vVM{vzXsCyj@_I z!H3lU`oeIQK`BXZMz>kRT?Qiuz6s2#3#h<|*t0#b7}jbbeWbLyiHoY=hgP ziHqfUgZ=})8_Wg_S3xsk70h0+`L8t$zZva7QSgT0H;Wx;+Uo(!FVH0NhGuB}~c zLvJv17PK4#uiwx)V;#uE!O$w8*u?1|RKwT`>Nq>F!PalE^e{|j&}ragQQ~FjbWk$j zRA_QxXy{O3;N)qFfRwAC{?`cxs}KBpq?MaaZ!lWWYvt_2bQ(0?vWOX}|MkVN%Zux0UC!Y`H!^8}I#~xe z7AZY|#21GE55q29DEp@4=@RU6fF?F!|<2Mo-FLz@*c# zo5Ah`_a<2XtAq87!U7hB0~`WOI*{QS(4xLg8aWQ!Nd2$%469lcj&f{c0(DE39&mx? zzjiWcHE?feg7&{0L|-wzYA`y$F^8MS;ot;=2RswFRTzGF=rQmwL9E|c!Em3!{($}u z=6?)-8TKFGy}{ZqUN52JV**I~nvAb5CIU1nCPuV9j7qVo*51If2QCxe?R{nR|!>6gv&v z0!%+3@uk2h$Ix~_`vJ!fCOL-QhSUcVGnuY2Zfnp@;Lcz=0WrsegM;CbgTet`0cIbD z!wd#jbQqmE96mGH9N`yWz68l%4ze3SOM?#Z7cl%`_%E>gApb$;FQE1YEB`}y{RJ9d zWiUFxKY?*wKYP)cav-@y&5za-}{Y-cnDwFd7n^fD+O=nh~KV0gx$>%i@R zRDUUK11&p@Vfx1~9mMc(n+KZz0xd*@)n6=U7&a|Yd%!Kg^p0V6gTe=H2PVdb1)vF8 z4*2*X>rRG$9!e|tA27daxW%z~1^a?%4S`=Qb`AckoDt)P3gFqUCgulBFBt5Y_!+>H zCj1Qi2ZUk$FNP-!KO3OaLnk2;!mS;OjTQ>ksWS_-`>^Yxujs{v~MG zu;C|Y{%aO|{E+bp!&ioFpkAaZ!$C%-K%ObgTNvIl8$0q`U_J&Z4?*?U4hADFjV>;w zrj8YhG&Q`K6Bs&Jlo)s#nEW8+7^wc*$)M`N5!56Cn$z^)xW&}M(9EddAPJfi2iJoV zqDxtJJE%Bt1VGd{aC~5L5olyca00Deft0HYM4vl+WH3wMnZv9G8n+4LnIOD`;iQ03 z0Ph6m7mzYRgK-JNPX;Rw{+Y~A8GatHbMO{me!?`9VV{Hk1ZFSD_y)NCQrN`6)55I4 z2$~-}b--DH;e>*b01s0COM_t_gA!=iP=m1xRBf$b+QFdbqTIlP-2VbK(I0SFF!3=o zg9ZdNxVW5}7?vE;Hb5FbY}m)4c!8rtNS2|WL-ztlLX!kT8ehucJeiMBDYaM7-r~%wP0ac^?3xr>={${WO%@aZEF9z0s9DgU+ zC&=AkHe>h;DHyFB{!Ordg53X_!|=^Q*MTEQNrYi5gQ5fP0p^Go+bnGF@t45*M4RX*>Vk45y#IBMVIhYO11Ad;1E}9^z`?-O$gl}Kf63GV ziT4$P`xtgM*gfFhz^%vdyJ6EK?G0So48IQOec;{)>+3Zzym9#DXm@~bM>A;IhTS3l z4a^k{{}k*Z__v|fU-k|B54bHD{xJwQ@O)slVffD=e}MlLQvJoS+d;j7FM-X1VH=}z z14jXu2E%R!D+lfci26$hv^>v2dIEUqua<+v1SSoJ9SV92xF;aiZ}5QHy`V9C7KWV+ zD;zW?FmW(!V9;Rb7J%h11|CTL#m~T`!0-XI(078n3TPm@fxiLX|Ki=l@Rz}qfe$ni z_=mxqf&T(%AtHk{XhkBt|Mg+PlNRF#JRg{i7|t+lR`B`6{EwlZ!T1EvV{Z8R`KJOM z4N4C=Am$(tl9-1II%lc>n9zf>kU@2e=Fv)-Wt!(K;Yu!t_R9vA~KW zUW=F(A=Ynn3MC!j0u9rxWY7Y2LfsRgVtC=P=?b3#_nHF_7_3rw zHZY%I0?k|TEnuDi$zKl`H!%EVuz#TYf%yx=tqJlB`5T1)Is9j}J;?h&xB`;D90V3H z{9-V=BENwd6i`M@{2Q3{K(o@Iu>yGgrF3AkfWiarzfDItb~`dKaDPzJad^(ie~3E) zQGX>cY;sWf!ED2Hf?*Sf3In$R6EDkV2A+rApeYLQI%)+*F@`M-st>#?lrAxBpP;JX zUBM*BvV5ZM2k#6ac>TrV@L6KZ0d7O~6AhmPOkCIvxOqVJ*8zS7ME$jgVK1Zgh4v55 z9~k}%a8Hr{!2CyGKWGH_0b>2(6VMvqll&W)UxB8}BlveRFJbuIVRVCkMKi4a5}d-Y zok#Z|cL~!4h9(A62JQkS8HUXXnrFS0gkbYu4)YndAIM!GUBUE=v9%{rfxC)n9^>|= z%qQF#i1mkOSvE7~B!Xvrn;ldPxItsWn;A3?a4T@Z`(L|3t%4Q&3!Hy5oI9{-C;tNH z6%D^wtQz`!rPRD^;5SG6n^_LwZf0OV- zfgdZ48u+`KUoiYwVW-94w-~nmQ1)8`Xn6zw6y|FkZyD_lunRDM0ktI@_>tm9ZU{Fy@kn65V{KH<+`1}#K1eZXIZw0@rBg@fHG{x+sJ46m~G z9pGQW^hDq@Xf}BpV*TMa7yb$M2`nd>K?@O$mvWqCzUOcpG?4gbF?{}O%7JzU-G<%( zr8_L!0~A|0z-!|bG+Q`6!sovnrWj0T&~@NUU;~ZeE4FeK7)?=VWk>|A`G@wu6hsa& zOmfh9z{$XL-f=U7!VxY5rb7&k3>u&{+wl3Xl}xv1*fH@ou>WEB&9KRV4>U~xS`pOq z0nz_@((sbOZUX-XQDX7H3vB3#Ge#pYy#Gun4ZNSvSu$f^A1GfUx0k%dK@VXSF^@k@wYlJ|1 z8GeH%nl~`NVF4|0>iGb#za&A!hPDUzA29FX04R$PYh=p7AxpP zFc&fWZ?Na#cW5@^=mE`yf;!;fc3p$Ok%n#srKXNeO?nKw8}wwjdziF9JEs)5I}qzP z4k$E2R?GWZgEa z(tp6}uN4d@8I2rtHZUtVoMx~S;5T4aRrr};C&IIp8D4)WG=P?|acyW)V7LuhrUhzL z>`u^oz_Aule>pOAFsLzb>;Sb7L4}@76Bom#13EJ}IS~CXF@d%TstdR(nCuv)H|Q?l zj$jf~*wUbzz!`z)e=&geZ8_)|F!M4TX0SQnZ`jPy@R7lW!PkHp-v8Rd@P}bHXgSIq z2k?C51#eJAW`BSW(f?w7!0@SQ!z3O9W;W3JOi-8ejKE3ILJm-k297Te!3hl87!(^i z3YbI~+L?5lxC5B(Ftjr$9`H(Fg3f;(0Ii>IP)^{eU=n9&Z_ovGyKXVAZ%BT?g*5+l z&|!n4QUZq|7i$A(DQUL=(|N{)4hfIAOc3*5vl#v@FxsG_!EDWRib1ZyKY{sn(z}9< zllU^2e?i*(9t;mSelXavf#=PCFxUuyhvg#}>=?LPC&TNnCksw;*c||^b8z^{VE2M& z2D6&ONdY?to>}nq8w}}8pw)C7bF|Yyjlcqq2}~*toeX-89CJ|Szd$ot6NSL*H$c-~ zIznJwQxNMn)EpZ{bX+)Om|Pe(GU>Q*7%-_YG=g+ZK+Jz7GMsR+1NA0A?G(FRI$g~Q z3?~@v7<3wt_kaCeWS60T15$0h;J?7}M*y@V40-(_r2gW$z-$7Z_TrzwECOOU`Up59 z>My1a(DDY*Fv|{x1)%y%ff2N#gX4e`eE-)IhDIG72Mz%#(BO=M1NRLk5s|$t+6Ooy zoDk!O!kr0u4jMC{%QmL)Xar{2BQ92#PGw#h{2~p9n=&72h@iHXD--t z@EkzQe@Q6pV$f>fN@#n*u#Z7+3dcI7mjag@q$Y6aAm+bZ7&bGh9pJE2I>WGuA?JXF z71KS2<|dv4oaTu6FByjIOLWh0LFT`7A8;fvePfuysLjCn0MY*vWBA`-{-6W2jqzK9 zZ3<5X^EHN}4Ym(-JrMI>pmobu4?yKSsL>U{-vFMpwPE0!fT+KIF`Vx(e&AQ3Z0GQ; z!T2Hn59UuCUnUqT@SH)+f6Zm-XD~m)QOYFauuVYm3TS^y$94(D1H7TIJ`n@M6oqyP z#RFUgOfm-B7!(<}3z%-O>=Vdj(kejAf1PD$_DDOxA;87Yun83J0!+soHZrWZz^%dr zpZ{tGtsZ%xH-SkX)F7Y0zk&G?!_Ee)3w$lk@cFMNO1~NGj_}W5e$()a!LEsa2iRY1 z3?dT{>o>kIoM$vXzysRHaSya!G=bSl;Xi}@q5dDri1qVq?Tm{KaLi#k!_dW`C%`>H zN%g=E20aGuC5Zl)8bddu(gBVcOz#+GG3bG&esmaiHRv&b#^NAt2@Vz(hHeJ%{PG(H z@LF^MHWmg$CY=MP6=3Z{2Hq6#u$WH+`x}N^pru?3m{kORfEW$%`b+i-!+&Ob&}`Qi z(7+U^+w}uf;2!|3pNH-L`pj@5gzz_(GfMWL`L9n7e;PI`@ZUkqe?4I6Vo*Ba zF@x!eM#llY1dk4;7YsWZlpb(TLeyVp8X8oT8aM{sLeud@(4xboI82A|w^IuyS{7!tkEK{$T$D<{tsy1ndv+pMZ}a zvc6(Cslf*sKm5U<$G|^>S%smp!H9u>2RG9C`BnzqgWUm4R~WWI79z@k209LK7qG$l zUktkx+B;Mqa7VD+W7yW9yP&&*O^jiGgQ5cW3q=3xJVUdG&IN7(p>qtI6Lb!68*uRm zY-UhETR(rw!MuTg5%a5te-3sH{7acN1AYZ;YT#edTm`SclzuqaF@fqYfgg}YJ7`6s z9VkJ<>#t`Fzd=h$Con%}U}V^oz(0%m3Cqa|khNiuv6KduREC{NdI{VUl#a7>Dkv3j z&uMzX)Y+j|$PF610FRkVu&N|Ldt^@(RPgQ5d>0K*jr&<-PRr18T<@MvK_sDJTdg%JaPgD`l1E(8A&#Q5R0hM%Bi8yA$n zHQWWwrwAxNYxu}u=g2PwA3tOT&3_4k>Sa*Z%-+!_z*&XiEQ3AD`i&P1T`Ztw^G*-I zX9Q?8Fez|!Fz7LOED(a%Uq?V|l0m&KP`67D#8BZ_$Ox*@km@gnMkS>H(ApJ&odS9; z922=zm{tntIdljh>aXVvCmS{;@JwWW!tyf-y3X?@qm6?O()zQ( zVHaqFSq2ja10REO1NQ<%{gne+KmULOw5oeEgU%0b3nm@`1_p&A-AMf}L5>!O#TPgV zn64;nW6*8j{=y`4psiscXe!v{JQ%=bVOVGH;pnC%#jG;Bl}KU~1@ zgGtYEK4@3eZ%|sD!2L$y6R7{SMHxPR_>JLw6Mw4wPv&n7{|?wL;QPV+kK;(g#wBO| zHo(_!Omt{*(Dma6?M>dspeq1gSkmU8*uqhasJ|vKw17tJ3z)7rY+!!UWs#1|2321*S6s6BracIP4Jh*D8jS0d@)g8<^iU{PM87 zz_)<;pTI>1g94t4W_bPe;J^=1UuGj@|MCI;CCqOeUNhK*AossOrMj^b{|DwT4(FNd z9r#Z&gBEWqdm!&$PG;C~K`DWI1JmJ#ZU(&q?mnhB47&~}DQJLt0pR@AzzSMfq~yX3 z+U?cFqW6G%0n;0Xoef$79VqMPH!v-5;5J}-z|hp71DZlQ!?>A212iKKUw`Pp@B=hW z0E&y#40@n(2~Y>qih<_?V*m07hCi(K4*VaOLCYJ!`3fzT461_m`pE&(Q`MsR&`Ky-mZ2ZtULM?;f>!b}FG1Kewv zRxorkE$a~4<75CC8)Oju$#BlWege-EW=n>144WkQdze=+oMJEw;6qx!v4Y_jgH1#J z0_GPiuNdu{_zwsdF#Kh(Uch?*QGcyuIOSlpfk%azli?79@df_n%o+kG6^v%^PiYo_ zxmhEj3smPWVA43y#i4YdTZe&zsX4(UfZ2q(2-22#z?uT`jt3|BxC0Ia18LA;^`r)c z5^fPD==%8wjA9I}6BI$cEis1H2E_&3Czym7=QOA;;eNs7!2*tV4-S^5g8~~IbcMKB zm<}>5XyRuOW^ueAU@(Q>KncEnehb525qlTD3(Q{}{x;Y*@H;Tyad4kr@@GqcS>Ua~bjraVwC!u>fo}|Y8~C0e)^9vuc)?&N2&%sr z{&0boZGaZ7DS$>r5bHONGeAy(U{-+)!A)pp;dr^o=z#kSME@(5qq9lLi38Lf*vX&= z>c6Wnbe>Qu;+AoOub)?C>=jt-!4be@BCwZ1FTi^OlMcg5hD9FU6A|k-L>yZg6kRwj za9sf{3~=P0;RV{~tKh&rfr*Kg0R(pNCNrF9G&0cXU{+%J(_p8?-?dn2!3*$?40!*G z_j%J#2fGUXDcnyPemZ~_(hGlK`OC1+fj@xx5hOj&VEv|aw!=7sX9jl~!%y%r7tAIM zcUkNmIFQcYcnI3REW**MoFD)iHq<%5w1T0F!HyAh{s!dy1P|6TpmYdY5+BO2-9a&d z;{p>4Xdc!PGKVag3tlL?^!NhR5#qa^Y26GU@mj)ZqzGFFtFAcU2{4=<@*uds+ z$ZlZ!&1B~QK7V63gIyz-0b0AuZ@>(#zZ_WqHJo3dpThHk+lt{|gZ%>j4CXq9zfJN9 z{6Cm4K;|n~2<~WVXV7QhNbs7%uoYBE1vt$~*v6vE(4By&zouMlSI9lUv4Bg0VVi*N z0qzf6G9X3+V*O!)$O*>940#P23QR2w8yj>OxLKIaFl^+|3E>uDx&djktq@(n(9K{6 zT9>N=nu}!MXW`Cc_~~Kyp?eMUD@Yn^V0g$do5}7VzX9_LhEE1|5BxVUuTl8LAlJaW zhw%YqJgPz9JHz=_`vW{57(wFz#t!}$nr#^VF&MAnzi{|~3%Go4U~yOKZqj?ev4Lp? z%Pt0`2JQt;It&{d%o6yT5dAL&(D@sn)$(gV>kk{a7YONqRwPc~ZeoJ1-(XdZu9mOU40jL8b!?8#Hu0xebKCD-uDo zU7H~J>wxGIh8+y69&pcadc?R>L+?QM2BvqQfy4vdAalUwvxDd}h7S!^1^qpTA1VB3 z0W~4iUxQla2l(eOH$dX+0plKzKLX|l_$L^DQTW?v_W<1O`VE>VLh665Vfevdc7Y$X z@8$=CT?o$x=9dhwK@%$|=SS@T9b+|z+?qmzahr3Wdf)#BP7PTrBU}OM+MV0hxP{52i>5Z zI1ZqU3OYaP1L*uH0d5x1;;sWcp!1^+GngFV7eVa*+QRUK$r`jZ9=hM*Bl8c2{T$W@ z_yZ9AuQv=I5{y8{Zae_(lX}3jLs$hgka)n~0CE0?48s-yMNsP$R6grAXcRGtFkENQ zJ;0s7Tm&h{K>NS898gS<2Cbjp+Mu7nWyvHAS-U;S*t}3D zfXjf1Sz$AS&Vg=*GZ;0CRq*K6RE1F3wG_AfsHZBYcxcA0?MhakoiP{e^6oNAEr5VC$luYse1Nr9<@L5YzA zw2&S&Zqwbs+y@C~(E5#y3_9RrqZmQ!h`2y*28nP_Uc0q`u|1nvw-{sOPxm|*un*NFQF z!$*ca4!RSZ-+*TN4)KHPFK|8ut>3u80qT}AKVmrHU?c&GQ}8MsP&*3JM$%x_VCeJE z0-a#;gQ3q+xq)K=(<|^sFb@T$E=c~;VD({`d_Z9mw@K4EhRFphK*vU%W0=g4T>ks!a z9B#7X;h(|$R^euYodOSN^Yv*4BL+Sj#QfJAmtPHb0{rus-!Qxkv6JB606Hs=!Ri7( zs7D4}KmUi}KZ9|B&jsc`3V%TRzY3U*0#1Vl53yQ zVD@HM&7wDfdjpdqB!4xq>M-nVP;lUA1b4fX!0YD~xK;UC5N5{<~IyK7>yixHh|BU051w%09!wQhv7fF zJp=y%X3#{KJ!snN4ybNAz+ZqkKk6F8Sp&fZ{Fj&wI?iyID)2-w8acdaum>H42w%VP zjG?nYNx^##lQKisA*BS44kn$3?iRfV+#8w{Ao;6<^$kM<B!D51?i98<=0D{ARLO;l1E|M&)0F{S$tq{?}TD7blE9@N8n% zaX83ebdhHhvkK2iCOZcHP0aB5FBOiC2Bi-iCTw~PogE4c912XDLR}(OkGR_q^Iv`p ziyagiGz^qj7#b54EErh0xEPua@EquFfz5vm<0mBAS9s?#8hJy?SO*{t8JPQtK*c{+zU`Cq%`p0B{1k}d|El>eXTz+Wy z%V5umeE#7((75pfo*m3$3}+c^7VvCf$`Cl+QUp4P18MyRXts-^fJuavG`7cnPQGbC(Mw1>xd!yz9jtUO=`5Olqx)>5bMG6~Jql3;d zE+eJ`ObrSO3@f;q;PuxIhI2;76V|L^wrV&hV84MUhdG1c6oXX*&jO_LHyVC8?Bn2X z;Rer@F!Z*7*AcB3;6EZf3DUpd5P0VBbAb_P3HlSz`G+6|OUq3rGY^JYNb5HkcAiiw z=$^s!oMUH`UIOg(Fn;7`JnjbCr0j{>V6&Y?Ym@)7@L5v^X zVfeei7S!7UEy=KV;JzUItmz|zjRP-y{l*MdkQ8{?2B_Vu!k2?w|@Fo{6h{7gp}7cx8m)o4!e`b%PC z+oJ^T045cMouIQHFEJfqT*;#5$UT9{4OV|WWB94CDWD&;Y#v-~O%Z<5^pX+0upd!> z3H$_&s!dRS!twKfT}uB1<~IUA8LdD=fL)OGJgENq!LZ4Izk~S`!x_kSQH72dpaB8o z^@kk$6qG=xMZIX~^3ZGGSRO9 zV7kGujX@VQ|Mda1+4%rR0&M;(LF5d>h7$@7($J>J0d6&>!%R&c3dtO5i1EWU44)f} zK*tb00BtZiz~7+!nBk^_(F~p$u(gg;7~U}aaojhBe+%mq9DImVlNWg2v|`Fr8qubLiQCnEwJDyRp9kT&hFX1Axm7dk6jp@cN7K z6T`m-W6)gEABHat#tuB7Zr9lcV^II=1EhV}An*!QFW+I_5tJveyI~b*J){D|N*1LD zpm_&a{dIt07lXoKZUdz=3>#P!4rojO?~_situu$8f4IVBH-pjv?+r{j9J>^?K$}dJ zlvXyVHE>TvoPYR$;j_bL(2CZ#48IhNTzF)JwV6&fY-8b{fmlDk15yirQ2fKNpMfob z{|fUDhFAt^1#YDE^9vY$JJ=ln&DARW=Gb(QX9{`2J|OENy#E#Y5VVT&p!^2rR}Lo`J{{nhz^uV=u)%0j{{+PTuL+>m z=>h2ih8ql585A2l3YcavTw$|MchssU8za@=5&X6S8DRN}5c%zqsK z?aQi>-2|Zd zFNU208V(v4m_k`P8I(LgYcFB-7uY?HAonnQbTV=PEu?oi>7WD(dFYr7xc|jr0}=r% zPT`&i>J%@qJIFr?G5-ZRe*+e$pkotSx(z@bG0;Lp`1yw#pqc#!?gdP*6!tZkfdzH4% z{A#cQ?Q44hTFrYQe}gb={}*UWjzawb8PJ;Tf1qP~FHAOJIL~1Wnq7jg-^k&Z(U5;o zY6H_7h8+i#7`OzOQy6YDEV{t`0lEI#%>vs0#q?sqjs~Rz8ViItK%0*QxIr^w;ISqS z7SL#+4(N=jGmJYJ5*)Y|FnK`wm7u*Ipy4P6@cA2zdIvc+fR~yWHSjP9voL|q-{9Y% z4Bx-}hv6Q0RE_Zu!xsk7Mlj|t49`L51R|Z^dynCaqVa={6U>`H6QBw_C%6p`{LwIG z;r+o}0IRa%FNsFhm0X*%cdSJ%^B^Hhii1iyU7#bKAPH-4BEdXaL4gt`n zh6V+O&JBq3=T|UwBq$u@n8>6i(3PN+z_6l8jbUfQB1Mi3PVn`I2N@0?P)gug#N5Pj zve77kMS@vL;YWj!5%T%-J3M|j*>B*#!2E^fALIUm{12F)F#KQut*aDB;3sir#fRA7jl2U4G&<8Ey6Juy;V9Y^2 zzn7EYfQAuhT_pq4Aq5)&J|$*`ro#+EJv+l z?9YJazaZyhuV6jHaEj6BAkPM728L4)3>tVkm@OFYF_?oy;QcSq+1H8(8a^<|FtrMR zj;s+9U|7bWctGO=(B#Px%xtMC&M|$%^P)IF-tX^YS=u1{}s2T!aW7^Cgk&b zA3FUwV8;ULe<}P3uw&9`V}8g4I%w3tff-hR2|PqQyA;iMb*r4K| zDd5xuufG&PwN=*yre_SD0-%K)POL1TbGBDaK#U(gXSjJm$w6-(^HGMAj0OQZqTK3C z2OZ5EdM_}qfR7(KePr0@;5$qC2^;8KNB!C=I})55I8aDt%@ zbZqYfg%ez$6Z;YCH>4H%7?c|r9xz!jv~}oD;(oyNlA({&9JGJg3vw=v2CI)kbBodg z4kIp5r086dHefo-(CnaflVbt{qW{Iv=%9Fk!-L5LbnrWacLCEG!FC1}2aX`b`gu8q z;|-f1=-gnEV>;fT3p%#V27sL6j#t(Qhn9UsiPqA0v|G|9E;mjuU7d$tb;p^vTaI{M(HgGsHNgRPz ztTzs{2`PGLT!gQmXPDv8tdPjSafC^PWvh#B19xE4ABHJ9s(dp75a&l7RA_QhxWdsO z!~tp~J8<(coe`Mmpd=tMfeF5Teoe!v2}&k9p!K4^57;U2TQI+1x_!v3kY@to{NATb zpBeTs@OOB>YWQ))?g#$@=NAdDIqU@bCm{BJePKG!xS4?;v<~(RXj16_vkb!lhD}U) z7ZB%1xifToC^2!*V&Z7%YS2sQZeY>{&CD9|UO=3GsHD&(pv21E!KB5|*`W7`dj^}1 zN>{_GBrZ@Mgw|i6`7iLwa|Na*@ODut4#6gd1rD47T+s1((E7uZ3!Ka{<~0zT~!vhB;hf&YN=9|6!Q#K`Lp|1tbm0Qa`+7|t^oC-9$O zw&FP7WURpd05kYMg-LV;-0~z%Qb^TiHUm#mj*|(!=eUm#QD8~Z$Q)B7x-nkbs0`w z*ffQogIUYzw1bgC|3akohYUX@?2qt$VE)VSj=_EbE69WY8RQN4Um%^o!SIpE2y}zM z8-@c8n*?|!g3eTNGGgEdnFF4$1npnm>7cZQdjsICvaBGHf`&Z^C?r>7am-gNy<50hiA#1`PZQ5cSs{(3K4b`adx5VfZ5eK1k|= z!*_;yf%8b~=ie~=RxnE7*}(n)G>`~7Pv)h~EWN{)3xV6?z4hD8C~TbPbA%ydu+=tf#U4_f)4k-#m&#LCd< zpb@|=!gR!;Q9!|kdm=3jq7qq++G-r3g!9buN<@}Am47LvZH<+(6{XAfn zz<)#dSwrN4eHs2yi1mk{B@jlS^`eOL4?!D;K@(w!@k0jCvJLJAF3|kfDhAN`hfLu6 zQx0$;tv}qzpaAM=g7z;j1YQ2Z!UR6gAwUSRejaq%;Q?;}(EeqGl@1(XOe~Z(zWz{yVV8qe18DsQXuC~A#{wn|(7{BYi7+mt z{ugM*#{jfHr9tHZhXxZ1!)EYCW|Z~w44R-`fQ&*rXd`n9lL%-L%LR@U#QJ$ThVM;= zkN7jVtr!kBm@m-z!7aq_uVFKY0k6N-F#OKgcY$4j`2`br*#>Cw+b;(2JtOe?3$*Ra z@IlTG`%|_t zD1$}|FDSG!C^K+IG+kh5VNhn!xPhp@Kq~~m$M!w|9eysrDZ+Gsp@Tz-fkToDzJ7iU z!*2$IBRm_L-!YwX*mOa63G)$f2I*PAjCB6SZ^wNX{5J`|Vff`?b%1XH==@$ryCeJy z5cL;${_8={1Lhx~^Lv~8KQw<}ILBZN+Q`fRJAXchVY7qQ6%Ej`@Gb`>P+|Un;TEG= z1Fr$o0@(g9(AFo=2|Pki1Uf(mjW&SVkdVU?VfUx7fc7tIfqIcg7#bKBI7l-vfi6u@ zao}uVf}U>!?td{DF?1|odLYmN+W!TrY7a0NG5AbCtUrXFfA|10YzV4+KQQb8oxc$P zA3tQh#B_Xut%2?d=FLn`8jKZqE_7Rg4w6#U{nTv$xi8>=zzfg}-3g9OO*$Rj8cGj1 zK%)<2O}YsNzB|0mmU~d@UCcv zpC7fO;Xi|Y2fqsQAGZGt_Q&}raDQO9WVATNcQ{Y&uq|nfLK;Z)S6eh)n-3(?MIA$=HLGsrF(EKukf`g|H z6H~*?0}2PYd6>8xHZdr9aP4S<)?W`8?HKwS7AJ7rm?Q^UKd&I|v1k^{_6e*>91%=4 z5OX{@I2tZ988GlFFzGN{W>|27pMjZ!;kUph4?crtc>ik+!*3?g-csfV4le}kK#Md| zKsWn<1_Y4K?>)_A)4;QVaRI~02D1k|2beV&K6@EB^lVVhg0!DcfaaGObQ?Gvm~22p z?hG6T;PdCf=Li`=`f;H158GSx7jW!gnkUfSsQ5r4gH4WMe@F5H4hyCP$oPhb$XU?* z@&OJ8B?ZvomJA(sZ0sz%PbesGS}+Mg=B-|c&SW~pVBe#&fO!SmDF&kkohQvw48I(V zcksMou7b>!crZKw&+PM`0G|W18?yo1+~ z?F8twjTX@P^A7e5{$0%p949~xA*Azrzcu__2s-uL+fm;N2{}+7y#w*C#y{DQq7&;iV!27?z^IsedOns32<-vM| zp_Ne))Z<{`SjeCOaxb`4Jk11F9N%+UG^wEplX__Ph~CoMlGm^t_hbU$;N$*|99y%6&) zNSta2EM&OB0IIFn6+sK>S@;{cl~_RQGx-qb_cCgM_T4n_EKmlWAEms2`$?09$~^}2 z1-vhs;PqD?!(>Qpm3yE$Lg6#F2~!6{7lX=?E)zukWue&apm>2Zg-M2?l_Bwfv;f-{ zhHVV02Rh;Rf34tCn2Ezyb72NX}&K)v;!1n{O|EmL3FCX9n74JV; z=7Aa&DGX7dbFtw2zZ_V*DRm z^yg3;Xdy=+lgNR61`Aub6PQ*(#y12QrZ7!q&~@VWV7kH5#-R&3S6`N88`DD2Wqb^f z{FNc{fT58=r+}M7=^aB8ht2_R4JI~$T@FecxDAqwtQw9#mUx z1}!*$(0_;7gyA29xq}V^Gj#n%14{)%pM&m%jxS748#){H9(m7jQqkDQpp@Xza|k-V z(ZH(0w1Sb3kz<0>3#P6Hy%XFUnABKyF=#FDTA>7Azrn!Jz@!JN0?vT;e}SfuSU?-x zK<7t6+lQd_hc6iH7-c3fzW}YHcJK$CvjjR6g=YaXeEr4;CQxrnAGBxfKj@y31I%9- z_OXCY5n%?kvKbf{4uI~v;V^IDJ-}?haR#(5<#6*qhW8EjNBDm*e_#Z!HEIxe$FRde zZ$WnllNL)y16LwP8))62g5C-49Zc~1zs`V`9fF+7z|_FV09rnDhG8*>#(`+i{a@fP zJRrCRG%eWF4LWj!gMrC`12oXO8#Drpxc^J=4Z{x=vkAQl+zKrx8#XEEfX|I*u?LM> zBc0#-n?b+9dx7%}hS~{sD*OkSL9GjY(99A1{0&gM#b^fqMqve}gNzmoI-qqs(39KY z^Izb$LJVc5i=@PV6yiH%_qgTjFh&>B}`hMWuB z28j7DImRswiBGsanD`VHfd=I~*zPfyH|To@j*c3~f3zsUT@;huo`6aEKae_0>kLpr|~G*4vpz-NK-5m5iDffsZ@ z@oxsZ2Rs(=^P|A`rzC*de&B7(4IF{sLjZI^3%B6=zd-#j&}t2)X`pj^K%1}cF)VM; zK^Z?h!?5{-!hsG;r85l84l1DS8)q0cGwOg==EKk5Si$hm!3uP3$_u7b44WqCEMa~j z@CtOE@B-xXH$E`fG4wMqA7J>wY01Rb&=>ji0oq>)UcV8*Cc?CoLD7Lz(BX*aMga}bjxcEZ5Hx+ z+`Nmw0kMAL3B%U~;9XHq7(m;jKzr60UM{f$-T$?W4YH=46}0})7&MmzT7PKd-~(C^ z`;);agCFhuC?$sO1(0(=8Mq)DnUzGi8<6L}7J`r6U~&N+(EwVu!2&v@*MUpG=@6uS z2wFeCvjKYceka2!2QC3F6^6|WO`y5IV~}zcy#5eWZHaKFfUZ|_@Rj#4o6Hf~_187*DfxiXZ{{qj5AipMsC&dq-^+*g&42lQ2 zK^56{&_=KVCZzeV&kY+M=vWA|F&%3#e86wfEywV^!}byC{MT;=yMz2&7{K>Dg036_ zAGp+kdjHqo2E&Iuf0)gfK<6Lw-(cS7@E^1`4C(ydIShM2#|VSY2WVqZJOVlogkd{q zkv!7*y>l40Iw&6CC}3g%wIL622Qbay*v6pSD2=p#`3%EGh6M*!7%;IpG=T~k1}4xZ zZk+~h#Qc}&8qnz|2Y5DctAHj%zJL!l2k-XU0PE{DfaZ=HYy$WVghBq=bAW#fXi=Y| z9RvRc#s`qLAL#zCzo1=*yO_lp&VjC@0v!_mr@F_%@)-e>Lbm;sBkY)WxWGfTIPp5K%z~bm}ks{0&h5OXmQG0T&Bs{P2JV$X}qf zvR$b2U*LTK!XUfs4)A~%B7$xNLs`E8UAFOu;Tvco3{=j8YBWCh{1?MfN9F>DI|Ak` zya$;J9PbE-v+y2dFL0bAP|PBGzz{U_#lUdXg|(0|#xYz#^az|GTF7w6Ay`24fMEbw zQ3DgBLW75b5C=;W2BSd{tc2kJ17iY11cMj@Yl9$+0Tu~k5N!YtSurpi^&1+97ltbf5jLJx=|+XaZd(pn$|+Z35kK z=Logy2m@;ZVo=I2Lz0uA`0NT zfT4kj(V)>sL72nk5Tii@h{1B0)#!kaLNEu*0Y!+KqfV>|4G{`q9HLE(35^j7;T(d8 zSrZy#6vQ}08=(F=>V)oZh9(9BP-~2Xp#j{qVPH7w#FEe;q2LA@C{JjFFj!0u@+z=- z2*Ul9AR56E#t_;dn!p;tD8>-jAdy+92uxwX1=R(Xk;QK}3QTlL2Y=f*Oa6 z3=Iwn0vs$%U`7xq(XzC7DhPP6z|c(2wD3cH_p?Zw7 zurVefTtf618FZ&Rm|j7(qj%ybU0Wz?j8=k%z&VLEr$G zk<7qxfF;3!L%^9u-~d=2Oc*fmIKcB4GXui~1{DSlNR9)`Gcd3U8oKxd2#c^BWi)hv zFjx&8JwPIGbCg*#4n`S-^@uKpGgzP^2={SaiE46UFO1mJdQv3NBKEpS;Oh|FjJi@;1dz{3#CAaa1! zfYAqo0U7EB)oe)hGJ^mkq=E(&mJW=NW*q~^0iKI-xBQv0C99)sB zW3&p|0b&;eqZfYi(2Y8_NBqc9FZ>~g@Y zmof5}0|OgFVuM5in*?JU1_K&~46X?*6^wEW?hS$uFc`43!H8BbLn~-Vy$lH;2Sz7w zy$mX&F&I$0KxqR!UCJTI#Dc_d038Vptt%ZEm>B{Nh#0W)Fgjx}K#7zAR0DHBs$&I4 zOa{a`3=U}ZGQ5HYF<^Ev!txh1Te&eHF(3vx;LBfdeA(ladl=%I4EJ@#v|B_!GPy42T;8XItU)L zBMa1sf>qF<0tRf51EUA1ItJCk;CdN_0r!`KOMsvViz}lb76vRGa)9m|YQl)~CKf~+ z30g11s^bO746th$7#v)EnS&j!Trf4^#biM9mjj~*QWbz!DL})JfdNz<3&HAT6ow-*qEE4R{G7@GC=(1q&Z8!`a4ML#4CaCqxp>Tx5 z5VGitfdSll2laPA_ct>rG%$dg=-`$iXp1A%91aGFM(F6-3XnM+j7kh04QvYFGhd)4 zaWHT&vN1G((((!hHc%15i?$U#PNFmSanEo52Ype4}NBD9cUg_EYhtX8&# z3@aS9Kr3P)YAnFsaZ(UC!J-7}fhh_|u!%7;fP4y40CuxN3yUHPha;1~ffg1<1`a1C zfeEcDil9q?;pPYku-h=6VJKn{Sio+;c!r^nLB@d{6!Jw3GSG7dI2aUKI24a?I4}t? zw1DnpOJI_@;Lf0UhNF-PW)1@b69Z^%5CaE;5@?f*f*^+glZy#xwGao?ISdR83JofX z9H2Zb&>&*K!okESz|dl#2yz%)4Y&b=tc8*10<#3e8F1i0=6o3#1R#T`3=9I06@VZH z+ypSg#zw%Qp#h|l0l`RsitzJ;)qsctk|2@+G+*Grz#!QGZty^a;3RBV1*8frr~pa< zU>1x}fKCT7NH9SA^dR5DWWluk|NsBN8CxF40Es}W0k8^CUWTdxNr4sH*VjXIK~-Q? z12Yh>n)OsN=l^f8!{By-@-l~pBbSU*3(Gw)z|{z}@EC&fvPeS!~mJX!NAeL zW&mChC()p!z|y0j#30e2W`dTNVOh9>fr|ljdn|(lvk(JIgCR;@7J}qut`@e9piIr; z)nc}gXN?0=Ue*A&=fR^XY~V7A;RcHtm;uXQpgbs{$if57gDe~l%sc@tCW<^D5m?zG zV8E`!c!HsrLFNFv379c~fn9;|j6gARUT&8Fmzfd`Ei8uMqClWUMUjCg5IHZiFf_0z zFmVVpF)%=bg-M`+K_LJsV+ypWC~)vFGl>{9sDKJVW(I~v1qBA4B&cgZ#d8C*0Howh zV9;P-VPIfk5Mb0`U=RR}6TtH_sK5mUxd1rpgS5dIAW;Se2CR7*mb;Pias#NrE!n`p z?7#@l$qbU9_7&6+2*m*^aluTmVsIe|(ghZR69Nz?DKV(Psu8FNOa&r)?yrYr2e=4K z2?Jcj{vR|`>wkwbu&DWe|2;TcfpkHeEKpS-5x5kT@t#_F8ONO276rth65lu1_Oo$5Y1`8pa7y_d`1HX z2ZjI!m1G7X0S4TFr2#C5U(E!N8a5^ni4#N2Ax4c5PBR!77+~%pLX9w9HDJk~0*nh7 zG8n`d*c%uhFjO?~ff$b%%o=1DFgw^E0sHF!>j#B1O!f@?mzwV}oNur{z*C@Xu;5&S z@dKV8${!dCKn67kJm=^*peMw<$LS42N2A^Yjt^Yw468LXPWkL$dca};Qq#eDhN;0p zM^Ku9=?%jo2Au~Q223psiy0ISa2hZ@Vg#FWKyYnCN2^|f_XMVA9Ni3h4>&e3EmK&` zqH%y@3)2Ud1durlqK6qy9I-Rt-@*J|;iZG!0iF%aiy2;jFuJ0>2oPGXLS&KhgdG{|Dg`mj4X)kNH0^|6l-{;~=nx;iQj|0RJv#9flVUb|-i?FtalJ zW-z+YvxWHrivq~56Rc_toeoNFoOhIX9d0n_eBk)dq@uE0VbKPT9n7B?96)Lwu=+4G zI4Kx-iwN-uG&ATN(cIF+(74${VZsWNh93+8AT2%J)c7y){=3fxIJQyB${E)O`=x=0x!0@BN zj)9*6#1N2U;%;D?!~jk|90ErePBPdf@K0cV%J7=Wj)`XivkJpc2BQp~4a$cgb~Ui1 zGITQNC2-GRdJ1AVa!lY-Vc6K9RKYQU=>Y>cqf4-=GIS^Cxp2=AdSbA$LC=9>f{+Ts zPK8wmI3_Y3gTw^~3ky>ti;fGo0n-tOMg|=Q4iP36f!z#R4jckZhrq=M!w%l01t(V6 zS@Cx@KV*2pV5h*t!K}342ZI%ZP6P8hW^laE;Qhkzm%%=R{{q9emY)pv0{M5CzcBpO zF!$o0p!|#toK_`RL28T~_%DE@nCugHCUC0={A4h4;F-XD3=&@!0uLB=Feou}FJOAW zu!~WT!J~mWfnfu~z6P!XOy?ke=U_d;u(3fQfm^`o7{f*eT?dH^LLv-X8I&D30+;|0M7ws1BO2i#sWGAm`_3Smj;VP!afeY1sxBVD;W9?>MY_|;Ix8a z7lYOWtp!Y{AaS9=>cgCb26A; zLGl*|?;3`mEOre13z%Op{9>?kVB5yDhT&&|y@dV*=0lMD<-mH6;e3m+LeCH8OAJSv ztQB}}Fk3MkZ7@#Y@nB|v#McVJISj3giU$}94&7jA6)<(hb$nTAFNg$EoGO$Qhn9aJ1N6u8(x zWu_U2K$8a~-dBjOXE?pUSV?CK^DBnKAB+xgJYZI3ICE%og3bmeEr?wW3~v~IGwc)U z-@^RD;TMAq2mb`-mkhrR>?icMFh79gFGhhs4gVQ!1$aOu67&q`- zU_Rgi&V!9Cc?!EE^b|POEPBPU>!g+fhX4}?Q^Nx#2d*Vd4;;YxtARC#VK;-G6UPpr zM*_PWlpHuVG3hd_Vo+e>*rxOV5*Hi{EG!!tbr>}am<}=QX3$XJXwXt%Sjn*902hM* zG=DL0WH9_@uyf$q;QRoR9X@dDFuY|jY~Y!|d;pTa7RY{J_`+bz!2f{x1IJ%bfq#Jc z1H(NAdk6jp%r7AM>j3MAj`IqJiv1_FO&HD#nIGW2z+A)dzrpyW&mralNPIO2JYwk2 zFjC-Nz@*1?SwQ&^#}p%RnsgjyGb9|~n8KvPu$y7Q0geWy4Uo8S5PZgTnqkuc{x!@x z45yjQ4*1Sx&H>ds2Y8k=!|Sgt4xbsUAMrn6{=x9w!Tx~%2j)KvzZpOk8FK|Be>n&& z1nE-XS-`B*aGJsFK>q~h9EQ&fn~v}(BI++5hW!jm0SpV6PcZyvSbTunj?0E&e~a=5 zjs*jTKF-49zSG2e|E+cn@r5P&mQC#00Ou#2B}4SggSDgXy2c_6dqV zxTBfm7`7i!e9{qtsJ|E(J{uS`@EA0+C>&-mIKsb)nTO#sN6`@;0cLppwZ-9oiTyGD z1m!yn{~4^$_+L`q1FFA{@n2xR0Lfn~Sf4SRK45f;UxAf{;iQ1jE#4j6x(=ro7#-qS z!U(Uw<}frnDjwiYU=rY1s$hD8;{y}ViR%HHCo~G0f*|31fT4+@y+LsS_m3uVhTbQP zPq-_XHjGG^l8n9QJYfWwd}hy|RUUx@Bv zI>lhLf z2$H`z1fD7UWU_PepTMk^@Q`6cLeB)@Ck!VUY#i2XV0MC}u?Chjhn)<12e~I0v9PUV zP;$_i!*rCPlLb^sA=h6!Ilyfs6^4}zN-iAJAeEg1#}uUci(#XHjsy2bE*_zU8VdqA zM3|m1H7e+MXrRdf=IcWH>9${!?P;lTDU}|A#WYBQnVBp%q(#rJE zK?_uWf$LKV(IpIx3`!B)B0@_TIvJE4I7FDHF!nPfJ8)fq*I$Cql}<8jD&U`>1X5$= z$TLBB36m#-kpmy1{$lKLc+Ftvz(0Z8h3O-M-2wgv<`jmH3?>e|NcGnOh93+@4E$}( zN-QTBHZbrvFfV92!)ni#_16MMP%})^f%^(mgu-G5MF;KzraKJVnRJiL zC}4orUn@A|7(RCxKH$$_mSZ@6z;FqFRdXA|l?EFfo(SewpjIXW1BdLIhTjTyUi=Kq zCm4P!*xlgY%KV1mG=u#?<^{}$Ao+rU^*_V8hRqN7e=tiioj+i_faeFZ8N;6m_6vMo zG9G}GhoJiFT7qGNccO4)!g2=10~`g+F$~)obUkce=%w(>}<+Az&(LUi=mrA=>WF{lL|w(Le>S48fg8+!oj$KA*GSK zfyskuGJ^t04G+Tt1_cHWc>ZGG%>cEL9QY?NYcL#VFgoD9fmwy=G=nv$!vg7=GJxu@ zKMdjw{tnDP82&TZ9^eO+^M4=}*$Z}X-Z{Yf!QsyYdqsvr%q2``8;l>E`N>@8a7V$M zh4%xq0VKW}1fCw)#h|Ca(aOXE%3qvZOiBz49SKSj41a{+_16KG%}RO<91Ki-EKMv5 zhfXMoonV;Yq2j}3$fN{mA07}~;;@@RkHLeZYaPh%99#lyEDYT&N(@{ZybK_5;UM~? z;kSy-5uOg_Weg`7j2d(%Fu&or(O{;)_kmde(ms5^xP;+9s1`oJT*B~&VSfVe1Lltm zKb7nYH8vpnU&|P7d)Uq3pWv*+aEf7515YD!2Ez>o3nxB9D9!ydWZ4HVGI4XoVAm(^*urOQ}u$eG_5;F_KErv}F zJPOPV3o4;{S>B566E7`v(3C%=;MLGuXHAen8Y;PZ(}9 z7(MW5P*f3k$xy@q?x!4JFzV#l%&Y~8FAu>jOzjMcjuHt>a~N8f6c2DDFi9}9GMFCX zC=?Qcg!2JV|EoiB0at`y8^fwbWd)8COhybn4Z06BESL-+`O8D(97Fem1qZk+n0i$} zX_aFmlMX{8gWdrS11=#*{(2!gyWyUL@wU82%#sYN7{Jw*1;aTF=pW$zAoN2?4Ac?31TCKho-3RbF#`1qR6z0Wz%zkq3ByYUs{?B$ zFgHQ$0{6cTD7kS@VNzjO>7eAGF@Z^iq0>Prfny4j0<8a~0xr6_MVM3sdL5J}5 zVOZ#p<-swbsSOer94tBx%_0g8+=5Ix4od|V25^KpO=W0wPzc}=O8= zAnLC#3}-=U739=|44Yl}CNeK&cV$=Fe@^=VK8RkJH~9laE8g4gXaJ@7bJgauv#$KF(_~FUckh` z(8r+Az@fp!#n9ED)X1^Ii3<`J8mvAJn;)p0;5K05W7*Wez`(@A#KhFmuttE3fvF3U zzZNixFzi2|>cCmRB+D?5L(zw$ut_GRT~haiS4Gnbh&iD8>ubZ-2mBt+R!rX;j1~A! za~mBvy2*Zxo`mu$NdDrGUBY6(AjKfx!2N>hHN!p!{w>UJ8Gbn!dGIb^c7n7I8Cd^0 zoM|vz;PZp=o4}bCV}<@7%vKEN8jO>8UogY#uPqKe3W^OJg-ll%?g%Xm;&xyXVQ3Xl z?Bobwg4QVv3=s4o;Vxj3V3^LKcu=~4390^SU{Kj0!NGKZX(EF{ z0C!J=dczC`1tBR0ME$k!!08Ty2O2w?&M+KmP)^}L(rwx_hheiIe*>caddTpT!73J1 zZ5{X}V8g(>f%yf)YX-Xmj7as@2Zq0(UZjJ&2E%yw-2MYtk4i*JaDWSqJgF%UbgF%XcxshQ>1GfMdG=DMhCNTV9+~nZXz@)%* zlEKV@r$AVS@i>Fw0lp2)43PWF93}nBKlts89Eu1pR(>?(h%6)p!|X35A$}0^$}W!ICdcV zUk4aE859mUO<-yOJAQ=$+ZpG{3NJjgHo(UZMHeu13Md`moY1Dgu$zJD0LO-)Hw=pz zG@Q6LG{O2`q7Ru4HW(e`+QYnn;RC}aP<8x_;Wopr4|4OUk1BF{XdyM zGW=O!cYuEb^K#Jm;V0e;&G7zLFVkrTg9A28m=#!lGT5Ea*~HD#@Eg>C0gXd~$Hz{v z>NIpSDj$&0ZBk&^$*B8*<4=>h!u<=2B6ww(;QcQTP`mdchYFJe!-giEBV0CW?hK0$ zseE9tVSx9)gc#Z&Ur{Vzs_(+&m)JcPI%I5sfY z9N{w%c42(XA>yEeG=8`NR4*Um4PoBs@Sj2c2n7O!{LTaC-_^K znVU`-*qz|n%>0z$w1UzBpAF3L{#Orc8;jx@W(Oewfi?r3GaSWCcMdGh(00&pV7dgU zQ$Y1se}m#fZV#p+#`XoeZ#XKLz8_fLpqY?Sp#<-LonvS!QaHfPGs%gi*+J((hXK44Flyk}Y0m06#h}xmvw-;(!z%{s4LmQ9`d><*ZWn)x^#X<; z5}@%z#s>@q403Gzjg0X6>j=Zm1$GYnyO{Mjelpml@JwK4VLI6WE?42>hbau5O?tuH zGnke&>^`8Ez%fBdg`u-yQ6cj9A*lY+bKsu9q|&fc0Ms=TQeo&+Pyi`Lj2}8Qg8E+q zZY>NO8FWCyhAc}O6%;@W==jDCUQqpIr{KT9SrIhWq~O!RtNzi zy8wTI;wKA!v)BcwPhq~o@YBNHTmJ%T{bd6hKUCp($!PD$Gl3b>BSRiPJi*YxqQ}78 zz_fq?RFQEsFg;*c!C=O~fiiyB$e`oMEx^RWu#iF5fh&OR3PUS{astZuAyX%Vo`d(K zLrWM|GU$QouP+Si8PpRv5dAOFr%Wd&fa@Og=bVgx{hUx+Cf+j@&i|J^itrABClU>X42HOQa9?X{*&US2k z#P0zgKa^dfv6I1$W4!_MTZf++G7kD1lyw;X71+t}Z-9*-GO+$*I6uKYfhEJ+nBjke zy#mh*;a?8tKqdbR#Q5P9&}cjhO97LB!gc{&7mfg?D-YH(C^ql}AjS{pIZS29W#A}a z6JXfJpvSZAo9#Wq7289Pw3b65c2Hp_RtOo=iXJdSQgr41Y=TsjP^81 zGblFjIWrqHo>36z;CaCW?|*G!`O9F-zNn?pQ?$o;Q<0`d+0ADRC+ z{b#Uk;6K1z;84S`nW6t6qW|@P;YX8^gFJ&U4}-UXL8FcVvkKEiP`%s$>wldPoWro4 zLD3=Ffk}p;U1MPbhXWG_LmNXv14jWDwExw>FpFX10nHEGCzRwEninW9(D=b5$I#!P z_<-Yw7NY;9u-QT50QV*)HkRg73Jn|#Tnvm23=0@E7!duhoeKXZ%wE9%NSdSR%ps!& z9tCC&hC>WW4Ll0m@c!3Bh93@U2L3I=4;XF;*f97X;Qq+)#$i9Gtb^BI$074yQo7*rS<8MMItuZGSaTkh6aZf2^<%>OqqK@b-@)_ z`w%q$b!vs3mj4oN1%?x#?tc$xpsc}2f#(Fb9;AH;8bAEYqVM4UOZf}Kp96LQ{PTpL zgC>;%_+^~or9YI~%rV@Xca2YPh+;C`0F#GaID8Y$5P~172-`+N&Kc zTuKZbF4_VdOPKf|e)nKK!U%FRhXB(NhK-C$fn0)2j7)PK)B`jGxtt*58xo>R7`qen zk~toq^8<-&j zi98I#2N=#U*fH?6Ao^dH4SfvC4crfytQht&C@%6?BD8_6%Tc3|V*#@lBrY^q{Te10 zC_LacZ0cj!%%I@FVan9Q*pQ$h!)3t41?zuFFimMtyue+-B%`odLD7MufN9TzCWe&< zIEt7uAm)JjU)P%qLF0b&7``u9n80JvJdfdMgT({BUz4?9{jVOLhYafu@K0cV!|*%6 z#zA=tbI+>Z3|629Fm(P4Tz@q*Kjk^a{Ey-P0pkaJC%EgF{!TQ1qWh!U0TN#;1ZOg| zDJUM`aA3N^uw6j$0!I*&EJM4M;yI3jCTN|)z%a+5og?u8hZEZ!hRqC$2e=&CVi@K! zDjwi0U~+(rZzzbIVQg+lNYLP7I>xa1g2Dj~87^L?$wwA^=&)$wfW-R>(RB=`9h6S+ zOk;kc@SkBp1K$qje3hFAj6U%GVT8`LH88w%_`zX!fPaGU8;0K;b`$g$F~0%zzb+t; zAO2JL>12O^{{XWI!+8etBRU(nb(GF>OnRiVfeYIIYGBD_*m*#&!JCVzhoPB4>4AqD z6Ax1}gTg0`4eHgN1`(6NAnJ z?gl0nkU5PU4O%)J-3(d>I1HGYAmbb0{uii?)WEI6aGu5N08az64#P=Cs{=kCxEmn( zYk}+qhCd9o4EzU#zc7Ku5Bm>r|6%yfU=A8c%z({*ePZ~}V0@701*3t(9R+;`--FBo z41XBRS$KaV#t#=U>_4EikYfRp!i0_tB?FE&CS`^W2PGShKTHacaPD9^z_3{W)MsQ; zYuMnh#)VUuiNRq*h6)2GV*F5a$%5SsN{t#Dx>Oiea40cwG%#s0aIz>hau_f`#}64q zUkm(ZFgn1qliAMUACtX0?;B=gjz0^`FYs<)W`N|c2aLNIelzZE;J@IU!*GqoZUO&6 z#w$)c1okrMBaI)fVEC+HbOO|;<~i+T;lQ_r`vJot2CEBtJD3F^;~OVf^%(ZMC{5s= zz;q2Xp4q_Bz@*1;n^CEO;{y}4{(8Xb;;^|v;fZtu(}@&D28{(>4NT{l7PDwDa2YUJ zK=PLYqcp>I&|Fdm(=CR^g%49WESQ8CwlyduNK`O&K;jfMe)ySV!vSsq<}*wO87vOT zi3p!jddy%Uz-PcL1j%0xvU?fq1?-#pzc7Dr_^V>?09ua1bf3Zg3jfFE0Lb{p3f89% zzZV!C;BQfW2cBl)TL6l82BQNkn_%^qhu{o`%^8Xf+zt#k1p2_^^IRefR~R%Lct15j z>#qY0(-^idP<+7cqjZm9d57WyE(<0;hV}-<2O3q#24c{#&LhFuOmvk>)H4#&&` zdLKAuusvbe$)FUVF@b4WLnniogT@Tx@xz_WdZAepnLuik9XLSahrJBi4jdDh;PqFV zz+?s;2QERTqYPUe6dgE1l%_DWGOTsr5JHR}f|r^o@U<`;iaqF@IwL75Mx!n4d8G)G~A6pNQyxJz;puVC3L`L0OIAXM<4^&kUwA zrjrfZ5_m5#!RxOVpka9i>8|!fhg|`S1Td4z0gLS{PO` zY;pjP!TxkG3*ea~?8emTV3xo;fm;KTFQDr;9QY?FzhU^vU~zz7fcZ(w&juS$egSST zNZAWozwv{?j)fnzH0+1NE=HaPZv}=Q4x1Qy8k}L{hb$Tm`yBKZbf+l2;@QOlo<`ZB z(8ZwC$gzNn3o<9A!Rp7b*+WNzn~CX6!)68r1`Y!yHVe?YFAfVXX#J(YD8aCWL6?Es zf$1*Gb|b}RjUuKw4DAYvCpgMr?L!R?IficywkrG?%=Z{R3m7Qy)T#3^Jf2|qK&OtI z3zEM$WLGHs0+pEynBOw|R?(X!zlHq`gSLa+1^q3|(E7_k^dG~whRq555zN&L=NpW5 zdHyhqG2HJk{><}-SpZspNlr}I#-PZ+9l#{QaGgPujrT*7EJHhk`2miPOo;l6VH;?w z?qib$Lpy^ahhzbhtiyB$)kB;G@bw!k4howX6g;>snA{X5vuS`D$vg~`om4(>8VE^1 z%GDL3%NTxv#y5U2rz@OiF>>Jfz^%@3-(~v;-2iT=U7+Qt1%=%VN(Z?%Fr_iCW>7jHv4KeeQXX+ zyk{^z!1IBdfeAdHyFl^-sM~db`vda@(7;q9{|DwTAO@)ZTEPlV^9NWjIh<**=kYzj z{K4T&gS7(h31%~f7*;bX9gsluzeJZft>n>B@aSNAqOjnA8Ux1)rZu3P%fhjM z30i+Sh(2I=!D45`J5Tv0!;1zx0bUMf6$TFmqXj$)obdkF28R0rb`1OzgsU0uGZ<^| zUtshgtz!2mXi5e;ED<>~H1IbcWYoFBm>M7%}iKP<|6|ipi>h zcLQ?@s3ol6vjDmN+RmwX06fjz&!Bj~yMXBe!*vG51H1tV@cL^W!~O=%1nwH9e;m^q zG(T`h2>lY6)1a*2ae@ur|9Z!=*+Juox1G~jhRIzT54cU(&M-6|P`Jr~RDbO{@QcHG zA^#KR+b-80SU2#dFlR8$TJULN22%a?kYNYMG|&|P!=M)~VhsFk!i5ZfJnb3$&j=&c zU#Ab6fo51z8BVeoIq=V6W?}GTuySCWzzpwyr4@8CEDGSBqNK{usgRV|JWFU9OQWNf zgSVJ7y#EF2nyCeJi!!A!bc!rG!8w7+WZ_x{W*5(kOz`3BqbiPO=z)#^RnZ{AJR2@D5N$tUnY0tv}>g z!2FV}60)=m=mkVd`bjbl?mS3t`yEqyt*f0bhT(reG)I zA`b~jm96N-5y-TKVJAZ#Xfzcu|Hbf=$tZzmf{+@+L55upy)!@!24*`4_L+$B!zBzq z8El;R1)86N76yPKB^|<;Aq=1YTG(*Jz?{MFi1LTFHwwlKJqH8}z$-d<8kk}A7poO$ zwyS|-5z`5VE(WCq8cUkAChQ9^Td4cSsSA?7G+6x^+8R_ExH*^(Ff3qbVB%nDV_?|m zpmKoQWD&IfTEHl#(8j2Fj>`toZz3f(ggktW_bT=3BzV4vs+vn4!>piEns(re_8VafnT5j(gs-l#lZTF z;ar1d0?&=+O$uL{j6d+4X#T|Tr_uHS#|gyz*A#~q2h$FY1g1L--x-uecngK@xNKvz zb?|T!dIf1GFfdGE=y1>kbrtV0>}Swy;4A>GnUv6V;Bat)&wm|DXk<{?z|jKo`w7sR z9Hv7Gn;JA6IG7OgUu&37u~=@BLPmiv41XAmkMR6pj%DCqVE=^Y0ka8sEUxDPeEg6l zm0`Dok`Y%YlcLHBCOrYJ8BHr9c6I0_a`hm_57h*AGbk~1G&ChJtZ>kC;F!py0jkkX zaP%<2#}8Q;b}}q*;4%>6VOY$d*TAuWfrVibgUSI82E_Pb0>e)hV+WoO%r&4X{sTH6 zKm&=4b_aAC;Oh^0FEIRJ0*}&s;6NTfJjj0lF@E?*;d_&P2hU4p6NWDm);zo?nEx@H zV>C?U`PB^_KWq^A%dopaZv#hzk^;ky1S1}<4xu-olzD<DJW(FM=&J|27 z96K2FI3yUD&MNZyXR^Be0u6ZxKfalM=&@4kb~phPHQ1+zo06I5xrS zFTpnqza4BB@MJzKcp{VyAVZU?0e9Sy=M z49$Vcpt;Bw49!c-9%vx-zkC>*6BKT6C@?iKG)!2)z`@A$MtLEV!WE7UN)?dyh61CU zLwloQ5=RbG7(?3v)dUU?rhAObC#XK)$Pn^?iC7%;zK zco<-CgvW$C2+}@;te@Y{qW_TV2g4sF>jQiroBuG}7qLIUkFrzanwrlrTx1eDz`qeuf6YD6&Y{?#k-#LwFpWXggTsmG4of@3#sl0Jn1mqt>j1;N zhV}-91spG!q*%6fBqm7KF#TqjdqDBg>m z!O+~G^nlwMv3`C9!yyLa2|7=>ts1^DZEoaUl6;Hd-T|Wq{wLhKAa;2$9ANn2u}^}( zf%&1z4+bkHy%y#NDnB@_1pFHq;p-2dfaWVRbS5ynFq~|#aNwEH{Dk2olTilG1Yvmn zmBP^Jp;g2&g=r~6XM>gl$2O+tEEffoGB_qMEnonzhm>Gd0d=JvJm!GvFM%uvi3vEk4gscT4iiCxS3+FS@r@n4PT*DnM+0*<(}@NP z1)c`xhYTlHY+}%9b%u{0K4e_!Bs*x?;S+|xOYI$X1eoFduLmq$3`!y#U0qrXI~FK0YBVq%VA$bc1R9`#jvsQc z9$^BlKjfI;#L~EtLjyEda)e>y0UZaf2~13a;I^oQ=n|0M6Er5UsW9whQE*_G!SqDJ zl0nI#YX%b=BrX<+K4Cb>V35Ewp;;Z`o@vaWF;Be&J~3tuNdD4bT*C01WuGJe1nzeU zpAXm-@K0cV*YK0Utblg{H@yA=jUU>H@H8+hf*OhpJPphb98NIUaqu9GA8IgkG3YmN zq%vtLbsYeeV@$6E__%a8a4b-Uk00hRG<)b&a9C(LGfY0916nL}fN3#=^tRm{&5qJ79NDa|!bch1;NYdYhD?{V&k?;qfEJ3H+~^ z%?_MDVE%^X4)d>s^Nq#|EPoL5UvnJV1xzn6Co#!5^a~iC^C)Dxa9}xyHiJeXa{sHJ zK{1H?F`LN3DGUl{Is%yPaJCC*9^xoqVh{k&zblBGVQA)1ap2z2#1AUoKWHd0onTnp zprOEFfEYjA!*JTc$bf%Cw=u{41)D$cEMWemaGJqVfu{s9|8)km?684<3G>5-*A5nk z_@^*GVEE0T$H2b>vHtK4!+8c<2Hp?MVh*5vQaZ1gO+ah58+|q)`d>K=-AsB494{7` zGxP-L9pqq9&fwV1q}R}~foTDB{RXQJXpZaw=L#+jg)WY~0~#$%8lc*Nfn$RbeEkM! zDXESFhk{TCV-us!0geew4jh{qbQm-?AjS`C7)~?jIr26z>oA>Uum_DF+OYg)u>*}C z!q;#70oAY!{0_n~;EtI80p=T!g&ZH?^%vtehW8D|4`hBY+c8{kFnqx0!EDEHr@@|+ z{{%C9|JNgijs|dV3smtcDR9kj(qrgu(0|H}wEyb>XhsZ_tuz=WGbn+U#Op9jV90Uc zR&ax@pBG)z(9xjO!m)$_w01>_p{s)lyaKS1dkSLy>m9=j7NZ9o3f%k*ml-y(@N{sg zG~5KGu@+_c`uR;P{~7l)^510s%$CM`c`VBvZ z<`W7H90p3R3>%mf4m0R5@hNO-&{5!)K&+oPVrXelOo;Gcy2Y@+L0y6KhL9Y?)&?Dv z^&1=v4U7hpbQFYr89p-@i0~+I^E7;7uyNowK&+pi!}49w)`1Vyo%|zU|A7C1@E?Z1 z0`?7jNb_HB7+yCRIq)+uCrG>iEnrq)Rsp5M4*m&<^&1lywlFMg;4To75ol)wb;P(t zKqD|t-U*2H8?zX;G%6}cpI|Ct>S<6_;{L%jjbnR*E@&Y=eEr55md#5PZb+Ll@iHtH zP`Su$z|_RB!9hoXn*p(Yql@8}hrtH^HOyHFhc0ZNz@Ol}y5X0AT@ydj{;v-Xe;DjV z_zxWZz!Jk?&BWitn4k!4P{I3OPnk}-*ro8#VqU^}TLktps5@|9K&+p) zVQ6L02MsNXu=FxKb~rJCX$r$u23;>^&{}lx_{I+21cnm~b`5?F%nO)KFc>rNGzfzx zdh9s(4=}^_f5|>!`3PQT&U^&KaOD5M4I2EkJK&GBe*Ox>T?TVU&I`;Y40l1(lpCC% zfcrAY>o*>NMj;qH8iXM0=e-(?UNG!n%46_ATEB6Oab}a6gQo$T3d2N(WYD&+DPy(;t0FA0Ua9o71pBFvGaFM|zf_DnH`hlAY8{BxN zfW|x-^gtCWeEs1Pj-R0QhZC5e3HcLLK5hGPm!4crSDUOMaoH^`Cqe=R<=;4rsA z(>aD_2AxJu2Brfn8yI+mG~oNc7BE^c^e`lXmd)Q_=x0(rz+EDAM_@aH?g5U1Nr?3u z3{M&i6Z|Wf?=c*0uwB6K!F-G1OM}e<9;EdfYZx9d**W-cU_8U{k-_Rf{|4qK48Ixb z9Ff;=d;^t!DLf~c%_ROe*e~S&$^4DsZlkh`*mZd964qn*#bS4nzoYpf!>4!Q=pE2tP-0+cWKn3~W?<`J*ubFEfZYEA?f=qi;As$6;5flx1RC=M z?P3FudBXR9f#$z#8TcK%KZyNd*x$&1fO!Kbje+LP;rqY-Fx;P@c!1{*D!ykrj1?4XsQy6pvz)jf=ObiSiOc7WDDcX0(UO>ly(-w=GmaQKAL z0iHX|X%kK!GRolF!TiYSL4$Dv&n?9MWzas~{Ri|nD1-I|fTr`fe{lR_FazyqhL0ci zF#L8fImEMtS(D+XgAr&bScl<+htUnc3Gn_GqYcAO2IUjHvzT8nY*bJFv0p?g7Pf;8x-GYW-#4z+~1&> z!104=634U|nkigAm|*=c2GDLVlN0wk&varn+) ze@puz_aBC@4Ezq-ADF%{ypu3@pHVI>2affMo-s z|20RUok8&g#|I_}hHwVm2HgTC35GT%MF*||wWTz*WIy$I!As z`2nYn7kvEi3_}xx!U2vgO-u|EJai^-YIN~7G#yX~(O%F6pZ{9HbPTjA?+Les!m&fP z4Sa8yEg6n2*tmfA1!Df|0K*RlJBIZQ%#S#JFxau_H!?r)xWToLgRcR3{_Et5T`qpJ zAlsHf#c2w|PlrhfJTnpVUnxwT3^z|4z%hjjY5cI0LCb+_ zLX!%|&IF|Zjww#53_Bf_T#(n#vodr#C>-Dr;bLXjD6q(dVKWF&bWEaqXhxhK7bE54bH5 z>o)`#dN~puyc3vY822(HUf}xBB+1auq58nPgh>Np&I%4WrDF}I3A``3aqI z{Q>72j1zQjFblDqY1o+1a|6-;0xg47^x;in{_~(!!V=UFngE&zYv2e(tlyZyuueeL zhrwBB4o44zIs=CTlNrMr4#fi;6|ng)(EhJR4h08JJ*M*v4GtOx9h_Y44igwOCU6*V z!PjprVR(IF+W{Xo={-y*514)8G2qq^dT_vKlFkl9|LY0E50`xh{2Q3xD1aA^ZeV`V z@S4M}iGK^C{`$i3&%xNicY@L$hCdS4O&kxJZ9pT6piMf^^&5>WDGED06drJ$0Iw}n z0&UY{qi_!w_2~9}-uMG?e2ecZP zco;S^JaEtgElS_au%Lk(G*=65A2RTIfadiM@;5l!F#KRLV(-V zp@zx!!14pkH4G&TybP)bw7+nC18ppNzzwgzJ~13=FnqvM;cN$*uYAb=gZUR|{RU{` z`3DDZdOjfV3RLer;MlUJqT;GWcULSZL^(qWEGP4M-HXBavd6b^V9u<Ao41?L|OzZ>ii z@c%@NAFgFMsZiv=v!PqZ;Rb^d1J43xMTVOUn*ww;Am+aqKr3|v9L$(?7)~r;Qs4lc zd(h0FbdY056MX%K>%^vm3KJN3m>fWz{UaO(Ts#b*O?ezPUC{dL0izXTZ%6V14i6^K zLVDE)952DUprgRd!f=_<=mL)+6RiG{ zodKG~Yv4b~e1YK$gCzsMBl9%*~27?1U8`yOi zeuEf$n4xp)Cm3f4K<2x-1en?x79QXTV3HBo&R}{#qku^WGDqLQFppt-qb{ib1={wd ztf29NNlavOgYpB83Z@3w_~Fq5n;CT&q8Wr71R59>K%4ti7&aTE9RMv*ftd3`bO*z~ zK${N!1>kMVN)0>1v!EM3e)xa|RDUsp%!pvH0~K)}Ks&;i_#2qv=Wje? zxH-WpL1&_~8pBD3P2h7X7;b{k-(ZIKzuXuaPbh(QX|X#rG8njVh=AHZko{lq^EXr+ zW-@4j>TXkpUKV9Xjw?(i3=);}jQk{Lllue>usK12i@WZbJ$%nJBb!C_8ZkG);omU!qGu`P2clY(rpWgO&$} z0FwzrtAla?M*!0@*!bZQhRz4zT?s5qCld`Sbfz)8IGl7a0l8-dBu+CJm$Lk1*yoTx zf%^%=O%^)`{Rzy^zXT1u;7G-0n>Sg<_3ie z9vTc>ki4^iQHEg~qy7bs0;WFkq#*+Ah$kV7|n3 ztidpWCxiJO)47I?2|7QxxtPFX%N(+67=AFw9blipd;;A6;$Oi02DCZC0o4D3gpUL3 zKZY+2#-MRXIfnBMh7WjtFn?k=+hDwq-vhpWLvRYi7J-Eh9Dz(SOzi;+k7yJyePC!6 zSlGbrz@!Bk|6yR516p#~qVa&qfT4{+nSmpb>4L%%21PdR8?gE-LF5d>h7NGEnFF+U zyMaT)>7c@j4g~@3nN0BY8|xf)b0{?MY~a!X&4V=XY+`!D^m>8O1MUUfT9EP(wEpn7 zgWU!G1MuP|kBp6@fk{hYXM^4&ZUd$SP#dX1 zx&gj^gM|UIEro%J0o3hs;5J}V0PR9*;AVg>rD15`O#zL0GUzPeRsgvfbSO#!(+LJ6 zhWQhi8Q8$_1>XO~V9N;F_QmoAv~BqTGiXJ}eg^&vi1EWe4M!C0Ir>g8+km18w0^^g z0K`i=h#9Ugjy90pCQM>-pn6u22CDKV^2P*UKY#AE>J=XS83WoR(aVc}ZA z#G}yAq`<(*#MHph$)Ul>Jwa#_B!3+cUBlFIKu?il2bYRMcY_y`bOX}@MNNiN46`R7 z_J6%$IMHZl)Mp{g&vcMsQv>e==7pe^up_?#TLUD%p!>f-A^(@*KZAJ#|4rtChJOs^ zDe@n@;rqXO7*0BD^5EIPtfO#3!Ds^i1ZG80hfq4Q0 zsOQTUz_5YA4AQQ9!0MyWS3lZxNS1|m$ zY`sC}LGuoVe+~u<_?HNGF#Kw?nV_?R8NPlVynk7Qze6k0;fE)v@@;wm>WDG*A3*fK zo-w^-*yO-Jfl;LabdVGWXkGV7Mxy|p35fL@?g^caN(tPc@xz-5dIcN;OezhXEJ_(% z6PObqWt{}03e(C2B@gaNOiKhhS@b+KE-;xfwSvxB0|8;QpXA|(gZ2k$%Ul>j@*gN`6 za8`q?X9JzT(ZR5Yk%OyAfun;Fd=SS2h8qle3|tM!{jWw)Uw8r&3rjbP258yF5r&1} z&1+6Uko+Yfx`eS)LCJ$-0+Sj?r-Rl3?g?yfK&6d?^aLjO`uQhJCl47U=tu~wG=cUn z^G{$t!tzql#(_t`8M=O6gRup)+smPUXZI0?-v`V<3(lV~++?x?ZEBbWDG$N*SBsGl zeksu$4ivfxAHIFGC-L>H+SbOtGNVvj@79 z5bHPO82&aGKJc%Y6vcA9(J+a>iaCtoe}icP|2xF|*BSv(_r!l2a}U!e4m$^alWrA; z-vav__?IBoZ`=YMqxXQnlDSOa|3v$zJU^KK37kK$`2qhMMEwOi7C@JUyLgg_!!`rm zBfN!7cO2Ro6c2DGBKCjHVQ6R2y}*5eO9pgQh=W7{6KF48Z>OUgjv_&GlyLR&j;og4DT6?8u_0z z!`IJ0W%=1)x1fIu^IC>q9Cj0UH!!~h)n60%7coD9oDaY#@Q3LihkXJ6M`jz)`5T~3 zRiKsU@*4aPl%Z=F8d>}sIui6AaDQ2)qtV5nB*49a>8ZeOCOrY}1+ewK4Xk<$yPNb5 zcpEV3G;|0kHE?fWdf~8|L2m-L0u!SD#nQl{)5vYW$HUOo0FKiG44avB7`PSG;QNM; z*9=w;EDM+w8eW3T0UcBVK5=USGkpGwk)a_#L4kv%iLs$WL4ko|0%$IhLC=AAEn@$& zufpO98b>&+n0P^3cMotVfYswVCG=>%wThYM*%Va1s>gH;Xlazh2am2Jp=y_MC+ZFzw=KZ_s|gk%7E^egmTpsN2QR&;Tk|1(>=Rngv$f z;7~y9|Js%Ci@~lzX92e+!z~7*2Hq7+yE;y_SkK_eV6uYLmC*Ay1o*+nMuGZY2e}iN zelUnJ=rbF!X}fHU~0^IBXSA4B!Y+I?B-K zpaW6^&0nDTuNMqf4g4+4nhZZ2tU%S)1BL^PcA$kE@cQcyxL4vY#eA&c=K{L~{TNmzX4f)$iVb~Wd(zh zf<{-DQd7qQB`u8;u>Ke5{KK6L3ZT{Upn*h12aX^nkp&=Ma0M{I_J0X3VcE@~VJXT5*n;NlbQ<>KCG}|@?n^qumaSz=VO@MpzweJbXW&u z9W|pELl1*u07nE<0K;?!#X}q=OtK8q9h47orXY=GYgm3T=z*3~E&zG30eleRD^M)M_b-FaKfK>)yny`#vmL|!3C0OLH<-m3 z-Zt1K@SI?VpMN-kp-qLcf%yrCpo4vZCTK1abpD2_1BWAQ{e}R;9);-)PeDC035IPB ziVPeFxeS=LFz7RJL^Q$AKRlt(#E~+CdjY7Q!odVbVImSmQl2b2`J8;yl0_uN#WH7p*3rf+q8Me&g zS8+bU@QY!~A$|jH`2Mdw45bYE2l!qve-QZ3V9((3k@*k9J_hRp{2$oi=Wma=5MpOU$N`-e~*V|#<{3yw-e{dI<6a--UT4g)1+hQ$qUjJjEvd>fhrbPjMLogcM= z;TD5g1J43xYlc(2mMi%mbY(J}YtRFUAnGrs7Y@4w^v`HNIPil(O@O}%wCR<@22`WL z*Ka&)_}O4opfiD6t?8nGkqZy#fa0GFMg>UyFNPF`ollf1B&IQ^Gu>5OVQ+;b)6p0>1?F zHHN;w33Fdu7p3+jI%&3}Q$(+N%KIt|Bgh2fi9q`%=*!n{)#@zzTPH;?O z;&AL@0UzB4QlsaugAue9T|45rH3LMiB`@c@Ga55+` za54xrur#wMIB+X~H(#qTa56B#*3S#BVdzv;I^eN^iHD(qRjGqhgo%S;HA9jDM+1EQ zyyz2#gAIlUcs~g5VL9VtbijWLvqsA430pXLUm@1d?_v52I>!6}^8<$e8um*32bjNs z`Y8n*#9NOu)QHyfrEodNMTB+ zVh&d(laNDuhvEW`3`G6K!SI>U1~hxZ&~$>;=mejEu!hrNh7ApJ3dr@>e^4#_QTPwT ze*t+8-UGs49R3SfHt-|$zd)yHSsl>b;H|>|J`QUEGwXuW4Mqp}8@LhUhoIgTg9ZbW z0K*nW#Rd*gM+`i^kpMe?19X1x{6-yxP7fv_jy(;^3Y;gHHZ%4g(A>bSfvCUEvotp@ zIiO)6#KU3CqH@5~5VSGBNrQn~1*!j~aBBf*Dx6t^p@?C_B;7U4s~Ao(XiwlzKpH<3 z_`zby0BSz5{9&+W_B+s3$n=9@nh<|WGrazKs_>J+%tdAbvx-M1sQ#M3ykyGB1$G`f zGuRR5_i}7xR08#q)CF!bfT|=X)rOM~EGjq<`5f2iE@r?f<&VV4TH! zi@A*9Zil&p4(K5Iv!Hp0JKd=BUjiHrOo|>I4q6Nx4Nech0~Fjm$m55CED8>sB1|p} z8yQp_xD1${faVk&xIt_F3qX@e5`s%u8U;YX!lc92$*>}TV*==0VumCKZh@vYNMCq? z=re_z44cY$=XR$j{M=*~z&C;MNXyBFO&R=CxK}{N=QS9YG=NW=7vO%v^|KK)9IE_; z;pYUq6#j|AUa;}Qg$_R$?HF_#!QHM+pl<1crV|WS2K)`mu=N|P91Qy$bQf_vV6tks z$Dq7`;Qo`<8JZb%9&#Hnz}L@ZR0$ z85J2k3MUDqw15tof52qH(66BA%J&e~Ct_fj!lJ{W06J|$0F)VBxB{615?UD)UAPmt z7+~XvM;Uf7EV#gFB*e_pbU?v?L!Zfkp-CXYfy+=2+W%T1x>n(Ji;)fQ9&SB`?vqAE zd@9VTOs5z&W$=GM*}u$U!^pSA`KS`8zsx>?c?oF6lmj2U|HUZqU*SK4@d=*-W($V5 zE36Oky%089a27mC0bf7Qki)V=Q0WK94(1Go4hFLZj)|Z{4x99fxPKt#zjPQu`@bZ1 zH0dyGV$e|FYLMb#=nBwF;5I@UKV;mfXjucfkB0Vql1lutpRl6jRGuxG4SRv z{9-gZptHf4LEs~Ug##a`{c{qu25$j;{f6Wh2Jo~OtlQ-eIsXvU|ANndv3_Lu*I@jh z|A(^;!?y-w2mTYx-x&TV*dO2lDF)|@27w0-Rt<{|@O5zMH0)?lI?y>o=^1FxnjA+% z6LkGX2kVi>O$-X4R#T6H6@!9=MV!q9a= zD}lLRaSub+5v>^<8$b)`8TAfuY(R`3dVspXpd*x91R72*5MnT3>S5Txq0=ZJ0vkV6 zaO7f~0@?!Xp)}2bOnO;8W<+<|Eg3JDw)OmYmZ z4Z07wYnb5c=ifGLZqT^I&Ea&;VbXym0V`CRc$u0VbS`k)G{Mh5T+Q;UVRr-ng60=vE{yRX& z11>NM@Snh};&7S)bTArX{YDCC{4jufF4Gf+PKQMS-Jl~iRyyc`_CO)lZ!mN+s3mYu zXgb2s$)R+B8+6cUrvs!90;|7RK_!0yw*b>o=0*mEfNlvUHPBpAfR-S9{RVFmX#X;3 zsmUXz6AVTRLHm~(POPv~;OT3Iub+R;@{`dXbmz-=hObQa1^gF4=N~fI7x2$Q)L&0P z!-k;a=ALqZsx6)XW)p#*491`X9^w5j(D@q*pla(;M;F5)(6%oPhFuIMsv0Q$uSSLi z4jckZM_9lUVW5K$H!?uaYy{_13DFjY&I1ad?KV$1IvJE6am?XTVc5x_7QnHg3BG^% z8N*2jqX?cE%ukq3Iv54$fYw8PWH3rVx<7>xJbrjUe^T=k&{>qAKI4-GzZvX2_%|T; zzfLq5ftt+^n4s%75*coQ>UX61FAat^(6YlPN-qT35|pQKfXex<0KEkqOOg6tprs2B zI#ih6Ff@87UEnb3@?n?^T78YQ{!pf)onc`BM?uq_mR3;xmCz&yO3xQ0Y7q68J=4*K zjSF-&@V4P2)4l`x8<>wU{7$gD#lHzr zfBj?l-e3&c^=!v*w88jA{|{z6f%^^i2|Q;Io;QqiQ!q6_Eo5JA$Tfdpd>E*Y-o14;_sdlu<%+*VL_0G%xl>wobubc3c^7BcAwbPA|F@ZQ0s>&Ok7 znME2uz5j2h>kFz<BEU`G*~#u|v=b6VUn%jt(ctMlg;Uu=N`!SPw8PU{N^0#lYkMD)1RN1i%ax z5Tgh(zHvZwfkFodXlqylgF*+R8WYC^rUZo!1~n#*NlgWi_927lM~8O~n;H0yG8a1B z6)~Q`G1{$k+oV1C5#6I6H}bl$=6v%yM%{{r(4Nc+%1 zV4=ey(6ad!W<`Yq0!Ev7CNOJioMbQ(;gLi1znB=h6qNpObU7(CbT}wX;F!V0&2Vvn zf&#}BCiwadKc>cpMGRbV03`Xz`19_AqE2v9s_0;hC`rrg9?cC8*3Oo z3fO^mBIhvtYA^$B4SVA9i^1*)|4QWfuhR@h48EXw&%>aDwm|1cfyTNK^IsU33dS7 zKW$=Ac;I2c#>X%@MFBJkh_rtG)PhZ*rN|mg=NN=m=qzB?nsm#-tRW`>v3?#jvJGB# z_<-S$1C;RqG!cfhe&blfK?m@0d`lQ!Hf(U<5nyITV%lM?vYLzcz{ zr2>u_PAZ@ip%XX|>o-_I`@a+&xB{5AFsxNja^bkxWx~+Q!0fBr~G)e}jwgPon z0{EveKV|snVh3VC*Kf>V{nm80!7PJk7IRv{$rh^wz8TEVB#y4IPvXDP3}3(T(4iwx zX#q!vlQQW1UJ;I^P9Fn09P|XZTVVY!$ofOYAdj%7kO{30x*!i;VE~OEatkmCLGl-P z{XBz`Lq-6T2}>`7z9Yv4rY}sLta=XI8<5t|gUXEpo(bG3OeYiUK-Z=`VL8bn2f8f< zx_>!?aS6jm1{oL7(hi=Vj5eV9>j=oF4*mj+w;=gTLtufx2}WZHokPk76W%avX5c-- zZJ+?UQN^c$xr+t7o=t<5gQ2ehG(Kje!EkGX(gco$OrW)?%8>Pk(D^Sv&>oG090pE2 z42v5SPH-A9`G9T&Tfkw!1wVgYmZ6PvA!rG@Aj5kt2dc0IpfHexu_V84L>1@kY5{|&|qe10IW-`K{e>%b8pB;x{FD8(HFItY9>8>o;^5b_u9~npg^;O^Ynd4NNKm+A2&9%qx)kUksZ- z#f||J4`(xj!hvoBCJvTnCWVG>c>k*b)c;~IW9(^QR$u|m?d%a441?}L8=_MjbM1}tZV>{WOwndLa{H-Rs{f!1FQ z0#6%uDd;J5cPJ?|bTTP1aBN`G;b>$~VBkVIe;%~Mnp=U1L5)FCgM-U}NoPT~<{~Z* z1H}B-8iw5rdQ71CN(InbY7GM>9j5LBOblEb5aWm7tK}x}PvF*MI?Z6k(6f`9k0H3x zU@6ZA#QKe$3jY}FoA@Ux2bQt6fnRyx>E9_vyyMX#%4u2Z#8RS1O|6#hvSkA)#!5KdP^|Ij< zsM?yqtiy1a!AheCbQme9M&m&`e}0C-H3k#VvUwg3b|ys@(0XHreg;JbE~Ng~e8%Ms ziVrwJb#7aOt^!vD(>>7nhl=&L6xb~L8$=! z{CQ;uiK|Q|480CYU^Of&9hg9aY4G*)tPG6~ilFcjWoUI!3E&p-Vr5wAv;ed+A38q2 zgV%}a0I0U=;8tQf)35<_c^L!K3kCz^`zs}%GyFVY7tk-l3}RR%@ZVtm#&Nf4KWNW2 zwEmI+pYCoCT0bw!aMr;nfM*IbE6YI#g8-fjNcX2O++xsU;BDYeYUpZMq@mU2rPTCe z!D0sUkuD==>!2R46f)g0_gN6-V zoGdulq3xfBRtDt;E~NRdxuDt#bjGg)LmP)O1E&L1guylj#RLxI`7h8+p92R269+?6 zgOUPgCzAuij0p-ZTn0|?`@hyO{0FU*Y2deDI(=Z%0o@JEISjX1HZ;g=U|$7k^EWWO zXDDW{2;g6${DR{rhusmr4a`e8UV_ei*Z`})7zMsH{0A-9v1GPjJjY<&!1I9Fg5ewo z=maMw==zODhLncg42udnCNNhp++omf(0ITkz|g~B?x4E?v3^5?VK;+P5=R3Q2Sc}l zo`VEvQ6D#>QiBK5{1*cQX#NZ2FVN)zpg{u_#zqFU22KOm_y%bH3+$c-E(?Yx1}o6| zUeLMUn;dvHAo^d>QvnYs{}A}c1YW22f#VN@wFBP=c4+^L;iw~Xfx{gEa~9r%>@Wsv zf#V&4au(hLhM-|}28N?9tc9#G4&ef#M_3CPV;mt2hB+XS1BT!=LJSQ|j0z1N3PPa8 zeCQ0w_Ads876!%yh6n~R2G#~JLxh2~fi;04h9QiB6{hB>2Wt^yOhdSW=m|y?hPZ+# z%&wyh3<(Sp3~UUH4M+?JhzQKh94tzV91Tti0v!xcMgxNaBY2NZ15AwQ?Wl!?)yAz*`ujwl1vUoAY0!3?1XL=9MdAdCW5AI5M7hXVozEDqpiCIdqQ1A_yD z00U^#D1!qFn85*VrZO@xI6~DNO#mfQ&?QI;NDRg%kccDHuA>gD35<~p)4<`w7|9Uk zAlks5!Wh92#vlrJvjcMi!wm*A23gQ%wHpj#47?qT35+)wwlVO+)i|*jH1a4oa|j#) zF%%(;7O)5`3>z32KvxEWFCl|51dKq*h=Jh%)Xfb{j0O!p3c?&BhoB6W!>mRJKq4#$ z6rpLui8Y}yLLrQUwF$})J@z2v?K98o?OG5Zb^BV~8X$N-#1pgf@seK<#Q^V{~i?NDz@=MPdX( zL?F`+3=9oSf(#7~3IZG~OpG8#5Gc{IGyaN(} z*>%*7wXiWJAzXs>7?dG;j145>E+GobJFaZZg$;KS%q2LFv4a@p61>M43mfkwluO7S z5QK!Gs~c-!E65ztW2}meF^S<4qQ_VY8+;PNB}5MhLh=_2M*ebXVl-%o0OdHy?e7c> z3~Yjl4G{@q60B{ai7gQcVG^uutci^vhA2#ps{?BRV+=z$gXjT95JQ+j-~ej@BM&1; z1ZtP76GK9SgaR7}V-uJGPOu3L93YVfn46tg6dE}coHzuU7(fgb4#6fCg+>XG2tp03 zLt}tK2nQ%zH3k@n@`ySyIvfa5VBuh8fZD~t%ouPW#6XmX)ftJ=5TYQ;!3yr7fr5yG z!HGejfklB4%wS|;U<6(3Ake_30NL%pz`)?@#Kh3hpa7bL1~C>WXn+>;HG)MNpmw=B zFeZR=6SVvRWh-`wh$zg>M`8Jk7nHvu7|R%B8yE|~IgS?=h71fW28=um&I|$vSU`Cv znL*-!NCE?ofHRB00g!tk*Z{O>6?DlTNDVUs!v)Y(5Cb^JK@>2s3L3ij1PF_;9Az|g z@CgtUVL8fb$lw#eEW!deN0~L_U{pa^kLY3$!vMlK5Cvi&+ygrJ7_{4}0mJ|elTBby zVdQ{_K<(miNfMN3a8nfQ05MqI7zI0A5=13h+!O^7=C}q3hKPhRiaLN8ETW984vqnW zAuJ#fs4)y2icTB>O(F`d5QeBis|Qr15vBw*XVl=ZKtO|qi;)S=IKTiBfve$R4raJ= zz|?@(hdG$R<$$6A2M=>F;}MVuBHkH`7-Yb80ZS3%nFcon(G%=Nj$jc)_(*sh6EJM) zNmPa~coLNb91aK>wDdqrC|-~GwkpS=AEVoWlB()-i8!sD3m#wTwo;c6I}3LNGL zu(NO;1T)kjj3WZ-GMsR~vw*5&2XSz{jHsY}!1XfBAdGT@r3q3~ORyl~9jO|{s92!} zaj+CI$~3qu2%bP^h>&Owv^ws9SI`hff&kncEf%jvp#?501Th&f*Bszs2xbsDz-qwg zgTa6db%SyqQoRhXpg{}=M##1j295)u;e3$A(A)yv&8iQa1 znBj)ZfT$5*bYMzgn8BdNz}bMrU<8XaK^*PiidrwjD`*e{TF!%#KD4+1oifP-DK6k5 zV1pbOm>3LT7rh`Djo_JbYR3*FN4?Ma)8=2Aj<+6oe4E&_n!r-QD97O5 zAou`-0ZSWoiP}oM9Kha^Ku~9%M9=e8pHtk38V(CUWQlDAO_4XMhE0<<-vfGzaR!V;LBfd zeC0tRf51EUA1ItJCk z;CdO#5J0Y%9b6q;Ky3q8MnPnTfCvjn1eOjVZ82C>gW6&+5qKL3d%X;)puw&I&8iouEpz2r%RxhJ49HDkmw_ZlAj$svaE!-~5 z>KG>S7p?|W9mDHoR0doPQwzgF77Zs(fuUrMNHsGqk%z_L4tuvfCD4~IRcr1gF&l>X(7W3M=b%b7O90CE1a|hxLVm3@~m*s z5`e1#H4MT20<{6a3=TFiMh;M)3}g`l$juBbpykhwOacKd0*V}=KM87>XHW4zYku;$To{VFBxsxzKK)$iR`nBw)~P zpm+kw90mp6t0IZnXxClrZgzf+T{}0aC@-T+|ey9jY8jRs;%wcNu>v5`qD~2<`W`GE|h<*M4 zda$MT|Dg=KL2rz&(Fbjdk z=oCN_5OcU%_!hE&a_*`Yu7w;RhExmNLIzN#hMQvnZoNAy2;5-NVq|G>P>|qY5n|+M za8`ilQwD|>0YwH-upDS%P~_loU=&ekQ4wU}aAt?A5l~<^f#lo<76HaHpe%fV-G=c5 zLjg)&29*g8pghQ-cm`zPh87OR1`bDfnE)!;ctGV52L}@aXrYxLXjD|7!2vBVgR(FK z6H7w_iy{M$5);D&aPDSghBj@$c^MQ23=9kt7#JANfQE%YX$O?M84!6HGRXzX)KHBA zu!b{KGuFHe%iTzM*#WALnSs#(l#@Yu8Dt;S7%}mi1J|uU;lrX^6*x!e$;s0NcQw<^y-hYqkW`sHa-$S(9 z!x&iX`mg^V>_LzkFaw(!s59Uu!_^RG7%(U>f|9KPgOev=)uSp#Ltr!nMnhmU1V%$( zGz3ONVC01WxV^*#nkROGwr)pW7>xROGz3ONU^E0qLtr!nMnhoahJXPB10w@NmyrQO k186#p*?_@;lbFzes%JG|SOC%oT6Mt4z;F;TEd@0M0H*o|zyJUM literal 42172 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 D8H#sR diff --git a/fpga/fpga_pm3_lf.bit b/fpga/fpga_pm3_lf.bit index b87ea9489e83fff194e39fcd747353d7aa0840b2..862b3148e58c107427150588d7e2960149acc3c5 100644 GIT binary patch literal 42172 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 From 982bef67050af8bd3533d65885a8005c24fbbcf6 Mon Sep 17 00:00:00 2001 From: n-hutton 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^)({tpm1k;{Yp MjVW7G7*9$A0OH{l^8f$< delta 65 zcmZ3xgJtaw7B&V>etv%biCm_dZYd0$ZZMF_z$hJ>X28J6z`($6z?h(b1R7JerZAqA F1_05H4X6MB 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}{tpm1k;{Yp Kj2ly8mH+_A=oN+l delta 63 zcmX?blIg%nCN>66etv%biCiXHZYd0$ZZMF_z)%pHX28J6z`($6z?i_OfCM(C#4G^- D%}Wh) 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;{Yp LAev)iO2iTX6w4KY delta 67 zcmdmUl4;LLCN>66etv%biChMHZYd0$ZZMF_z)%pHX28J6z`($6z?cA{ITVn<#*~O9 E0P`~qZvX%Q 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}{tpm1k;{Yp Lj0_u7qL%;w+u0R^ delta 64 zcmdmgl4<`*CN>66etv%biCo6oZYd0$ZZMF_z)%pHX28J6z`($6z?i_upnwE6rbI6R E0M@AuZ~y=R 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;{Yp LAev)iO2iTX6w4KY delta 67 zcmdmUl4;LLCN>66etv%biChMHZYd0$ZZMF_z)%pHX28J6z`($6z?cA{ITVn<#*~O9 E0P`~qZvX%Q 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")