What We Don't Know Yet

A framework for thinking about iOS Lockdown Mode research without pretending confidence where there isn't any. Built to learn with you, not to tell you what to think.
Open Question 1
Can you actually predict exploitability from static properties (entitlements, descriptor validation, constraint patterns)?
Our Hypothesis
If a service rejects file paths + URLs but accepts arbitrary XPC descriptors without strict type validation, you can fuzz it for descriptor confusion. Constraint pattern → exploitability signature.
How to Test This
1. Extract entitlements from 10-15 persisted services (static analysis)
2. Probe each with test message types (dynamic probing)
3. Document the rejection patterns
4. Fuzz only the "predictably permissive" services
5. Compare: did the predicted-exploitable ones actually crash more often?
What Could Be Wrong
Confounding: A service that accepts descriptors might accept them for *semantic* reasons (it needs file descriptors to work), not because of validation gaps. You can't crash it in a useful way. The descriptor acceptance doesn't predict exploitability—it predicts necessity.

Context dependency: Whether a service "validates" descriptors might depend on the call sequence leading up to it. Static analysis can't see that. You might categorize a service as "permissive" when it's actually "permissive only after initialization."

Signal vs. Noise: A service might crash frequently on malformed descriptors, but all those crashes might be "benign" (caught at the IPC boundary, no kernel memory corruption, no privilege escalation potential). You'd categorize it as exploitable when it's actually just crashy.
Confidence Spectrum
CERTAIN
CONFIDENT
UNCERTAIN
UNKNOWN
Certain
We know this works
Confident
Probably true
Uncertain
Could be either way
Unknown
Need to find out
← Our hypothesis sits around here: "UNCERTAIN." We have a plausible mechanism, but we haven't validated it under Lockdown Mode with real services.
Open Question 2
Does reducing from 120 to 12 targets actually improve research quality, or just move your blindness?
Our Hypothesis
By filtering targets through entitlement analysis, you reduce noise. Fewer targets = higher crash-per-testcase ratio = more exploitable crashes. You spend less time on policy-blocked surfaces.
How to Test This
Phase A: Fuzz all 120 persisted services for 500 testcases each. Record crashes.
Phase B: Identify the 12 with highest crash rates. Fuzz those 12 for 5000 testcases each.
Phase C: Compare. Did Phase B find more exploitable crashes per testcase than Phase A?
What Could Be Wrong
Bias: The "12 high-interest" targets might just be "services that crash easily on garbage input," not "services that are exploitable." Frequent crashes ≠ security-relevant crashes. You might be optimizing toward the wrong metric entirely.

Diversity loss: By filtering to 12, you might miss vulnerabilities in the 108 you discarded. Those services might have *rarer* but *more exploitable* bugs. You've optimized for signal density at the cost of surface coverage.

Lockdown-specific effects: A service that accepts descriptors freely *pre-Lockdown* might have been hardened post-Lockdown. Your static analysis might miss that hardening. You predict HIGH INTEREST based on pre-Lockdown data, but the service is actually neutered.
Open Question 3
What does "exploitable" actually mean under Lockdown Mode?
The Blind Spot
We've been using "exploitable" to mean "can produce a crash." But under Lockdown Mode, crashing a service might not *matter*. The restrictions prevent you from doing anything useful with the crash.

Example: You find a use-after-free in iMessage attachment handling. You crash the imagent service. Under Lockdown Mode, you can't send attachments anyway (Lockdown blocks attachment types). The crash is real, but the exploitation path is blocked by policy, not by the vulnerability.

Or: A service crashes on a crafted descriptor. But the crash is caught at the IPC boundary. No kernel memory corruption. No privilege escalation. Just a service restart. That's a finding, but is it "exploitable" in any meaningful sense?
How to Test This
For each crash you find:
1. Can you cause it to leak memory? ← Test
2. Can you cause it to escalate privilege? ← Test
3. Can you cause it to violate Lockdown restrictions? ← Test
4. Is the crash even *useful* given Lockdown's constraints? ← Analyze

Most crashes will fail (3) and (4). That doesn't make them useless findings, but it changes what "exploitable" means.
Open Question 4
Are the constraint patterns you observe stable across contexts, or do they depend on call sequences you can't statically predict?
The Problem
You probe a service in isolation: send it a file path message. It rejects it. You categorize it as "rejects file paths."

But what if that service only validates file paths *after* being initialized by another message? What if the rejection pattern changes depending on the service's internal state? Your static analysis sees "file path = rejected" globally. Reality is "file path = rejected except in state 0x42 after message sequence ABC."

You can't know this without exhaustive fuzzing, which defeats the purpose of the filtering.
How to Test This
Build a stateful fuzzer that doesn't reset the service between messages. Let it explore the state space. Document what constraint patterns emerge. Compare to your static predictions. How often does reality diverge from prediction?

A Framework for Learning (Not Proving)

Step 1: Map the Unknown
Before you build the triage layer, document what you *don't know* about these 120 services. For each one:
• What entitlements does it require?
• What descriptor types does it accept?
• What happens when you send it malformed data?
• Does its behavior change based on Lockdown Mode?

This is data collection, not hypothesis testing. Just observation. Make it searchable, queryable, version-controllable.
Step 2: Generate Hypotheses (Not Conclusions)
From the observations, generate falsifiable hypotheses:
• "Service X accepts descriptors without type checking"
• "Service Y's validation depends on previous message sequence"
• "Service Z's behavior changed between iOS 25 and 26"

Each hypothesis should have a concrete test attached.
Step 3: Run the Tests (Accept Wrong Answers)
For each hypothesis, design a minimal test. Expect to be wrong. Document what you expected vs. what you found. The mismatches are where learning happens.
Step 4: Build the Triage Layer on Evidence, Not Confidence
Only *after* testing, build the triage tool. It categorizes services based on what you actually found, not on assumptions. The categories themselves are hypotheses, not conclusions. Document the uncertainty in the output.
Step 5: Publish the Unknowns
When you release this (as a tool, paper, or research finding), include a "Things We Don't Know Yet" section. What questions does this raise? What edge cases do we still not understand? What would break this categorization?

That section will be more valuable than the categorization itself.

Infrastructure You'll Need

Service Observation Logger
Runs on SRD. For each service, sends test messages (file paths, URLs, descriptors of various types) and logs the responses. Not fuzzing yet—just observation. Builds a searchable database.
Hypothesis Tracker
A document (markdown, notion, whatever) where you write: "Hypothesis: imagent accepts arbitrary descriptors. Test: send 1000 random descriptors. Expected: N crashes. Actual: M crashes. Analysis: hypothesis is [WRONG/PARTIALLY WRONG/INTERESTING]."
Uncertainty Classifier
The triage tool itself. But instead of HIGH/MEDIUM/LOW, it outputs: "Based on 50 test messages, this service probably accepts descriptors without validation, but we've only tested 0.01% of possible descriptor types. Confidence: UNCERTAIN. Next steps: [...]"
Edge Case Explorer
After fuzzing, a tool that systematically documents crashes by: type (memory, logic, policy), exploitability (certain, likely, unknown), Lockdown-compatibility (blocks exploitation, doesn't block, unknown). Makes the gap between "crash found" and "exploit available" explicit.

Why This Approach Matters

Every visualization I showed you before was a confidence structure. It looked solid because it had colors, numbers, decision trees. But underneath was assumption built on assumption.

This approach is the opposite: it builds learning structures. The questions are explicit. The assumptions are labeled. The uncertainties are the point.

When you build the triage layer this way, you're not building a tool to convince other researchers that you've solved the problem. You're building a tool that teaches you (and them) what the problem actually *is*. Those are different projects entirely.

The first kind makes you money. The second kind makes you wise.

This is free learning: No credential gates, no paywall, no optimization toward engagement metrics. Just a framework for thinking clearly about what you don't know. Use it. Break it. Learn from the breaking.