Getting Started with Predicate Injections
What is NSPredicate
NSPredicate is a class in Apple's Foundation framework. Apple's documentation describes it as "a definition of logical conditions for constraining a search for a fetch or for in-memory filtering." Think of it as C#'s LINQ, specifically the where clause, or a lightweight SQL filter. You write things like:
That feels harmless. But NSPredicate is built on top of NSExpression, and NSExpression supports a much richer syntax than simple comparisons. Since OS X 10.5 (2007, the dawn of iOS), NSFunctionExpression has supported the FUNCTION keyword, which allows arbitrary method invocations on arbitrary objects:
There is also CAST, which grants reflection-based access to any Objective-C class:
Together, these keywords turn NSPredicate format strings into what Security Researcher CodeColorist called the eval() of Objective-C. It is a full scripting language embedded in what looks like a simple filter API. As Project Zero's writeup notes: "NSPredicates using the FUNCTION keyword are effectively Objective-C scripts."
Evolution of NSPredicate Injections
2019: DezhouInstrumenz (Real World CTF)
Security researcher CodeColorist created a CTF challenge on a real iPhone XR requiring contestants to achieve code execution through a calculator app that evaluated NSExpression(format: userInput). The solution demonstrated that FUNCTION + CAST could call arbitrary methods, and that [CNFileServices dlsym::] combined with [NSInvocation invokeUsingIMP:] could bypass both ASLR and PAC to call any C function.
January 2021: See No Eval
CodeColorist published the blog post and talk See No Eval: Runtime Dynamic Code Execution in Objective-C, publicly documenting that NSExpression and NSPredicate format strings are a code injection vector. The post identified the vulnerable API families (predicateWithFormat:, expressionWithFormat:, etc.) and noted that parameter binding remains safe. The post also described Apple's NSPredicateVisitor protocol as a potential defense, and warned that without proper validation, it could be an inter-process attack surface.
September 2021: FORCEDENTRY
Citizen Lab revealed a zero-click iMessage exploit used by NSO Group's Pegasus to target a Saudi activist. Google Project Zero's analysis showed the sandbox escape relied entirely on logic bugs instead of memory corruption. The JBIG2 virtual machine's sole purpose was to deserialize and evaluate an NSFunctionExpression, which then sent a crafted NSPredicate via NSXPC to the unsandboxed CommCenter process. The trick was a PTSection containing a PTRow whose condition was an attacker-controlled NSPredicate. When deserialized, [PTRow initWithCoder:] called [self->condition allowEvaluation], and [PTSection _shouldEnableRow:] then called [row->condition evaluateWithObject:self->settings], finally executing arbitrary code in CommCenter.
iOS 15.1: Apple's First Mitigations
In response, Apple added:
- Method call restrictions: Only methods from
_NSPredicateUtilitieswere allowed inFUNCTIONexpressions. - CAST restrictions:
CAST("SomeClass", "Class")was forbidden. - Denylists: Lists of forbidden classes and selectors (e.g.,
NSBundle,NSInvocation,NSCoder) that posed clear security risks. - Removed gadgets:
[CNFileServices dlsym::]was removed; a magic canary was added toNSInvocation.
These were enforced by a global variable __predicateSecurityFlags but only for first-party Apple processes. Third-party apps had a near-empty denylist and were still fully exploitable.
February 2023: Apple's Predicament (CVE-2023-23530 & CVE-2023-23531)
Austin Emmitt at Trellix Advanced Research Center discovered that Apple's mitigations could be completely bypassed:
- CVE-2023-23530 Denylist Bypass: Using methods not on the denylist (like
[NSValue getValue:]or later[NSString getCString:]), an attacker could perform an arbitrary write to overwrite__predicateSecurityFlags, set denylist lengths to zero, and restore NSPredicate to its fully unrestricted state. A single flag value controlled all security restrictions. - CVE-2023-23531 NSPredicateVisitor Bypass: Nearly every daemon implements its own
NSPredicateVisitorto validate incoming predicates. These visitors check theexpressionTypeproperty to decide whether additional scrutiny is needed (e.g., filtering out function expressions). ButexpressionTypeis read directly from serialized data, and the sender sets it. By setting allexpressionTypevalues to 0 (constant value), function expressions masquerade as harmless constants and bypass all validation.
With these bypasses, Emmitt demonstrated code execution in coreduetd (root on macOS, accessing calendar/photos), appstored (installing arbitrary apps), OSLogService (reading sensitive logs), and SpringBoard on iPad, which can access camera, microphone, location, call history, and wipe the device. He also found a PAC bypass replacement for the removed dlsym gadget: +[DTCompanionControlServiceV2 dlsymFunc] invoked via -[RBStrokeAccumulator applyFunction:info:].
These were fixed in iOS 16.3 and macOS 13.2.
Finding Potential Predicate Injections
When untrusted input becomes part of the predicate string, the parser treats it as predicate syntax. An attacker can inject any predicate keyword to build a query as well as FUNCTION and CAST expressions to call arbitrary Objective-C methods in the target process. While looking for predicate injections, try to search for predicate-related symbols and strings.
Predicates are built using Objective-C method calls, so start by searching the symbol table for selector references:

In Swift binaries, the symbols are mangled. A hit like sym._$sSo11NSPredicateC10FoundationE6format_ABSSh_s7CVarArg_pdtcfC decodes to NSPredicate.init(format:). The U flag means it is imported from Foundation, confirming the app calls it.
Next, search for predicate query fragments in the binary's strings:

Fragmented strings like username == ' and ' AND password == ' appearing separately is a strong indicator. It means the predicate is being constructed dynamically by concatenating or interpolating user input between those fragments. A single static predicate string like username == %@ AND password == %@ is safe (that is parameter binding). Fragments split around user input are the vulnerability.
Use a disassembler/decompiler like radare2 to find which class and method constructs the predicate:
Once you identify the method, use a disassembler/decompiler like radare2 to disassemble it and trace how the predicate string is built.
Look for:
- String references that match the predicate fragments you found before
- Calls to
NSPredicate.init(format:)orpredicateWithFormat: - Data flow from user input sources (text fields, URL parameters, network responses) into the predicate string
If user-controlled data reaches the predicate string through concatenation or interpolation before it hits the parser, you have found a predicate injection.
If the predicate is passed to evaluateWithObject: or filteredArrayUsingPredicate:, it gets executed against an object in the target process.
Implications and Defense
Objective-C and Swift code that concatenates untrusted input into predicate strings is essentially running an interpreter on attacker data. This is the same mistake as SQL injection: treating user input as part of a query language, not a value.
A defensive approach should include:
- Parameter binding: always use
%@substitution inpredicateWithFormat:over string concatenation. This treats user input as a literal value, not predicate syntax. - Block-based predicates: use
NSPredicate.predicateWithBlock:instead of string-based predicates where possible, since blocks don't parse any language. - Never unarchive and evaluate untrusted
NSPredicateobjects: any predicate received over XPC or from a file should be considered untrusted. Validate allexpressionTypefields independently, do not trust the deserialized values. - Length and type checking on any dynamically constructed predicate strings, since even a simple heap overflow that overwrites a predicate string can be turned into reliable arbitrary code execution.
Try It Yourself
You can try our free lab Injecta Vault to learn how predicate injection vulnerabilities work.

References
- Ian Beer, A deep dive into an NSO zero-click iMessage exploit: Remote Code Execution, Google Project Zero, December 2021. https://projectzero.google/2021/12/a-deep-dive-into-nso-zero-click.html
- Ian Beer & Samuel Groß, FORCEDENTRY: Sandbox Escape, Google Project Zero, March 2022. https://googleprojectzero.blogspot.com/2022/03/forcedentry-sandbox-escape.html
- Apple Developer Documentation, NSPredicate, https://developer.apple.com/documentation/foundation/nspredicate
- CodeColorist (Zhi Zhou), See No Eval: Runtime Dynamic Code Execution in Objective-C, January 2021. https://speakerdeck.com/chichou/see-no-eval-runtime-dynamic-code-execution-in-objective-c
- CodeColorist, DezhouInstrumenz CTF challenge source code. https://github.com/ChiChou/DezhouInstrumenz
- Austin Emmitt, Trellix Advanced Research Center Discovers a New Privilege Escalation Bug Class on macOS and iOS, Trellix, February 2023. https://www.trellix.com/en-gb/blogs/research/trellix-advanced-research-center-discovers-a-new-privilege-escalation-bug-class-on-macos-and-ios/
- Austin Emmitt, Apple's Predicament: NSPredicate Exploitation on iOS and macOS, Black Hat USA 2023. https://www.blackhat.com/us-23/briefings/schedule/#apples-predicament-nspredicate-exploits-on-ios-and-macos-32451, Video: https://www.youtube.com/watch?v=jZj8EEBp8xE
- NVD, CVE-2023-23530 Detail, https://nvd.nist.gov/vuln/detail/cve-2023-23530; NVD, CVE-2023-23531 Detail, https://nvd.nist.gov/vuln/detail/cve-2023-23531
- junun0, NSPredicate exploit on iOS and macOS, November 2024. https://juuun0.github.io/blog/2024/11/01/nspredicate-exploit-on-ios-and-macos.html
- Apple Security Advisory HT213605, About the security content of iOS 16.3 and iPadOS 16.3, https://support.apple.com/en-us/HT213605; Apple Security Advisory HT213606, About the security content of macOS Ventura 13.2, https://support.apple.com/en-us/HT213606
Company
Registration:
97390453
VAT:
NL868032281B01