From JavaScript to Objective-C: iOS Userland Exploitation, pwn1OS in N1CTF
Thank the author for the great challenge. We S1uM4i got the first blood of this challenge and we are the only team that solved it. The challenge is very interesting and I learned a lot from it. I will try to explain the exploitation in detail.
Analysis
The application registers a URL Scheme, you can find it in Info.plist
1 | <key>CFBundleURLSchemes</key> |
So we can open the application by opening a URL like n1ctf://aaa/bbb/ccc
. The application will parse the URL and do some actions according to the URL.
1 | void __cdecl -[SceneDelegate scene:openURLContexts:](SceneDelegate *self, SEL a2, id a3, id a4) |
Then do a classical class dump first
1 | @interface ScriptInterface : NSObject |
And an instance of ScriptInterface
is explosed to the context of JavaScript
1 | v11 = objc_msgSend_new(&OBJC_CLASS___ScriptInterface, v18); |
All these are the subclass of ScriptInterface
. So we can call their methods in the context of JavaScript.
Also
1 | + (_Bool)isSelectorExcludedFromWebScript:(SEL)arg1; |
always returns NO
, which means all the methods are exposed to JavaScript. Considering the following code:
1 | a = n1ctf.$makeCoreService(); |
We can see that the dealloc
method is exposed to JavaScript. So we can call dealloc
on any object to free it. This is a very powerful primitive that let us use after free any ScriptInterface
and subclass object.
In -[CoreService dealloc]
, it will call -[NSInvocation invoke]
on its property @property NSInvocation * cancelRequest;
. So we can craft a fake NSInvocation
object to call any method on any object. For example, +[BackDoor getFlag:]
. (Actually in a challenge designed by me for my undergraduate school’s CTF, there is a similar technique, e.g. using NSInvocation
to call arbitrary C functions`)
To build up the addrof()
primitive, we can make use of the error message when calling a non-exist method on and object. I took the implementation from CodeColorist’s writeup for CVE-2021-1748
1 | // copy from CVE-2021-1784 |
Another challenge is how to do the heap spray. Luckily we have a method
1 | @interface HTTRequest : ScriptInterface |
-[HTTRequest addMultiPartData:]
takes a base64 encoded string as input, and decodes it to store in NSData *_data
. So we can use this method to do heap spray.
Exploit
So the exploitation looks like this:
- Free a
CoreService
object - Reallocate it and perform a type confusion, convert it to a
NSConcreteData
object to do memory disclosure - Leak dyld_shared_cache, pwn1OS base address, tagged
NSMethodSignature
address andcookie
- Set up the arguments for
+[BackDoor getFlag:]
- Craft fake
NSInvocation
object to call+[BackDoor getFlag:]
Script
I have no idea about how to write elegant JavaScript so don’t blame me.
The full payload generated script is here:
1 | String.prototype.toDataURI = function () { |