KernelSU module + Flipper Zero FAP that bridges both devices into a unified pentesting platform over USB CDC serial / BT rfcomm. Android side: bridge daemon, WebUI (:8089), bind mount namespace isolation stealth engine. Flipper side: proper FAP with 4-view GUI, GPIO/SubGHz/IR/file command handlers, async event streaming.
352 lines
11 KiB
C
352 lines
11 KiB
C
/**
|
|
* FlipperDroid — main application entry point
|
|
* Handles GUI, input, lifecycle. Workers in fd_bridge.c.
|
|
*/
|
|
|
|
#include "fd_app.h"
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
|
|
#define TAG "FlipperDroid"
|
|
|
|
/* ---- GPIO pin table ---- */
|
|
static const struct {
|
|
uint8_t id;
|
|
const GpioPin* pin;
|
|
const char* name;
|
|
} gpio_defs[] = {
|
|
{2, &gpio_ext_pa2, "A2"},
|
|
{3, &gpio_ext_pa3, "A3"},
|
|
{4, &gpio_ext_pa4, "A4"},
|
|
{6, &gpio_ext_pa6, "A6"},
|
|
{7, &gpio_ext_pa7, "A7"},
|
|
{13, &gpio_ext_pb2, "B2"},
|
|
{14, &gpio_ext_pb3, "B3"},
|
|
{15, &gpio_ext_pb13, "B13"},
|
|
{16, &gpio_ext_pb14, "B14"},
|
|
{17, &gpio_ext_pc0, "C0"},
|
|
{18, &gpio_ext_pc1, "C1"},
|
|
{19, &gpio_ext_pc3, "C3"},
|
|
};
|
|
#define GPIO_DEF_COUNT (sizeof(gpio_defs) / sizeof(gpio_defs[0]))
|
|
|
|
/* ---- Logging ---- */
|
|
void fd_log(FdApp* app, const char* fmt, ...) {
|
|
va_list args;
|
|
va_start(args, fmt);
|
|
char buf[FD_LOG_COLS + 1];
|
|
vsnprintf(buf, sizeof(buf), fmt, args);
|
|
va_end(args);
|
|
|
|
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
|
uint8_t idx = (app->log.head + app->log.count) % FD_LOG_LINES;
|
|
if(app->log.count < FD_LOG_LINES) {
|
|
app->log.count++;
|
|
} else {
|
|
app->log.head = (app->log.head + 1) % FD_LOG_LINES;
|
|
}
|
|
strncpy(app->log.lines[idx], buf, FD_LOG_COLS);
|
|
app->log.lines[idx][FD_LOG_COLS] = '\0';
|
|
furi_mutex_release(app->mutex);
|
|
|
|
FURI_LOG_I(TAG, "%s", buf);
|
|
}
|
|
|
|
/* ---- Draw ---- */
|
|
void fd_draw_callback(Canvas* canvas, void* ctx) {
|
|
FdApp* app = (FdApp*)ctx;
|
|
furi_mutex_acquire(app->mutex, FuriWaitForever);
|
|
|
|
canvas_clear(canvas);
|
|
canvas_set_font(canvas, FontPrimary);
|
|
|
|
switch(app->current_view) {
|
|
case FdViewStatus: {
|
|
/* Title bar */
|
|
canvas_draw_str(canvas, 0, 10, "FlipperDroid");
|
|
canvas_set_font(canvas, FontSecondary);
|
|
|
|
/* Connection status */
|
|
if(app->connected) {
|
|
canvas_draw_str(canvas, 72, 10, "[LINKED]");
|
|
} else {
|
|
canvas_draw_str(canvas, 68, 10, "[WAITING]");
|
|
}
|
|
|
|
/* Stats */
|
|
char buf[40];
|
|
canvas_draw_str(canvas, 0, 22, "Status:");
|
|
canvas_draw_str(canvas, 44, 22, app->connected ? "Connected" : "Listening on USB");
|
|
|
|
snprintf(buf, sizeof(buf), "RX: %lu TX: %lu ERR: %lu",
|
|
app->rx_count, app->tx_count, app->err_count);
|
|
canvas_draw_str(canvas, 0, 32, buf);
|
|
|
|
/* Subsystem status */
|
|
snprintf(buf, sizeof(buf), "SubGHz: %s %lu Hz",
|
|
app->subghz_rx_active ? "RX" : "idle",
|
|
app->subghz_freq);
|
|
canvas_draw_str(canvas, 0, 42, buf);
|
|
|
|
snprintf(buf, sizeof(buf), "IR: %s GPIO: %u pins",
|
|
app->ir_rx_active ? "RX" : "idle", app->gpio_count);
|
|
canvas_draw_str(canvas, 0, 52, buf);
|
|
|
|
/* Nav hint */
|
|
canvas_draw_str(canvas, 0, 63, "<GPIO SubGHz> OK:Log Bk:Exit");
|
|
break;
|
|
}
|
|
|
|
case FdViewGpio: {
|
|
canvas_draw_str(canvas, 0, 10, "GPIO Pins");
|
|
canvas_set_font(canvas, FontSecondary);
|
|
|
|
for(uint8_t i = 0; i < app->gpio_count && i < 12; i++) {
|
|
uint8_t row = i / 4;
|
|
uint8_t col = i % 4;
|
|
uint8_t x = col * 32;
|
|
uint8_t y = 20 + row * 14;
|
|
|
|
char buf[12];
|
|
FdGpioEntry* g = &app->gpio[i];
|
|
|
|
if(i == app->selected_gpio) {
|
|
canvas_draw_box(canvas, x, y - 8, 30, 12);
|
|
canvas_set_color(canvas, ColorWhite);
|
|
}
|
|
|
|
if(g->initialized) {
|
|
snprintf(buf, sizeof(buf), "%s:%c", g->name, g->value ? 'H' : 'L');
|
|
} else {
|
|
snprintf(buf, sizeof(buf), "%s:--", g->name);
|
|
}
|
|
canvas_draw_str(canvas, x + 1, y, buf);
|
|
canvas_set_color(canvas, ColorBlack);
|
|
}
|
|
|
|
canvas_draw_str(canvas, 0, 63, "<Stat ^v:Sel OK:Read >SG");
|
|
break;
|
|
}
|
|
|
|
case FdViewSubghz: {
|
|
canvas_draw_str(canvas, 0, 10, "Sub-GHz");
|
|
canvas_set_font(canvas, FontSecondary);
|
|
|
|
char buf[40];
|
|
snprintf(buf, sizeof(buf), "Freq: %lu Hz", app->subghz_freq);
|
|
canvas_draw_str(canvas, 0, 22, buf);
|
|
|
|
snprintf(buf, sizeof(buf), "RSSI: %.1f dBm", (double)app->subghz_last_rssi);
|
|
canvas_draw_str(canvas, 0, 32, buf);
|
|
|
|
canvas_draw_str(canvas, 0, 42, app->subghz_rx_active ? "State: RECEIVING" : "State: IDLE");
|
|
|
|
snprintf(buf, sizeof(buf), "Packets: %lu", app->rx_count);
|
|
canvas_draw_str(canvas, 0, 52, buf);
|
|
|
|
canvas_draw_str(canvas, 0, 63, "<GPIO OK:RX Tog >Log");
|
|
break;
|
|
}
|
|
|
|
case FdViewLog: {
|
|
canvas_draw_str(canvas, 0, 10, "Log");
|
|
canvas_set_font(canvas, FontKeyboard);
|
|
|
|
for(uint8_t i = 0; i < app->log.count && i < 6; i++) {
|
|
uint8_t idx = (app->log.head + i) % FD_LOG_LINES;
|
|
canvas_draw_str(canvas, 0, 20 + i * 8, app->log.lines[idx]);
|
|
}
|
|
|
|
canvas_set_font(canvas, FontSecondary);
|
|
canvas_draw_str(canvas, 0, 63, "<SubGHz Bk:Exit");
|
|
break;
|
|
}
|
|
}
|
|
|
|
furi_mutex_release(app->mutex);
|
|
}
|
|
|
|
/* ---- Input ---- */
|
|
void fd_input_callback(InputEvent* event, void* ctx) {
|
|
FdApp* app = (FdApp*)ctx;
|
|
furi_message_queue_put(app->input_queue, event, FuriWaitForever);
|
|
}
|
|
|
|
/* ---- Alloc / Free ---- */
|
|
FdApp* fd_app_alloc(void) {
|
|
FdApp* app = malloc(sizeof(FdApp));
|
|
memset(app, 0, sizeof(FdApp));
|
|
|
|
app->mutex = furi_mutex_alloc(FuriMutexTypeRecursive);
|
|
app->input_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
|
app->gui = furi_record_open(RECORD_GUI);
|
|
app->notifications = furi_record_open(RECORD_NOTIFICATION);
|
|
app->current_view = FdViewStatus;
|
|
app->running = true;
|
|
|
|
/* Init GPIO table */
|
|
app->gpio_count = GPIO_DEF_COUNT;
|
|
for(uint8_t i = 0; i < GPIO_DEF_COUNT; i++) {
|
|
app->gpio[i].id = gpio_defs[i].id;
|
|
app->gpio[i].pin = gpio_defs[i].pin;
|
|
app->gpio[i].name = gpio_defs[i].name;
|
|
app->gpio[i].initialized = false;
|
|
app->gpio[i].output = false;
|
|
app->gpio[i].value = false;
|
|
}
|
|
|
|
/* Create viewport */
|
|
app->view_port = view_port_alloc();
|
|
view_port_draw_callback_set(app->view_port, fd_draw_callback, app);
|
|
view_port_input_callback_set(app->view_port, fd_input_callback, app);
|
|
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
|
|
|
|
return app;
|
|
}
|
|
|
|
void fd_app_free(FdApp* app) {
|
|
/* Stop workers */
|
|
app->running = false;
|
|
|
|
if(app->rx_thread) {
|
|
furi_thread_join(app->rx_thread);
|
|
furi_thread_free(app->rx_thread);
|
|
}
|
|
if(app->event_thread) {
|
|
furi_thread_join(app->event_thread);
|
|
furi_thread_free(app->event_thread);
|
|
}
|
|
|
|
/* Cleanup SubGHz */
|
|
if(app->subghz_rx_active) {
|
|
furi_hal_subghz_idle();
|
|
app->subghz_rx_active = false;
|
|
}
|
|
|
|
/* Reset GPIO pins to analog (safe default) */
|
|
for(uint8_t i = 0; i < app->gpio_count; i++) {
|
|
if(app->gpio[i].initialized) {
|
|
furi_hal_gpio_init(app->gpio[i].pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
|
|
}
|
|
}
|
|
|
|
/* Remove GUI */
|
|
view_port_enabled_set(app->view_port, false);
|
|
gui_remove_view_port(app->gui, app->view_port);
|
|
view_port_free(app->view_port);
|
|
|
|
furi_message_queue_free(app->input_queue);
|
|
furi_mutex_free(app->mutex);
|
|
|
|
furi_record_close(RECORD_NOTIFICATION);
|
|
furi_record_close(RECORD_GUI);
|
|
|
|
free(app);
|
|
}
|
|
|
|
/* ---- Main loop ---- */
|
|
int32_t fd_app_run(FdApp* app) {
|
|
fd_log(app, "Bridge starting...");
|
|
|
|
/* Launch RX worker — listens for commands on USB CDC */
|
|
app->rx_thread = furi_thread_alloc_ex("FdRx", 4096, fd_rx_worker, app);
|
|
furi_thread_start(app->rx_thread);
|
|
|
|
/* Launch event worker — pushes async events to phone */
|
|
app->event_thread = furi_thread_alloc_ex("FdEvt", 2048, fd_event_worker, app);
|
|
furi_thread_start(app->event_thread);
|
|
|
|
fd_log(app, "Listening on USB CDC...");
|
|
notification_message(app->notifications, &sequence_blink_start_blue);
|
|
|
|
/* Input loop */
|
|
InputEvent event;
|
|
while(app->running) {
|
|
if(furi_message_queue_get(app->input_queue, &event, 100) == FuriStatusOk) {
|
|
if(event.type != InputTypePress && event.type != InputTypeRepeat) continue;
|
|
|
|
switch(event.key) {
|
|
case InputKeyBack:
|
|
app->running = false;
|
|
break;
|
|
|
|
case InputKeyRight:
|
|
if(app->current_view == FdViewStatus) app->current_view = FdViewSubghz;
|
|
else if(app->current_view == FdViewGpio) app->current_view = FdViewSubghz;
|
|
else if(app->current_view == FdViewSubghz) app->current_view = FdViewLog;
|
|
break;
|
|
|
|
case InputKeyLeft:
|
|
if(app->current_view == FdViewLog) app->current_view = FdViewSubghz;
|
|
else if(app->current_view == FdViewSubghz) app->current_view = FdViewGpio;
|
|
else if(app->current_view == FdViewGpio) app->current_view = FdViewStatus;
|
|
break;
|
|
|
|
case InputKeyOk:
|
|
if(app->current_view == FdViewStatus) {
|
|
app->current_view = FdViewLog;
|
|
} else if(app->current_view == FdViewGpio) {
|
|
/* Read selected GPIO pin */
|
|
uint8_t sel = app->selected_gpio;
|
|
if(sel < app->gpio_count) {
|
|
if(!app->gpio[sel].initialized) {
|
|
furi_hal_gpio_init(
|
|
app->gpio[sel].pin, GpioModeInput, GpioPullNo, GpioSpeedLow);
|
|
app->gpio[sel].initialized = true;
|
|
}
|
|
app->gpio[sel].value = furi_hal_gpio_read(app->gpio[sel].pin);
|
|
fd_log(app, "GPIO %s = %s",
|
|
app->gpio[sel].name,
|
|
app->gpio[sel].value ? "HIGH" : "LOW");
|
|
}
|
|
} else if(app->current_view == FdViewSubghz) {
|
|
/* Toggle SubGHz RX */
|
|
if(app->subghz_rx_active) {
|
|
furi_hal_subghz_idle();
|
|
app->subghz_rx_active = false;
|
|
fd_log(app, "SubGHz RX stopped");
|
|
} else if(app->subghz_freq > 0) {
|
|
furi_hal_subghz_idle();
|
|
furi_hal_subghz_set_frequency(app->subghz_freq);
|
|
furi_hal_subghz_rx();
|
|
app->subghz_rx_active = true;
|
|
fd_log(app, "SubGHz RX on %lu Hz", app->subghz_freq);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case InputKeyUp:
|
|
if(app->current_view == FdViewGpio && app->selected_gpio > 0)
|
|
app->selected_gpio--;
|
|
break;
|
|
|
|
case InputKeyDown:
|
|
if(app->current_view == FdViewGpio && app->selected_gpio < app->gpio_count - 1)
|
|
app->selected_gpio++;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
view_port_update(app->view_port);
|
|
}
|
|
|
|
/* Periodic screen refresh */
|
|
view_port_update(app->view_port);
|
|
}
|
|
|
|
notification_message(app->notifications, &sequence_blink_stop);
|
|
fd_log(app, "Bridge stopped");
|
|
return 0;
|
|
}
|
|
|
|
/* ---- Entry point ---- */
|
|
int32_t flipperdroid_app(void* p) {
|
|
UNUSED(p);
|
|
FdApp* app = fd_app_alloc();
|
|
fd_app_run(app);
|
|
fd_app_free(app);
|
|
return 0;
|
|
}
|