Hacking IOS Xamarin apps with Frida

During a recent test I was tasked with breaking an API used by an iOS app, I did the usual thing of setting up a fake Wireless AP and transparently piping all web traffic through Burp but straight away noticed that the app was signing all requests with an Amazon-Style signing process. While I could see the requests in Burp I couldn’t replay or alter them and the API wouldn’t accept anything that wasn’t signed.

Extracting the IPA file resulted in a whole folder full of what looked like .net DLL files and associated “aotdata” files..

britney

Further digging showed that the app was based on the Xamarin framework and this was totally normal and not something to panic about. At this point I thought “hey, this is neat, I can throw these into dnSpy and read some nicely formatted code!”, which I did and was greeted with nothing but unimplemented empty headers for classes and methods.

It turns out that Apple don’t allow any kind of exciting emulated or interpreted stuff on their platforms, Xamarin (being based on .net) would fall into this category. To get round this the code is compiled “ahead of time” (AOT) and dumped into a bunch of files that I couldn’t figure out how to decompile. The Mono runtime performs some magic to load these into memory on demand and redirect function calls to them. This means two things:

1) This is probably way beyond my ARM reverse engineering skills

2) Frida cant see any of the functions to run the frida-trace tool on them.

At this point I took a step back to actually redefine this problem and focus myself a bit, I decided the ultimate aim was to find the values being used to sign requests. Once I had those there was an Android version that I could probably reverse engineer the signing process from.

The C# headers I mentioned above turned out to be very useful for this, eventually after some digging I found some classes that contained fields called “AccessId” and “SecretKey”  in a class called “AppSettings”, however they werent being set at initialisation time so the values werent in the C# parts of the app for me to read. There were also two methods called “getAccessId” and “getSecretKey” that probably did what I needed them to do.
So from there there were two obvious paths for me to take:

1) Instantiate a copy of the Settings class and call the “get” methods for the two fields that I wanted to read

or

2) Intercept a call to both of those methods and steal the results from the return values.

I managed to do both of these and my notes are below should anyone else be stuck in this situation 🙂

Tools Used

There are a whole bunch of really useful tools for dealing with Mono code via Frida, I’d recommend installing:

Method 1: Instantiating the config class

First step is to install all of the tools above and make sure they work. Deploy Frida to a rooted iPhone or bundle the gadget in a repackaged/signed IPA first, there are plenty of tutorials on the Internet to do this so I wont repeat them here.

We’re going to inject a script into the app that instantiates the classes that we want and then call methods on it, to do this we need a wrapper script:

const FridaInject = require('frida-inject')
const Frida = require('frida')

async function main() {
    const device = await Frida.getUsbDevice();

    FridaInject({
      // target app
      name: 'Thingy',
      device: device,
      clean: true,
        scripts: [
            'console.log("Lets break stuff!")',
            // file name of the js to inject
            'getkeys1.js'
        ],
        onAttach: session => console.log('Attached to process'),
      onDetach: (session, reason) => console.log('Detached from process'),
        onLoad: script => console.log('Script loaded'),
                          onUnload: script => console.log('Script unloaded')
      });

}
main()

Now we’re going to walk through the writing of the “getkeys1.js” script. This script is going to:

  • Find C# Assemblies that contain the classes we want to use
  • Find the Methods and classes we want to use within the assemblies
  • Instantiate an “AppSettings” class
  • Run the “getAccessId” and “getSecretKey” methods on the new object
  • Unbox the structs that the methods have returned. I’ll go into unboxing of things as we encounter them

For anyone thats done it this is really similar to programming in C# using reflection, you first have to find a reference to a Class, then its constructor, then create an object, call the constructor on it etc..

The first step is to write a helper to find references to Classes, this gets used a lot so its worth spending the time on it:

function getClass(name){
    var k = 0;
    MonoApiHelper.AssemblyForeach(function(assemb){
        var image = MonoApi.mono_assembly_get_image(assemb);
        var klass = MonoApiHelper.ClassFromName(image, name);
        if (klass != 0){
            k = klass;
        }
    });
    return k;
}

This returns a pointer to a given class or 0 if not found, use it like this:

var appValueClass = getClass("com.thingy.IOS.Platform_Specific.AppSettings");

Once you have this you can start creating objects of its type, to start you need a reference to the current AppDomain. Everything you create will be within this, think of it as a way of segmenting C# apps from each other, luckily the Mono runtime (and by extension the Frida Mono tools library) provides methods for this:

const domain = MonoApi.mono_domain_get()

Now you can create an empty object of this type:

var iosAppSettingsObj = MonoApiHelper.ObjectNew(appValueClass, domain);

This variable is a pointer to the newly allocated object, note that this does not run the constructor method so its likely that nothing in it has been initialised and the fields we want are empty. We need to run the “.ctor” method to get the object ready for use

Referring to the decompiled C# code we can see that the “IOSAppSettings” class contstructor requires an instance of “Plugin.Settings.SettingsImplementation” to run. We can go and get a reference to this class like we did above, create an empty one and run its constructor:

// find a class definition for the object
var pluginKlass = getClass("Plugin.Settings.SettingsImplementation");
// create the object
var pluginObject = MonoApiHelper.ObjectNew(pluginKlass, domain);
// get a ref to the constructor method
var pluginConstructor = MonoApiHelper.ClassGetMethodFromName(pluginKlass, ".ctor");
// apply the constructor method to the object:
var result = MonoApiHelper.RuntimeInvoke(pluginConstructor, pluginObject, NULL);

Done! the “pluginObject” is now ready to be stuck into the constructor for the IOSAppSettings object!

As before we get a reference to its constructor, apply it to the object we created, call it (with the plugin object above). I also noticed a method called “LoadApplication” that looks important, so lets find and run that too:

//find the constructor
var appValConstructor = MonoApiHelper.ClassGetMethodFromName(appValueClass, ".ctor");
//run it on the iosAppValueObject supplying "pluginObject" as a parameter to it
var res2 = MonoApiHelper.RuntimeInvoke(appValConstructor, iosAppValueObj, pluginObject);

// now find the "loadapplication" method
var method = MonoApiHelper.ClassGetMethodFromName(k,"LoadApplication", 0);
// run it on the iosAppValueObj we created above
var result = MonoApiHelper.RuntimeInvoke(method, iosAppSettingsObj, NULL);

By this point “result” should contain a fully instantiated and loaded copy of the app settings. It has some fields that we’re interested in and will try to read below.

First thing to do is get a list of Fields that the class has:

// list of "Fields" the class contains, we hope this has the secretkey one in it
var fields = MonoApiHelper.ClassGetFields(appValueClass);

We also need a reference to System.Guid and its “ToString” class:

var systemGuidKlass = getClass("System.Guid");
var guidToString = MonoApiHelper.ClassGetMethodFromName(systemGuidKlass, "ToString" );

Now we need to iterate over them and:

  • Read the value of the field
  • if the name is “AccessId” or “SecretKey” then pull the value out
  • Un-box the resulting value
  • call “ToString()” on the resulting GUID object and print it out

This is the hard part, I spent a day or so wondering why the result of the “ToString()” call looked like a pointer value, what I didnt realise is that System.Guid is actually a struct and not an object, in order to return it Mono boxes it in an object which is represented by… a pointer.

Following the pointer gave us the actual pointer to call “ToString()” on.

The code for the above looks a little like this:

// iterate over each one calling a function to process the field
fields.forEach(function (f) {
    // FieldGetValueObject retrieves the value of the field from an object
    var fr = MonoApiHelper.FieldGetValueObject(f, result);
    // this will be a value we print in a moment
    var v = "non";
    // Get the fields name
    var fn = MonoApiHelper.FieldGetName(f);

    // if the field is interesting then lets start the unboxing process
    if (fn == "AccessId" || fn == "SecretKey"){
        console.log("pulling out " + fn);
        // these are "System.guid" objects. So find the "ToString" method and call it
        // System.guid is a struct, which Mono boxes, so call the unboxing method to get the actual object
        var unboxfr = MonoApi.mono_object_unbox(fr);
        // now run the "ToString" method on it
        var r = MonoApiHelper.RuntimeInvoke(guidToString, unboxfr, NULL);
        v = MonoApiHelper.StringToUtf8(r);
        // v now contains a string value of the guid!
    } else {
        //field was boring, just convert it to a string and print it
        v = MonoApiHelper.StringToUtf8(fr);
    }
    //print "fieldname:value"
    console.log(fn+ " : " + v);

This will print all object fields and hopefully include the accessid and secretkey.

fields 0x107350d08,0x107350d28,0x107350d48,0x107350d68,0x107350d88,0x107350da8,0x107350dc8,0x107350de8
Name : Thingy.IOS
Version : 1.0.0.0
Language : en-GB
Region : GB
pulling out AccessId
AccessId : XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX
pulling out SecretKey
SecretKey : XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXX
Value : Thingy.IOS:1.0.0.0

Wasnt that fun!

There are some neat concepts demonstrated in the script above, we can find read the C# definitions of objects, find them, create them, run their methods and return the results.

Method 2: Intercepting C# method results

At the start of this post I mentioned how I couldnt seem to run frida-trace on any of the C# methods that were exposed by the AOT compiled code, after some further research I managed to find a method for intercepting C# method using the main Frida tool itself.

The general plan for this is:

  • Find the method name on a class we want to trace in the C# headers
  • Mangle it to match the odd format that the AOT compilation process applies to it
  • Find a reference to that function in memory using Frida
  • Write an Interceptor function to catch the function being called
  • Grab the response

First step is the same as before, we want to find the “GetSecretKey()” method on the
“com.Thingy.IOS.Platform_Specific.IOSSettingsValues” class. When the AOT compiler runs it creates a C method for every C# object/method compilation, when these methods are used the Mono runtime looks the method up in the Procedure Link Table (PLT) and loads the corresponding code using a dynamic loader.

The method names get messed with when they’re put in the PLT, for example “com.Thingy.IOS.Platform_Specific.IOSSettingsValues.GetSecretKey()” becomes “com_Thingy_IOS_Platform_Specific_IOSSettingsValues_GetSecretKey”.

You can hunt for these method names using Frida-CLI:

root@phoenix:~/fr# frida -U Thingy
     ____
    / _  |   Frida 12.4.1 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at http://www.frida.re/docs/home/

[iOS Device::Thingy]-> DebugSymbol.findFunctionsMatching("com_Thingy_IOS_Platform_Specific_*")                                               
[
    "0x100240e70",
    "0x100240f10",
    "0x100240f40",
    "0x100240f60",
    "0x100240ff0",
    "0x100241020",
    "0x1002410b0",
    "0x100241330",
    "0x100241470",
    "0x1002414a0",
    "0x1002415b0"
]
[iOS Device::Thingy]->  

“DebugSymbol.findFunctionsMatching” searches for functions using wildcards, the responses are pointers to the functions. They can be converted to names by piping it through a map() call:

[iOS Device::Thingy]-> DebugSymbol.findFunctionsMatching("com_Thingy_IOS_Platform_Specific_*").map(DebugSymbol.fromAddress).join("\n");      
.....
"0x100240e70 Thingy.IOS!Com_Thingy_IOS_Platform_Specific_IOSAppSettings_GetAccessKey
"0x100240ef80 Thingy.IOS!Com_Thingy_IOS_Platform_Specific_IOSAppSettings_GetSecretKey
.....

Notice the “GetAccessId” and “GetSecretKey” methods there? Bingo! Now you know the full name of the methods you can write an Interceptor tool:

// get a list of function pointers for the GetSecretKey method
var funcPtrs = DebugSymbol.findFunctionsNamed("com_Thingy_IOS_Platform_Specific_IOSAppSettings_GetSecretKey");
// its the first one in the list, trust me
var funcPtr = funcPtrs[0];

// set up the interceptor to trap the function above
Interceptor.attach(funcPtr, {
    onEnter : function (args){
        // called when the function starts, we dont care about this so just print a warning
        console.log("keys called, args : " + args[0]);
    },
    onLeave: function (ret){
        // this is called when the functions ends
        // ret should contain the return value from the function. See the notes below because this relates to ARM64 wierdness and the next line doesnt make sense without it
        var final = this.context.x1 + ":" + this.context.x0;
        console.log("Secret Key: " + final);
    }
});

Run this with

> frida -U -f Thingy -l key.js --no-pause 

Notice that -f is the full app name and not its display name. We want to start this script as the app starts up, so kill it first on the phone. “key.ps1” is the file containing the script above and –no-pause makes sure the app starts up once Frida attaches to it.

The app will start on the phone and the script will run, search for the function, trap it and wait for it to be called:

     ____
    / _  |   Frida 12.4.1 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at http://www.frida.re/docs/home/
Spawned `Thingy`. Resuming main thread!                      
[iOS Device::Thingy]-> keys called, args : 0x1256cd940
keys done..
access id : 0x048f9a1d0addaae6:0x5303273e5f98c140
keys called, args : 0x1256cd940
keys done..
Secret Key: 0x7d9c0ecacbff411b:0x336d1134fff7275a
[iOS Device::Thingy]->  

Now heres the fun part, the return value (ret) in the “onLeave” function contains half of the GUID in a mangled format. I think that the GUID is stored as a pair of Int64 values internally, if we examine the registers when the call ends we can see the other half in register x1:

{"pc":"0x1002e1020",
"sp":"0x16fd5b610",
"x0":"0x048f9a1d0addaae6",
"x1":"0x5303273e5f98c140",
...etc...
}

Reading the ARM64 documentation shows that registers X0->X7 are used as parameter passing and result returning registers (http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.den0024a/ch09s01s01.html). I cant quite see how to decode the values into a GUID automatically but it should be easy enough with enough thinking. During the actual test the values were quite obviously the same as the ones returned by method 1 so I’m counting this as a win 🙂

This method can be used to track and intercept all C# calls that the application makes, you just need to figure the name out first. Reading the value of arguments and responses is more of a challenge though, if the method returns an object it’ll likely return a pointer to it, you can follow this to the MonoObject it represents but you’ll need to start reading the Mono source code to figure out the format of the struct and pull data out. I’ve managed this for simple things like reading the name of a class and method from memory but it’d be more of a challenge for bigger objects.

Further research

Burp Integration

Someone has created a Burp plugin called “Brida” that allows you to call Native methods through Burp with data from it. This could be really useful when an application signs its requests (like the example above does) and it wasn’t possible to reverse engineer the signing process. It may be possible to bundle a burp request into an object and pass it into the signing tool on the app then get the phone to send it.

The challenge with this is getting Brida to spot the C# methods in the PLT as Frida itself doesn’t see them when you use “frida-trace” to find them.

If it cant then its probably possible to use the object creation stuff to do it, i.e. create whatever request object the app uses and fill it with our data. I dont know how Garbage collection works with this so its likely the app will die if Burp thrashes it with requests.

 

Advertisements

2 thoughts on “Hacking IOS Xamarin apps with Frida

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s