mfkey.c 58 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626
  1. #pragma GCC optimize("O3")
  2. #pragma GCC optimize("-funroll-all-loops")
  3. // TODO: Add keys to top of the user dictionary, not the bottom
  4. // TODO: More efficient dictionary bruteforce by scanning through hardcoded very common keys and previously found dictionary keys first?
  5. // (a cache for napi_key_already_found_for_nonce)
  6. // TODO: Remove icon from application to save 1.5 KB
  7. // TODO: Selectively unroll loops to reduce binary size
  8. // TODO: Optimize assembly of filter, state_loop, and/or evenparity32
  9. // TODO: Investigate collecting the parity during Mfkey32 attacks to further optimize the attack
  10. // TODO: Why different sscanf between Mfkey32 and Nested?
  11. // TODO: "Read tag again with NFC app" message upon completion, "Complete. Keys added: <n>"
  12. // TODO: Separate Mfkey32 and Nested functions where possible to reduce branch statements
  13. // TODO: More accurate timing for Nested
  14. // TODO: Eliminate OOM crashes (updated application is larger)
  15. #include <furi.h>
  16. #include <furi_hal.h>
  17. #include "time.h"
  18. #include <gui/gui.h>
  19. #include <gui/elements.h>
  20. #include <input/input.h>
  21. #include <stdlib.h>
  22. #include "mfkeynested_icons.h"
  23. #include <inttypes.h>
  24. #include <stdio.h>
  25. #include <string.h>
  26. #include <stdint.h>
  27. #include <unistd.h>
  28. #include <storage/storage.h>
  29. #include <toolbox/stream/buffered_file_stream.h>
  30. #include <lib/toolbox/args.h>
  31. #include <lib/flipper_format/flipper_format.h>
  32. #include <dolphin/dolphin.h>
  33. #include <notification/notification_messages.h>
  34. #define MF_CLASSIC_DICT_FLIPPER_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc")
  35. #define MF_CLASSIC_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc")
  36. #define MF_CLASSIC_NONCE_PATH EXT_PATH("nfc/.mfkey32.log")
  37. #define MF_CLASSIC_NESTED_NONCE_PATH EXT_PATH("nfc/.nested")
  38. #define TAG "MFKey"
  39. #define NFC_MF_CLASSIC_KEY_LEN (13)
  40. #define MAX_NAME_LEN 32
  41. #define MAX_PATH_LEN 64
  42. #define MIN_RAM 115632 // TODO: May no longer be accurate, crashes are getting frequent
  43. #define LF_POLY_ODD (0x29CE5C)
  44. #define LF_POLY_EVEN (0x870804)
  45. #define CONST_M1_1 (LF_POLY_EVEN << 1 | 1)
  46. #define CONST_M2_1 (LF_POLY_ODD << 1)
  47. #define CONST_M1_2 (LF_POLY_ODD)
  48. #define CONST_M2_2 (LF_POLY_EVEN << 1 | 1)
  49. #define BIT(x, n) ((x) >> (n)&1)
  50. #define BEBIT(x, n) BIT(x, (n) ^ 24)
  51. #define SWAPENDIAN(x) \
  52. ((x) = ((x) >> 8 & 0xff00ff) | ((x)&0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16)
  53. //#define SIZEOF(arr) sizeof(arr) / sizeof(*arr)
  54. static int eta_round_time = 56;
  55. static int eta_total_time = 900;
  56. // MSB_LIMIT: Chunk size (out of 256)
  57. static int MSB_LIMIT = 16;
  58. struct Crypto1State {
  59. uint32_t odd, even;
  60. };
  61. struct Msb {
  62. int tail;
  63. uint32_t states[768];
  64. };
  65. typedef enum {
  66. EventTypeTick,
  67. EventTypeKey,
  68. } EventType;
  69. typedef struct {
  70. EventType type;
  71. InputEvent input;
  72. } PluginEvent;
  73. typedef enum {
  74. MissingNonces,
  75. ZeroNonces,
  76. } MFKeyError;
  77. typedef enum {
  78. Ready,
  79. Initializing,
  80. DictionaryAttack,
  81. MFKeyAttack,
  82. Complete,
  83. Error,
  84. Help,
  85. } MFKeyState;
  86. // TODO: Can we eliminate any of the members of this struct?
  87. typedef struct {
  88. FuriMutex* mutex;
  89. MFKeyError err;
  90. MFKeyState mfkey_state;
  91. int cracked;
  92. int unique_cracked;
  93. int num_completed;
  94. int total;
  95. int dict_count;
  96. int search;
  97. int eta_timestamp;
  98. int eta_total;
  99. int eta_round;
  100. bool mfkey32_present;
  101. bool nested_present;
  102. bool is_thread_running;
  103. bool close_thread_please;
  104. FuriThread* mfkeythread;
  105. } ProgramState;
  106. typedef enum { mfkey32, static_nested } AttackType;
  107. typedef struct {
  108. AttackType attack;
  109. uint64_t key; // key
  110. uint32_t uid; // serial number
  111. uint32_t nt0; // tag challenge first
  112. uint32_t nt1; // tag challenge second
  113. uint32_t uid_xor_nt0; // uid ^ nt0
  114. uint32_t uid_xor_nt1; // uid ^ nt1
  115. // Mfkey32
  116. uint32_t p64; // 64th successor of nt0
  117. uint32_t p64b; // 64th successor of nt1
  118. uint32_t nr0_enc; // first encrypted reader challenge
  119. uint32_t ar0_enc; // first encrypted reader response
  120. uint32_t nr1_enc; // second encrypted reader challenge
  121. uint32_t ar1_enc; // second encrypted reader response
  122. // Nested
  123. uint32_t ks1_1_enc; // first encrypted keystream
  124. uint32_t ks1_2_enc; // second encrypted keystream
  125. char par_1_str[5]; // first parity bits (string representation)
  126. char par_2_str[5]; // second parity bits (string representation)
  127. uint8_t par_1; // first parity bits
  128. uint8_t par_2; // second parity bits
  129. } MfClassicNonce;
  130. typedef struct {
  131. Stream* stream;
  132. uint32_t total_nonces;
  133. MfClassicNonce* remaining_nonce_array;
  134. size_t remaining_nonces;
  135. } MfClassicNonceArray;
  136. typedef enum {
  137. MfClassicDictTypeSystem,
  138. MfClassicDictTypeUser,
  139. } MfClassicDictType;
  140. typedef struct {
  141. Stream* stream;
  142. uint32_t total_keys;
  143. } MfClassicDict;
  144. static const uint8_t table[256] = {
  145. 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3,
  146. 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4,
  147. 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4,
  148. 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5,
  149. 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2,
  150. 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5,
  151. 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4,
  152. 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6,
  153. 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8};
  154. static const uint8_t lookup1[256] = {
  155. 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0,
  156. 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16,
  157. 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8,
  158. 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24,
  159. 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0,
  160. 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24,
  161. 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0,
  162. 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24,
  163. 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 0, 0, 16, 16, 0, 16, 0, 0,
  164. 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24,
  165. 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24};
  166. static const uint8_t lookup2[256] = {
  167. 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,
  168. 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,
  169. 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,
  170. 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,
  171. 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,
  172. 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,
  173. 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,
  174. 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,
  175. 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6};
  176. uint32_t prng_successor(uint32_t x, uint32_t n) {
  177. SWAPENDIAN(x);
  178. while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31;
  179. return SWAPENDIAN(x);
  180. }
  181. static inline int filter(uint32_t const x) {
  182. uint32_t f;
  183. f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff];
  184. f |= 0x0d938 >> (x >> 16 & 0xf) & 1;
  185. return BIT(0xEC57E80A, f);
  186. }
  187. static inline uint8_t evenparity32(uint32_t x) {
  188. if((table[x & 0xff] + table[(x >> 8) & 0xff] + table[(x >> 16) & 0xff] + table[x >> 24]) % 2 ==
  189. 0) {
  190. return 0;
  191. } else {
  192. return 1;
  193. }
  194. //return ((table[x & 0xff] + table[(x >> 8) & 0xff] + table[(x >> 16) & 0xff] + table[x >> 24]) % 2) & 0xFF;
  195. }
  196. static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) {
  197. int p = data[item] >> 25;
  198. p = p << 1 | evenparity32(data[item] & mask1);
  199. p = p << 1 | evenparity32(data[item] & mask2);
  200. data[item] = p << 24 | (data[item] & 0xffffff);
  201. }
  202. void crypto1_get_lfsr(struct Crypto1State* state, uint64_t* lfsr) {
  203. int i;
  204. for(*lfsr = 0, i = 23; i >= 0; --i) {
  205. *lfsr = *lfsr << 1 | BIT(state->odd, i ^ 3);
  206. *lfsr = *lfsr << 1 | BIT(state->even, i ^ 3);
  207. }
  208. }
  209. static inline uint32_t crypt_word(struct Crypto1State* s) {
  210. // "in" and "x" are always 0 (last iteration)
  211. uint32_t res_ret = 0;
  212. uint32_t feedin, t;
  213. for(int i = 0; i <= 31; i++) {
  214. res_ret |= (filter(s->odd) << (24 ^ i)); //-V629
  215. feedin = LF_POLY_EVEN & s->even;
  216. feedin ^= LF_POLY_ODD & s->odd;
  217. s->even = s->even << 1 | (evenparity32(feedin));
  218. t = s->odd, s->odd = s->even, s->even = t;
  219. }
  220. return res_ret;
  221. }
  222. static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x) {
  223. uint8_t ret;
  224. uint32_t feedin, t, next_in;
  225. for(int i = 0; i <= 31; i++) {
  226. next_in = BEBIT(in, i);
  227. ret = filter(s->odd);
  228. feedin = ret & (!!x);
  229. feedin ^= LF_POLY_EVEN & s->even;
  230. feedin ^= LF_POLY_ODD & s->odd;
  231. feedin ^= !!next_in;
  232. s->even = s->even << 1 | (evenparity32(feedin));
  233. t = s->odd, s->odd = s->even, s->even = t;
  234. }
  235. return;
  236. }
  237. static inline uint32_t crypt_word_ret(struct Crypto1State* s, uint32_t in, int x) {
  238. uint32_t ret = 0;
  239. uint32_t feedin, t, next_in;
  240. uint8_t next_ret;
  241. for(int i = 0; i <= 31; i++) {
  242. next_in = BEBIT(in, i);
  243. next_ret = filter(s->odd);
  244. feedin = next_ret & (!!x);
  245. feedin ^= LF_POLY_EVEN & s->even;
  246. feedin ^= LF_POLY_ODD & s->odd;
  247. feedin ^= !!next_in;
  248. s->even = s->even << 1 | (evenparity32(feedin));
  249. t = s->odd, s->odd = s->even, s->even = t;
  250. ret |= next_ret << (24 ^ i);
  251. }
  252. return ret;
  253. }
  254. static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x) {
  255. uint8_t ret;
  256. uint32_t feedin, t, next_in;
  257. for(int i = 31; i >= 0; i--) {
  258. next_in = BEBIT(in, i);
  259. s->odd &= 0xffffff;
  260. t = s->odd, s->odd = s->even, s->even = t;
  261. ret = filter(s->odd);
  262. feedin = ret & (!!x);
  263. feedin ^= s->even & 1;
  264. feedin ^= LF_POLY_EVEN & (s->even >>= 1);
  265. feedin ^= LF_POLY_ODD & s->odd;
  266. feedin ^= !!next_in;
  267. s->even |= (evenparity32(feedin)) << 23;
  268. }
  269. return;
  270. }
  271. bool key_already_found_for_nonce(uint64_t* keyarray, int keyarray_size, MfClassicNonce* nonce) {
  272. for(int k = 0; k < keyarray_size; k++) {
  273. struct Crypto1State temp = {0, 0};
  274. for(int i = 0; i < 24; i++) {
  275. (&temp)->odd |= (BIT(keyarray[k], 2 * i + 1) << (i ^ 3));
  276. (&temp)->even |= (BIT(keyarray[k], 2 * i) << (i ^ 3));
  277. }
  278. if(nonce->attack == mfkey32) {
  279. crypt_word_noret(&temp, nonce->uid_xor_nt1, 0);
  280. crypt_word_noret(&temp, nonce->nr1_enc, 1);
  281. if(nonce->ar1_enc == (crypt_word(&temp) ^ nonce->p64b)) {
  282. return true;
  283. }
  284. } else if(nonce->attack == static_nested) {
  285. uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0);
  286. if(nonce->ks1_1_enc == expected_ks1) {
  287. return true;
  288. }
  289. }
  290. }
  291. return false;
  292. }
  293. int check_state(struct Crypto1State* t, MfClassicNonce* n) {
  294. if(!(t->odd | t->even)) return 0;
  295. if(n->attack == mfkey32) {
  296. rollback_word_noret(t, 0, 0);
  297. rollback_word_noret(t, n->nr0_enc, 1);
  298. rollback_word_noret(t, n->uid_xor_nt0, 0);
  299. struct Crypto1State temp = {t->odd, t->even};
  300. crypt_word_noret(t, n->uid_xor_nt1, 0);
  301. crypt_word_noret(t, n->nr1_enc, 1);
  302. if(n->ar1_enc == (crypt_word(t) ^ n->p64b)) {
  303. crypto1_get_lfsr(&temp, &(n->key));
  304. return 1;
  305. }
  306. return 0;
  307. } else if(n->attack == static_nested) {
  308. struct Crypto1State temp = {t->odd, t->even};
  309. rollback_word_noret(t, n->uid_xor_nt1, 0);
  310. if(n->ks1_1_enc == crypt_word_ret(t, n->uid_xor_nt0, 0)) {
  311. rollback_word_noret(&temp, n->uid_xor_nt1, 0);
  312. crypto1_get_lfsr(&temp, &(n->key));
  313. return 1;
  314. }
  315. return 0;
  316. }
  317. return 0;
  318. }
  319. static inline int state_loop(
  320. unsigned int* states_buffer,
  321. int xks,
  322. int m1,
  323. int m2,
  324. unsigned int in,
  325. uint8_t and_val) {
  326. int states_tail = 0;
  327. int round = 0, s = 0, xks_bit = 0, round_in = 0;
  328. for(round = 1; round <= 12; round++) {
  329. xks_bit = BIT(xks, round);
  330. if(round > 4) {
  331. round_in = ((in >> (2 * (round - 4))) & and_val) << 24;
  332. }
  333. for(s = 0; s <= states_tail; s++) {
  334. states_buffer[s] <<= 1;
  335. if((filter(states_buffer[s]) ^ filter(states_buffer[s] | 1)) != 0) {
  336. states_buffer[s] |= filter(states_buffer[s]) ^ xks_bit;
  337. if(round > 4) {
  338. update_contribution(states_buffer, s, m1, m2);
  339. states_buffer[s] ^= round_in;
  340. }
  341. } else if(filter(states_buffer[s]) == xks_bit) {
  342. // TODO: Refactor
  343. if(round > 4) {
  344. states_buffer[++states_tail] = states_buffer[s + 1];
  345. states_buffer[s + 1] = states_buffer[s] | 1;
  346. update_contribution(states_buffer, s, m1, m2);
  347. states_buffer[s++] ^= round_in;
  348. update_contribution(states_buffer, s, m1, m2);
  349. states_buffer[s] ^= round_in;
  350. } else {
  351. states_buffer[++states_tail] = states_buffer[++s];
  352. states_buffer[s] = states_buffer[s - 1] | 1;
  353. }
  354. } else {
  355. states_buffer[s--] = states_buffer[states_tail--];
  356. }
  357. }
  358. }
  359. return states_tail;
  360. }
  361. int binsearch(unsigned int data[], int start, int stop) {
  362. int mid, val = data[stop] & 0xff000000;
  363. while(start != stop) {
  364. mid = (stop - start) >> 1;
  365. if((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000))
  366. stop = start + mid;
  367. else
  368. start += mid + 1;
  369. }
  370. return start;
  371. }
  372. void quicksort(unsigned int array[], int low, int high) {
  373. //if (SIZEOF(array) == 0)
  374. // return;
  375. if(low >= high) return;
  376. int middle = low + (high - low) / 2;
  377. unsigned int pivot = array[middle];
  378. int i = low, j = high;
  379. while(i <= j) {
  380. while(array[i] < pivot) {
  381. i++;
  382. }
  383. while(array[j] > pivot) {
  384. j--;
  385. }
  386. if(i <= j) { // swap
  387. int temp = array[i];
  388. array[i] = array[j];
  389. array[j] = temp;
  390. i++;
  391. j--;
  392. }
  393. }
  394. if(low < j) {
  395. quicksort(array, low, j);
  396. }
  397. if(high > i) {
  398. quicksort(array, i, high);
  399. }
  400. }
  401. int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2, unsigned int in) {
  402. in <<= 24;
  403. for(data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) {
  404. if((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) {
  405. data[tbl] |= filter(data[tbl]) ^ bit;
  406. update_contribution(data, tbl, m1, m2);
  407. data[tbl] ^= in;
  408. } else if(filter(data[tbl]) == bit) {
  409. data[++end] = data[tbl + 1];
  410. data[tbl + 1] = data[tbl] | 1;
  411. update_contribution(data, tbl, m1, m2);
  412. data[tbl++] ^= in;
  413. update_contribution(data, tbl, m1, m2);
  414. data[tbl] ^= in;
  415. } else {
  416. data[tbl--] = data[end--];
  417. }
  418. }
  419. return end;
  420. }
  421. int old_recover(
  422. unsigned int odd[],
  423. int o_head,
  424. int o_tail,
  425. int oks,
  426. unsigned int even[],
  427. int e_head,
  428. int e_tail,
  429. int eks,
  430. int rem,
  431. int s,
  432. MfClassicNonce* n,
  433. unsigned int in,
  434. int first_run) {
  435. int o, e, i;
  436. if(rem == -1) {
  437. for(e = e_head; e <= e_tail; ++e) {
  438. even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN) ^ (!!(in & 4));
  439. for(o = o_head; o <= o_tail; ++o, ++s) {
  440. struct Crypto1State temp = {0, 0};
  441. temp.even = odd[o];
  442. temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD);
  443. if(check_state(&temp, n)) {
  444. return -1;
  445. }
  446. }
  447. }
  448. return s;
  449. }
  450. if(first_run == 0) {
  451. for(i = 0; (i < 4) && (rem-- != 0); i++) {
  452. oks >>= 1;
  453. eks >>= 1;
  454. in >>= 2;
  455. o_tail = extend_table(
  456. odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1, 0);
  457. if(o_head > o_tail) return s;
  458. e_tail = extend_table(
  459. even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1, in & 3);
  460. if(e_head > e_tail) return s;
  461. }
  462. }
  463. first_run = 0;
  464. quicksort(odd, o_head, o_tail);
  465. quicksort(even, e_head, e_tail);
  466. while(o_tail >= o_head && e_tail >= e_head) {
  467. if(((odd[o_tail] ^ even[e_tail]) >> 24) == 0) {
  468. o_tail = binsearch(odd, o_head, o = o_tail);
  469. e_tail = binsearch(even, e_head, e = e_tail);
  470. s = old_recover(
  471. odd, o_tail--, o, oks, even, e_tail--, e, eks, rem, s, n, in, first_run);
  472. if(s == -1) {
  473. break;
  474. }
  475. } else if((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) {
  476. o_tail = binsearch(odd, o_head, o_tail) - 1;
  477. } else {
  478. e_tail = binsearch(even, e_head, e_tail) - 1;
  479. }
  480. }
  481. return s;
  482. }
  483. static inline int sync_state(ProgramState* program_state) {
  484. int ts = furi_hal_rtc_get_timestamp();
  485. program_state->eta_round = program_state->eta_round - (ts - program_state->eta_timestamp);
  486. program_state->eta_total = program_state->eta_total - (ts - program_state->eta_timestamp);
  487. program_state->eta_timestamp = ts;
  488. if(program_state->close_thread_please) {
  489. return 1;
  490. }
  491. return 0;
  492. }
  493. int calculate_msb_tables(
  494. int oks,
  495. int eks,
  496. int msb_round,
  497. MfClassicNonce* n,
  498. unsigned int* states_buffer,
  499. struct Msb* odd_msbs,
  500. struct Msb* even_msbs,
  501. unsigned int* temp_states_odd,
  502. unsigned int* temp_states_even,
  503. unsigned int in,
  504. ProgramState* program_state) {
  505. //FURI_LOG_I(TAG, "MSB GO %i", msb_iter); // DEBUG
  506. unsigned int msb_head = (MSB_LIMIT * msb_round); // msb_iter ranges from 0 to (256/MSB_LIMIT)-1
  507. unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1));
  508. int states_tail = 0, tail = 0;
  509. int i = 0, j = 0, semi_state = 0, found = 0;
  510. unsigned int msb = 0;
  511. in = ((in >> 16 & 0xff) | (in << 16) | (in & 0xff00)) << 1;
  512. // TODO: Why is this necessary?
  513. memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb));
  514. memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb));
  515. for(semi_state = 1 << 20; semi_state >= 0; semi_state--) {
  516. if(semi_state % 32768 == 0) {
  517. if(sync_state(program_state) == 1) {
  518. return 0;
  519. }
  520. }
  521. if(filter(semi_state) == (oks & 1)) { //-V547
  522. states_buffer[0] = semi_state;
  523. states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1, 0, 0);
  524. for(i = states_tail; i >= 0; i--) {
  525. msb = states_buffer[i] >> 24;
  526. if((msb >= msb_head) && (msb < msb_tail)) {
  527. found = 0;
  528. for(j = 0; j < odd_msbs[msb - msb_head].tail - 1; j++) {
  529. if(odd_msbs[msb - msb_head].states[j] == states_buffer[i]) {
  530. found = 1;
  531. break;
  532. }
  533. }
  534. if(!found) {
  535. tail = odd_msbs[msb - msb_head].tail++;
  536. odd_msbs[msb - msb_head].states[tail] = states_buffer[i];
  537. }
  538. }
  539. }
  540. }
  541. if(filter(semi_state) == (eks & 1)) { //-V547
  542. states_buffer[0] = semi_state;
  543. states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2, in, 3);
  544. for(i = 0; i <= states_tail; i++) {
  545. msb = states_buffer[i] >> 24;
  546. if((msb >= msb_head) && (msb < msb_tail)) {
  547. found = 0;
  548. for(j = 0; j < even_msbs[msb - msb_head].tail; j++) {
  549. if(even_msbs[msb - msb_head].states[j] == states_buffer[i]) {
  550. found = 1;
  551. break;
  552. }
  553. }
  554. if(!found) {
  555. tail = even_msbs[msb - msb_head].tail++;
  556. even_msbs[msb - msb_head].states[tail] = states_buffer[i];
  557. }
  558. }
  559. }
  560. }
  561. }
  562. oks >>= 12;
  563. eks >>= 12;
  564. for(i = 0; i < MSB_LIMIT; i++) {
  565. if(sync_state(program_state) == 1) {
  566. return 0;
  567. }
  568. // TODO: Why is this necessary?
  569. memset(temp_states_even, 0, sizeof(unsigned int) * (1280));
  570. memset(temp_states_odd, 0, sizeof(unsigned int) * (1280));
  571. memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int));
  572. memcpy(temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int));
  573. int res = old_recover(
  574. temp_states_odd,
  575. 0,
  576. odd_msbs[i].tail,
  577. oks,
  578. temp_states_even,
  579. 0,
  580. even_msbs[i].tail,
  581. eks,
  582. 3,
  583. 0,
  584. n,
  585. in >> 16,
  586. 1);
  587. if(res == -1) {
  588. return 1;
  589. }
  590. //odd_msbs[i].tail = 0;
  591. //even_msbs[i].tail = 0;
  592. }
  593. return 0;
  594. }
  595. bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_state) {
  596. bool found = false;
  597. unsigned int* states_buffer = malloc(sizeof(unsigned int) * (2 << 9));
  598. struct Msb* odd_msbs = (struct Msb*)malloc(MSB_LIMIT * sizeof(struct Msb));
  599. struct Msb* even_msbs = (struct Msb*)malloc(MSB_LIMIT * sizeof(struct Msb));
  600. unsigned int* temp_states_odd = malloc(sizeof(unsigned int) * (1280));
  601. unsigned int* temp_states_even = malloc(sizeof(unsigned int) * (1280));
  602. int oks = 0, eks = 0;
  603. int i = 0, msb = 0;
  604. for(i = 31; i >= 0; i -= 2) {
  605. oks = oks << 1 | BEBIT(ks2, i);
  606. }
  607. for(i = 30; i >= 0; i -= 2) {
  608. eks = eks << 1 | BEBIT(ks2, i);
  609. }
  610. int bench_start = furi_hal_rtc_get_timestamp();
  611. program_state->eta_total = eta_total_time;
  612. program_state->eta_timestamp = bench_start;
  613. for(msb = 0; msb <= ((256 / MSB_LIMIT) - 1); msb++) {
  614. program_state->search = msb;
  615. program_state->eta_round = eta_round_time;
  616. program_state->eta_total = eta_total_time - (eta_round_time * msb);
  617. if(calculate_msb_tables(
  618. oks,
  619. eks,
  620. msb,
  621. n,
  622. states_buffer,
  623. odd_msbs,
  624. even_msbs,
  625. temp_states_odd,
  626. temp_states_even,
  627. in,
  628. program_state)) {
  629. int bench_stop = furi_hal_rtc_get_timestamp();
  630. FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start);
  631. found = true;
  632. break;
  633. }
  634. if(program_state->close_thread_please) {
  635. break;
  636. }
  637. }
  638. free(states_buffer);
  639. free(odd_msbs);
  640. free(even_msbs);
  641. free(temp_states_odd);
  642. free(temp_states_even);
  643. return found;
  644. }
  645. bool napi_mf_classic_dict_check_presence(MfClassicDictType dict_type) {
  646. Storage* storage = furi_record_open(RECORD_STORAGE);
  647. bool dict_present = false;
  648. if(dict_type == MfClassicDictTypeSystem) {
  649. dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_FLIPPER_PATH, NULL) == FSE_OK;
  650. } else if(dict_type == MfClassicDictTypeUser) {
  651. dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_USER_PATH, NULL) == FSE_OK;
  652. }
  653. furi_record_close(RECORD_STORAGE);
  654. return dict_present;
  655. }
  656. MfClassicDict* napi_mf_classic_dict_alloc(MfClassicDictType dict_type) {
  657. MfClassicDict* dict = malloc(sizeof(MfClassicDict));
  658. Storage* storage = furi_record_open(RECORD_STORAGE);
  659. dict->stream = buffered_file_stream_alloc(storage);
  660. furi_record_close(RECORD_STORAGE);
  661. bool dict_loaded = false;
  662. do {
  663. if(dict_type == MfClassicDictTypeSystem) {
  664. if(!buffered_file_stream_open(
  665. dict->stream,
  666. MF_CLASSIC_DICT_FLIPPER_PATH,
  667. FSAM_READ_WRITE,
  668. FSOM_OPEN_EXISTING)) {
  669. buffered_file_stream_close(dict->stream);
  670. break;
  671. }
  672. } else if(dict_type == MfClassicDictTypeUser) {
  673. if(!buffered_file_stream_open(
  674. dict->stream, MF_CLASSIC_DICT_USER_PATH, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) {
  675. buffered_file_stream_close(dict->stream);
  676. break;
  677. }
  678. }
  679. // Check for newline ending
  680. if(!stream_eof(dict->stream)) {
  681. if(!stream_seek(dict->stream, -1, StreamOffsetFromEnd)) break;
  682. uint8_t last_char = 0;
  683. if(stream_read(dict->stream, &last_char, 1) != 1) break;
  684. if(last_char != '\n') {
  685. FURI_LOG_D(TAG, "Adding new line ending");
  686. if(stream_write_char(dict->stream, '\n') != 1) break;
  687. }
  688. if(!stream_rewind(dict->stream)) break;
  689. }
  690. // Read total amount of keys
  691. FuriString* next_line;
  692. next_line = furi_string_alloc();
  693. while(true) {
  694. if(!stream_read_line(dict->stream, next_line)) {
  695. FURI_LOG_T(TAG, "No keys left in dict");
  696. break;
  697. }
  698. FURI_LOG_T(
  699. TAG,
  700. "Read line: %s, len: %zu",
  701. furi_string_get_cstr(next_line),
  702. furi_string_size(next_line));
  703. if(furi_string_get_char(next_line, 0) == '#') continue;
  704. if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue;
  705. dict->total_keys++;
  706. }
  707. furi_string_free(next_line);
  708. stream_rewind(dict->stream);
  709. dict_loaded = true;
  710. FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", dict->total_keys);
  711. } while(false);
  712. if(!dict_loaded) {
  713. // TODO: Does this close twice?
  714. buffered_file_stream_close(dict->stream);
  715. //stream_free(dict->stream);
  716. free(dict);
  717. dict = NULL;
  718. }
  719. return dict;
  720. }
  721. bool napi_mf_classic_dict_add_key_str(MfClassicDict* dict, FuriString* key) {
  722. furi_assert(dict);
  723. furi_assert(dict->stream);
  724. FURI_LOG_I(TAG, "Saving key: %s", furi_string_get_cstr(key));
  725. furi_string_cat_printf(key, "\n");
  726. bool key_added = false;
  727. do {
  728. if(!stream_seek(dict->stream, 0, StreamOffsetFromEnd)) break;
  729. if(!stream_insert_string(dict->stream, key)) break;
  730. dict->total_keys++;
  731. key_added = true;
  732. } while(false);
  733. furi_string_left(key, 12);
  734. return key_added;
  735. }
  736. void napi_mf_classic_dict_free(MfClassicDict* dict) {
  737. // TODO: Track free state at the time this is called to ensure double free does not happen
  738. furi_assert(dict);
  739. furi_assert(dict->stream);
  740. buffered_file_stream_close(dict->stream);
  741. stream_free(dict->stream);
  742. free(dict);
  743. }
  744. static void napi_mf_classic_dict_int_to_str(uint8_t* key_int, FuriString* key_str) {
  745. furi_string_reset(key_str);
  746. for(size_t i = 0; i < 6; i++) {
  747. furi_string_cat_printf(key_str, "%02X", key_int[i]);
  748. }
  749. }
  750. static void napi_mf_classic_dict_str_to_int(FuriString* key_str, uint64_t* key_int) {
  751. uint8_t key_byte_tmp;
  752. *key_int = 0ULL;
  753. for(uint8_t i = 0; i < 12; i += 2) {
  754. args_char_to_hex(
  755. furi_string_get_char(key_str, i), furi_string_get_char(key_str, i + 1), &key_byte_tmp);
  756. *key_int |= (uint64_t)key_byte_tmp << (8 * (5 - i / 2));
  757. }
  758. }
  759. uint32_t napi_mf_classic_dict_get_total_keys(MfClassicDict* dict) {
  760. furi_assert(dict);
  761. return dict->total_keys;
  762. }
  763. bool napi_mf_classic_dict_rewind(MfClassicDict* dict) {
  764. furi_assert(dict);
  765. furi_assert(dict->stream);
  766. return stream_rewind(dict->stream);
  767. }
  768. bool napi_mf_classic_dict_get_next_key_str(MfClassicDict* dict, FuriString* key) {
  769. furi_assert(dict);
  770. furi_assert(dict->stream);
  771. bool key_read = false;
  772. furi_string_reset(key);
  773. while(!key_read) {
  774. if(!stream_read_line(dict->stream, key)) break;
  775. if(furi_string_get_char(key, 0) == '#') continue;
  776. if(furi_string_size(key) != NFC_MF_CLASSIC_KEY_LEN) continue;
  777. furi_string_left(key, 12);
  778. key_read = true;
  779. }
  780. return key_read;
  781. }
  782. bool napi_mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key) {
  783. furi_assert(dict);
  784. furi_assert(dict->stream);
  785. FuriString* temp_key;
  786. temp_key = furi_string_alloc();
  787. bool key_read = napi_mf_classic_dict_get_next_key_str(dict, temp_key);
  788. if(key_read) {
  789. napi_mf_classic_dict_str_to_int(temp_key, key);
  790. }
  791. furi_string_free(temp_key);
  792. return key_read;
  793. }
  794. bool napi_mf_classic_dict_is_key_present_str(MfClassicDict* dict, FuriString* key) {
  795. furi_assert(dict);
  796. furi_assert(dict->stream);
  797. FuriString* next_line;
  798. next_line = furi_string_alloc();
  799. bool key_found = false;
  800. stream_rewind(dict->stream);
  801. while(!key_found) { //-V654
  802. if(!stream_read_line(dict->stream, next_line)) break;
  803. if(furi_string_get_char(next_line, 0) == '#') continue;
  804. if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue;
  805. furi_string_left(next_line, 12);
  806. if(!furi_string_equal(key, next_line)) continue;
  807. key_found = true;
  808. }
  809. furi_string_free(next_line);
  810. return key_found;
  811. }
  812. bool napi_mf_classic_dict_is_key_present(MfClassicDict* dict, uint8_t* key) {
  813. FuriString* temp_key;
  814. temp_key = furi_string_alloc();
  815. napi_mf_classic_dict_int_to_str(key, temp_key);
  816. bool key_found = napi_mf_classic_dict_is_key_present_str(dict, temp_key);
  817. furi_string_free(temp_key);
  818. return key_found;
  819. }
  820. bool napi_key_already_found_for_nonce(MfClassicDict* dict, MfClassicNonce* nonce) {
  821. bool found = false;
  822. uint64_t k = 0;
  823. napi_mf_classic_dict_rewind(dict);
  824. while(napi_mf_classic_dict_get_next_key(dict, &k)) {
  825. struct Crypto1State temp = {0, 0};
  826. int i;
  827. for(i = 0; i < 24; i++) {
  828. (&temp)->odd |= (BIT(k, 2 * i + 1) << (i ^ 3));
  829. (&temp)->even |= (BIT(k, 2 * i) << (i ^ 3));
  830. }
  831. if(nonce->attack == mfkey32) {
  832. crypt_word_noret(&temp, nonce->uid_xor_nt1, 0);
  833. crypt_word_noret(&temp, nonce->nr1_enc, 1);
  834. if(nonce->ar1_enc == (crypt_word(&temp) ^ nonce->p64b)) {
  835. found = true;
  836. break;
  837. }
  838. } else if(nonce->attack == static_nested) {
  839. uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0);
  840. if(nonce->ks1_1_enc == expected_ks1) {
  841. found = true;
  842. break;
  843. }
  844. }
  845. }
  846. return found;
  847. }
  848. bool napi_mf_classic_nonces_check_presence() {
  849. Storage* storage = furi_record_open(RECORD_STORAGE);
  850. bool nonces_present = storage_common_stat(storage, MF_CLASSIC_NONCE_PATH, NULL) == FSE_OK;
  851. furi_record_close(RECORD_STORAGE);
  852. return nonces_present;
  853. }
  854. bool distance_in_nonces_file(const char* file_path, const char* file_name) {
  855. char full_path[MAX_PATH_LEN];
  856. snprintf(full_path, sizeof(full_path), "%s/%s", file_path, file_name);
  857. bool distance_present = false;
  858. Storage* storage = furi_record_open(RECORD_STORAGE);
  859. Stream* file_stream = buffered_file_stream_alloc(storage);
  860. FuriString* line_str;
  861. line_str = furi_string_alloc();
  862. if(buffered_file_stream_open(file_stream, full_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
  863. while(true) {
  864. if(!stream_read_line(file_stream, line_str)) break;
  865. if(furi_string_search_str(line_str, "distance") != FURI_STRING_FAILURE) {
  866. distance_present = true;
  867. break;
  868. }
  869. }
  870. }
  871. buffered_file_stream_close(file_stream);
  872. stream_free(file_stream);
  873. furi_string_free(line_str);
  874. furi_record_close(RECORD_STORAGE);
  875. return distance_present;
  876. }
  877. bool napi_mf_classic_nested_nonces_check_presence() {
  878. Storage* storage = furi_record_open(RECORD_STORAGE);
  879. if(!(storage_dir_exists(storage, MF_CLASSIC_NESTED_NONCE_PATH))) {
  880. furi_record_close(RECORD_STORAGE);
  881. return false;
  882. }
  883. bool nonces_present = false;
  884. File* dir = storage_file_alloc(storage);
  885. char filename_buffer[MAX_NAME_LEN];
  886. FileInfo file_info;
  887. if(storage_dir_open(dir, MF_CLASSIC_NESTED_NONCE_PATH)) {
  888. while(storage_dir_read(dir, &file_info, filename_buffer, MAX_NAME_LEN)) {
  889. // We only care about Static Nested files
  890. if(!(file_info.flags & FSF_DIRECTORY) && strstr(filename_buffer, ".nonces") &&
  891. !(distance_in_nonces_file(MF_CLASSIC_NESTED_NONCE_PATH, filename_buffer))) {
  892. nonces_present = true;
  893. break;
  894. }
  895. }
  896. }
  897. storage_dir_close(dir);
  898. storage_file_free(dir);
  899. furi_record_close(RECORD_STORAGE);
  900. return nonces_present;
  901. }
  902. bool load_mfkey32_nonces(
  903. MfClassicNonceArray* nonce_array,
  904. ProgramState* program_state,
  905. MfClassicDict* system_dict,
  906. bool system_dict_exists,
  907. MfClassicDict* user_dict) {
  908. bool array_loaded = false;
  909. do {
  910. // https://github.com/flipperdevices/flipperzero-firmware/blob/5134f44c09d39344a8747655c0d59864bb574b96/applications/services/storage/filesystem_api_defines.h#L8-L22
  911. if(!buffered_file_stream_open(
  912. nonce_array->stream, MF_CLASSIC_NONCE_PATH, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) {
  913. buffered_file_stream_close(nonce_array->stream);
  914. break;
  915. }
  916. // Check for newline ending
  917. if(!stream_eof(nonce_array->stream)) {
  918. if(!stream_seek(nonce_array->stream, -1, StreamOffsetFromEnd)) break;
  919. uint8_t last_char = 0;
  920. if(stream_read(nonce_array->stream, &last_char, 1) != 1) break;
  921. if(last_char != '\n') {
  922. FURI_LOG_D(TAG, "Adding new line ending");
  923. if(stream_write_char(nonce_array->stream, '\n') != 1) break;
  924. }
  925. if(!stream_rewind(nonce_array->stream)) break;
  926. }
  927. // Read total amount of nonces
  928. FuriString* next_line;
  929. next_line = furi_string_alloc();
  930. while(!(program_state->close_thread_please)) {
  931. if(!stream_read_line(nonce_array->stream, next_line)) {
  932. FURI_LOG_T(TAG, "No nonces left");
  933. break;
  934. }
  935. FURI_LOG_T(
  936. TAG,
  937. "Read line: %s, len: %zu",
  938. furi_string_get_cstr(next_line),
  939. furi_string_size(next_line));
  940. if(!furi_string_start_with_str(next_line, "Sec")) continue;
  941. const char* next_line_cstr = furi_string_get_cstr(next_line);
  942. MfClassicNonce res = {0};
  943. res.attack = mfkey32;
  944. int i = 0;
  945. char* endptr;
  946. for(i = 0; i <= 17; i++) {
  947. if(i != 0) {
  948. next_line_cstr = strchr(next_line_cstr, ' ');
  949. if(next_line_cstr) {
  950. next_line_cstr++;
  951. } else {
  952. break;
  953. }
  954. }
  955. unsigned long value = strtoul(next_line_cstr, &endptr, 16);
  956. switch(i) {
  957. case 5:
  958. res.uid = value;
  959. break;
  960. case 7:
  961. res.nt0 = value;
  962. break;
  963. case 9:
  964. res.nr0_enc = value;
  965. break;
  966. case 11:
  967. res.ar0_enc = value;
  968. break;
  969. case 13:
  970. res.nt1 = value;
  971. break;
  972. case 15:
  973. res.nr1_enc = value;
  974. break;
  975. case 17:
  976. res.ar1_enc = value;
  977. break;
  978. default:
  979. break; // Do nothing
  980. }
  981. next_line_cstr = endptr;
  982. }
  983. res.p64 = prng_successor(res.nt0, 64);
  984. res.p64b = prng_successor(res.nt1, 64);
  985. res.uid_xor_nt0 = res.uid ^ res.nt0;
  986. res.uid_xor_nt1 = res.uid ^ res.nt1;
  987. (program_state->total)++;
  988. if((system_dict_exists && napi_key_already_found_for_nonce(system_dict, &res)) ||
  989. (napi_key_already_found_for_nonce(user_dict, &res))) {
  990. (program_state->cracked)++;
  991. (program_state->num_completed)++;
  992. continue;
  993. }
  994. FURI_LOG_I(TAG, "No key found for %8lx %8lx", res.uid, res.ar1_enc);
  995. // TODO: Refactor
  996. nonce_array->remaining_nonce_array = realloc( //-V701
  997. nonce_array->remaining_nonce_array,
  998. sizeof(MfClassicNonce) * ((nonce_array->remaining_nonces) + 1));
  999. nonce_array->remaining_nonces++;
  1000. nonce_array->remaining_nonce_array[(nonce_array->remaining_nonces) - 1] = res;
  1001. nonce_array->total_nonces++;
  1002. }
  1003. furi_string_free(next_line);
  1004. buffered_file_stream_close(nonce_array->stream);
  1005. //stream_free(nonce_array->stream);
  1006. array_loaded = true;
  1007. FURI_LOG_I(TAG, "Loaded %lu Mfkey32 nonces", nonce_array->total_nonces);
  1008. } while(false);
  1009. return array_loaded;
  1010. }
  1011. int binaryStringToInt(const char* binStr) {
  1012. int result = 0;
  1013. while(*binStr) {
  1014. result <<= 1;
  1015. if(*binStr == '1') {
  1016. result |= 1;
  1017. }
  1018. binStr++;
  1019. }
  1020. return result;
  1021. }
  1022. bool load_nested_nonces(
  1023. MfClassicNonceArray* nonce_array,
  1024. ProgramState* program_state,
  1025. MfClassicDict* system_dict,
  1026. bool system_dict_exists,
  1027. MfClassicDict* user_dict) {
  1028. Storage* storage = furi_record_open(RECORD_STORAGE);
  1029. File* dir = storage_file_alloc(storage);
  1030. char filename_buffer[MAX_NAME_LEN];
  1031. FileInfo file_info;
  1032. FuriString* next_line = furi_string_alloc();
  1033. if(!storage_dir_open(dir, MF_CLASSIC_NESTED_NONCE_PATH)) {
  1034. storage_dir_close(dir);
  1035. storage_file_free(dir);
  1036. furi_record_close(RECORD_STORAGE);
  1037. furi_string_free(next_line);
  1038. return false;
  1039. }
  1040. while(storage_dir_read(dir, &file_info, filename_buffer, MAX_NAME_LEN)) {
  1041. if(!(file_info.flags & FSF_DIRECTORY) && strstr(filename_buffer, ".nonces") &&
  1042. !(distance_in_nonces_file(MF_CLASSIC_NESTED_NONCE_PATH, filename_buffer))) {
  1043. char full_path[MAX_PATH_LEN];
  1044. snprintf(
  1045. full_path,
  1046. sizeof(full_path),
  1047. "%s/%s",
  1048. MF_CLASSIC_NESTED_NONCE_PATH,
  1049. filename_buffer);
  1050. // TODO: We should only need READ_WRITE here if we plan on adding a newline to the end of the file if has none
  1051. if(!buffered_file_stream_open(
  1052. nonce_array->stream, full_path, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) {
  1053. buffered_file_stream_close(nonce_array->stream);
  1054. continue;
  1055. }
  1056. while(stream_read_line(nonce_array->stream, next_line)) {
  1057. if(furi_string_search_str(next_line, "Nested:") != FURI_STRING_FAILURE) {
  1058. MfClassicNonce res = {0};
  1059. res.attack = static_nested;
  1060. int parsed = sscanf(
  1061. furi_string_get_cstr(next_line),
  1062. "Nested: %*s %*s cuid 0x%" PRIx32 " nt0 0x%" PRIx32 " ks0 0x%" PRIx32
  1063. " par0 %4[01] nt1 0x%" PRIx32 " ks1 0x%" PRIx32 " par1 %4[01]",
  1064. &res.uid,
  1065. &res.nt0,
  1066. &res.ks1_1_enc,
  1067. res.par_1_str,
  1068. &res.nt1,
  1069. &res.ks1_2_enc,
  1070. res.par_2_str);
  1071. if(parsed != 7) continue;
  1072. res.par_1 = binaryStringToInt(res.par_1_str);
  1073. res.par_2 = binaryStringToInt(res.par_2_str);
  1074. res.uid_xor_nt0 = res.uid ^ res.nt0;
  1075. res.uid_xor_nt1 = res.uid ^ res.nt1;
  1076. (program_state->total)++;
  1077. if((system_dict_exists &&
  1078. napi_key_already_found_for_nonce(system_dict, &res)) ||
  1079. (napi_key_already_found_for_nonce(user_dict, &res))) {
  1080. (program_state->cracked)++;
  1081. (program_state->num_completed)++;
  1082. continue;
  1083. }
  1084. nonce_array->remaining_nonce_array = realloc(
  1085. nonce_array->remaining_nonce_array,
  1086. sizeof(MfClassicNonce) * (nonce_array->remaining_nonces + 1));
  1087. nonce_array->remaining_nonce_array[nonce_array->remaining_nonces] = res;
  1088. nonce_array->remaining_nonces++;
  1089. nonce_array->total_nonces++;
  1090. }
  1091. }
  1092. buffered_file_stream_close(nonce_array->stream);
  1093. }
  1094. }
  1095. storage_dir_close(dir);
  1096. storage_file_free(dir);
  1097. furi_record_close(RECORD_STORAGE);
  1098. furi_string_free(next_line);
  1099. FURI_LOG_I(TAG, "Loaded %lu Static Nested nonces", nonce_array->total_nonces);
  1100. return true;
  1101. }
  1102. MfClassicNonceArray* napi_mf_classic_nonce_array_alloc(
  1103. MfClassicDict* system_dict,
  1104. bool system_dict_exists,
  1105. MfClassicDict* user_dict,
  1106. ProgramState* program_state) {
  1107. MfClassicNonceArray* nonce_array = malloc(sizeof(MfClassicNonceArray));
  1108. MfClassicNonce* remaining_nonce_array_init = malloc(sizeof(MfClassicNonce) * 1);
  1109. nonce_array->remaining_nonce_array = remaining_nonce_array_init;
  1110. Storage* storage = furi_record_open(RECORD_STORAGE);
  1111. nonce_array->stream = buffered_file_stream_alloc(storage);
  1112. furi_record_close(RECORD_STORAGE);
  1113. bool array_loaded = false;
  1114. if(program_state->mfkey32_present) {
  1115. array_loaded = load_mfkey32_nonces(
  1116. nonce_array, program_state, system_dict, system_dict_exists, user_dict);
  1117. }
  1118. if(program_state->nested_present) {
  1119. array_loaded |= load_nested_nonces(
  1120. nonce_array, program_state, system_dict, system_dict_exists, user_dict);
  1121. }
  1122. if(!array_loaded) {
  1123. free(nonce_array);
  1124. nonce_array = NULL;
  1125. }
  1126. return nonce_array;
  1127. }
  1128. void napi_mf_classic_nonce_array_free(MfClassicNonceArray* nonce_array) {
  1129. // TODO: Track free state at the time this is called to ensure double free does not happen
  1130. furi_assert(nonce_array);
  1131. furi_assert(nonce_array->stream);
  1132. buffered_file_stream_close(nonce_array->stream);
  1133. stream_free(nonce_array->stream);
  1134. free(nonce_array);
  1135. }
  1136. static void finished_beep() {
  1137. // Beep to indicate completion
  1138. NotificationApp* notification = furi_record_open("notification");
  1139. notification_message(notification, &sequence_audiovisual_alert);
  1140. notification_message(notification, &sequence_display_backlight_on);
  1141. furi_record_close("notification");
  1142. }
  1143. void mfkey(ProgramState* program_state) {
  1144. uint64_t found_key; // recovered key
  1145. size_t keyarray_size = 0;
  1146. uint64_t* keyarray = malloc(sizeof(uint64_t) * 1);
  1147. uint32_t i = 0, j = 0;
  1148. // Check for nonces
  1149. program_state->mfkey32_present = napi_mf_classic_nonces_check_presence();
  1150. program_state->nested_present = napi_mf_classic_nested_nonces_check_presence();
  1151. if(!(program_state->mfkey32_present) && !(program_state->nested_present)) {
  1152. program_state->err = MissingNonces;
  1153. program_state->mfkey_state = Error;
  1154. free(keyarray);
  1155. return;
  1156. }
  1157. // Read dictionaries (optional)
  1158. MfClassicDict* system_dict = {0};
  1159. bool system_dict_exists = napi_mf_classic_dict_check_presence(MfClassicDictTypeSystem);
  1160. MfClassicDict* user_dict = {0};
  1161. bool user_dict_exists = napi_mf_classic_dict_check_presence(MfClassicDictTypeUser);
  1162. uint32_t total_dict_keys = 0;
  1163. if(system_dict_exists) {
  1164. system_dict = napi_mf_classic_dict_alloc(MfClassicDictTypeSystem);
  1165. total_dict_keys += napi_mf_classic_dict_get_total_keys(system_dict);
  1166. }
  1167. user_dict = napi_mf_classic_dict_alloc(MfClassicDictTypeUser);
  1168. if(user_dict_exists) {
  1169. total_dict_keys += napi_mf_classic_dict_get_total_keys(user_dict);
  1170. }
  1171. user_dict_exists = true;
  1172. program_state->dict_count = total_dict_keys;
  1173. program_state->mfkey_state = DictionaryAttack;
  1174. // Read nonces
  1175. MfClassicNonceArray* nonce_arr;
  1176. nonce_arr = napi_mf_classic_nonce_array_alloc(
  1177. system_dict, system_dict_exists, user_dict, program_state);
  1178. if(system_dict_exists) {
  1179. napi_mf_classic_dict_free(system_dict);
  1180. }
  1181. if(nonce_arr->total_nonces == 0) {
  1182. // Nothing to crack
  1183. program_state->err = ZeroNonces;
  1184. program_state->mfkey_state = Error;
  1185. napi_mf_classic_nonce_array_free(nonce_arr);
  1186. napi_mf_classic_dict_free(user_dict);
  1187. free(keyarray);
  1188. return;
  1189. }
  1190. if(memmgr_get_free_heap() < MIN_RAM) {
  1191. // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed
  1192. eta_round_time *= 2;
  1193. eta_total_time *= 2;
  1194. MSB_LIMIT /= 2;
  1195. }
  1196. program_state->mfkey_state = MFKeyAttack;
  1197. // TODO: Work backwards on this array and free memory
  1198. for(i = 0; i < nonce_arr->total_nonces; i++) {
  1199. MfClassicNonce next_nonce = nonce_arr->remaining_nonce_array[i];
  1200. if(key_already_found_for_nonce(keyarray, keyarray_size, &next_nonce)) {
  1201. nonce_arr->remaining_nonces--;
  1202. (program_state->cracked)++;
  1203. (program_state->num_completed)++;
  1204. continue;
  1205. }
  1206. FURI_LOG_I(TAG, "Beginning recovery for %8lx", next_nonce.uid);
  1207. if(next_nonce.attack == mfkey32) {
  1208. if(!recover(&next_nonce, next_nonce.ar0_enc ^ next_nonce.p64, 0, program_state)) {
  1209. if(program_state->close_thread_please) {
  1210. break;
  1211. }
  1212. // No key found in recover()
  1213. (program_state->num_completed)++;
  1214. continue;
  1215. }
  1216. } else if(next_nonce.attack == static_nested) {
  1217. if(!recover(
  1218. &next_nonce,
  1219. next_nonce.ks1_2_enc,
  1220. next_nonce.nt1 ^ next_nonce.uid,
  1221. program_state)) {
  1222. if(program_state->close_thread_please) {
  1223. break;
  1224. }
  1225. // No key found in recover()
  1226. (program_state->num_completed)++;
  1227. continue;
  1228. }
  1229. }
  1230. (program_state->cracked)++;
  1231. (program_state->num_completed)++;
  1232. found_key = next_nonce.key;
  1233. bool already_found = false;
  1234. for(j = 0; j < keyarray_size; j++) {
  1235. if(keyarray[j] == found_key) {
  1236. already_found = true;
  1237. break;
  1238. }
  1239. }
  1240. if(already_found == false) {
  1241. // New key
  1242. keyarray = realloc(keyarray, sizeof(uint64_t) * (keyarray_size + 1)); //-V701
  1243. keyarray_size += 1;
  1244. keyarray[keyarray_size - 1] = found_key;
  1245. (program_state->unique_cracked)++;
  1246. }
  1247. }
  1248. // TODO: Update display to show all keys were found
  1249. // TODO: Prepend found key(s) to user dictionary file
  1250. //FURI_LOG_I(TAG, "Unique keys found:");
  1251. for(i = 0; i < keyarray_size; i++) {
  1252. //FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]);
  1253. FuriString* temp_key = furi_string_alloc();
  1254. furi_string_cat_printf(temp_key, "%012" PRIX64, keyarray[i]);
  1255. napi_mf_classic_dict_add_key_str(user_dict, temp_key);
  1256. furi_string_free(temp_key);
  1257. }
  1258. if(keyarray_size > 0) {
  1259. // TODO: Should we use DolphinDeedNfcMfcAdd?
  1260. dolphin_deed(DolphinDeedNfcMfcAdd);
  1261. }
  1262. napi_mf_classic_nonce_array_free(nonce_arr);
  1263. napi_mf_classic_dict_free(user_dict);
  1264. free(keyarray);
  1265. //FURI_LOG_I(TAG, "mfkey function completed normally"); // DEBUG
  1266. program_state->mfkey_state = Complete;
  1267. // No need to alert the user if they asked it to stop
  1268. if(!(program_state->close_thread_please)) {
  1269. finished_beep();
  1270. }
  1271. return;
  1272. }
  1273. // Screen is 128x64 px
  1274. static void render_callback(Canvas* const canvas, void* ctx) {
  1275. furi_assert(ctx);
  1276. ProgramState* program_state = ctx;
  1277. furi_mutex_acquire(program_state->mutex, FuriWaitForever);
  1278. char draw_str[44] = {};
  1279. canvas_clear(canvas);
  1280. canvas_draw_frame(canvas, 0, 0, 128, 64);
  1281. canvas_draw_frame(canvas, 0, 15, 128, 64);
  1282. canvas_set_font(canvas, FontPrimary);
  1283. canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "MFKey");
  1284. canvas_draw_icon(canvas, 114, 4, &I_mfkey);
  1285. if(program_state->is_thread_running && program_state->mfkey_state == MFKeyAttack) {
  1286. float eta_round = (float)1 - ((float)program_state->eta_round / (float)eta_round_time);
  1287. float eta_total = (float)1 - ((float)program_state->eta_total / (float)eta_total_time);
  1288. float progress = (float)program_state->num_completed / (float)program_state->total;
  1289. if(eta_round < 0) {
  1290. // Round ETA miscalculated
  1291. eta_round = 1;
  1292. program_state->eta_round = 0;
  1293. }
  1294. if(eta_total < 0) {
  1295. // Total ETA miscalculated
  1296. eta_total = 1;
  1297. program_state->eta_total = 0;
  1298. }
  1299. canvas_set_font(canvas, FontSecondary);
  1300. snprintf(
  1301. draw_str,
  1302. sizeof(draw_str),
  1303. "Cracking: %d/%d - in prog.",
  1304. program_state->num_completed,
  1305. program_state->total);
  1306. elements_progress_bar_with_text(canvas, 5, 18, 118, progress, draw_str);
  1307. snprintf(
  1308. draw_str,
  1309. sizeof(draw_str),
  1310. "Round: %d/%d - ETA %02d Sec",
  1311. (program_state->search) + 1, // Zero indexed
  1312. 256 / MSB_LIMIT,
  1313. program_state->eta_round);
  1314. elements_progress_bar_with_text(canvas, 5, 31, 118, eta_round, draw_str);
  1315. snprintf(draw_str, sizeof(draw_str), "Total ETA %03d Sec", program_state->eta_total);
  1316. elements_progress_bar_with_text(canvas, 5, 44, 118, eta_total, draw_str);
  1317. } else if(program_state->is_thread_running && program_state->mfkey_state == DictionaryAttack) {
  1318. canvas_set_font(canvas, FontSecondary);
  1319. snprintf(
  1320. draw_str, sizeof(draw_str), "Dict solves: %d (in progress)", program_state->cracked);
  1321. canvas_draw_str_aligned(canvas, 10, 18, AlignLeft, AlignTop, draw_str);
  1322. snprintf(draw_str, sizeof(draw_str), "Keys in dict: %d", program_state->dict_count);
  1323. canvas_draw_str_aligned(canvas, 26, 28, AlignLeft, AlignTop, draw_str);
  1324. } else if(program_state->mfkey_state == Complete) {
  1325. // TODO: Scrollable list view to see cracked keys if user presses down
  1326. elements_progress_bar_with_text(canvas, 5, 18, 118, 1, draw_str);
  1327. canvas_set_font(canvas, FontSecondary);
  1328. snprintf(draw_str, sizeof(draw_str), "Complete");
  1329. canvas_draw_str_aligned(canvas, 40, 31, AlignLeft, AlignTop, draw_str);
  1330. snprintf(
  1331. draw_str,
  1332. sizeof(draw_str),
  1333. "Keys added to user dict: %d",
  1334. program_state->unique_cracked);
  1335. canvas_draw_str_aligned(canvas, 10, 41, AlignLeft, AlignTop, draw_str);
  1336. } else if(program_state->mfkey_state == Ready) {
  1337. canvas_set_font(canvas, FontSecondary);
  1338. canvas_draw_str_aligned(canvas, 50, 30, AlignLeft, AlignTop, "Ready");
  1339. elements_button_center(canvas, "Start");
  1340. elements_button_right(canvas, "Help");
  1341. } else if(program_state->mfkey_state == Help) {
  1342. canvas_set_font(canvas, FontSecondary);
  1343. canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces using Detect");
  1344. canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "Reader or FlipperNested.");
  1345. canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "Devs: noproto, AG, ALiberty");
  1346. canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "Thanks: bettse, Foxushka");
  1347. } else if(program_state->mfkey_state == Error) {
  1348. canvas_draw_str_aligned(canvas, 50, 25, AlignLeft, AlignTop, "Error");
  1349. canvas_set_font(canvas, FontSecondary);
  1350. if(program_state->err == MissingNonces) {
  1351. canvas_draw_str_aligned(canvas, 25, 36, AlignLeft, AlignTop, "No nonces found");
  1352. } else if(program_state->err == ZeroNonces) {
  1353. canvas_draw_str_aligned(canvas, 15, 36, AlignLeft, AlignTop, "Nonces already cracked");
  1354. } else {
  1355. // Unhandled error
  1356. }
  1357. } else {
  1358. // Unhandled program state
  1359. }
  1360. furi_mutex_release(program_state->mutex);
  1361. }
  1362. static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) {
  1363. furi_assert(event_queue);
  1364. PluginEvent event = {.type = EventTypeKey, .input = *input_event};
  1365. furi_message_queue_put(event_queue, &event, FuriWaitForever);
  1366. }
  1367. static void mfkey_state_init(ProgramState* program_state) {
  1368. program_state->is_thread_running = false;
  1369. program_state->mfkey_state = Ready;
  1370. program_state->cracked = 0;
  1371. program_state->unique_cracked = 0;
  1372. program_state->num_completed = 0;
  1373. program_state->total = 0;
  1374. program_state->dict_count = 0;
  1375. }
  1376. // Entrypoint for worker thread
  1377. static int32_t mfkey_worker_thread(void* ctx) {
  1378. ProgramState* program_state = ctx;
  1379. program_state->is_thread_running = true;
  1380. program_state->mfkey_state = Initializing;
  1381. //FURI_LOG_I(TAG, "Hello from the mfkey worker thread"); // DEBUG
  1382. mfkey(program_state);
  1383. program_state->is_thread_running = false;
  1384. return 0;
  1385. }
  1386. void start_mfkey_thread(ProgramState* program_state) {
  1387. if(!program_state->is_thread_running) {
  1388. furi_thread_start(program_state->mfkeythread);
  1389. }
  1390. }
  1391. int32_t mfkey_main() {
  1392. FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent));
  1393. ProgramState* program_state = malloc(sizeof(ProgramState));
  1394. mfkey_state_init(program_state);
  1395. program_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
  1396. if(!program_state->mutex) {
  1397. FURI_LOG_E(TAG, "cannot create mutex\r\n");
  1398. free(program_state);
  1399. return 255;
  1400. }
  1401. // Set system callbacks
  1402. ViewPort* view_port = view_port_alloc();
  1403. view_port_draw_callback_set(view_port, render_callback, program_state);
  1404. view_port_input_callback_set(view_port, input_callback, event_queue);
  1405. // Open GUI and register view_port
  1406. Gui* gui = furi_record_open(RECORD_GUI);
  1407. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  1408. program_state->mfkeythread = furi_thread_alloc();
  1409. furi_thread_set_name(program_state->mfkeythread, "MFKey Worker");
  1410. furi_thread_set_stack_size(program_state->mfkeythread, 2048);
  1411. furi_thread_set_context(program_state->mfkeythread, program_state);
  1412. furi_thread_set_callback(program_state->mfkeythread, mfkey_worker_thread);
  1413. PluginEvent event;
  1414. for(bool main_loop = true; main_loop;) {
  1415. FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100);
  1416. furi_mutex_acquire(program_state->mutex, FuriWaitForever);
  1417. if(event_status == FuriStatusOk) {
  1418. // press events
  1419. if(event.type == EventTypeKey) {
  1420. if(event.input.type == InputTypePress) {
  1421. switch(event.input.key) {
  1422. case InputKeyUp:
  1423. break;
  1424. case InputKeyDown:
  1425. break;
  1426. case InputKeyRight:
  1427. if(!program_state->is_thread_running &&
  1428. program_state->mfkey_state == Ready) {
  1429. program_state->mfkey_state = Help;
  1430. view_port_update(view_port);
  1431. }
  1432. break;
  1433. case InputKeyLeft:
  1434. break;
  1435. case InputKeyOk:
  1436. if(!program_state->is_thread_running &&
  1437. program_state->mfkey_state == Ready) {
  1438. start_mfkey_thread(program_state);
  1439. view_port_update(view_port);
  1440. }
  1441. break;
  1442. case InputKeyBack:
  1443. if(!program_state->is_thread_running &&
  1444. program_state->mfkey_state == Help) {
  1445. program_state->mfkey_state = Ready;
  1446. view_port_update(view_port);
  1447. } else {
  1448. program_state->close_thread_please = true;
  1449. if(program_state->is_thread_running && program_state->mfkeythread) {
  1450. // Wait until thread is finished
  1451. furi_thread_join(program_state->mfkeythread);
  1452. }
  1453. program_state->close_thread_please = false;
  1454. main_loop = false;
  1455. }
  1456. break;
  1457. default:
  1458. break;
  1459. }
  1460. }
  1461. }
  1462. }
  1463. view_port_update(view_port);
  1464. furi_mutex_release(program_state->mutex);
  1465. }
  1466. furi_thread_free(program_state->mfkeythread);
  1467. view_port_enabled_set(view_port, false);
  1468. gui_remove_view_port(gui, view_port);
  1469. furi_record_close("gui");
  1470. view_port_free(view_port);
  1471. furi_message_queue_free(event_queue);
  1472. furi_mutex_free(program_state->mutex);
  1473. free(program_state);
  1474. return 0;
  1475. }