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:
481
system/bin/flipperdroidd
Normal file
481
system/bin/flipperdroidd
Normal 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
|
||||
Reference in New Issue
Block a user