0x01. Intro
The NSPredicate
was first mentioned in the iMessage 0-click exploit chain, also known as FORCEDENTRY, and it was used to escape to sandbox. Security mitigations such as PAC, code signing, sandboxing, and ASLR make exploitation more difficult in iOS.
In this article, we will break down what NSPredicate is and how it is used.
This post is based on the Black Hat USA 2023 Presentation! You can watch the full video on their YouTube.
0x02. Background Knowledge
Before that, we need to understand selector, KeyPath, and NSPredicate.
a. selector & KeyPath
selector
According to the documentation, a selector has two meanings in Objective-C:
It can be used to refer simply to the name of a method when it’s used in a source-code message to an object. It also, though, refers to the unique identifier that replaces the name when the source code is compiled.
Here is a sample code snippet in Objective-C:
#import <Foundation/Foundation.h>
// Define a simple class
@interface Greeter : NSObject
- (void)sayHello;
@end
@implementation Greeter
- (void)sayHello {
NSLog(@"Hello from Objective-C!");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Create an instance of Greeter
Greeter *greeter = [[Greeter alloc] init];
[greeter sayHello];
}
return 0;
}
The ‘Greeter’ class has a ‘sayHello’ instance method, and it is allocated and initialized before calling the method. Finally, we reach the [greeter sayHello];
call. According to the Objective-C syntax, sayHello
is the selector inside the brackets - this is the first meaning of a selector.
The second meaning of a selector can be found in the compiled binary. In the following code, the ‘Buyer’ class is defined with a ‘sayHello’ method, just like Greeter, but with different behavior.
#import <Foundation/Foundation.h>
// Define a simple class
@interface Greeter : NSObject
- (void)sayHello;
@end
@interface Buyer : NSObject
- (void)sayHello;
@end
@implementation Greeter
- (void)sayHello {
NSLog(@"Hello from Objective-C!");
}
@end
@implementation Buyer
- (void)sayHello {
NSLog(@"Goodbye from Objective-C!");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Create an instance of Greeter
Greeter *greeter = [[Greeter alloc] init];
[greeter sayHello];
Buyer *buyer = [[Buyer alloc] init];
[buyer sayHello];
}
return 0;
}
Here is the result of the decompiled binary:
100000a88 struct objc_method_entry_t method_sayHello =
100000a88 {
100000a88 rptr_t name = selRef_sayHello
100000a8c rptr_t types = selTypes_sayHello
100000a90 rptr_t imp = -[Buyer sayHello]
100000a94 }
100000a98 struct objc_method_list_t method_list_Greeter =
100000a98 {
100000a98 uint32_t obsolete = 0x8000000c
100000a9c uint32_t count = 0x1
100000aa0 }
100000aa0 struct objc_method_entry_t method_sayHello =
100000aa0 {
100000aa0 rptr_t name = selRef_sayHello
100000aa4 rptr_t types = selTypes_sayHello
100000aa8 rptr_t imp = -[Greeter sayHello]
100000aac }
Both classes have methods with the same name, but they perform different operations. However, the binary treats them as the same selector - that’s why the second meaning of a selector is a unique identifer at runtime.
KeyPath
In some cases, a nested class is needed during development. While a single property is easy to access, this one is not. If you access multiple properties directly, it becomes complicated, so you can use a KeyPath - a predefined path used as a variable.
b. NSPredicate
NSPredicate is defined in the Apple Developer Documentation as:
A definition of logical conditions for constraining a search for a fetch or for in-memory filtering.
NSPredicate is composed of three parts: a target, a comparison operator, and a value. The following is an example of how to use it:
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSArray *people = @[
@{@"name": @"Alice", @"age": @30},
@{@"name": @"Bob", @"age": @25},
@{@"name": @"Charlie", @"age": @35}
];
// Predicate: age >= 30
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"age >= %d", 30];
NSArray *filtered = [people filteredArrayUsingPredicate:predicate];
NSLog(@"People aged 30 or older: %@", filtered);
}
return 0;
}
The people array contains three dictionaries, and each one has name and age fields. The filtered array contains the dictionaries where the age is greater than or equal to a specified value, using NSPredicate.
- target:
age
- comparison operator:
>=
- value:
30
c. XPC
XPC is commonly used in iOS and macOS to call methods in a remote process from the current process; It is an interprocess communication mechanism. Arguments and callback functions can also be passed, and NSPredicate is often used to filter the returned results.
0x02. Deep Dive
This video goes into more detail about how NSPredicate is structured. You don’t need to understand everything, but try to remember this: The leftExpression, which was referred to as the ‘target’ when explaining NSPredicate, can be either an NSFunctionExpression or an NSKeyPathExpression. That also means predicates are built using NSExpression and NSPredicateOperator instances.
As mentioned in See No Eval, function expressions enable various tricks - including PAC mitigation bypasses using methods like [CNFileServices dlsym::]
and [NSInvocation invokeUsingIMP:]
.
[CNFileServices dlsym::]
: Can be used to obtain the signed address of C function.[NSInvocation invokeUsingIMP:]
: Can be used to call a function pointer by bypassing PAC.
a. scripting with NSPredicate
The following syntax is used to write expressions in the format field of an NSPredicate:
- NSVariableExpression:
$VarName
=> Declare variable. - NSVariableAssignmentExpression:
$VarName := Value
=> Assign value into variable. - NSFunctionExpression:
FNCTION( ... )
=> Function Expression
b. mitigations #1
In iOS 15, three limitations were added to use of NSPredicate after the disclosure of FORCEDENTRY exploit chain:
- Action limitations using a denylist - certain actions are restricted via a denylist in NSPredicate.
CAST()
restrictions - The use of “Class” as the second argument inCAST()
is no longer allowed.- Method call limitations - Only methods from the
_NSPredicateUtilities
class are allowed when invoking methods.
These changes are enforced by the global variable __predicateSecurityFlags
, but they only restrict first-party apps. In addition, [CNFileServices dlsym::]
was removed and a magic canary was introcued for NSInvocation, which has greatly restricted the possible ways it can be exploited.
c. bypassing #1
The above mitigations were bypassed using an arbitrary write via the -[NSValue getValue:]
method, which was used to overwrite the __predicateSecurityFlags
variable.
FUNCTIOn("\x00", "getValue:", $__predicateSecurityFlags)
d. mitigations & bypassing #2
To strengthen the initially insufficient mitigations, pointer-type arguments were restricted in function expressions. Nevertheless, this restriction can still be bypassed using -[NSString getCString:]
in place of getValue.
FUNCTION("\x00", "getCString:", addr)
e. bypass PAC
As a replacement for the removed [CNFileServices dlsym::]
, the method +[DTCompanionControlServiceV2 dlsymFunc]
can be used and triggered through -[RBStrokeAccumulator applyFunction:info:]
.
“DT” belongs to a framework related to Developer Tools, while RB appears to be part of the RenderBox framework. As this information is not publicly documented, reverse engineering my be necessary to verify it.
Although the added mitigations make exploitation more complicated, the presentation states the PAC could still be bypassed using this technique up to iOS 16.3 Beta.
0x03. Exploitation
a. CVE-2023-27937
Each daemon has its own implementation os NSPredicateVisitor, which inspect the expressionType
field to detect potentailly dangerous expressions. Yet, during deserialization, the expressionType
is parsed from serialized input without validating the sender process.
If all expressionType
values are set to 0, the validation can be bypassed, leading to a vulnerability identified as CVE-2023-27937.
0xFF. References
- YouTube - Apple’s Predicament: NSPredicate Exploits on iOS and macOS
- Apple Developer Documentation - Selectors
- Tistory article - selector in ObjC
- StackOverflow - About KeyPath
- Apple Developer Documentation - NSPredicate
- Tistory article - NSPredicate
- Medium article - NSPredicate + Objective-C
- Apple Developer Documentation - XPC
- Medium article - Breaking iOS: XPC