From 619445da4b30afa004db0dab289e1bbe74cfaa48 Mon Sep 17 00:00:00 2001 From: noproto Date: Tue, 8 Oct 2024 22:42:30 -0400 Subject: [PATCH] Add MFKey to firmware --- ReadMe.md | 34 +- applications/ReadMe.md | 1 + applications/system/application.fam | 1 + applications/system/mfkey/.catalog/README.md | 12 + .../system/mfkey/.catalog/changelog.md | 18 + .../system/mfkey/.catalog/screenshots/1.png | Bin 0 -> 4277 bytes .../system/mfkey/.catalog/screenshots/2.png | Bin 0 -> 6980 bytes .../system/mfkey/.catalog/screenshots/3.png | Bin 0 -> 5937 bytes applications/system/mfkey/application.fam | 28 + applications/system/mfkey/crypto1.c | 22 + applications/system/mfkey/crypto1.h | 256 +++++ applications/system/mfkey/images/mfkey.png | Bin 0 -> 107 bytes applications/system/mfkey/init_plugin.c | 356 +++++++ applications/system/mfkey/mfkey.c | 915 ++++++++++++++++++ applications/system/mfkey/mfkey.h | 108 +++ applications/system/mfkey/mfkey.png | Bin 0 -> 107 bytes applications/system/mfkey/plugin_interface.h | 13 + 17 files changed, 1747 insertions(+), 17 deletions(-) create mode 100644 applications/system/mfkey/.catalog/README.md create mode 100644 applications/system/mfkey/.catalog/changelog.md create mode 100644 applications/system/mfkey/.catalog/screenshots/1.png create mode 100644 applications/system/mfkey/.catalog/screenshots/2.png create mode 100644 applications/system/mfkey/.catalog/screenshots/3.png create mode 100644 applications/system/mfkey/application.fam create mode 100644 applications/system/mfkey/crypto1.c create mode 100644 applications/system/mfkey/crypto1.h create mode 100644 applications/system/mfkey/images/mfkey.png create mode 100644 applications/system/mfkey/init_plugin.c create mode 100644 applications/system/mfkey/mfkey.c create mode 100644 applications/system/mfkey/mfkey.h create mode 100644 applications/system/mfkey/mfkey.png create mode 100644 applications/system/mfkey/plugin_interface.h diff --git a/ReadMe.md b/ReadMe.md index e1e4a6a39..0420d969d 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -14,29 +14,29 @@ Flipper Zero resources: Coming from the **Official Firmware** (OFW), you'll get: -* Newly supported cards and protocols -* Fixes and performance improvements for existing cards and protocols -* Reduced memory usage of core applications -* A curated list of community applications with a focus on utility +- Newly supported cards and protocols +- Fixes and performance improvements for existing cards and protocols +- Reduced memory usage of core applications +- A curated list of community applications with a focus on utility Coming from other custom firmware, you'll get: -* All of the above -* General stability improvements -* Reduced memory usage -* Minimal theming +- All of the above +- General stability improvements +- Reduced memory usage +- Minimal theming # Xero features -* **MIFARE Classic Key recovery improvements** - * *MIFARE Classic Accelerated dictionary attack*: dictionary attacks reduced to several seconds - checks ~3500 keys per second - * *MIFARE Classic Nested attack support*: collects nested nonces to be cracked by MFKey - * *MIFARE Classic Static encrypted backdoor support*: collects static encrypted nonces to be cracked by MFKey using NXP/Fudan backdoor -* **MFKey 3.0**: Mfkey32, Static Nested, and Static Encrypted attacks all on your Flipper Zero (coming soon!) -* **MIFARE Ultralight C Dictionary attack** (coming soon!) -* **MIFARE Ultralight C Emulation** (coming soon!) -* **NFC app memory improvements** (coming soon!) -* **Minimal theme** (coming soon!) +- **MIFARE Classic Key recovery improvements** + - *MIFARE Classic Accelerated dictionary attack*: dictionary attacks reduced to several seconds - checks ~3500 keys per second + - *MIFARE Classic Nested attack support*: collects nested nonces to be cracked by MFKey + - *MIFARE Classic Static encrypted backdoor support*: collects static encrypted nonces to be cracked by MFKey using NXP/Fudan backdoor +- **MFKey 3.0**: Mfkey32, Static Nested, and Static Encrypted attacks all on your Flipper Zero +- **MIFARE Ultralight C Dictionary attack** (coming soon!) +- **MIFARE Ultralight C Emulation** (coming soon!) +- **NFC app memory improvements** (coming soon!) +- **Minimal theme** (coming soon!) # Contributing diff --git a/applications/ReadMe.md b/applications/ReadMe.md index 44bd8c5d7..dd664f37b 100644 --- a/applications/ReadMe.md +++ b/applications/ReadMe.md @@ -79,3 +79,4 @@ Utility apps not visible in other menus, plus few external apps pre-packaged wit - `snake_game` - Snake game - `storage_move_to_sd` - Data migration tool for internal storage - `updater` - Update service & application +- `mfkey` - MIFARE Classic key recovery tool diff --git a/applications/system/application.fam b/applications/system/application.fam index c5f81defa..42e273c40 100644 --- a/applications/system/application.fam +++ b/applications/system/application.fam @@ -6,6 +6,7 @@ App( "updater_app", "js_app", "js_app_start", + "mfkey", # "archive", ], ) diff --git a/applications/system/mfkey/.catalog/README.md b/applications/system/mfkey/.catalog/README.md new file mode 100644 index 000000000..7f130b41f --- /dev/null +++ b/applications/system/mfkey/.catalog/README.md @@ -0,0 +1,12 @@ +# Flipper Zero MFKey + +This application allows you to calculate the keys of MIFARE Classic cards using the Mfkey32 and Nested algorithms directly on your Flipper Zero. After collecting the nonces using the Extract MF Keys feature of the NFC app, they can be used to calculate the keys to the card in the MFKey app. + +## Usage + +After collecting nonces using the Extract MF Keys option, press the Start button in the MFKey app and wait for it to finish. The calculation can take more than 10 minutes, so you'll have to be patient. After the calculation is complete, the keys will be saved to the user key dictionary. + +## Credits + +Developers: noproto, AG, Flipper Devices, WillyJL +Thanks: AloneLiberty, Foxushka, bettse, Equip diff --git a/applications/system/mfkey/.catalog/changelog.md b/applications/system/mfkey/.catalog/changelog.md new file mode 100644 index 000000000..2b05351e0 --- /dev/null +++ b/applications/system/mfkey/.catalog/changelog.md @@ -0,0 +1,18 @@ +## 3.0 + - Added Static Encrypted Nested key recovery, added NFC app support, dropped FlipperNested support +## 2.7 + - Mfkey32 recovery is 30% faster, fix UI and slowdown bugs +## 2.6 + - Version bump for catalog build system +## 2.5 + - Plugin path fixed +## 2.4 + - Update API for app rename +## 2.3 + - Update API v65.0 +## 2.0 + - Added Nested key recovery, use new KeysDict API, fix crashes, more efficient RAM utilization, faster +## 1.1 + - Rework application with new NFC API +## 1.0 + - Initial release diff --git a/applications/system/mfkey/.catalog/screenshots/1.png b/applications/system/mfkey/.catalog/screenshots/1.png new file mode 100644 index 0000000000000000000000000000000000000000..c3cc6886773d50587294fc05a8afa563e0458fec GIT binary patch literal 4277 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 literal 0 HcmV?d00001 diff --git a/applications/system/mfkey/.catalog/screenshots/2.png b/applications/system/mfkey/.catalog/screenshots/2.png new file mode 100644 index 0000000000000000000000000000000000000000..9fc4407e59b9325ddd61586244ac152706ec1917 GIT binary patch literal 6980 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ǯ?neT@~YA$GQ|E+iSE{IhH6FCgTN$@4PJ znyux-9IK-Od&6$qM`ZQ;XGpg^|khDai3>OI-B<3Y6F&B_+n*kT$}u<-=88`fS^} z!}~RlNSc>My0U#cKaeGWX;a4iaCKev2gW)kYr@uWF9>SK7@FCQ zp?RcuCMuMyX4cJ8H41bvH0ERms6aMuH%V&Azl&klq+Aye$Y`SlW%nG)MS_W>5s2q) z)*!E}>tizzU|Lg|%)GeKWeJGcGJA(}M|Uo>sZF>HOuSX@R*%W#(6B(IiiCOnaGkWD zU~*;K{98wNTrXwWB6(DWTiI2R-{4!m?30okZ{BLMb~$#bGb`5Yv8-ejpd4QW>4$C; z+V8EtD;)^_L%CPCr~*^o8SZ@H^>e|4EdH=dil8OY+4vVK8f0Ns0PheqdlSDs*}#mO zmh2bl{y_;CK93mjOMV`L82sSxuG;5@0yxH9a14&D%nxyC?Z^ttIq+S@SD?vCyoO=NnMz{s2#c^NY%|L+m#y5ZjG9JnOUn()Ug8 zEF%q}DgeUr22VMSzkaDz{pt>FwD|Dt0zncC`vGmr!2DJRN5(BM(oFn=yEu)XV`)#; z_7cd(k0DK4a04nJAgg=OHgn5-X(d>tJySVBu}=g?q!rHi6dcJGF(*=9+`F)>3B>Am zqHoZRTzMYVRvw`*2F0q_4SZ%b>y@`}{kRYGa>}0h*KvZhd0mR=NZ(xD&x0PH_%Ee^ zkdH<2vK6nq8G<8c&&jz#wXFl$G>%~S`FFTrxzAZB7Tq?c3_B^r@LKg2<;A_USzQ{s zc9ITPu~2M|^FvT?fPWy>>%z+#Em3&82XR} z{%7p1r{-@;Jr)>Cn9kwkmSO3Y5^mt`K5a?x<|Vh*C;k-pwv5I(*AStM%(>pPM@18r zBmg0sC_xe0 zlshBy0wwTAu0l!pwXtseg|Ft~v0Uryaec%h>Mw$y`VCF(hymow-2$KC{p*6pnqRC`t+L z@mofGO>UE$U-0M;nf39t){0v=Q%jlWcA|yn*Y;cr7p1SkmEIub&63haW-^bilStRg z?1HxDr>dBz+tH2;%k8F!Q;4#|N$8fI*WD zkPwrQ5)qRWkq|c)mscQ*MG{g%VqywnVlJ?`n*T*`^SJBc82FzAIRz={e+bE8l5jFX p;D0LkxVWLceeSq<{s%`|OhHoiA5LsS;tZJsbTm-vW!LQ<{}(gqvjhMD literal 0 HcmV?d00001 diff --git a/applications/system/mfkey/.catalog/screenshots/3.png b/applications/system/mfkey/.catalog/screenshots/3.png new file mode 100644 index 0000000000000000000000000000000000000000..7eebc6377c82fd002f60f96077578c0f4307f94c GIT binary patch literal 5937 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 X{6C#c`qnxSI6zy&K)p)UKIT6F&zg|h literal 0 HcmV?d00001 diff --git a/applications/system/mfkey/application.fam b/applications/system/mfkey/application.fam new file mode 100644 index 000000000..dfd513847 --- /dev/null +++ b/applications/system/mfkey/application.fam @@ -0,0 +1,28 @@ +App( + appid="mfkey", + name="MFKey", + apptype=FlipperAppType.EXTERNAL, + targets=["f7"], + entry_point="mfkey_main", + requires=[ + "gui", + "storage", + ], + stack_size=1 * 1024, + fap_icon="mfkey.png", + fap_category="NFC", + fap_author="@noproto", + fap_icon_assets="images", + fap_weburl="https://github.com/noproto/FlipperMfkey", + fap_description="MIFARE Classic key recovery tool", + fap_version="3.0", +) + +App( + appid="mfkey_init_plugin", + apptype=FlipperAppType.PLUGIN, + entry_point="init_plugin_ep", + requires=["mfkey"], + sources=["init_plugin.c"], + fal_embedded=True, +) diff --git a/applications/system/mfkey/crypto1.c b/applications/system/mfkey/crypto1.c new file mode 100644 index 000000000..e862b14d1 --- /dev/null +++ b/applications/system/mfkey/crypto1.c @@ -0,0 +1,22 @@ +#pragma GCC optimize("O3") +#pragma GCC optimize("-funroll-all-loops") + +#include +#include "crypto1.h" +#include "mfkey.h" + +#define BIT(x, n) ((x) >> (n) & 1) + +void crypto1_get_lfsr(struct Crypto1State* state, MfClassicKey* lfsr) { + int i; + uint64_t lfsr_value = 0; + for(i = 23; i >= 0; --i) { + lfsr_value = lfsr_value << 1 | BIT(state->odd, i ^ 3); + lfsr_value = lfsr_value << 1 | BIT(state->even, i ^ 3); + } + + // Assign the key value to the MfClassicKey struct + for(i = 0; i < 6; ++i) { + lfsr->data[i] = (lfsr_value >> ((5 - i) * 8)) & 0xFF; + } +} diff --git a/applications/system/mfkey/crypto1.h b/applications/system/mfkey/crypto1.h new file mode 100644 index 000000000..9caf5b35e --- /dev/null +++ b/applications/system/mfkey/crypto1.h @@ -0,0 +1,256 @@ +#ifndef CRYPTO1_H +#define CRYPTO1_H + +#include +#include "mfkey.h" +#include +#include + +#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_EVEN (0x870804) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define SWAPENDIAN(x) \ + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) + +static inline uint32_t prng_successor(uint32_t x, uint32_t n); +static inline int filter(uint32_t const x); +static inline uint8_t evenparity32(uint32_t x); +static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2); +void crypto1_get_lfsr(struct Crypto1State* state, MfClassicKey* lfsr); +static inline uint32_t crypt_word(struct Crypto1State* s); +static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x); +static inline uint32_t crypt_word_ret(struct Crypto1State* s, uint32_t in, int x); +static uint32_t crypt_word_par( + struct Crypto1State* s, + uint32_t in, + int is_encrypted, + uint32_t nt_plain, + uint8_t* parity_keystream_bits); +static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x); +static inline uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb); +static inline uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb); + +static const uint8_t lookup1[256] = { + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, + 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24}; +static const uint8_t lookup2[256] = { + 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, + 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, + 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, + 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, + 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, + 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, + 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, + 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, + 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6}; + +static inline int filter(uint32_t const x) { + uint32_t f; + f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff]; + f |= 0x0d938 >> (x >> 16 & 0xf) & 1; + return BIT(0xEC57E80A, f); +} + +#ifndef __ARM_ARCH_7EM__ +static inline uint8_t evenparity32(uint32_t x) { + return __builtin_parity(x); +} +#endif + +#ifdef __ARM_ARCH_7EM__ +static inline uint8_t evenparity32(uint32_t x) { + uint32_t result; + __asm__ volatile("eor r1, %[x], %[x], lsr #16 \n\t" // r1 = x ^ (x >> 16) + "eor r1, r1, r1, lsr #8 \n\t" // r1 = r1 ^ (r1 >> 8) + "eor r1, r1, r1, lsr #4 \n\t" // r1 = r1 ^ (r1 >> 4) + "eor r1, r1, r1, lsr #2 \n\t" // r1 = r1 ^ (r1 >> 2) + "eor r1, r1, r1, lsr #1 \n\t" // r1 = r1 ^ (r1 >> 1) + "and %[result], r1, #1 \n\t" // result = r1 & 1 + : [result] "=r"(result) + : [x] "r"(x) + : "r1"); + return result; +} +#endif + +static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) { + int p = data[item] >> 25; + p = p << 1 | evenparity32(data[item] & mask1); + p = p << 1 | evenparity32(data[item] & mask2); + data[item] = p << 24 | (data[item] & 0xffffff); +} + +static inline uint32_t crypt_word(struct Crypto1State* s) { + // "in" and "x" are always 0 (last iteration) + uint32_t res_ret = 0; + uint32_t feedin, t; + for(int i = 0; i <= 31; i++) { + res_ret |= (filter(s->odd) << (24 ^ i)); //-V629 + feedin = LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + } + return res_ret; +} + +static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x) { + uint8_t ret; + uint32_t feedin, t, next_in; + for(int i = 0; i <= 31; i++) { + next_in = BEBIT(in, i); + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + } + return; +} + +static inline uint32_t crypt_word_ret(struct Crypto1State* s, uint32_t in, int x) { + uint32_t ret = 0; + uint32_t feedin, t, next_in; + uint8_t next_ret; + for(int i = 0; i <= 31; i++) { + next_in = BEBIT(in, i); + next_ret = filter(s->odd); + feedin = next_ret & (!!x); + feedin ^= LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + ret |= next_ret << (24 ^ i); + } + return ret; +} + +static uint8_t get_nth_byte(uint32_t value, int n) { + if(n < 0 || n > 3) { + // Handle invalid input + return 0; + } + return (value >> (8 * (3 - n))) & 0xFF; +} + +static uint8_t crypt_bit(struct Crypto1State* s, uint8_t in, int is_encrypted) { + uint32_t feedin, t; + uint8_t ret = filter(s->odd); + feedin = ret & !!is_encrypted; + feedin ^= !!in; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= LF_POLY_EVEN & s->even; + s->even = s->even << 1 | evenparity32(feedin); + t = s->odd, s->odd = s->even, s->even = t; + return ret; +} + +static inline uint32_t crypt_word_par( + struct Crypto1State* s, + uint32_t in, + int is_encrypted, + uint32_t nt_plain, + uint8_t* parity_keystream_bits) { + uint32_t ret = 0; + *parity_keystream_bits = 0; // Reset parity keystream bits + + for(int i = 0; i < 32; i++) { + uint8_t bit = crypt_bit(s, BEBIT(in, i), is_encrypted); + ret |= bit << (24 ^ i); + // Save keystream parity bit + if((i + 1) % 8 == 0) { + *parity_keystream_bits |= + (filter(s->odd) ^ nfc_util_even_parity8(get_nth_byte(nt_plain, i / 8))) + << (3 - (i / 8)); + } + } + return ret; +} + +static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x) { + uint8_t ret; + uint32_t feedin, t, next_in; + for(int i = 31; i >= 0; i--) { + next_in = BEBIT(in, i); + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= s->even & 1; + feedin ^= LF_POLY_EVEN & (s->even >>= 1); + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even |= (evenparity32(feedin)) << 23; + } + return; +} + +// TODO: +/* +uint32_t rollback_word(struct Crypto1State *s, uint32_t in, int x) { + uint32_t res_ret = 0; + uint8_t ret; + uint32_t feedin, t, next_in; + for (int i = 31; i >= 0; i--) { + next_in = BEBIT(in, i); + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= s->even & 1; + feedin ^= LF_POLY_EVEN & (s->even >>= 1); + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even |= (evenparity32(feedin)) << 23; + res_ret |= (ret << (24 ^ i)); + } + return res_ret; +} +*/ + +uint8_t napi_lfsr_rollback_bit(struct Crypto1State* s, uint32_t in, int fb) { + int out; + uint8_t ret; + uint32_t t; + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; + + out = s->even & 1; + out ^= LF_POLY_EVEN & (s->even >>= 1); + out ^= LF_POLY_ODD & s->odd; + out ^= !!in; + out ^= (ret = filter(s->odd)) & !!fb; + + s->even |= evenparity32(out) << 23; + return ret; +} + +uint32_t napi_lfsr_rollback_word(struct Crypto1State* s, uint32_t in, int fb) { + int i; + uint32_t ret = 0; + for(i = 31; i >= 0; --i) + ret |= napi_lfsr_rollback_bit(s, BEBIT(in, i), fb) << (i ^ 24); + return ret; +} + +static inline uint32_t prng_successor(uint32_t x, uint32_t n) { + SWAPENDIAN(x); + while(n--) + x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; + return SWAPENDIAN(x); +} + +#endif // CRYPTO1_H diff --git a/applications/system/mfkey/images/mfkey.png b/applications/system/mfkey/images/mfkey.png new file mode 100644 index 0000000000000000000000000000000000000000..f1694bb335a8619e53cd3b98651ba995cc7a1caf GIT binary patch literal 107 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xGmzZ=C-xtZVhivIasB`QKad%E=yDy9Qt)(f z4B@z*{KD=)L3iUrapuFX*xK~i1d{R-5*QT~m>F7Tu$Q|1zuOE{%i!ti=d#Wzp$P!I C{~rec literal 0 HcmV?d00001 diff --git a/applications/system/mfkey/init_plugin.c b/applications/system/mfkey/init_plugin.c new file mode 100644 index 000000000..8540a8f2d --- /dev/null +++ b/applications/system/mfkey/init_plugin.c @@ -0,0 +1,356 @@ +#include +#include +#include +#include +#include +#include +#include "mfkey.h" +#include "crypto1.h" +#include "plugin_interface.h" +#include + +#define TAG "MFKey" + +// TODO: Remove defines that are not needed +#define MF_CLASSIC_NONCE_PATH EXT_PATH("nfc/.mfkey32.log") +#define MF_CLASSIC_NESTED_NONCE_PATH EXT_PATH("nfc/.nested.log") +#define MAX_NAME_LEN 32 +#define MAX_PATH_LEN 64 + +#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_EVEN (0x870804) +#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) +#define CONST_M2_1 (LF_POLY_ODD << 1) +#define CONST_M1_2 (LF_POLY_ODD) +#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define SWAPENDIAN(x) \ + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) + +bool key_already_found_for_nonce_in_dict(KeysDict* dict, MfClassicNonce* nonce) { + // This function must not be passed the CUID dictionary + bool found = false; + uint8_t key_bytes[sizeof(MfClassicKey)]; + keys_dict_rewind(dict); + while(keys_dict_get_next_key(dict, key_bytes, sizeof(MfClassicKey))) { + uint64_t k = bit_lib_bytes_to_num_be(key_bytes, sizeof(MfClassicKey)); + struct Crypto1State temp = {0, 0}; + for(int i = 0; i < 24; i++) { + (&temp)->odd |= (BIT(k, 2 * i + 1) << (i ^ 3)); + (&temp)->even |= (BIT(k, 2 * i) << (i ^ 3)); + } + if(nonce->attack == mfkey32) { + crypt_word_noret(&temp, nonce->uid_xor_nt1, 0); + crypt_word_noret(&temp, nonce->nr1_enc, 1); + if(nonce->ar1_enc == (crypt_word(&temp) ^ nonce->p64b)) { + found = true; + break; + } + } else if(nonce->attack == static_nested || nonce->attack == static_encrypted) { + uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0); + if(nonce->ks1_1_enc == expected_ks1) { + found = true; + break; + } + } + } + return found; +} + +bool napi_mf_classic_mfkey32_nonces_check_presence() { + Storage* storage = furi_record_open(RECORD_STORAGE); + + bool nonces_present = storage_common_stat(storage, MF_CLASSIC_NONCE_PATH, NULL) == FSE_OK; + + furi_record_close(RECORD_STORAGE); + + return nonces_present; +} + +bool napi_mf_classic_nested_nonces_check_presence() { + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* stream = buffered_file_stream_alloc(storage); + bool nonces_present = false; + FuriString* line = furi_string_alloc(); + + do { + if(!buffered_file_stream_open( + stream, MF_CLASSIC_NESTED_NONCE_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { + break; + } + + while(stream_read_line(stream, line)) { + if(furi_string_search_str(line, "dist 0") != FURI_STRING_FAILURE) { + nonces_present = true; + break; + } + } + + } while(false); + + furi_string_free(line); + buffered_file_stream_close(stream); + stream_free(stream); + furi_record_close(RECORD_STORAGE); + + return nonces_present; +} + +int binaryStringToInt(const char* binStr) { + int result = 0; + while(*binStr) { + result <<= 1; + if(*binStr == '1') { + result |= 1; + } + binStr++; + } + return result; +} + +bool load_mfkey32_nonces( + MfClassicNonceArray* nonce_array, + ProgramState* program_state, + KeysDict* system_dict, + bool system_dict_exists, + KeysDict* user_dict) { + bool array_loaded = false; + + do { + // https://github.com/flipperdevices/flipperzero-firmware/blob/5134f44c09d39344a8747655c0d59864bb574b96/applications/services/storage/filesystem_api_defines.h#L8-L22 + if(!buffered_file_stream_open( + nonce_array->stream, MF_CLASSIC_NONCE_PATH, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) { + buffered_file_stream_close(nonce_array->stream); + break; + } + + // Check for newline ending + if(!stream_eof(nonce_array->stream)) { + if(!stream_seek(nonce_array->stream, -1, StreamOffsetFromEnd)) break; + uint8_t last_char = 0; + if(stream_read(nonce_array->stream, &last_char, 1) != 1) break; + if(last_char != '\n') { + //FURI_LOG_D(TAG, "Adding new line ending"); + if(stream_write_char(nonce_array->stream, '\n') != 1) break; + } + if(!stream_rewind(nonce_array->stream)) break; + } + + // Read total amount of nonces + FuriString* next_line; + next_line = furi_string_alloc(); + while(!(program_state->close_thread_please)) { + if(!stream_read_line(nonce_array->stream, next_line)) { + //FURI_LOG_T(TAG, "No nonces left"); + break; + } + /* + FURI_LOG_T( + TAG, + "Read line: %s, len: %zu", + furi_string_get_cstr(next_line), + furi_string_size(next_line)); + */ + if(!furi_string_start_with_str(next_line, "Sec")) continue; + const char* next_line_cstr = furi_string_get_cstr(next_line); + MfClassicNonce res = {0}; + res.attack = mfkey32; + int i = 0; + char* endptr; + for(i = 0; i <= 17; i++) { + if(i != 0) { + next_line_cstr = strchr(next_line_cstr, ' '); + if(next_line_cstr) { + next_line_cstr++; + } else { + break; + } + } + unsigned long value = strtoul(next_line_cstr, &endptr, 16); + switch(i) { + case 5: + res.uid = value; + break; + case 7: + res.nt0 = value; + break; + case 9: + res.nr0_enc = value; + break; + case 11: + res.ar0_enc = value; + break; + case 13: + res.nt1 = value; + break; + case 15: + res.nr1_enc = value; + break; + case 17: + res.ar1_enc = value; + break; + default: + break; // Do nothing + } + next_line_cstr = endptr; + } + res.p64 = prng_successor(res.nt0, 64); + res.p64b = prng_successor(res.nt1, 64); + res.uid_xor_nt0 = res.uid ^ res.nt0; + res.uid_xor_nt1 = res.uid ^ res.nt1; + + (program_state->total)++; + if((system_dict_exists && key_already_found_for_nonce_in_dict(system_dict, &res)) || + (key_already_found_for_nonce_in_dict(user_dict, &res))) { + (program_state->cracked)++; + (program_state->num_completed)++; + continue; + } + //FURI_LOG_I(TAG, "No key found for %8lx %8lx", res.uid, res.ar1_enc); + // TODO: Refactor + nonce_array->remaining_nonce_array = realloc( //-V701 + nonce_array->remaining_nonce_array, + sizeof(MfClassicNonce) * ((nonce_array->remaining_nonces) + 1)); + nonce_array->remaining_nonces++; + nonce_array->remaining_nonce_array[(nonce_array->remaining_nonces) - 1] = res; + nonce_array->total_nonces++; + } + furi_string_free(next_line); + buffered_file_stream_close(nonce_array->stream); + + array_loaded = true; + //FURI_LOG_I(TAG, "Loaded %lu Mfkey32 nonces", nonce_array->total_nonces); + } while(false); + + return array_loaded; +} + +bool load_nested_nonces( + MfClassicNonceArray* nonce_array, + ProgramState* program_state, + KeysDict* system_dict, + bool system_dict_exists, + KeysDict* user_dict) { + if(!buffered_file_stream_open( + nonce_array->stream, MF_CLASSIC_NESTED_NONCE_PATH, FSAM_READ, FSOM_OPEN_EXISTING)) { + return false; + } + + FuriString* next_line = furi_string_alloc(); + bool array_loaded = false; + + while(stream_read_line(nonce_array->stream, next_line)) { + const char* line = furi_string_get_cstr(next_line); + + // Only process lines ending with "dist 0" + if(!strstr(line, "dist 0")) { + continue; + } + + MfClassicNonce res = {0}; + res.attack = static_encrypted; + + int parsed = sscanf( + line, + "Sec %*d key %*c cuid %" PRIx32 " nt0 %" PRIx32 " ks0 %" PRIx32 + " par0 %4[01] nt1 %" PRIx32 " ks1 %" PRIx32 " par1 %4[01]", + &res.uid, + &res.nt0, + &res.ks1_1_enc, + res.par_1_str, + &res.nt1, + &res.ks1_2_enc, + res.par_2_str); + + if(parsed >= 4) { // At least one nonce is present + res.par_1 = binaryStringToInt(res.par_1_str); + res.uid_xor_nt0 = res.uid ^ res.nt0; + + if(parsed == 7) { // Both nonces are present + res.attack = static_nested; + res.par_2 = binaryStringToInt(res.par_2_str); + res.uid_xor_nt1 = res.uid ^ res.nt1; + } + + (program_state->total)++; + if((system_dict_exists && key_already_found_for_nonce_in_dict(system_dict, &res)) || + (key_already_found_for_nonce_in_dict(user_dict, &res))) { + (program_state->cracked)++; + (program_state->num_completed)++; + continue; + } + + nonce_array->remaining_nonce_array = realloc( + nonce_array->remaining_nonce_array, + sizeof(MfClassicNonce) * (nonce_array->remaining_nonces + 1)); + nonce_array->remaining_nonce_array[nonce_array->remaining_nonces] = res; + nonce_array->remaining_nonces++; + nonce_array->total_nonces++; + array_loaded = true; + } + } + + furi_string_free(next_line); + buffered_file_stream_close(nonce_array->stream); + + //FURI_LOG_I(TAG, "Loaded %lu Static Nested nonces", nonce_array->total_nonces); + return array_loaded; +} + +MfClassicNonceArray* napi_mf_classic_nonce_array_alloc( + KeysDict* system_dict, + bool system_dict_exists, + KeysDict* user_dict, + ProgramState* program_state) { + MfClassicNonceArray* nonce_array = malloc(sizeof(MfClassicNonceArray)); + MfClassicNonce* remaining_nonce_array_init = malloc(sizeof(MfClassicNonce) * 1); + nonce_array->remaining_nonce_array = remaining_nonce_array_init; + Storage* storage = furi_record_open(RECORD_STORAGE); + nonce_array->stream = buffered_file_stream_alloc(storage); + furi_record_close(RECORD_STORAGE); + + if(program_state->mfkey32_present) { + load_mfkey32_nonces( + nonce_array, program_state, system_dict, system_dict_exists, user_dict); + } + + if(program_state->nested_present) { + load_nested_nonces(nonce_array, program_state, system_dict, system_dict_exists, user_dict); + } + + return nonce_array; +} + +void napi_mf_classic_nonce_array_free(MfClassicNonceArray* nonce_array) { + // TODO: Track free state at the time this is called to ensure double free does not happen + furi_assert(nonce_array); + furi_assert(nonce_array->stream); + + // TODO: Already closed? + buffered_file_stream_close(nonce_array->stream); + stream_free(nonce_array->stream); + free(nonce_array); +} + +/* Actual implementation of app<>plugin interface */ +static const MfkeyPlugin init_plugin = { + .name = "Initialization Plugin", + .napi_mf_classic_mfkey32_nonces_check_presence = + &napi_mf_classic_mfkey32_nonces_check_presence, + .napi_mf_classic_nested_nonces_check_presence = &napi_mf_classic_nested_nonces_check_presence, + .napi_mf_classic_nonce_array_alloc = &napi_mf_classic_nonce_array_alloc, + .napi_mf_classic_nonce_array_free = &napi_mf_classic_nonce_array_free, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor init_plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &init_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* init_plugin_ep() { + return &init_plugin_descriptor; +} diff --git a/applications/system/mfkey/mfkey.c b/applications/system/mfkey/mfkey.c new file mode 100644 index 000000000..ae5cc90f2 --- /dev/null +++ b/applications/system/mfkey/mfkey.c @@ -0,0 +1,915 @@ +#pragma GCC optimize("O3") +#pragma GCC optimize("-funroll-all-loops") + +// TODO: More efficient dictionary bruteforce by scanning through hardcoded very common keys and previously found dictionary keys first? +// (a cache for key_already_found_for_nonce_in_dict) +// TODO: Selectively unroll loops to reduce binary size +// TODO: Collect parity during Mfkey32 attacks to further optimize the attack +// TODO: Why different sscanf between Mfkey32 and Nested? +// TODO: "Read tag again with NFC app" message upon completion, "Complete. Keys added: " +// TODO: Separate Mfkey32 and Nested functions where possible to reduce branch statements +// TODO: Find ~1 KB memory leak +// TODO: Use seednt16 to reduce static encrypted key candidates: https://gist.github.com/noproto/8102f8f32546564cd674256e62ff76ea +// https://eprint.iacr.org/2024/1275.pdf section X +// TODO: Static Encrypted: Minimum RAM for adding to keys dict (avoid crashes) +// TODO: Static Encrypted: Optimize KeysDict or buffer keys to write in chunks + +#include +#include +#include +#include +#include "mfkey_icons.h" +#include +#include +#include +#include +#include +#include +#include +#include "mfkey.h" +#include "crypto1.h" +#include "plugin_interface.h" +#include +#include +#include + +#define TAG "MFKey" + +// TODO: Remove defines that are not needed +#define KEYS_DICT_SYSTEM_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") +#define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") +#define MAX_NAME_LEN 32 +#define MAX_PATH_LEN 64 + +#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_EVEN (0x870804) +#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) +#define CONST_M2_1 (LF_POLY_ODD << 1) +#define CONST_M1_2 (LF_POLY_ODD) +#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define SWAPENDIAN(x) \ + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) +//#define SIZEOF(arr) sizeof(arr) / sizeof(*arr) + +static int eta_round_time = 44; +static int eta_total_time = 705; +// MSB_LIMIT: Chunk size (out of 256) +static int MSB_LIMIT = 16; + +static inline int + check_state(struct Crypto1State* t, MfClassicNonce* n, ProgramState* program_state) { + if(!(t->odd | t->even)) return 0; + if(n->attack == mfkey32) { + uint32_t rb = (napi_lfsr_rollback_word(t, 0, 0) ^ n->p64); + if(rb != n->ar0_enc) { + return 0; + } + rollback_word_noret(t, n->nr0_enc, 1); + rollback_word_noret(t, n->uid_xor_nt0, 0); + struct Crypto1State temp = {t->odd, t->even}; + crypt_word_noret(t, n->uid_xor_nt1, 0); + crypt_word_noret(t, n->nr1_enc, 1); + if(n->ar1_enc == (crypt_word(t) ^ n->p64b)) { + crypto1_get_lfsr(&temp, &(n->key)); + return 1; + } + } else if(n->attack == static_nested) { + struct Crypto1State temp = {t->odd, t->even}; + rollback_word_noret(t, n->uid_xor_nt1, 0); + if(n->ks1_1_enc == crypt_word_ret(t, n->uid_xor_nt0, 0)) { + rollback_word_noret(&temp, n->uid_xor_nt1, 0); + crypto1_get_lfsr(&temp, &(n->key)); + return 1; + } + } else if(n->attack == static_encrypted) { + // TODO: Parity bits from rollback_word? + if(n->ks1_1_enc == napi_lfsr_rollback_word(t, n->uid_xor_nt0, 0)) { + // Reduce with parity + uint8_t local_parity_keystream_bits; + struct Crypto1State temp = {t->odd, t->even}; + if((crypt_word_par(&temp, n->uid_xor_nt0, 0, n->nt0, &local_parity_keystream_bits) == + n->ks1_1_enc) && + (local_parity_keystream_bits == n->par_1)) { + // Found key candidate + crypto1_get_lfsr(t, &(n->key)); + program_state->num_candidates++; + keys_dict_add_key(program_state->cuid_dict, n->key.data, sizeof(MfClassicKey)); + } + } + } + return 0; +} + +static inline int state_loop( + unsigned int* states_buffer, + int xks, + int m1, + int m2, + unsigned int in, + uint8_t and_val) { + int states_tail = 0; + int round = 0, s = 0, xks_bit = 0, round_in = 0; + + for(round = 1; round <= 12; round++) { + xks_bit = BIT(xks, round); + if(round > 4) { + round_in = ((in >> (2 * (round - 4))) & and_val) << 24; + } + + for(s = 0; s <= states_tail; s++) { + states_buffer[s] <<= 1; + + if((filter(states_buffer[s]) ^ filter(states_buffer[s] | 1)) != 0) { + states_buffer[s] |= filter(states_buffer[s]) ^ xks_bit; + if(round > 4) { + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= round_in; + } + } else if(filter(states_buffer[s]) == xks_bit) { + // TODO: Refactor + if(round > 4) { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = states_buffer[s] | 1; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s++] ^= round_in; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= round_in; + } else { + states_buffer[++states_tail] = states_buffer[++s]; + states_buffer[s] = states_buffer[s - 1] | 1; + } + } else { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } + + return states_tail; +} + +int binsearch(unsigned int data[], int start, int stop) { + int mid, val = data[stop] & 0xff000000; + while(start != stop) { + mid = (stop - start) >> 1; + if((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000)) + stop = start + mid; + else + start += mid + 1; + } + return start; +} +void quicksort(unsigned int array[], int low, int high) { + //if (SIZEOF(array) == 0) + // return; + if(low >= high) return; + int middle = low + (high - low) / 2; + unsigned int pivot = array[middle]; + int i = low, j = high; + while(i <= j) { + while(array[i] < pivot) { + i++; + } + while(array[j] > pivot) { + j--; + } + if(i <= j) { // swap + int temp = array[i]; + array[i] = array[j]; + array[j] = temp; + i++; + j--; + } + } + if(low < j) { + quicksort(array, low, j); + } + if(high > i) { + quicksort(array, i, high); + } +} +int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2, unsigned int in) { + in <<= 24; + for(data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) { + if((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) { + data[tbl] |= filter(data[tbl]) ^ bit; + update_contribution(data, tbl, m1, m2); + data[tbl] ^= in; + } else if(filter(data[tbl]) == bit) { + data[++end] = data[tbl + 1]; + data[tbl + 1] = data[tbl] | 1; + update_contribution(data, tbl, m1, m2); + data[tbl++] ^= in; + update_contribution(data, tbl, m1, m2); + data[tbl] ^= in; + } else { + data[tbl--] = data[end--]; + } + } + return end; +} + +int old_recover( + unsigned int odd[], + int o_head, + int o_tail, + int oks, + unsigned int even[], + int e_head, + int e_tail, + int eks, + int rem, + int s, + MfClassicNonce* n, + unsigned int in, + int first_run, + ProgramState* program_state) { + int o, e, i; + if(rem == -1) { + for(e = e_head; e <= e_tail; ++e) { + even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN) ^ (!!(in & 4)); + for(o = o_head; o <= o_tail; ++o, ++s) { + struct Crypto1State temp = {0, 0}; + temp.even = odd[o]; + temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD); + if(check_state(&temp, n, program_state)) { + return -1; + } + } + } + return s; + } + if(first_run == 0) { + for(i = 0; (i < 4) && (rem-- != 0); i++) { + oks >>= 1; + eks >>= 1; + in >>= 2; + o_tail = extend_table( + odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1, 0); + if(o_head > o_tail) return s; + e_tail = extend_table( + even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1, in & 3); + if(e_head > e_tail) return s; + } + } + first_run = 0; + quicksort(odd, o_head, o_tail); + quicksort(even, e_head, e_tail); + while(o_tail >= o_head && e_tail >= e_head) { + if(((odd[o_tail] ^ even[e_tail]) >> 24) == 0) { + o_tail = binsearch(odd, o_head, o = o_tail); + e_tail = binsearch(even, e_head, e = e_tail); + s = old_recover( + odd, + o_tail--, + o, + oks, + even, + e_tail--, + e, + eks, + rem, + s, + n, + in, + first_run, + program_state); + if(s == -1) { + break; + } + } else if((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) { + o_tail = binsearch(odd, o_head, o_tail) - 1; + } else { + e_tail = binsearch(even, e_head, e_tail) - 1; + } + } + return s; +} + +static inline int sync_state(ProgramState* program_state) { + int ts = furi_hal_rtc_get_timestamp(); + int elapsed_time = ts - program_state->eta_timestamp; + if(elapsed_time < program_state->eta_round) { + program_state->eta_round -= elapsed_time; + } else { + program_state->eta_round = 0; + } + if(elapsed_time < program_state->eta_total) { + program_state->eta_total -= elapsed_time; + } else { + program_state->eta_total = 0; + } + program_state->eta_timestamp = ts; + if(program_state->close_thread_please) { + return 1; + } + return 0; +} + +int calculate_msb_tables( + int oks, + int eks, + int msb_round, + MfClassicNonce* n, + unsigned int* states_buffer, + struct Msb* odd_msbs, + struct Msb* even_msbs, + unsigned int* temp_states_odd, + unsigned int* temp_states_even, + unsigned int in, + ProgramState* program_state) { + //FURI_LOG_I(TAG, "MSB GO %i", msb_iter); // DEBUG + unsigned int msb_head = (MSB_LIMIT * msb_round); // msb_iter ranges from 0 to (256/MSB_LIMIT)-1 + unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); + int states_tail = 0, tail = 0; + int i = 0, j = 0, semi_state = 0, found = 0; + unsigned int msb = 0; + in = ((in >> 16 & 0xff) | (in << 16) | (in & 0xff00)) << 1; + // TODO: Why is this necessary? + memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + + for(semi_state = 1 << 20; semi_state >= 0; semi_state--) { + if(semi_state % 32768 == 0) { + if(sync_state(program_state) == 1) { + return 0; + } + } + + if(filter(semi_state) == (oks & 1)) { //-V547 + states_buffer[0] = semi_state; + states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1, 0, 0); + + for(i = states_tail; i >= 0; i--) { + msb = states_buffer[i] >> 24; + if((msb >= msb_head) && (msb < msb_tail)) { + found = 0; + for(j = 0; j < odd_msbs[msb - msb_head].tail - 1; j++) { + if(odd_msbs[msb - msb_head].states[j] == states_buffer[i]) { + found = 1; + break; + } + } + + if(!found) { + tail = odd_msbs[msb - msb_head].tail++; + odd_msbs[msb - msb_head].states[tail] = states_buffer[i]; + } + } + } + } + + if(filter(semi_state) == (eks & 1)) { //-V547 + states_buffer[0] = semi_state; + states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2, in, 3); + + for(i = 0; i <= states_tail; i++) { + msb = states_buffer[i] >> 24; + if((msb >= msb_head) && (msb < msb_tail)) { + found = 0; + + for(j = 0; j < even_msbs[msb - msb_head].tail; j++) { + if(even_msbs[msb - msb_head].states[j] == states_buffer[i]) { + found = 1; + break; + } + } + + if(!found) { + tail = even_msbs[msb - msb_head].tail++; + even_msbs[msb - msb_head].states[tail] = states_buffer[i]; + } + } + } + } + } + + oks >>= 12; + eks >>= 12; + + for(i = 0; i < MSB_LIMIT; i++) { + if(sync_state(program_state) == 1) { + return 0; + } + // TODO: Why is this necessary? + memset(temp_states_even, 0, sizeof(unsigned int) * (1280)); + memset(temp_states_odd, 0, sizeof(unsigned int) * (1280)); + memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int)); + memcpy(temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int)); + int res = old_recover( + temp_states_odd, + 0, + odd_msbs[i].tail, + oks, + temp_states_even, + 0, + even_msbs[i].tail, + eks, + 3, + 0, + n, + in >> 16, + 1, + program_state); + if(res == -1) { + return 1; + } + //odd_msbs[i].tail = 0; + //even_msbs[i].tail = 0; + } + + return 0; +} + +void** allocate_blocks(const size_t* block_sizes, int num_blocks) { + void** block_pointers = malloc(num_blocks * sizeof(void*)); + + for(int i = 0; i < num_blocks; i++) { + if(memmgr_heap_get_max_free_block() < block_sizes[i]) { + // Not enough memory, free previously allocated blocks + for(int j = 0; j < i; j++) { + free(block_pointers[j]); + } + free(block_pointers); + return NULL; + } + + block_pointers[i] = malloc(block_sizes[i]); + } + + return block_pointers; +} + +bool is_full_speed() { + return MSB_LIMIT == 16; +} + +bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_state) { + bool found = false; + const size_t block_sizes[] = {49216, 49216, 5120, 5120, 4096}; + const size_t reduced_block_sizes[] = {24608, 24608, 5120, 5120, 4096}; + const int num_blocks = sizeof(block_sizes) / sizeof(block_sizes[0]); + void** block_pointers = allocate_blocks(block_sizes, num_blocks); + if(block_pointers == NULL) { + // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed + if(is_full_speed()) { + //eta_round_time *= 2; + eta_total_time *= 2; + MSB_LIMIT /= 2; + } + block_pointers = allocate_blocks(reduced_block_sizes, num_blocks); + if(block_pointers == NULL) { + // System has less than 70 KB of RAM - should never happen so we don't reduce speed further + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } + // Adjust estimates for static encrypted attacks + if(n->attack == static_encrypted) { + eta_round_time *= 4; + eta_total_time *= 4; + if(is_full_speed()) { + eta_round_time *= 4; + eta_total_time *= 4; + } + } + struct Msb* odd_msbs = block_pointers[0]; + struct Msb* even_msbs = block_pointers[1]; + unsigned int* temp_states_odd = block_pointers[2]; + unsigned int* temp_states_even = block_pointers[3]; + unsigned int* states_buffer = block_pointers[4]; + int oks = 0, eks = 0; + int i = 0, msb = 0; + for(i = 31; i >= 0; i -= 2) { + oks = oks << 1 | BEBIT(ks2, i); + } + for(i = 30; i >= 0; i -= 2) { + eks = eks << 1 | BEBIT(ks2, i); + } + int bench_start = furi_hal_rtc_get_timestamp(); + program_state->eta_total = eta_total_time; + program_state->eta_timestamp = bench_start; + for(msb = 0; msb <= ((256 / MSB_LIMIT) - 1); msb++) { + program_state->search = msb; + program_state->eta_round = eta_round_time; + program_state->eta_total = eta_total_time - (eta_round_time * msb); + if(calculate_msb_tables( + oks, + eks, + msb, + n, + states_buffer, + odd_msbs, + even_msbs, + temp_states_odd, + temp_states_even, + in, + program_state)) { + //int bench_stop = furi_hal_rtc_get_timestamp(); + //FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start); + found = true; + break; + } + if(program_state->close_thread_please) { + break; + } + } + // Free the allocated blocks + for(int i = 0; i < num_blocks; i++) { + free(block_pointers[i]); + } + free(block_pointers); + return found; +} + +bool key_already_found_for_nonce_in_solved( + MfClassicKey* keyarray, + int keyarray_size, + MfClassicNonce* nonce) { + for(int k = 0; k < keyarray_size; k++) { + uint64_t key_as_int = bit_lib_bytes_to_num_be(keyarray[k].data, sizeof(MfClassicKey)); + struct Crypto1State temp = {0, 0}; + for(int i = 0; i < 24; i++) { + (&temp)->odd |= (BIT(key_as_int, 2 * i + 1) << (i ^ 3)); + (&temp)->even |= (BIT(key_as_int, 2 * i) << (i ^ 3)); + } + if(nonce->attack == mfkey32) { + crypt_word_noret(&temp, nonce->uid_xor_nt1, 0); + crypt_word_noret(&temp, nonce->nr1_enc, 1); + if(nonce->ar1_enc == (crypt_word(&temp) ^ nonce->p64b)) { + return true; + } + } else if(nonce->attack == static_nested) { + uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0); + if(nonce->ks1_1_enc == expected_ks1) { + return true; + } + } + } + return false; +} + +#pragma GCC push_options +#pragma GCC optimize("Os") +static void finished_beep() { + // Beep to indicate completion + NotificationApp* notification = furi_record_open("notification"); + notification_message(notification, &sequence_audiovisual_alert); + notification_message(notification, &sequence_display_backlight_on); + furi_record_close("notification"); +} + +void mfkey(ProgramState* program_state) { + uint32_t ks_enc = 0, nt_xor_uid = 0; + MfClassicKey found_key; // Recovered key + size_t keyarray_size = 0; + MfClassicKey* keyarray = malloc(sizeof(MfClassicKey) * 1); + uint32_t i = 0, j = 0; + //FURI_LOG_I(TAG, "Free heap before alloc(): %zub", memmgr_get_free_heap()); + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface); + flipper_application_preload(app, APP_ASSETS_PATH("plugins/mfkey_init_plugin.fal")); + flipper_application_map_to_memory(app); + const FlipperAppPluginDescriptor* app_descriptor = + flipper_application_plugin_get_descriptor(app); + const MfkeyPlugin* init_plugin = app_descriptor->entry_point; + // Check for nonces + program_state->mfkey32_present = init_plugin->napi_mf_classic_mfkey32_nonces_check_presence(); + program_state->nested_present = init_plugin->napi_mf_classic_nested_nonces_check_presence(); + if(!(program_state->mfkey32_present) && !(program_state->nested_present)) { + program_state->err = MissingNonces; + program_state->mfkey_state = Error; + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + free(keyarray); + return; + } + // Read dictionaries (optional) + KeysDict* system_dict = {0}; + bool system_dict_exists = keys_dict_check_presence(KEYS_DICT_SYSTEM_PATH); + KeysDict* user_dict = {0}; + bool user_dict_exists = keys_dict_check_presence(KEYS_DICT_USER_PATH); + uint32_t total_dict_keys = 0; + if(system_dict_exists) { + system_dict = + keys_dict_alloc(KEYS_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey)); + total_dict_keys += keys_dict_get_total_keys(system_dict); + } + user_dict = keys_dict_alloc(KEYS_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey)); + if(user_dict_exists) { + total_dict_keys += keys_dict_get_total_keys(user_dict); + } + user_dict_exists = true; + program_state->dict_count = total_dict_keys; + program_state->mfkey_state = DictionaryAttack; + // Read nonces + MfClassicNonceArray* nonce_arr; + nonce_arr = init_plugin->napi_mf_classic_nonce_array_alloc( + system_dict, system_dict_exists, user_dict, program_state); + if(system_dict_exists) { + keys_dict_free(system_dict); + } + if(nonce_arr->total_nonces == 0) { + // Nothing to crack + program_state->err = ZeroNonces; + program_state->mfkey_state = Error; + init_plugin->napi_mf_classic_nonce_array_free(nonce_arr); + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + keys_dict_free(user_dict); + free(keyarray); + return; + } + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + // TODO: Track free state at the time this is called to ensure double free does not happen + furi_assert(nonce_arr); + furi_assert(nonce_arr->stream); + // TODO: Already closed? + buffered_file_stream_close(nonce_arr->stream); + stream_free(nonce_arr->stream); + //FURI_LOG_I(TAG, "Free heap after free(): %zub", memmgr_get_free_heap()); + program_state->mfkey_state = MFKeyAttack; + // TODO: Work backwards on this array and free memory + for(i = 0; i < nonce_arr->total_nonces; i++) { + MfClassicNonce next_nonce = nonce_arr->remaining_nonce_array[i]; + if(key_already_found_for_nonce_in_solved(keyarray, keyarray_size, &next_nonce)) { + nonce_arr->remaining_nonces--; + (program_state->cracked)++; + (program_state->num_completed)++; + continue; + } + //FURI_LOG_I(TAG, "Beginning recovery for %8lx", next_nonce.uid); + FuriString* cuid_dict_path; + switch(next_nonce.attack) { + case mfkey32: + ks_enc = next_nonce.ar0_enc ^ next_nonce.p64; + nt_xor_uid = 0; + break; + case static_nested: + ks_enc = next_nonce.ks1_2_enc; + nt_xor_uid = next_nonce.uid_xor_nt1; + break; + case static_encrypted: + ks_enc = next_nonce.ks1_1_enc; + nt_xor_uid = next_nonce.uid_xor_nt0; + cuid_dict_path = furi_string_alloc_printf( + "%s/mf_classic_dict_%08lx.nfc", EXT_PATH("nfc/assets"), next_nonce.uid); + // May need RECORD_STORAGE? + program_state->cuid_dict = keys_dict_alloc( + furi_string_get_cstr(cuid_dict_path), + KeysDictModeOpenAlways, + sizeof(MfClassicKey)); + break; + } + + if(!recover(&next_nonce, ks_enc, nt_xor_uid, program_state)) { + if((next_nonce.attack == static_encrypted) && (program_state->cuid_dict)) { + keys_dict_free(program_state->cuid_dict); + } + if(program_state->close_thread_please) { + break; + } + // No key found in recover() or static encrypted + (program_state->num_completed)++; + continue; + } + (program_state->cracked)++; + (program_state->num_completed)++; + found_key = next_nonce.key; + bool already_found = false; + for(j = 0; j < keyarray_size; j++) { + if(memcmp(keyarray[j].data, found_key.data, MF_CLASSIC_KEY_SIZE) == 0) { + already_found = true; + break; + } + } + if(already_found == false) { + // New key + keyarray = realloc(keyarray, sizeof(MfClassicKey) * (keyarray_size + 1)); //-V701 + keyarray_size += 1; + keyarray[keyarray_size - 1] = found_key; + (program_state->unique_cracked)++; + } + } + // TODO: Update display to show all keys were found + // TODO: Prepend found key(s) to user dictionary file + //FURI_LOG_I(TAG, "Unique keys found:"); + for(i = 0; i < keyarray_size; i++) { + //FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]); + keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); + } + if(keyarray_size > 0) { + dolphin_deed(DolphinDeedNfcMfcAdd); + } + free(nonce_arr); + keys_dict_free(user_dict); + free(keyarray); + if(program_state->mfkey_state == Error) { + return; + } + //FURI_LOG_I(TAG, "mfkey function completed normally"); // DEBUG + program_state->mfkey_state = Complete; + // No need to alert the user if they asked it to stop + if(!(program_state->close_thread_please)) { + finished_beep(); + } + return; +} + +// Screen is 128x64 px +static void render_callback(Canvas* const canvas, void* ctx) { + furi_assert(ctx); + ProgramState* program_state = ctx; + furi_mutex_acquire(program_state->mutex, FuriWaitForever); + char draw_str[44] = {}; + + canvas_draw_frame(canvas, 0, 0, 128, 64); + canvas_draw_frame(canvas, 0, 15, 128, 64); + + // FontSecondary by default, title is drawn at the end + snprintf(draw_str, sizeof(draw_str), "RAM: %zub", memmgr_get_free_heap()); + canvas_draw_str_aligned(canvas, 48, 5, AlignLeft, AlignTop, draw_str); + canvas_draw_icon(canvas, 114, 4, &I_mfkey); + if(program_state->mfkey_state == MFKeyAttack) { + float eta_round = (float)1 - ((float)program_state->eta_round / (float)eta_round_time); + float eta_total = (float)1 - ((float)program_state->eta_total / (float)eta_total_time); + float progress = (float)program_state->num_completed / (float)program_state->total; + if(eta_round < 0 || eta_round > 1) { + // Round ETA miscalculated + eta_round = 1; + program_state->eta_round = 0; + } + if(eta_total < 0 || eta_round > 1) { + // Total ETA miscalculated + eta_total = 1; + program_state->eta_total = 0; + } + snprintf( + draw_str, + sizeof(draw_str), + "Cracking: %d/%d - in prog.", + program_state->num_completed, + program_state->total); + elements_progress_bar_with_text(canvas, 5, 18, 118, progress, draw_str); + snprintf( + draw_str, + sizeof(draw_str), + "Round: %d/%d - ETA %02d Sec", + (program_state->search) + 1, // Zero indexed + 256 / MSB_LIMIT, + program_state->eta_round); + elements_progress_bar_with_text(canvas, 5, 31, 118, eta_round, draw_str); + snprintf(draw_str, sizeof(draw_str), "Total ETA %03d Sec", program_state->eta_total); + elements_progress_bar_with_text(canvas, 5, 44, 118, eta_total, draw_str); + } else if(program_state->mfkey_state == DictionaryAttack) { + snprintf( + draw_str, sizeof(draw_str), "Dict solves: %d (in progress)", program_state->cracked); + canvas_draw_str_aligned(canvas, 10, 18, AlignLeft, AlignTop, draw_str); + snprintf(draw_str, sizeof(draw_str), "Keys in dict: %d", program_state->dict_count); + canvas_draw_str_aligned(canvas, 26, 28, AlignLeft, AlignTop, draw_str); + } else if(program_state->mfkey_state == Complete) { + // TODO: Scrollable list view to see cracked keys if user presses down + elements_progress_bar(canvas, 5, 18, 118, 1); + canvas_draw_str_aligned(canvas, 64, 31, AlignCenter, AlignTop, "Complete"); + snprintf( + draw_str, + sizeof(draw_str), + "Keys added to user dict: %d", + program_state->unique_cracked); + canvas_draw_str_aligned(canvas, 64, 41, AlignCenter, AlignTop, draw_str); + if(program_state->num_candidates > 0) { + snprintf( + draw_str, + sizeof(draw_str), + "SEN key candidates: %d", + program_state->num_candidates); + canvas_draw_str_aligned(canvas, 64, 51, AlignCenter, AlignTop, draw_str); + } + } else if(program_state->mfkey_state == Ready) { + canvas_draw_str_aligned(canvas, 50, 30, AlignLeft, AlignTop, "Ready"); + elements_button_center(canvas, "Start"); + elements_button_right(canvas, "Help"); + } else if(program_state->mfkey_state == Help) { + canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces by reading"); + canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "tag or reader in NFC app:"); + canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "https://docs.flipper.net/"); + canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "nfc/mfkey32"); + } else if(program_state->mfkey_state == Error) { + canvas_draw_str_aligned(canvas, 50, 25, AlignLeft, AlignTop, "Error"); + if(program_state->err == MissingNonces) { + canvas_draw_str_aligned(canvas, 25, 36, AlignLeft, AlignTop, "No nonces found"); + } else if(program_state->err == ZeroNonces) { + canvas_draw_str_aligned(canvas, 15, 36, AlignLeft, AlignTop, "Nonces already cracked"); + } else if(program_state->err == InsufficientRAM) { + canvas_draw_str_aligned(canvas, 35, 36, AlignLeft, AlignTop, "No free RAM"); + } else { + // Unhandled error + } + } else { + // Unhandled program state + } + // Title + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "MFKey"); + furi_mutex_release(program_state->mutex); +} + +static void input_callback(InputEvent* input_event, void* event_queue) { + furi_assert(event_queue); + furi_message_queue_put((FuriMessageQueue*)event_queue, input_event, FuriWaitForever); +} + +static void mfkey_state_init(ProgramState* program_state) { + program_state->mfkey_state = Ready; + program_state->cracked = 0; + program_state->unique_cracked = 0; + program_state->num_completed = 0; + program_state->num_candidates = 0; + program_state->total = 0; + program_state->dict_count = 0; +} + +// Entrypoint for worker thread +static int32_t mfkey_worker_thread(void* ctx) { + ProgramState* program_state = ctx; + program_state->mfkey_state = Initializing; + mfkey(program_state); + return 0; +} + +int32_t mfkey_main() { + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); + + ProgramState* program_state = malloc(sizeof(ProgramState)); + + mfkey_state_init(program_state); + + program_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, program_state); + view_port_input_callback_set(view_port, input_callback, event_queue); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + program_state->mfkeythread = + furi_thread_alloc_ex("MFKeyWorker", 2048, mfkey_worker_thread, program_state); + + InputEvent input_event; + for(bool main_loop = true; main_loop;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &input_event, 100); + + furi_mutex_acquire(program_state->mutex, FuriWaitForever); + + if(event_status == FuriStatusOk) { + if(input_event.type == InputTypePress) { + switch(input_event.key) { + case InputKeyRight: + if(program_state->mfkey_state == Ready) { + program_state->mfkey_state = Help; + } + break; + case InputKeyOk: + if(program_state->mfkey_state == Ready) { + furi_thread_start(program_state->mfkeythread); + } + break; + case InputKeyBack: + if(program_state->mfkey_state == Help) { + program_state->mfkey_state = Ready; + } else { + program_state->close_thread_please = true; + // Wait until thread is finished + furi_thread_join(program_state->mfkeythread); + main_loop = false; + } + break; + default: + break; + } + } + } + + furi_mutex_release(program_state->mutex); + view_port_update(view_port); + } + + // Thread joined in back event handler + furi_thread_free(program_state->mfkeythread); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close(RECORD_GUI); + view_port_free(view_port); + furi_message_queue_free(event_queue); + furi_mutex_free(program_state->mutex); + free(program_state); + + return 0; +} +#pragma GCC pop_options diff --git a/applications/system/mfkey/mfkey.h b/applications/system/mfkey/mfkey.h new file mode 100644 index 000000000..4a7ab3423 --- /dev/null +++ b/applications/system/mfkey/mfkey.h @@ -0,0 +1,108 @@ +#ifndef MFKEY_H +#define MFKEY_H + +#include +#include +#include +#include +#include +#include +#include + +struct Crypto1State { + uint32_t odd, even; +}; +struct Msb { + int tail; + uint32_t states[768]; +}; + +typedef enum { + MissingNonces, + ZeroNonces, + InsufficientRAM, +} MFKeyError; + +typedef enum { + Ready, + Initializing, + DictionaryAttack, + MFKeyAttack, + Complete, + Error, + Help, +} MFKeyState; + +// TODO: Can we eliminate any of the members of this struct? +typedef struct { + FuriMutex* mutex; + MFKeyError err; + MFKeyState mfkey_state; + int cracked; + int unique_cracked; + int num_completed; + int num_candidates; + int total; + int dict_count; + int search; + int eta_timestamp; + int eta_total; + int eta_round; + bool mfkey32_present; + bool nested_present; + bool close_thread_please; + FuriThread* mfkeythread; + KeysDict* cuid_dict; +} ProgramState; + +typedef enum { + mfkey32, + static_nested, + static_encrypted +} AttackType; + +typedef struct { + AttackType attack; + MfClassicKey key; // key + uint32_t uid; // serial number + uint32_t nt0; // tag challenge first + uint32_t nt1; // tag challenge second + uint32_t uid_xor_nt0; // uid ^ nt0 + uint32_t uid_xor_nt1; // uid ^ nt1 + union { + // Mfkey32 + struct { + uint32_t p64; // 64th successor of nt0 + uint32_t p64b; // 64th successor of nt1 + uint32_t nr0_enc; // first encrypted reader challenge + uint32_t ar0_enc; // first encrypted reader response + uint32_t nr1_enc; // second encrypted reader challenge + uint32_t ar1_enc; // second encrypted reader response + }; + // Nested + struct { + uint32_t ks1_1_enc; // first encrypted keystream + uint32_t ks1_2_enc; // second encrypted keystream + char par_1_str[5]; // first parity bits (string representation) + char par_2_str[5]; // second parity bits (string representation) + uint8_t par_1; // first parity bits + uint8_t par_2; // second parity bits + }; + }; +} MfClassicNonce; + +typedef struct { + Stream* stream; + uint32_t total_nonces; + MfClassicNonce* remaining_nonce_array; + size_t remaining_nonces; +} MfClassicNonceArray; + +struct KeysDict { + Stream* stream; + size_t key_size; + size_t key_size_symbols; + size_t total_keys; +}; + +#endif // MFKEY_H diff --git a/applications/system/mfkey/mfkey.png b/applications/system/mfkey/mfkey.png new file mode 100644 index 0000000000000000000000000000000000000000..f1694bb335a8619e53cd3b98651ba995cc7a1caf GIT binary patch literal 107 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xGmzZ=C-xtZVhivIasB`QKad%E=yDy9Qt)(f z4B@z*{KD=)L3iUrapuFX*xK~i1d{R-5*QT~m>F7Tu$Q|1zuOE{%i!ti=d#Wzp$P!I C{~rec literal 0 HcmV?d00001 diff --git a/applications/system/mfkey/plugin_interface.h b/applications/system/mfkey/plugin_interface.h new file mode 100644 index 000000000..e7ca438b8 --- /dev/null +++ b/applications/system/mfkey/plugin_interface.h @@ -0,0 +1,13 @@ +#pragma once + +#define PLUGIN_APP_ID "mfkey" +#define PLUGIN_API_VERSION 1 + +typedef struct { + const char* name; + bool (*napi_mf_classic_mfkey32_nonces_check_presence)(); + bool (*napi_mf_classic_nested_nonces_check_presence)(); + MfClassicNonceArray* ( + *napi_mf_classic_nonce_array_alloc)(KeysDict*, bool, KeysDict*, ProgramState*); + void (*napi_mf_classic_nonce_array_free)(MfClassicNonceArray*); +} MfkeyPlugin;