CodeColorist
See No Eval: Runtime Dynamic Code Execution in Objective-C

See No Eval: Runtime Dynamic Code Execution in Objective-C

I designed the challenge Dezhou Instrumentz for RealWorldCTF. For further explaination I gave a talk regarding the motivation and expected solution for it:

The challenge is about abusing runtime feature of Objective-C to execute arbitrary unsigned code on iOS (even with PAC). This is surprising because dynamically parsing and executing code (eval) is usually seen in script interpreters, not for a compiled language like Objective-C. I didn't have too much time preparing that talk so I'm about to add more detail on the chapters it didn't cover.

NSPredicate and NSExpression

They are both from the Foundation framework, well-documented as below.

Both of them accept a format string to compile to an abstract synatx tree. This is done by the function _qfqp2_performParsing from Foundation.framework.

Here is an example:

name == 'Apple'

It's gonna be translated to the following abstract syntax tree. The execution is directly on this tree, no byte code or just-in-time compilation involved.

Abstract Syntax Tree

NSExpression can be an operand of another NSPredicate instance, or used independently. In fact, the initializer of NSExpression simply creates a new NSPredicate and returns one of the operand.

It supports compound mathematical expressions, so we can use NSExpression to create a calculator. All of those arithmetic operators are going to be translated to invocations on a private class _NSPredicateUtilities.

Foundation.framework/_NSPredicateUtilities.h

Furthermore, we can extend the operators by dynamically adding methods to this class, just like what I did in the challenge:

DezhouInstrumenz/DezhouInstrumenz/Math.swift.gyb

Arbitrary Code Execution

Besides the common usage, there are also special operators that allows arbitrary runtime invocation.

It's clearly documented in the official documentation of NSExpression that it supports Function Expresion, that allows performing arbitrary selector.

Function Expressions

In OS X v10.4, NSExpression only supports a predefined set of functions: sum, count, min, max, and average. These predefined functions were accessed in the predicate syntax using custom keywords (for example, MAX(1, 5, 10)).

In macOS 10.5 and later, function expressions also support arbitrary method invocations. To use this extended functionality, you can now use the syntax FUNCTION(receiver, selectorName, arguments, ...), for example:

FUNCTION(@"/Developer/Tools/otest", @"lastPathComponent") => @"otest"

So this is a [obj performSelector:NSSelectorFromString(str)] equivalent.

Generally we can use string and number literals in the expression, which will be translated to NSString and NSNumber respectively. There is a CAST operator that allows converting datatypes with lossy string representations, for example, CAST(####, "NSDate"). It doesn't mention that when the second parameter is Class, this equals NSClassFromString.

With arbitrary class lookup and arbitrary selector invocation, we now have full Objective-C runtime access.

For example, this line of code reads out the content of /etc/passwd

Here is a python script for converting payloads to expression format:

This is very close to even better than the SeLector-Oriented Programming by Project Zero. It's just like the eval() function of Objective-C. PAC doesn't stop this sort of execution at all.

If you prefer classic ROP, here is a method for PC-control, -[NSInvocation invokeUsingIMP:]. A problem for this method is that it won't do anything when the target property is nil. There is no way for both setting initializing the property and then reuse the reference to call its method, because predicates don't support lines and variables (at this moment).

Luckily I found this gadget -[NSInvocationOperation initWithTarget:selector:object:] that can initialize the property and return the reference in a chaining call favor.

So the payload for PC-control looks like this:

You can defeat ASLR by leveraging -[CNFileServices dlsym::] or -[ABFileServices dlsym::]. If those classes are not avaliable, use NSBundle to load their modules first.

Writing An Interpreter

Both NSExpresson and NSPredicate acts as an interpreter that exposes runtime reflection interfaces to a dynamic string (scripting). There are several frameworks that have similar design, but for different purposes:

Dynamically loading remote script to execute native methods is considered voilating AppStore review guide.

This includes any code which passes arbitrary parameters to dynamic methods such as dlopen(), dlsym(), respondsToSelector:, performSelector:, method_exchangeImplementations(), and running remote scripts in order to change app behavior or call SPI, based on the contents of the downloaded script. Even if the remote resource is not intentionally malicious, it could easily be hijacked via a Man In The Middle (MiTM) attack, which can pose a serious security vulnerability to users of your app.

Message from Apple Review... - Apple Developer Forums

Compared to known dyanmic execution frameworks, NSExpression and NSPredicate are totally legitimate. You don't have to introduce suspecious symbols like NSSelectorFromString, the runtime does the job for you. The code pattern is hard to spot. It looks like you're just filtering an array with a dynamic predicate. Innocent, isn't it?

Though we've got access to Objective-C runtime, there are some limitations for the expression that makes it hard to program the payload.

  • It's only an expression, so an one-liner at a time
  • No control flow. However, we can use compound logic operators to partially implement it
  • No local variables. There is a workaround.
  • Still powerful to do plenty of things.

Because of those limitations, we can't initialize an object and call its different methods multiple times, unless the API is designed to be chaining calls. For example, it's impossible to call the following methods one by one.

Fore local variables, there is Assignment Expression. The syntax looks like this:

It's only avaliable when the context argument of the method -[NSExpression expressionValueWithObject:context:] is a valid NSMutableDictionary, then the evaluation result writes a key-value pair back to this mutable dictionary. Just reuse the same context in a loop, we can have a script interpreter that supports variables.

A sample script that concats two strings:

Potential Attack Surfaces

We knew that it's better to use parameter binding to avoid SQL injection. There is parameter binding for predicates, too.

There is a family of methods for creating predicate out of a string.

  • +[NSPredicate predicateWithFormat:]
  • +[NSPredicate predicateWithFormat:argumentArray:]
  • +[NSPredicate predicateWithFormat:arguments:]
  • +[NSExpression expressionWithFormat:]
  • +[NSExpression expressionWithFormat:argumentArray:]
  • +[NSExpression expressionWithFormat:arguments:]

For those methods that accepts arguments or argumentArray, they are safe to use because they work just like parameter binding. But if you create a dynamic string from user input and feed it to the format string, it's going to be both a format string vulnerability and a code injection. At the time of writing this article, Xcode doesn't consider them format string bugs and no warning is generated.

So how does Apple itself prevent the code injection?

Serialization

Both NSPredicate and NSExpression are NSSecureCoding serializable.

Only keyed-coding is allowed. Besides, there is a flag that determines whether the expression is executable.

If the decoder class confirms to NSSecureCoding, the executable flag will be disabled:

The caller must explicitly calls -[NSPredicate allowsEvaluation] before using it.

Sanitization

The abstract syntax tree is created once the predicate is compiled. We can always manually check the type of each nodes and write visitors on your own like this:

The snippet above is from Foundation to validate the predicate field of App Extensions.

App Extension Programming Guide: Handling Common Scenarios

The keys in the NSExtensionActivationRule dictionary are sufficient to meet the filtering needs of typical app extensions. If you need to do more complex or more specific filtering, such as distinguishing between public.url and public.image, you can create a predicate statement. Then, use the bare string that represents the predicate as the value of the NSExtensionActivationRule key. (At runtime, the system compiles this string into an NSPredicate object.)

Besides, there is an undocumented protocol for visiting the AST:

We can create a delegate to visit all the nodes to check whether the operations are safe. These methods are from dyld_shared_cache:

For example. PHQuery is associated to PHFetchOptions class when reading from photos. Without a proper validation, it could be an inter-process attack surface to bypass TCC. I've seen similar validations in a developer disk image daemon, a possible persistence vector that doesn't require rootfs remount (I need to remind you again, this execution technique works on PAC), the log command of macOS that is able to get arbitrary task ports.

So I guess it's hard to find real cases in Apple's own code because they handled it so carefully.

Follow-ups

Updated on Apr 2022

Seems like this post inspired some exploit technique in the wild: FORCEDENTRY: Sandbox Escape

Updated on Oct 2023

An analysis of an in-the-wild iOS Safari WebContent to GPU Process exploit

meme

Updated on Jan 2024

Another round. Part of Operation Triangulation's attack chain.

attack chain

Operation Triangulation’ attack chain