Skip to content

iOS Frida Configuration โ€‹

Learn AWS hacking from zero to hero with htARTE (HackTricks AWS Red Team Expert)!

Other ways to support HackTricks:

WhiteIntel โ€‹

WhiteIntel is a dark-web fueled search engine that offers free functionalities to check if a company or its customers have been compromised by stealer malwares.

Their primary goal of WhiteIntel is to combat account takeovers and ransomware attacks resulting from information-stealing malware.

You can check their website and try their engine for free at:

โ›“๏ธ External Link

Installing Frida โ€‹

Steps to install Frida on a Jailbroken device:

  1. Open Cydia/Sileo app.
  2. Navigate to Manage -> Sources -> Edit -> Add.
  3. Enter "https://build.frida.re" as the URL.
  4. Go to the newly added Frida source.
  5. Install the Frida package.

If you are using Corellium you will need to download the Frida release from https://github.com/frida/frida/releases (frida-gadget-[yourversion]-ios-universal.dylib.gz) and unpack and copy to the dylib location Frida asks for, e.g.: /Users/[youruser]/.cache/frida/gadget-ios.dylib

After installed, you can use in your PC the command frida-ls-devices and check that the device appears (your PC needs to be able to access it).
Execute also frida-ps -Uia to check the running processes of the phone.

Frida without Jailbroken device & without patching the app โ€‹

Check this blog post about how to use Frida in non-jailbroken devices without patching the app: https://mrbypass.medium.com/unlocking-potential-exploring-frida-objection-on-non-jailbroken-devices-without-application-ed0367a84f07

Frida Client Installation โ€‹

Install frida tools:

bash
pip install frida-tools
pip install frida

With the Frida server installed and the device running and connected, check if the client is working:

bash
frida-ls-devices  # List devices
frida-ps -Uia     # Get running processes

Frida Trace โ€‹

bash
# Functions
## Trace all functions with the word "log" in their name
frida-trace -U <program> -i "*log*"
frida-trace -U <program> -i "*log*" | swift demangle # Demangle names

# Objective-C
## Trace all methods of all classes
frida-trace -U <program> -m "*[* *]"

## Trace all methods with the word "authentication" from classes that start with "NE"
frida-trace -U <program> -m "*[NE* *authentication*]"

# Plug-In
## To hook a plugin that is momentarely executed prepare Frida indicating the ID of the Plugin binary
frida-trace -U -W <if-plugin-bin> -m '*[* *]'

Get all classes and methods โ€‹

  • Auto complete: Just execute frida -U <program>
  • Get all available classes (filter by string)
/tmp/script.js
javascript
// frida -U <program> -l /tmp/script.js

var filterClass = "filterstring";

if (ObjC.available) {
    for (var className in ObjC.classes) {
        if (ObjC.classes.hasOwnProperty(className)) {
            if (!filterClass || className.includes(filterClass)) {
                console.log(className);
            }
        }
    }
} else {
    console.log("Objective-C runtime is not available.");
}
  • Get all methods of a class (filter by string)
/tmp/script.js
javascript
// frida -U <program> -l /tmp/script.js

var specificClass = "YourClassName";
var filterMethod = "filtermethod";

if (ObjC.available) {
    if (ObjC.classes.hasOwnProperty(specificClass)) {
        var methods = ObjC.classes[specificClass].$ownMethods;
        for (var i = 0; i < methods.length; i++) {
            if (!filterMethod || methods[i].includes(filterClass)) {
                console.log(specificClass + ': ' + methods[i]);
            }
        }
    } else {
        console.log("Class not found.");
    }
} else {
    console.log("Objective-C runtime is not available.");
}
  • Call a function
javascript
// Find the address of the function to call
const func_addr = Module.findExportByName("<Prog Name>", "<Func Name>");
// Declare the function to call
const func = new NativeFunction(
   func_addr,
   "void", ["pointer", "pointer", "pointer"], {
});

var arg0 = null;

// In this case to call this function we need to intercept a call to it to copy arg0
Interceptor.attach(wg_log_addr, {
   onEnter: function(args) {
       arg0 = new NativePointer(args[0]);
   }
});

// Wait untill a call to the func occurs 
while (! arg0) {
   Thread.sleep(1);
   console.log("waiting for ptr");
}


var arg1 = Memory.allocUtf8String('arg1');
var txt = Memory.allocUtf8String('Some text for arg2');
wg_log(arg0, arg1, txt);

console.log("loaded");

Frida Fuzzing โ€‹

Frida Stalker โ€‹

From the docs: Stalker is Fridaโ€™s code tracing engine. It allows threads to be followed, capturing every function, every block, even every instruction which is executed.

You have an example implementing Frida Stalker in https://github.com/poxyran/misc/blob/master/frida-stalker-example.py

This is another example to attach Frida Stalker every time a function is called:

javascript
console.log("loading");
const wg_log_addr = Module.findExportByName("<Program>", "<function_name>");
const wg_log = new NativeFunction(
    wg_log_addr,
    "void", ["pointer", "pointer", "pointer"], {
});

Interceptor.attach(wg_log_addr, {
    onEnter: function(args) {
        console.log(`logging the following message: ${args[2].readCString()}`);
        
        Stalker.follow({ 
            events: {
                // only collect coverage for newly encountered blocks
                compile: true,
            },
            onReceive: function (events) {
                const bbs = Stalker.parse(events, {
                    stringify: false,
                    annotate: false
                });
                console.log("Stalker trace of write_msg_to_log: \n" + bbs.flat().map(DebugSymbol.fromAddress).join('\n'));
            }
        });
    },
    onLeave: function(retval) {
        Stalker.unfollow();
        Stalker.flush();  // this is important to get all events  
    }
});

โŒ

This is interesting from debugging purposes but for fuzzing, to be constantly .follow() and .unfollow() is very inefficient.

Fpicker โ€‹

fpicker is a Frida-based fuzzing suite that offers a variety of fuzzing modes for in-process fuzzing, such as an AFL++ mode or a passive tracing mode. It should run on all platforms that are supported by Frida.

bash
# Get fpicker
git clone https://github.com/ttdennis/fpicker
cd fpicker

# Get Frida core devkit and prepare fpicker
wget https://github.com/frida/frida/releases/download/16.1.4/frida-core-devkit-16.1.4-[yourOS]-[yourarchitecture].tar.xz
# e.g. https://github.com/frida/frida/releases/download/16.1.4/frida-core-devkit-16.1.4-macos-arm64.tar.xz 
tar -xf ./*tar.xz
cp libfrida-core.a libfrida-core-[yourOS].a #libfrida-core-macos.a

# Install fpicker
make fpicker-[yourOS] # fpicker-macos
# This generates ./fpicker

# Install radamsa (fuzzer generator)
brew install radamsa
  • Prepare the FS:
bash
# From inside fpicker clone
mkdir -p examples/wg-log # Where the fuzzing script will be
mkdir -p examples/wg-log/out # For code coverage and crashes
mkdir -p examples/wg-log/in # For starting inputs

# Create at least 1 input for the fuzzer
echo Hello World > examples/wg-log/in/0
  • Fuzzer script (examples/wg-log/myfuzzer.js):
examples/wg-log/myfuzzer.js
javascript
// Import the fuzzer base class
import { Fuzzer } from "../../harness/fuzzer.js";

class WGLogFuzzer extends Fuzzer {

    constructor() {
        console.log("WGLogFuzzer constructor called")

        // Get and declare the function we are going to fuzz
        var wg_log_addr = Module.findExportByName("<Program name>", "<func name to fuzz>");
        var wg_log_func = new NativeFunction(
            wg_log_addr,
            "void", ["pointer", "pointer", "pointer"], {
        });

        // Initialize the object
        super("<Program nane>", wg_log_addr, wg_log_func);
        this.wg_log_addr = wg_log_addr; // We cannot use "this" before calling "super"
        
        console.log("WGLogFuzzer in the middle");
        
        // Prepare the second argument to pass to the fuzz function
        this.tag = Memory.allocUtf8String("arg2");
        
        // Get the first argument we need to pass from a call to the functino we want to fuzz
        var wg_log_global_ptr = null;
        console.log(this.wg_log_addr);
        Interceptor.attach(this.wg_log_addr, {
            onEnter: function(args) {
                console.log("Entering in the function to get the first argument");
                wg_log_global_ptr = new NativePointer(args[0]);
            }
         });

        while (! wg_log_global_ptr) {
            Thread.sleep(1)
        }
        this.wg_log_global_ptr = wg_log_global_ptr;
        console.log("WGLogFuzzer prepare ended")
    }


    // This function is called by the fuzzer with the first argument being a pointer into memory
    // where the payload is stored and the second the length of the input.
    fuzz(payload, len) {
        // Get a pointer to payload being a valid C string (with a null byte at the end)
        var payload_cstring = payload.readCString(len);
        this.payload = Memory.allocUtf8String(payload_cstring);

        // Debug and fuzz
        this.debug_log(this.payload, len);
        // Pass the 2 first arguments we know the function needs and finally the payload to fuzz
        this.target_function(this.wg_log_global_ptr, this.tag, this.payload);
    }
}

const f = new WGLogFuzzer();
rpc.exports.fuzzer = f;
  • Compile the fuzzer:
bash
# From inside fpicker clone
## Compile from "myfuzzer.js" to "harness.js"
frida-compile examples/wg-log/myfuzzer.js -o harness.js
  • Call fuzzer fpicker using radamsa:
bash
# Indicate fpicker to fuzz a program with the harness.js script and which folders to use
fpicker -v --fuzzer-mode active -e attach -p <Program to fuzz> -D usb -o examples/wg-log/out/ -i examples/wg-log/in/ -f harness.js --standalone-mutator cmd --mutator-command "radamsa"
# You can find code coverage and crashes in examples/wg-log/out/

โŒ

In this case we aren't restarting the app or restoring the state after each payload. So, if Frida finds a crash the next inputs after that payload might also crash the app (because the app is in a unstable state) even if the input shouldn't crash the app.

Moreover, Frida will hook into exception signals of iOS, so when Frida finds a crash, probably an iOS crash reports won't be generated.

To prevent this, for example, we could restart the app after each Frida crash.

Logs & Crashes โ€‹

You can check the macOS console or the log cli to check macOS logs.
You can check also the logs from iOS using idevicesyslog.
Some logs will omit information adding <private>. To show all the info you need to install some profile from https://developer.apple.com/bug-reporting/profiles-and-logs/ to enable that private info.

If you don't know what to do:

sh
vim /Library/Preferences/Logging/com.apple.system.logging.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>Enable-Private-Data</key>
        <true/>
</dict>
</plist>

killall -9 logd

You can check the crashes in:

  • iOS
    • Settings โ†’ Privacy โ†’ Analytics & Improvements โ†’ Analytics Data
    • /private/var/mobile/Library/Logs/CrashReporter/
  • macOS:
    • /Library/Logs/DiagnosticReports/
    • ~/Library/Logs/DiagnosticReports

โš ๏ธ

iOS only stores 25 crashes of the same app, so you need to clean that or iOS will stop creating crashes.

Frida Android Tutorials โ€‹

References โ€‹

WhiteIntel โ€‹

WhiteIntel is a dark-web fueled search engine that offers free functionalities to check if a company or its customers have been compromised by stealer malwares.

Their primary goal of WhiteIntel is to combat account takeovers and ransomware attacks resulting from information-stealing malware.

You can check their website and try their engine for free at:

โ›“๏ธ External Link
Learn AWS hacking from zero to hero with htARTE (HackTricks AWS Red Team Expert)!

Other ways to support HackTricks: