FRIDA: Objective-C hook n+1 arguments

  • We will read a method that has two arguments. For this purpose, we will write a sample program to call [fileManager fileExistsAtPath: isDirectory:].

  • We will also use FRIDA's REPL to get the method pointer differently.

Sample code:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSString *filepath = @"/Users/azurda/Desktop/test.c";
        NSFileManager *fileManager = [NSFileManager defaultManager];
        BOOL res;

        if ([fileManager fileExistsAtPath:filepath isDirectory:&res]) {
            NSLog(@"File exists.");
        }
        else {
            NSLog(@"File does not exist.");
        }
    }
    return 0;
}

isDirectory will be nil in case the target path is not a folder.

Now we build the file and fire up frida's REPL. From there, we will now see how to get the pointer to a method using the ObjC.classes object which maps ObjC classes to JavaScript objects. If we write in FRIDA's REPL ObjC.classes we can see that it begins to autocomplete:

Screenshot 2021-02-16 at 22.54.09.png

For instance if we want to know all methods available in ObjC.classes.NSFileManager we can do it using $ownMethods which returns an array containing native method names exposed by the object class:

[Local::objCLI]-> ObjC.classes.NSFileManager.$ownMethods
[
    "+ defaultManager",
    "- dealloc",
    "- delegate",
    "- setDelegate:",
    "- fileExistsAtPath:",
    "- createDirectoryAtPath:withIntermediateDirectories:attributes:error:",
    "- createDirectoryAtURL:withIntermediateDirectories:attributes:error:",
    "- homeDirectoryForCurrentUser",
    "- URLsForDirectory:inDomains:",
    "- getRelationship:ofDirectoryAtURL:toItemAtURL:error:",
    "- enumeratorAtURL:includingPropertiesForKeys:options:errorHandler:",
    "- temporaryDirectory",
    "- stringWithFileSystemRepresentation:length:",
    "- removeItemAtPath:error:",
    "- enumeratorAtPath:",
    "- contentsOfDirectoryAtPath:error:",
    "- isExecutableFileAtPath:",
    "- destinationOfSymbolicLinkAtPath:error:",
    ...

If we take a look at the list, we can see that the - fileExistsAtPath:isDirectory: method is available to us. We can access it and then its member .implementation which returns a pointer to the mapped object:

[Local::objCLI]-> t = ObjC.classes.NSFileManager['- fileExistsAtPath:isDirectory:'].implementation
function
[Local::objCLI]-> ptr(t)
"0x7fff2117d115"

Once we have this information, we can do something similar to what we have seen before except that this time it won't be args[2] the element storing our interesting parameter but args[3] instead:

Interceptor.attach(ptr(t), {
   onEnter: function(args) { 
     this.isDir = args[3];
   }, 
   onLeave: function(retval) {
     let objCIsDir = new ObjC.Object(this.isDir); console.log(objCIsDir); 
   }
})

And once we %resume:

[Local::objCLI]-> %resume
2021-02-16 22:48:51.725 objCLI[96805:23540150] File exists.
[Local::objCLI]-> nil

It says our .c file is not a directory so the parameter is set to nil after method execution.