Frida for all! Pt. 2

Frida is a wonderful tool. Now that there’s no excuse for not having it installed (see previous post) let’s do a few worked examples.

NB - before the Accuracy Police come after me, the example presented below is illustrative only. It’s real code, and will work if an app ever behaves in this way, but it isn’t the only way to do something in frida, and it is mainly intended as a ‘worked example’ for educational purposes only!

Worked Examples:

See the Frida Docs for how to do the basics. I wrote a chunk of it, so I’m probably to blame if it doesn’t work!!

The important things you need to know are as follows:

Frida let’s you explore a function:

what it does require is at least the function name. You can use frida-trace to hook into a function, and give it wildcards in the -i option to get it to look through the module/function table in the process and find all possible matches (it’s a basic regex).

Let’s suppose we want to turn off certificate pinning in a mobile app connected over USB. First we might seek out all SSL-related functions like so: frida-trace -U -i '*SSL*' com.mycool.app

Let’s have a look at what these switches do:

  • -U tells frida to go over USB to a device. Else it will look at the local process list by default. You can also run it over port 27042 by using the -R flag to connect to a remotely running frida service. This is how best to do it for iOS at time, and can be achieved by portforwarding over ssh with;
    • ssh -L27042:localhost:27042 root@(deviceIP)
  • -i tells frida what functions to instrument. It takes wildcards (!!!) which is really really useful!
  • com.mycool.app is the app name. If you don’t know it, but know the PID from frida-ps, you can use -p (PID) instead.

Now that we have the list of functions loaded, frida-trace will tell you which ones are invoked at what point, and it will do this in real time. If the app is cert. pinned, then you’ll see something like SSL_VERIFY firing as a function name, but not getting anywhere after that. Let’s make a leap of logic and assume that this is our function we want to have a play with - how do we do that?

Frida lets you interact with a process’ functional flow:

Now we know that our function is SSL_VERIFY, how do we affect it? This is where we start writing a python script.

First, here’s a skeleton script:

import frida 	#we've all done this...
import sys 	#I tend to include this so I can bring in args

# First, let's attach to the process:
session = frida.get_usb_device(1000).attach("com.mycool.app")

# Now we create a script:
script = session.create_script("""
JAVASCRIPT GOES HERE (we're getting to this)
""")
# Here's how we decide how to process messages from the process we make 
# by using send() in the javascript...
# First we create a message handler function:
def on_message(message, data):
    print(message)

# and then tell frida to use this function to process the messages:
script.on('message', on_message)
# This is the line that makes the magic happen!
script.load()
# This is the line that keeps the script open...
sys.stdin.read()

Now that we have our skeleton script, let’s think about what we want to do in the JS API for Frida.

What we need is to be able to intercept the function called SSL_VERIFY, and change its behaviour. What behaviour is this? Well, let’s think how a certificate pinning function generally works:

  1. the app starts and SSL connection with some func
  2. The app invokes SSL_VERIFY to check the certificate is valid
    • this returns True/1/Valid/whatever if the certificate is valid
    • this returns False/0/invalid/negative-whatever if the cert isn’t valid

So let’s first see what the output is by writing the following javascript:

// Let's tell the frida script to find the pointer to SSL_VERIFY...
point3r = Module.findExportByName(null, "SSL_VERIFY");
// Note that the 'null' can be a .dll or .so file that contains the function we want to hook.
//
// and now let's intercept it, and pull out the return value (retval) as follows:
Interceptor.attach(ptr(point3r), {
	onEnter: function(args) {
		// Here we can affect the input argument array...
		// But that is not our goal this day!
	},
	onLeave: function(retval) {
		// Here we can read the values out:
		send("The output is: " + hexdump(retval));
	}
});

Hope fully this example is reasonably obvious… If not, then I suggest that the reader look at other sources of knowledge first - a grasp of python and JS is probably required to understand this. Also, look at how the stack is made/maintained for a process would be handy…

Anyway, let’s put all this together:

import frida    #we've all done this...
import sys      #I tend to include this so I can bring in args

session = frida.get_usb_device(1000).attach("com.mycool.app")
script = session.create_script("""
point3r = Module.findExportByName(null, "SSL_VERIFY");
Interceptor.attach(ptr(point3r), {
        onEnter: function(args) {
        }, // <-- this comma is really important... remeber it...
        onLeave: function(retval) {
                send("The output is: " + hexdump(retval));
        }
});
""")

def on_message(message, data):
    print(message)

script.on('message', on_message)
script.load()
sys.stdin.read()

When this runs, the script will output (as python JSON to screen) the value that it spits out when we try and do a successful/unsuccessful SSL connection. Let’s suppose that SSL_VERIFY spits out a 0 if the SSL is good, and other error codes if something is wrong. The following script will interact with the retval and proceed to return 0 as if everything is fine…

import frida    #we've all done this...
import sys      #I tend to include this so I can bring in args

session = frida.get_usb_device(1000).attach("com.mycool.app")
script = session.create_script("""
send("I'm in...");
point3r = Module.findExportByName(null, "SSL_VERIFY");
Interceptor.attach(ptr(point3r), {
        onEnter: function(args) {
        }, // <-- this comma is really important... remember it...
        onLeave: function(retval) {
                send("Sending fake answer...");
		retval.replace(0);
		send("Done my magic...");
        }
});
""")

# This is much prettier...
def on_message(message, data):
    if message['type'] == 'error':
        print("[!] " + message['stack'])
    elif message['type'] == 'send':
        print("[i] " + message['payload'])
    else:
        print(message)

script.on('message', on_message)
script.load()
sys.stdin.read()

Now, this is a relatively untested script (mainly because it’s totally made up…) but this should demonstrate how to think about approaching app instrumentation with frida! :D

Moar to follow… probably some actually worked examples, with notes as we go for.. something?