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.
This commit is contained in:
sssnake
2026-03-31 21:26:58 -07:00
commit be81a92d44
22 changed files with 4191 additions and 0 deletions

481
system/bin/flipperdroidd Normal file
View File

@@ -0,0 +1,481 @@
#!/system/bin/sh
# FlipperDroid Bridge Daemon
# Manages bidirectional communication between Android and Flipper Zero
# Protocol: binary framed messages over USB CDC (ttyACM) or BT rfcomm
CONFIG_DIR="/data/adb/flipperdroid"
CONFIG_FILE="$CONFIG_DIR/config.sh"
LOG_FILE="$CONFIG_DIR/logs/flipperdroid.log"
FIFO_TX="$CONFIG_DIR/bridge_tx"
FIFO_RX="$CONFIG_DIR/bridge_rx"
CMD_FIFO="$CONFIG_DIR/cmd_fifo"
RESP_DIR="$CONFIG_DIR/responses"
MAGIC_HI=$(printf '\xFD')
MAGIC_LO=$(printf '\x01')
log() {
local level="$1"; shift
local min_level=$(cat "$CONFIG_DIR/log_level" 2>/dev/null || echo 2)
[ "$level" -le "$min_level" ] && echo "[$(date '+%Y-%m-%d %H:%M:%S')] [bridge] $*" >> "$LOG_FILE"
}
source "$CONFIG_FILE" 2>/dev/null
mkdir -p "$RESP_DIR"
# CRC8 lookup (Dallas/Maxim polynomial 0x31)
crc8() {
local data="$1"
local crc=0
local i j byte
for i in $(echo "$data" | sed 's/../& /g'); do
byte=$((16#$i))
crc=$((crc ^ byte))
for j in $(seq 1 8); do
if [ $((crc & 0x80)) -ne 0 ]; then
crc=$(( ((crc << 1) ^ 0x31) & 0xFF ))
else
crc=$(( (crc << 1) & 0xFF ))
fi
done
done
printf '%02x' $crc
}
# Build a protocol frame: magic(2) + len(2) + cmd(1) + payload(N) + crc8(1)
build_frame() {
local cmd_hex="$1"
local payload_hex="$2"
local payload_len=$((${#payload_hex} / 2))
local total_len=$((payload_len + 1)) # cmd + payload
local len_hi=$(printf '%02x' $((total_len >> 8)))
local len_lo=$(printf '%02x' $((total_len & 0xFF)))
local frame_data="${len_hi}${len_lo}${cmd_hex}${payload_hex}"
local crc=$(crc8 "$frame_data")
echo "FD01${frame_data}${crc}"
}
# Send raw hex frame to Flipper
send_raw() {
local hex="$1"
local dev=$(cat "$CONFIG_DIR/flipper_dev" 2>/dev/null)
[ -z "$dev" ] || [ ! -c "$dev" ] && return 1
echo "$hex" | xxd -r -p > "$dev"
}
# Send command and wait for response
send_cmd() {
local cmd="$1"
local payload="${2:-}"
local timeout="${3:-5}"
local frame=$(build_frame "$cmd" "$payload")
local dev=$(cat "$CONFIG_DIR/flipper_dev" 2>/dev/null)
[ -z "$dev" ] || [ ! -c "$dev" ] && echo "ERR:no_device" && return 1
log 3 "TX: cmd=0x${cmd} payload=${payload:-<none>}"
# Send frame
echo "$frame" | xxd -r -p > "$dev" 2>/dev/null
if [ $? -ne 0 ]; then
log 1 "TX failed — device gone?"
echo "disconnected" > "$CONFIG_DIR/status"
echo "ERR:tx_failed"
return 1
fi
# Read response with timeout
local response=""
response=$(timeout "$timeout" dd if="$dev" bs=1 count=256 2>/dev/null | xxd -p | tr -d '\n')
if [ -z "$response" ]; then
log 1 "RX timeout for cmd 0x${cmd}"
echo "ERR:timeout"
return 1
fi
log 3 "RX: $response"
# Parse response — extract after magic bytes
local resp_cmd=$(echo "$response" | cut -c9-10)
local resp_payload=$(echo "$response" | cut -c11-)
if [ "$resp_cmd" = "fe" ]; then
echo "OK:${resp_payload}"
elif [ "$resp_cmd" = "ff" ]; then
echo "ERR:${resp_payload}"
else
echo "RAW:${response}"
fi
}
#############################
# Handshake with Flipper
#############################
handshake() {
log 2 "Initiating handshake with Flipper Zero..."
# Send PING
local resp=$(send_cmd "01" "" 3)
if ! echo "$resp" | grep -q "^OK"; then
log 1 "Handshake PING failed: $resp"
return 1
fi
log 2 "PING OK"
# Get version
resp=$(send_cmd "02" "" 3)
if echo "$resp" | grep -q "^OK:"; then
local version_hex=$(echo "$resp" | sed 's/OK://')
echo "$version_hex" | xxd -r -p > "$CONFIG_DIR/flipper_version" 2>/dev/null
log 2 "Flipper version: $(cat "$CONFIG_DIR/flipper_version")"
fi
# Get capabilities
resp=$(send_cmd "03" "" 3)
if echo "$resp" | grep -q "^OK:"; then
echo "$resp" | sed 's/OK://' > "$CONFIG_DIR/flipper_caps"
log 2 "Capabilities: $(cat "$CONFIG_DIR/flipper_caps")"
fi
echo "connected" > "$CONFIG_DIR/status"
log 2 "Handshake complete — bridge active"
return 0
}
#############################
# Command FIFO handler
# WebUI and other tools write commands here
#############################
process_api_command() {
local cmd_line="$1"
local req_id=$(echo "$cmd_line" | cut -d'|' -f1)
local subsystem=$(echo "$cmd_line" | cut -d'|' -f2)
local action=$(echo "$cmd_line" | cut -d'|' -f3)
local params=$(echo "$cmd_line" | cut -d'|' -f4-)
log 3 "API cmd: $req_id $subsystem $action $params"
local result=""
case "$subsystem" in
system)
case "$action" in
ping) result=$(send_cmd "01") ;;
version) result=$(send_cmd "02") ;;
caps) result=$(send_cmd "03") ;;
status) result=$(send_cmd "04") ;;
esac
;;
gpio)
case "$action" in
init)
local pin=$(echo "$params" | cut -d',' -f1)
local mode=$(echo "$params" | cut -d',' -f2)
local pin_hex=$(printf '%02x' "$pin")
local mode_hex=$(printf '%02x' "$mode")
result=$(send_cmd "10" "${pin_hex}${mode_hex}")
;;
write)
local pin=$(echo "$params" | cut -d',' -f1)
local val=$(echo "$params" | cut -d',' -f2)
result=$(send_cmd "11" "$(printf '%02x%02x' "$pin" "$val")")
;;
read)
local pin=$(echo "$params" | cut -d',' -f1)
result=$(send_cmd "12" "$(printf '%02x' "$pin")")
;;
pwm)
local pin=$(echo "$params" | cut -d',' -f1)
local freq=$(echo "$params" | cut -d',' -f2)
local duty=$(echo "$params" | cut -d',' -f3)
local pin_hex=$(printf '%02x' "$pin")
local freq_hex=$(printf '%08x' "$freq")
local duty_hex=$(printf '%02x' "$duty")
result=$(send_cmd "13" "${pin_hex}${freq_hex}${duty_hex}")
;;
adc)
local pin=$(echo "$params" | cut -d',' -f1)
result=$(send_cmd "14" "$(printf '%02x' "$pin")")
;;
esac
;;
subghz)
case "$action" in
set_freq)
local freq=$(echo "$params" | cut -d',' -f1)
result=$(send_cmd "20" "$(printf '%08x' "$freq")")
;;
tx)
local data_hex="$params"
result=$(send_cmd "21" "$data_hex" 10)
;;
rx_start) result=$(send_cmd "22") ;;
rx_stop) result=$(send_cmd "23") ;;
get_rssi) result=$(send_cmd "24") ;;
set_mod)
local mod=$(echo "$params" | cut -d',' -f1)
local bw=$(echo "$params" | cut -d',' -f2)
result=$(send_cmd "25" "$(printf '%02x%02x' "$mod" "$bw")")
;;
replay)
local slot=$(echo "$params" | cut -d',' -f1)
result=$(send_cmd "26" "$(printf '%02x' "$slot")")
;;
esac
;;
rfid)
case "$action" in
read) result=$(send_cmd "30" "" 10) ;;
emulate) result=$(send_cmd "31" "$params") ;;
write) result=$(send_cmd "32" "$params") ;;
esac
;;
nfc)
case "$action" in
poll) result=$(send_cmd "40" "" 10) ;;
read_full) result=$(send_cmd "41" "" 15) ;;
emulate) result=$(send_cmd "42" "$params") ;;
relay_start) result=$(send_cmd "43") ;;
relay_stop) result=$(send_cmd "44") ;;
raw_exchange) result=$(send_cmd "45" "$params" 10) ;;
esac
;;
ir)
case "$action" in
tx)
local proto=$(echo "$params" | cut -d',' -f1)
local addr=$(echo "$params" | cut -d',' -f2)
local cmd=$(echo "$params" | cut -d',' -f3)
result=$(send_cmd "50" "$(printf '%02x%08x%08x' "$proto" "$addr" "$cmd")")
;;
tx_raw) result=$(send_cmd "51" "$params" 10) ;;
rx_start) result=$(send_cmd "52") ;;
rx_stop) result=$(send_cmd "53") ;;
replay) result=$(send_cmd "54" "$(printf '%02x' "$params")") ;;
esac
;;
ibutton)
case "$action" in
read) result=$(send_cmd "60" "" 10) ;;
emulate) result=$(send_cmd "61" "$params") ;;
write) result=$(send_cmd "62" "$params") ;;
esac
;;
badusb)
case "$action" in
start) result=$(send_cmd "70") ;;
exec) result=$(send_cmd "71" "$(echo -n "$params" | xxd -p)") ;;
stop) result=$(send_cmd "72") ;;
esac
;;
file)
case "$action" in
list) result=$(send_cmd "90" "$(echo -n "$params" | xxd -p)" 10) ;;
read) result=$(send_cmd "91" "$(echo -n "$params" | xxd -p)" 10) ;;
write) result=$(send_cmd "92" "$params" 10) ;;
delete) result=$(send_cmd "93" "$(echo -n "$params" | xxd -p)") ;;
esac
;;
*)
result="ERR:unknown_subsystem"
;;
esac
# Write result to response file
echo "$result" > "$RESP_DIR/${req_id}"
log 3 "Result for $req_id: $result"
}
#############################
# Async event listener
# Reads unsolicited events from Flipper
#############################
event_listener() {
local dev=$(cat "$CONFIG_DIR/flipper_dev" 2>/dev/null)
[ -z "$dev" ] || [ ! -c "$dev" ] && return
log 2 "Event listener started on $dev"
while true; do
# Read from device, look for event frames (cmd >= 0xA0)
local raw=$(dd if="$dev" bs=1 count=2 2>/dev/null | xxd -p)
# Check for magic bytes
if [ "$raw" = "fd01" ]; then
# Read length
local len_hex=$(dd if="$dev" bs=1 count=2 2>/dev/null | xxd -p)
local len=$((16#$len_hex))
# Read cmd + payload + crc
local data=$(dd if="$dev" bs=1 count=$((len + 1)) 2>/dev/null | xxd -p)
local event_cmd=$(echo "$data" | cut -c1-2)
local event_payload=$(echo "$data" | cut -c3-$((${#data} - 2)))
case "$event_cmd" in
a0) # GPIO interrupt
local pin=$((16#$(echo "$event_payload" | cut -c1-2)))
local val=$((16#$(echo "$event_payload" | cut -c3-4)))
log 3 "GPIO IRQ: pin=$pin val=$val"
echo "gpio_irq|$pin|$val|$(date +%s)" >> "$CONFIG_DIR/events"
;;
a1) # SubGHz RX data
log 3 "SubGHz RX: $event_payload"
echo "subghz_rx|$event_payload|$(date +%s)" >> "$CONFIG_DIR/events"
;;
a2) # IR RX
log 3 "IR RX: $event_payload"
echo "ir_rx|$event_payload|$(date +%s)" >> "$CONFIG_DIR/events"
;;
a3) # NFC field detected
log 3 "NFC field event: $event_payload"
echo "nfc_field|$event_payload|$(date +%s)" >> "$CONFIG_DIR/events"
;;
a4) # Button press on Flipper
local btn=$((16#$(echo "$event_payload" | cut -c1-2)))
local state=$((16#$(echo "$event_payload" | cut -c3-4)))
log 3 "Button: btn=$btn state=$state"
echo "button|$btn|$state|$(date +%s)" >> "$CONFIG_DIR/events"
;;
a5) # CPU offload request from Flipper
local task_id=$(echo "$event_payload" | cut -c1-8)
local workload=$(echo "$event_payload" | cut -c9-)
log 2 "CPU offload request: task=$task_id"
handle_cpu_offload "$task_id" "$workload" &
;;
esac
fi
done
}
#############################
# CPU sharing — run Flipper's workload on phone
#############################
handle_cpu_offload() {
local task_id="$1"
local workload_hex="$2"
log 2 "Processing CPU offload task $task_id"
# Decode workload — it's a simple bytecode or shell command
local workload=$(echo "$workload_hex" | xxd -r -p)
local result=""
# Execute in sandboxed context
result=$(echo "$workload" | timeout 30 sh 2>&1)
local exit_code=$?
# Send result back to Flipper
local status_hex=$(printf '%02x' $exit_code)
local result_hex=$(echo -n "$result" | xxd -p | tr -d '\n')
send_cmd "81" "${task_id}${status_hex}${result_hex}" 5
log 2 "CPU task $task_id complete (exit=$exit_code)"
}
#############################
# Connection monitor
#############################
connection_monitor() {
while true; do
sleep 10
local dev=$(cat "$CONFIG_DIR/flipper_dev" 2>/dev/null)
local status=$(cat "$CONFIG_DIR/status" 2>/dev/null)
if [ "$status" = "connected" ]; then
# Check if device still exists
if [ ! -c "$dev" ]; then
log 1 "Flipper device $dev disappeared — disconnected"
echo "disconnected" > "$CONFIG_DIR/status"
# Try to rediscover
sleep 5
local new_dev=""
for tty in /dev/ttyACM*; do
[ -c "$tty" ] && new_dev="$tty" && break
done
if [ -n "$new_dev" ]; then
log 2 "Flipper reconnected on $new_dev"
echo "$new_dev" > "$CONFIG_DIR/flipper_dev"
stty -F "$new_dev" ${BAUD_RATE:-115200} raw -echo -echoe -echok 2>/dev/null
handshake && echo "connected" > "$CONFIG_DIR/status"
fi
else
# Heartbeat ping
local resp=$(send_cmd "01" "" 2)
if ! echo "$resp" | grep -q "^OK"; then
log 1 "Heartbeat failed — marking disconnected"
echo "disconnected" > "$CONFIG_DIR/status"
fi
fi
elif [ "$status" = "disconnected" ]; then
# Try to find Flipper
for tty in /dev/ttyACM*; do
if [ -c "$tty" ]; then
log 2 "Found new device $tty, attempting handshake"
echo "$tty" > "$CONFIG_DIR/flipper_dev"
stty -F "$tty" ${BAUD_RATE:-115200} raw -echo -echoe -echok 2>/dev/null
if handshake; then
echo "connected" > "$CONFIG_DIR/status"
fi
break
fi
done
fi
done
}
#############################
# Main loop
#############################
log 2 "FlipperDroid bridge daemon starting"
# Create command FIFO
rm -f "$CMD_FIFO"
mkfifo "$CMD_FIFO" 2>/dev/null
chmod 660 "$CMD_FIFO"
# Initial handshake
if [ "$(cat "$CONFIG_DIR/status" 2>/dev/null)" = "connected" ]; then
handshake
fi
# Start connection monitor in background
connection_monitor &
MONITOR_PID=$!
# Start event listener in background
event_listener &
LISTENER_PID=$!
# Store PIDs for cleanup
echo "$MONITOR_PID" > "$CONFIG_DIR/monitor.pid"
echo "$LISTENER_PID" > "$CONFIG_DIR/listener.pid"
# Main command processing loop — reads from FIFO
log 2 "Bridge daemon ready, listening on $CMD_FIFO"
while true; do
if read -r cmd_line < "$CMD_FIFO" 2>/dev/null; then
[ -n "$cmd_line" ] && process_api_command "$cmd_line" &
else
sleep 1
fi
done