Files
FlipperDroid/flipper/fd_bridge.c
sssnake be81a92d44 Initial commit — FlipperDroid v0.1.0-poc
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.
2026-03-31 21:26:58 -07:00

527 lines
17 KiB
C

/**
* FlipperDroid Bridge — protocol handlers and USB I/O
*
* RX worker reads framed commands from USB CDC, dispatches to handlers.
* Event worker pushes async events (SubGHz RX, GPIO, buttons) to phone.
* All hardware access happens here.
*/
#include "fd_app.h"
#include <furi_hal_power.h>
#include <furi_hal_infrared.h>
#include <infrared.h>
#include <string.h>
#include <stdio.h>
#define TAG "FdBridge"
/* ---- USB CDC I/O ---- */
void fd_usb_tx(const uint8_t* data, uint16_t len) {
furi_hal_cdc_send(FD_CDC_CHANNEL, (uint8_t*)data, len);
}
uint16_t fd_usb_rx(uint8_t* buf, uint16_t max_len, uint32_t timeout_ms) {
uint32_t start = furi_get_tick();
uint16_t total = 0;
while(total < max_len) {
int32_t n = furi_hal_cdc_receive(FD_CDC_CHANNEL, buf + total, max_len - total);
if(n > 0) total += n;
if(furi_get_tick() - start > timeout_ms) break;
if(total >= max_len) break;
furi_delay_ms(1);
}
return total;
}
/* ---- Frame TX ---- */
static void fd_tx_frame(FdApp* app, uint8_t resp_cmd, const uint8_t* payload, uint16_t plen) {
uint16_t frame_len = 1 + plen; /* cmd + payload */
uint8_t hdr[5] = {
FD_MAGIC_HI, FD_MAGIC_LO,
(frame_len >> 8) & 0xFF, frame_len & 0xFF,
resp_cmd
};
/* CRC over [len_hi, len_lo, cmd, payload...] */
uint8_t crc_in[2 + 1 + FD_MAX_PAYLOAD];
crc_in[0] = hdr[2];
crc_in[1] = hdr[3];
crc_in[2] = resp_cmd;
if(payload && plen > 0) memcpy(crc_in + 3, payload, plen);
uint8_t crc = fd_crc8(crc_in, 3 + plen);
furi_mutex_acquire(app->mutex, FuriWaitForever);
fd_usb_tx(hdr, 5);
if(payload && plen > 0) fd_usb_tx(payload, plen);
fd_usb_tx(&crc, 1);
app->tx_count++;
furi_mutex_release(app->mutex);
}
void fd_send_ok(FdApp* app, const uint8_t* data, uint16_t len) {
fd_tx_frame(app, FD_RESP_OK, data, len);
}
void fd_send_err(FdApp* app, FdError err, const char* msg) {
uint16_t mlen = msg ? strlen(msg) : 0;
uint8_t buf[1 + 256];
buf[0] = (uint8_t)err;
if(msg && mlen > 0) memcpy(buf + 1, msg, mlen > 255 ? 255 : mlen);
fd_tx_frame(app, FD_RESP_ERR, buf, 1 + (mlen > 255 ? 255 : mlen));
app->err_count++;
}
void fd_send_event(FdApp* app, FdEvent ev, const uint8_t* data, uint16_t len) {
fd_tx_frame(app, (uint8_t)ev, data, len);
}
/* ---- GPIO helpers ---- */
static FdGpioEntry* fd_find_gpio(FdApp* app, uint8_t pin_id) {
for(uint8_t i = 0; i < app->gpio_count; i++) {
if(app->gpio[i].id == pin_id) return &app->gpio[i];
}
return NULL;
}
/* ---- Command handlers ---- */
static void fd_cmd_system(FdApp* app, uint8_t cmd, const uint8_t* p, uint16_t len) {
UNUSED(p);
UNUSED(len);
switch(cmd) {
case FdCmdPing:
fd_send_ok(app, (const uint8_t*)"PONG", 4);
fd_log(app, "PING -> PONG");
if(!app->connected) {
app->connected = true;
app->connect_tick = furi_get_tick();
notification_message(app->notifications, &sequence_blink_start_green);
fd_log(app, "Android connected!");
}
break;
case FdCmdVersion: {
char buf[64];
int n = snprintf(buf, sizeof(buf), "%s v%s (%s)",
FD_DEVICE_NAME, FD_VERSION,
furi_hal_version_get_name_ptr() ? furi_hal_version_get_name_ptr() : "Flipper");
fd_send_ok(app, (const uint8_t*)buf, n);
break;
}
case FdCmdCapabilities: {
uint8_t caps = FD_CAP_GPIO | FD_CAP_SUBGHZ | FD_CAP_IR | FD_CAP_CPU;
fd_send_ok(app, &caps, 1);
break;
}
case FdCmdStatus: {
uint8_t st[9];
st[0] = furi_hal_power_get_pct();
int16_t temp = (int16_t)(furi_hal_power_get_battery_temperature(FuriHalPowerICFuelGauge) * 10);
st[1] = (temp >> 8) & 0xFF;
st[2] = temp & 0xFF;
uint32_t up = (furi_get_tick() - app->connect_tick) / 1000;
st[3] = (up >> 24) & 0xFF;
st[4] = (up >> 16) & 0xFF;
st[5] = (up >> 8) & 0xFF;
st[6] = up & 0xFF;
st[7] = app->subghz_rx_active ? 1 : 0;
st[8] = app->ir_rx_active ? 1 : 0;
fd_send_ok(app, st, 9);
break;
}
default:
fd_send_err(app, FdErrUnknownCmd, "bad sys cmd");
break;
}
}
static void fd_cmd_gpio(FdApp* app, uint8_t cmd, const uint8_t* p, uint16_t len) {
switch(cmd) {
case FdCmdGpioInit: {
if(len < 2) { fd_send_err(app, FdErrInvalidParams, "need pin,mode"); return; }
FdGpioEntry* g = fd_find_gpio(app, p[0]);
if(!g) { fd_send_err(app, FdErrInvalidParams, "bad pin"); return; }
switch(p[1]) {
case 0:
furi_hal_gpio_init(g->pin, GpioModeInput, GpioPullNo, GpioSpeedLow);
g->output = false;
break;
case 1:
furi_hal_gpio_init(g->pin, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
g->output = true;
break;
case 2:
furi_hal_gpio_init(g->pin, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedVeryHigh);
g->output = true;
break;
case 3:
furi_hal_gpio_init(g->pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
g->output = false;
break;
default:
fd_send_err(app, FdErrInvalidParams, "bad mode");
return;
}
g->initialized = true;
fd_send_ok(app, NULL, 0);
fd_log(app, "GPIO %s init mode %u", g->name, p[1]);
break;
}
case FdCmdGpioWrite: {
if(len < 2) { fd_send_err(app, FdErrInvalidParams, "need pin,val"); return; }
FdGpioEntry* g = fd_find_gpio(app, p[0]);
if(!g) { fd_send_err(app, FdErrInvalidParams, "bad pin"); return; }
if(!g->initialized) { fd_send_err(app, FdErrDisabled, "pin not init"); return; }
furi_hal_gpio_write(g->pin, p[1] ? true : false);
g->value = p[1] ? true : false;
fd_send_ok(app, NULL, 0);
break;
}
case FdCmdGpioRead: {
if(len < 1) { fd_send_err(app, FdErrInvalidParams, "need pin"); return; }
FdGpioEntry* g = fd_find_gpio(app, p[0]);
if(!g) { fd_send_err(app, FdErrInvalidParams, "bad pin"); return; }
if(!g->initialized) {
/* Auto-init as input for convenience */
furi_hal_gpio_init(g->pin, GpioModeInput, GpioPullNo, GpioSpeedLow);
g->initialized = true;
g->output = false;
}
uint8_t val = furi_hal_gpio_read(g->pin) ? 1 : 0;
g->value = val;
fd_send_ok(app, &val, 1);
break;
}
default:
fd_send_err(app, FdErrNotSupported, "gpio cmd n/a");
break;
}
}
static void fd_cmd_subghz(FdApp* app, uint8_t cmd, const uint8_t* p, uint16_t len) {
switch(cmd) {
case FdCmdSubghzSetFreq: {
if(len < 4) { fd_send_err(app, FdErrInvalidParams, "need 4B freq"); return; }
uint32_t freq = ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
((uint32_t)p[2] << 8) | p[3];
bool valid = (freq >= 300000000 && freq <= 348000000) ||
(freq >= 387000000 && freq <= 464000000) ||
(freq >= 779000000 && freq <= 928000000);
if(!valid) { fd_send_err(app, FdErrInvalidParams, "bad freq range"); return; }
app->subghz_freq = freq;
furi_hal_subghz_idle();
furi_hal_subghz_set_frequency(freq);
fd_send_ok(app, NULL, 0);
fd_log(app, "SubGHz freq=%lu", freq);
break;
}
case FdCmdSubghzTx: {
if(len < 1) { fd_send_err(app, FdErrInvalidParams, "need data"); return; }
if(app->subghz_freq == 0) { fd_send_err(app, FdErrInvalidParams, "set freq first"); return; }
furi_hal_subghz_idle();
furi_hal_subghz_set_frequency(app->subghz_freq);
furi_hal_subghz_write_packet(p, len);
furi_hal_subghz_tx();
furi_delay_ms(100);
furi_hal_subghz_idle();
fd_send_ok(app, NULL, 0);
fd_log(app, "SubGHz TX %u bytes", len);
break;
}
case FdCmdSubghzRxStart: {
if(app->subghz_freq == 0) { fd_send_err(app, FdErrInvalidParams, "set freq first"); return; }
furi_hal_subghz_idle();
furi_hal_subghz_set_frequency(app->subghz_freq);
furi_hal_subghz_rx();
app->subghz_rx_active = true;
fd_send_ok(app, NULL, 0);
fd_log(app, "SubGHz RX start");
break;
}
case FdCmdSubghzRxStop:
furi_hal_subghz_idle();
app->subghz_rx_active = false;
fd_send_ok(app, NULL, 0);
fd_log(app, "SubGHz RX stop");
break;
case FdCmdSubghzGetRssi: {
float rssi = furi_hal_subghz_get_rssi();
app->subghz_last_rssi = rssi;
int16_t ri = (int16_t)(rssi * 10);
uint8_t rb[2] = {(ri >> 8) & 0xFF, ri & 0xFF};
fd_send_ok(app, rb, 2);
break;
}
default:
fd_send_err(app, FdErrNotSupported, "subghz cmd n/a");
break;
}
}
static void fd_cmd_ir(FdApp* app, uint8_t cmd, const uint8_t* p, uint16_t len) {
switch(cmd) {
case FdCmdIrTx: {
if(len < 9) { fd_send_err(app, FdErrInvalidParams, "need 9B"); return; }
uint32_t addr = ((uint32_t)p[1] << 24) | ((uint32_t)p[2] << 16) |
((uint32_t)p[3] << 8) | p[4];
uint32_t command = ((uint32_t)p[5] << 24) | ((uint32_t)p[6] << 16) |
((uint32_t)p[7] << 8) | p[8];
InfraredMessage msg = {
.protocol = p[0],
.address = addr,
.command = command,
.repeat = false,
};
infrared_send(&msg, 1);
fd_send_ok(app, NULL, 0);
fd_log(app, "IR TX proto=%u", p[0]);
break;
}
case FdCmdIrRxStart:
app->ir_rx_active = true;
fd_send_ok(app, NULL, 0);
fd_log(app, "IR RX start");
break;
case FdCmdIrRxStop:
app->ir_rx_active = false;
fd_send_ok(app, NULL, 0);
fd_log(app, "IR RX stop");
break;
default:
fd_send_err(app, FdErrNotSupported, "ir cmd n/a");
break;
}
}
static void fd_cmd_file(FdApp* app, uint8_t cmd, const uint8_t* p, uint16_t len) {
Storage* storage = furi_record_open(RECORD_STORAGE);
switch(cmd) {
case FdCmdFileList: {
char path[256] = "/ext";
if(len > 0 && len < sizeof(path)) {
memcpy(path, p, len);
path[len] = '\0';
}
File* dir = storage_file_alloc(storage);
if(!storage_dir_open(dir, path)) {
fd_send_err(app, FdErrHardware, "open dir fail");
storage_file_free(dir);
break;
}
char name[128];
FileInfo info;
uint8_t resp[FD_MAX_PAYLOAD];
uint16_t pos = 0;
while(storage_dir_read(dir, &info, name, sizeof(name))) {
uint16_t nlen = strlen(name);
if(pos + nlen + 3 > sizeof(resp)) break;
resp[pos++] = (info.flags & FSF_DIRECTORY) ? 'd' : 'f';
resp[pos++] = ',';
memcpy(resp + pos, name, nlen);
pos += nlen;
resp[pos++] = '\n';
}
storage_dir_close(dir);
storage_file_free(dir);
fd_send_ok(app, resp, pos);
fd_log(app, "ls %s -> %u entries", path, pos);
break;
}
case FdCmdFileRead: {
if(len < 1) { fd_send_err(app, FdErrInvalidParams, "need path"); break; }
char path[256];
uint16_t pl = (len < 255) ? len : 255;
memcpy(path, p, pl);
path[pl] = '\0';
File* file = storage_file_alloc(storage);
if(!storage_file_open(file, path, FSAM_READ, FSOM_OPEN_EXISTING)) {
fd_send_err(app, FdErrHardware, "open fail");
storage_file_free(file);
break;
}
uint8_t buf[FD_MAX_PAYLOAD];
uint16_t rd = storage_file_read(file, buf, sizeof(buf));
storage_file_close(file);
storage_file_free(file);
fd_send_ok(app, buf, rd);
fd_log(app, "read %s %uB", path, rd);
break;
}
case FdCmdFileWrite: {
if(len < 3) { fd_send_err(app, FdErrInvalidParams, "need plen+path+data"); break; }
uint16_t plen_f = ((uint16_t)p[0] << 8) | p[1];
if(2 + plen_f > len) { fd_send_err(app, FdErrInvalidParams, "bad plen"); break; }
char path[256];
uint16_t cpy = (plen_f < 255) ? plen_f : 255;
memcpy(path, p + 2, cpy);
path[cpy] = '\0';
File* file = storage_file_alloc(storage);
if(!storage_file_open(file, path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
fd_send_err(app, FdErrHardware, "create fail");
storage_file_free(file);
break;
}
uint16_t dlen = len - 2 - plen_f;
storage_file_write(file, p + 2 + plen_f, dlen);
storage_file_close(file);
storage_file_free(file);
fd_send_ok(app, NULL, 0);
fd_log(app, "write %s %uB", path, dlen);
break;
}
case FdCmdFileDelete: {
if(len < 1) { fd_send_err(app, FdErrInvalidParams, "need path"); break; }
char path[256];
uint16_t pl = (len < 255) ? len : 255;
memcpy(path, p, pl);
path[pl] = '\0';
if(storage_simply_remove(storage, path))
fd_send_ok(app, NULL, 0);
else
fd_send_err(app, FdErrHardware, "delete fail");
break;
}
default:
fd_send_err(app, FdErrUnknownCmd, "bad file cmd");
break;
}
furi_record_close(RECORD_STORAGE);
}
/* ---- Command dispatch ---- */
void fd_handle_cmd(FdApp* app, uint8_t cmd, const uint8_t* payload, uint16_t len) {
FURI_LOG_D(TAG, "CMD 0x%02X len=%u", cmd, len);
app->rx_count++;
if(cmd <= 0x04) fd_cmd_system(app, cmd, payload, len);
else if(cmd <= 0x15) fd_cmd_gpio(app, cmd, payload, len);
else if(cmd <= 0x26) fd_cmd_subghz(app, cmd, payload, len);
else if(cmd >= 0x50 && cmd <= 0x54) fd_cmd_ir(app, cmd, payload, len);
else if(cmd >= 0x90 && cmd <= 0x93) fd_cmd_file(app, cmd, payload, len);
else fd_send_err(app, FdErrUnknownCmd, "unknown");
}
/* ---- RX worker ---- */
int32_t fd_rx_worker(void* ctx) {
FdApp* app = (FdApp*)ctx;
FURI_LOG_I(TAG, "RX worker started");
while(app->running) {
/* Scan for magic bytes */
uint8_t magic[2];
if(fd_usb_rx(magic, 2, 100) < 2) continue;
if(magic[0] != FD_MAGIC_HI || magic[1] != FD_MAGIC_LO) continue;
/* Read frame length */
uint8_t lb[2];
if(fd_usb_rx(lb, 2, 500) < 2) continue;
uint16_t flen = ((uint16_t)lb[0] << 8) | lb[1];
if(flen > FD_MAX_PAYLOAD + 1) {
FURI_LOG_W(TAG, "Frame too big: %u", flen);
continue;
}
/* Read cmd + payload + crc */
uint8_t frame[FD_MAX_PAYLOAD + 2];
uint16_t got = fd_usb_rx(frame, flen + 1, 1000);
if(got < flen + 1) {
FURI_LOG_W(TAG, "Short frame: %u/%u", got, flen + 1);
continue;
}
/* Verify CRC */
uint8_t crc_in[2 + FD_MAX_PAYLOAD + 1];
crc_in[0] = lb[0];
crc_in[1] = lb[1];
memcpy(crc_in + 2, frame, flen);
uint8_t expected = fd_crc8(crc_in, 2 + flen);
if(expected != frame[flen]) {
FURI_LOG_W(TAG, "CRC fail: %02X vs %02X", frame[flen], expected);
continue;
}
/* Dispatch */
fd_handle_cmd(app, frame[0], frame + 1, flen - 1);
notification_message(app->notifications, &sequence_blink_cyan_10);
}
FURI_LOG_I(TAG, "RX worker stopped");
return 0;
}
/* ---- Event worker ---- */
int32_t fd_event_worker(void* ctx) {
FdApp* app = (FdApp*)ctx;
FURI_LOG_I(TAG, "Event worker started");
while(app->running) {
/* SubGHz RX check */
if(app->subghz_rx_active) {
if(furi_hal_subghz_is_rx_data_crc_valid()) {
uint8_t pkt[64];
furi_hal_subghz_read_packet(pkt, sizeof(pkt));
float rssi = furi_hal_subghz_get_rssi();
app->subghz_last_rssi = rssi;
/* Pack: data(64) + rssi_x10(2) + freq(4) */
uint8_t ev[70];
memcpy(ev, pkt, 64);
int16_t ri = (int16_t)(rssi * 10);
ev[64] = (ri >> 8) & 0xFF;
ev[65] = ri & 0xFF;
ev[66] = (app->subghz_freq >> 24) & 0xFF;
ev[67] = (app->subghz_freq >> 16) & 0xFF;
ev[68] = (app->subghz_freq >> 8) & 0xFF;
ev[69] = app->subghz_freq & 0xFF;
fd_send_event(app, FdEventSubghzRx, ev, 70);
notification_message(app->notifications, &sequence_blink_green_10);
fd_log(app, "SubGHz RX rssi=%.1f", (double)rssi);
}
}
furi_delay_ms(10);
}
FURI_LOG_I(TAG, "Event worker stopped");
return 0;
}