init_plugin.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. // TODO: Remove includes that are not needed
  2. #include <furi_hal.h>
  3. #include <gui/gui.h>
  4. #include <gui/elements.h>
  5. #include <inttypes.h>
  6. #include <toolbox/keys_dict.h>
  7. #include <toolbox/stream/buffered_file_stream.h>
  8. #include <notification/notification_messages.h>
  9. #include <nfc/protocols/mf_classic/mf_classic.h>
  10. #include "mfkey.h"
  11. #include "common.h"
  12. #include "crypto1.h"
  13. #include "plugin_interface.h"
  14. #include <flipper_application/flipper_application.h>
  15. // TODO: Remove defines that are not needed
  16. #define KEYS_DICT_SYSTEM_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc")
  17. #define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc")
  18. #define MF_CLASSIC_NONCE_PATH EXT_PATH("nfc/.mfkey32.log")
  19. #define MF_CLASSIC_NESTED_NONCE_PATH EXT_PATH("nfc/.nested")
  20. #define TAG "MFKey"
  21. #define MAX_NAME_LEN 32
  22. #define MAX_PATH_LEN 64
  23. #define LF_POLY_ODD (0x29CE5C)
  24. #define LF_POLY_EVEN (0x870804)
  25. #define CONST_M1_1 (LF_POLY_EVEN << 1 | 1)
  26. #define CONST_M2_1 (LF_POLY_ODD << 1)
  27. #define CONST_M1_2 (LF_POLY_ODD)
  28. #define CONST_M2_2 (LF_POLY_EVEN << 1 | 1)
  29. #define BIT(x, n) ((x) >> (n) & 1)
  30. #define BEBIT(x, n) BIT(x, (n) ^ 24)
  31. #define SWAPENDIAN(x) \
  32. ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16)
  33. /* Main methods */
  34. bool key_already_found_for_nonce_in_dict(KeysDict* dict, MfClassicNonce* nonce) {
  35. bool found = false;
  36. uint8_t key_bytes[sizeof(MfClassicKey)];
  37. keys_dict_rewind(dict);
  38. while(keys_dict_get_next_key(dict, key_bytes, sizeof(MfClassicKey))) {
  39. uint64_t k = napi_nfc_util_bytes2num(key_bytes, sizeof(MfClassicKey));
  40. struct Crypto1State temp = {0, 0};
  41. for(int i = 0; i < 24; i++) {
  42. (&temp)->odd |= (BIT(k, 2 * i + 1) << (i ^ 3));
  43. (&temp)->even |= (BIT(k, 2 * i) << (i ^ 3));
  44. }
  45. if(nonce->attack == mfkey32) {
  46. crypt_word_noret(&temp, nonce->uid_xor_nt1, 0);
  47. crypt_word_noret(&temp, nonce->nr1_enc, 1);
  48. if(nonce->ar1_enc == (crypt_word(&temp) ^ nonce->p64b)) {
  49. found = true;
  50. break;
  51. }
  52. } else if(nonce->attack == static_nested) {
  53. uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0);
  54. if(nonce->ks1_1_enc == expected_ks1) {
  55. found = true;
  56. break;
  57. }
  58. }
  59. }
  60. return found;
  61. }
  62. bool napi_mf_classic_mfkey32_nonces_check_presence() {
  63. Storage* storage = furi_record_open(RECORD_STORAGE);
  64. bool nonces_present = storage_common_stat(storage, MF_CLASSIC_NONCE_PATH, NULL) == FSE_OK;
  65. furi_record_close(RECORD_STORAGE);
  66. return nonces_present;
  67. }
  68. bool distance_in_nonces_file(const char* file_path, const char* file_name) {
  69. char full_path[MAX_PATH_LEN];
  70. snprintf(full_path, sizeof(full_path), "%s/%s", file_path, file_name);
  71. bool distance_present = false;
  72. Storage* storage = furi_record_open(RECORD_STORAGE);
  73. Stream* file_stream = buffered_file_stream_alloc(storage);
  74. FuriString* line_str;
  75. line_str = furi_string_alloc();
  76. if(buffered_file_stream_open(file_stream, full_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
  77. while(true) {
  78. if(!stream_read_line(file_stream, line_str)) break;
  79. if(furi_string_search_str(line_str, "distance") != FURI_STRING_FAILURE) {
  80. distance_present = true;
  81. break;
  82. }
  83. }
  84. }
  85. buffered_file_stream_close(file_stream);
  86. stream_free(file_stream);
  87. furi_string_free(line_str);
  88. furi_record_close(RECORD_STORAGE);
  89. return distance_present;
  90. }
  91. bool napi_mf_classic_nested_nonces_check_presence() {
  92. Storage* storage = furi_record_open(RECORD_STORAGE);
  93. if(!(storage_dir_exists(storage, MF_CLASSIC_NESTED_NONCE_PATH))) {
  94. furi_record_close(RECORD_STORAGE);
  95. return false;
  96. }
  97. bool nonces_present = false;
  98. File* dir = storage_file_alloc(storage);
  99. char filename_buffer[MAX_NAME_LEN];
  100. FileInfo file_info;
  101. if(storage_dir_open(dir, MF_CLASSIC_NESTED_NONCE_PATH)) {
  102. while(storage_dir_read(dir, &file_info, filename_buffer, MAX_NAME_LEN)) {
  103. // We only care about Static Nested files
  104. if(!(file_info.flags & FSF_DIRECTORY) && strstr(filename_buffer, ".nonces") &&
  105. !(distance_in_nonces_file(MF_CLASSIC_NESTED_NONCE_PATH, filename_buffer))) {
  106. nonces_present = true;
  107. break;
  108. }
  109. }
  110. }
  111. storage_dir_close(dir);
  112. storage_file_free(dir);
  113. furi_record_close(RECORD_STORAGE);
  114. return nonces_present;
  115. }
  116. int binaryStringToInt(const char* binStr) {
  117. int result = 0;
  118. while(*binStr) {
  119. result <<= 1;
  120. if(*binStr == '1') {
  121. result |= 1;
  122. }
  123. binStr++;
  124. }
  125. return result;
  126. }
  127. bool load_mfkey32_nonces(
  128. MfClassicNonceArray* nonce_array,
  129. ProgramState* program_state,
  130. KeysDict* system_dict,
  131. bool system_dict_exists,
  132. KeysDict* user_dict) {
  133. bool array_loaded = false;
  134. do {
  135. // https://github.com/flipperdevices/flipperzero-firmware/blob/5134f44c09d39344a8747655c0d59864bb574b96/applications/services/storage/filesystem_api_defines.h#L8-L22
  136. if(!buffered_file_stream_open(
  137. nonce_array->stream, MF_CLASSIC_NONCE_PATH, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) {
  138. buffered_file_stream_close(nonce_array->stream);
  139. break;
  140. }
  141. // Check for newline ending
  142. if(!stream_eof(nonce_array->stream)) {
  143. if(!stream_seek(nonce_array->stream, -1, StreamOffsetFromEnd)) break;
  144. uint8_t last_char = 0;
  145. if(stream_read(nonce_array->stream, &last_char, 1) != 1) break;
  146. if(last_char != '\n') {
  147. //FURI_LOG_D(TAG, "Adding new line ending");
  148. if(stream_write_char(nonce_array->stream, '\n') != 1) break;
  149. }
  150. if(!stream_rewind(nonce_array->stream)) break;
  151. }
  152. // Read total amount of nonces
  153. FuriString* next_line;
  154. next_line = furi_string_alloc();
  155. while(!(program_state->close_thread_please)) {
  156. if(!stream_read_line(nonce_array->stream, next_line)) {
  157. //FURI_LOG_T(TAG, "No nonces left");
  158. break;
  159. }
  160. /*
  161. FURI_LOG_T(
  162. TAG,
  163. "Read line: %s, len: %zu",
  164. furi_string_get_cstr(next_line),
  165. furi_string_size(next_line));
  166. */
  167. if(!furi_string_start_with_str(next_line, "Sec")) continue;
  168. const char* next_line_cstr = furi_string_get_cstr(next_line);
  169. MfClassicNonce res = {0};
  170. res.attack = mfkey32;
  171. int i = 0;
  172. char* endptr;
  173. for(i = 0; i <= 17; i++) {
  174. if(i != 0) {
  175. next_line_cstr = strchr(next_line_cstr, ' ');
  176. if(next_line_cstr) {
  177. next_line_cstr++;
  178. } else {
  179. break;
  180. }
  181. }
  182. unsigned long value = strtoul(next_line_cstr, &endptr, 16);
  183. switch(i) {
  184. case 5:
  185. res.uid = value;
  186. break;
  187. case 7:
  188. res.nt0 = value;
  189. break;
  190. case 9:
  191. res.nr0_enc = value;
  192. break;
  193. case 11:
  194. res.ar0_enc = value;
  195. break;
  196. case 13:
  197. res.nt1 = value;
  198. break;
  199. case 15:
  200. res.nr1_enc = value;
  201. break;
  202. case 17:
  203. res.ar1_enc = value;
  204. break;
  205. default:
  206. break; // Do nothing
  207. }
  208. next_line_cstr = endptr;
  209. }
  210. res.p64 = prng_successor(res.nt0, 64);
  211. res.p64b = prng_successor(res.nt1, 64);
  212. res.uid_xor_nt0 = res.uid ^ res.nt0;
  213. res.uid_xor_nt1 = res.uid ^ res.nt1;
  214. (program_state->total)++;
  215. if((system_dict_exists && key_already_found_for_nonce_in_dict(system_dict, &res)) ||
  216. (key_already_found_for_nonce_in_dict(user_dict, &res))) {
  217. (program_state->cracked)++;
  218. (program_state->num_completed)++;
  219. continue;
  220. }
  221. //FURI_LOG_I(TAG, "No key found for %8lx %8lx", res.uid, res.ar1_enc);
  222. // TODO: Refactor
  223. nonce_array->remaining_nonce_array = realloc( //-V701
  224. nonce_array->remaining_nonce_array,
  225. sizeof(MfClassicNonce) * ((nonce_array->remaining_nonces) + 1));
  226. nonce_array->remaining_nonces++;
  227. nonce_array->remaining_nonce_array[(nonce_array->remaining_nonces) - 1] = res;
  228. nonce_array->total_nonces++;
  229. }
  230. furi_string_free(next_line);
  231. buffered_file_stream_close(nonce_array->stream);
  232. //stream_free(nonce_array->stream);
  233. array_loaded = true;
  234. //FURI_LOG_I(TAG, "Loaded %lu Mfkey32 nonces", nonce_array->total_nonces);
  235. } while(false);
  236. return array_loaded;
  237. }
  238. bool load_nested_nonces(
  239. MfClassicNonceArray* nonce_array,
  240. ProgramState* program_state,
  241. KeysDict* system_dict,
  242. bool system_dict_exists,
  243. KeysDict* user_dict) {
  244. Storage* storage = furi_record_open(RECORD_STORAGE);
  245. File* dir = storage_file_alloc(storage);
  246. char filename_buffer[MAX_NAME_LEN];
  247. FileInfo file_info;
  248. FuriString* next_line = furi_string_alloc();
  249. if(!storage_dir_open(dir, MF_CLASSIC_NESTED_NONCE_PATH)) {
  250. storage_dir_close(dir);
  251. storage_file_free(dir);
  252. furi_record_close(RECORD_STORAGE);
  253. furi_string_free(next_line);
  254. return false;
  255. }
  256. while(storage_dir_read(dir, &file_info, filename_buffer, MAX_NAME_LEN)) {
  257. if(!(file_info.flags & FSF_DIRECTORY) && strstr(filename_buffer, ".nonces") &&
  258. !(distance_in_nonces_file(MF_CLASSIC_NESTED_NONCE_PATH, filename_buffer))) {
  259. char full_path[MAX_PATH_LEN];
  260. snprintf(
  261. full_path,
  262. sizeof(full_path),
  263. "%s/%s",
  264. MF_CLASSIC_NESTED_NONCE_PATH,
  265. filename_buffer);
  266. // TODO: We should only need READ_WRITE here if we plan on adding a newline to the end of the file if has none
  267. if(!buffered_file_stream_open(
  268. nonce_array->stream, full_path, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) {
  269. buffered_file_stream_close(nonce_array->stream);
  270. continue;
  271. }
  272. while(stream_read_line(nonce_array->stream, next_line)) {
  273. if(furi_string_search_str(next_line, "Nested:") != FURI_STRING_FAILURE) {
  274. MfClassicNonce res = {0};
  275. res.attack = static_nested;
  276. int parsed = sscanf(
  277. furi_string_get_cstr(next_line),
  278. "Nested: %*s %*s cuid 0x%" PRIx32 " nt0 0x%" PRIx32 " ks0 0x%" PRIx32
  279. " par0 %4[01] nt1 0x%" PRIx32 " ks1 0x%" PRIx32 " par1 %4[01]",
  280. &res.uid,
  281. &res.nt0,
  282. &res.ks1_1_enc,
  283. res.par_1_str,
  284. &res.nt1,
  285. &res.ks1_2_enc,
  286. res.par_2_str);
  287. if(parsed != 7) continue;
  288. res.par_1 = binaryStringToInt(res.par_1_str);
  289. res.par_2 = binaryStringToInt(res.par_2_str);
  290. res.uid_xor_nt0 = res.uid ^ res.nt0;
  291. res.uid_xor_nt1 = res.uid ^ res.nt1;
  292. (program_state->total)++;
  293. if((system_dict_exists &&
  294. key_already_found_for_nonce_in_dict(system_dict, &res)) ||
  295. (key_already_found_for_nonce_in_dict(user_dict, &res))) {
  296. (program_state->cracked)++;
  297. (program_state->num_completed)++;
  298. continue;
  299. }
  300. nonce_array->remaining_nonce_array = realloc(
  301. nonce_array->remaining_nonce_array,
  302. sizeof(MfClassicNonce) * (nonce_array->remaining_nonces + 1));
  303. nonce_array->remaining_nonce_array[nonce_array->remaining_nonces] = res;
  304. nonce_array->remaining_nonces++;
  305. nonce_array->total_nonces++;
  306. }
  307. }
  308. buffered_file_stream_close(nonce_array->stream);
  309. }
  310. }
  311. storage_dir_close(dir);
  312. storage_file_free(dir);
  313. furi_record_close(RECORD_STORAGE);
  314. furi_string_free(next_line);
  315. //FURI_LOG_I(TAG, "Loaded %lu Static Nested nonces", nonce_array->total_nonces);
  316. return true;
  317. }
  318. MfClassicNonceArray* napi_mf_classic_nonce_array_alloc(
  319. KeysDict* system_dict,
  320. bool system_dict_exists,
  321. KeysDict* user_dict,
  322. ProgramState* program_state) {
  323. MfClassicNonceArray* nonce_array = malloc(sizeof(MfClassicNonceArray));
  324. MfClassicNonce* remaining_nonce_array_init = malloc(sizeof(MfClassicNonce) * 1);
  325. nonce_array->remaining_nonce_array = remaining_nonce_array_init;
  326. Storage* storage = furi_record_open(RECORD_STORAGE);
  327. nonce_array->stream = buffered_file_stream_alloc(storage);
  328. furi_record_close(RECORD_STORAGE);
  329. bool array_loaded = false;
  330. if(program_state->mfkey32_present) {
  331. array_loaded = load_mfkey32_nonces(
  332. nonce_array, program_state, system_dict, system_dict_exists, user_dict);
  333. }
  334. if(program_state->nested_present) {
  335. array_loaded |= load_nested_nonces(
  336. nonce_array, program_state, system_dict, system_dict_exists, user_dict);
  337. }
  338. if(!array_loaded) {
  339. free(nonce_array);
  340. nonce_array = NULL;
  341. }
  342. return nonce_array;
  343. }
  344. void napi_mf_classic_nonce_array_free(MfClassicNonceArray* nonce_array) {
  345. // TODO: Track free state at the time this is called to ensure double free does not happen
  346. furi_assert(nonce_array);
  347. furi_assert(nonce_array->stream);
  348. buffered_file_stream_close(nonce_array->stream);
  349. stream_free(nonce_array->stream);
  350. free(nonce_array);
  351. }
  352. /* End main methods */
  353. /* Actual implementation of app<>plugin interface */
  354. static const MfkeyPlugin init_plugin = {
  355. .name = "Initialization Plugin",
  356. .napi_mf_classic_mfkey32_nonces_check_presence =
  357. &napi_mf_classic_mfkey32_nonces_check_presence,
  358. .napi_mf_classic_nested_nonces_check_presence = &napi_mf_classic_nested_nonces_check_presence,
  359. .napi_mf_classic_nonce_array_alloc = &napi_mf_classic_nonce_array_alloc,
  360. .napi_mf_classic_nonce_array_free = &napi_mf_classic_nonce_array_free,
  361. };
  362. /* Plugin descriptor to comply with basic plugin specification */
  363. static const FlipperAppPluginDescriptor init_plugin_descriptor = {
  364. .appid = PLUGIN_APP_ID,
  365. .ep_api_version = PLUGIN_API_VERSION,
  366. .entry_point = &init_plugin,
  367. };
  368. /* Plugin entry point - must return a pointer to const descriptor */
  369. const FlipperAppPluginDescriptor* init_plugin_ep() {
  370. return &init_plugin_descriptor;
  371. }