hacktricks/mobile-pentesting/ios-pentesting/frida-configuration-in-ios.md
2024-02-10 13:03:23 +00:00

17 KiB

Configurazione di Frida su iOS

Impara l'hacking di AWS da zero a esperto con htARTE (HackTricks AWS Red Team Expert)!

Altri modi per supportare HackTricks:

Installazione di Frida

Passaggi per installare Frida su un dispositivo Jailbroken:

  1. Apri l'app Cydia/Sileo.
  2. Vai su Gestisci -> Sorgenti -> Modifica -> Aggiungi.
  3. Inserisci "https://build.frida.re" come URL.
  4. Vai alla nuova sorgente di Frida appena aggiunta.
  5. Installa il pacchetto Frida.

Se stai usando Corellium, dovrai scaricare la versione di Frida da https://github.com/frida/frida/releases (frida-gadget-[tua versione]-ios-universal.dylib.gz) e decomprimere e copiare nella posizione dylib richiesta da Frida, ad esempio: /Users/[tuo utente]/.cache/frida/gadget-ios.dylib

Dopo l'installazione, puoi utilizzare il comando frida-ls-devices sul tuo PC e verificare che il dispositivo appaia (il tuo PC deve essere in grado di accedervi).
Esegui anche frida-ps -Uia per verificare i processi in esecuzione sul telefono.

Frida senza dispositivo Jailbroken e senza patch dell'app

Consulta questo post sul blog su come utilizzare Frida su dispositivi non jailbroken senza patch dell'app: https://mrbypass.medium.com/unlocking-potential-exploring-frida-objection-on-non-jailbroken-devices-without-application-ed0367a84f07

Installazione del client Frida

Installa gli strumenti di Frida:

pip install frida-tools
pip install frida

Con il server Frida installato e il dispositivo in esecuzione e connesso, verifica se il client sta funzionando:

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

Frida Trace

Frida Trace is a powerful dynamic instrumentation tool that allows you to trace function calls and monitor the behavior of an iOS application in real-time. It can be used for various purposes, such as debugging, reverse engineering, and vulnerability analysis.

To configure Frida Trace in iOS, follow these steps:

  1. Install Frida: First, you need to install Frida on your iOS device. You can do this by using the Frida command-line tools or by downloading the Frida package from the official website.

  2. Connect to the iOS device: Once Frida is installed, connect your iOS device to your computer using a USB cable. Make sure that the device is unlocked and the screen is active.

  3. Start the Frida server: Open a terminal window and start the Frida server by running the following command:

    frida-server -l 0.0.0.0
    

    This will start the Frida server and listen for incoming connections on all network interfaces.

  4. Install the Frida client: On your computer, install the Frida client by running the following command:

    pip install frida-tools
    

    This will install the Frida client, which allows you to interact with the Frida server running on the iOS device.

  5. Connect to the iOS application: To trace a specific iOS application, you need to know its bundle identifier. You can find this information by opening the application's Info.plist file or by using tools like ideviceinstaller or frida-ps.

    Once you have the bundle identifier, run the following command to connect to the application:

    frida -U -l <script.js> <bundle_identifier>
    

    Replace <script.js> with the path to your Frida script and <bundle_identifier> with the bundle identifier of the application you want to trace.

  6. Write the Frida script: In your Frida script, you can define the functions you want to trace and specify the actions to be performed when these functions are called. You can also access and modify the arguments and return values of the traced functions.

    Here is an example of a Frida script that traces the NSLog function:

    Interceptor.attach(Module.findExportByName(null, 'NSLog'), {
        onEnter: function(args) {
            console.log('NSLog called with arguments: ' + args[0].readUtf8String());
        }
    });
    

    Save your Frida script with a .js extension.

  7. Run the Frida script: Finally, run the Frida script by executing the following command:

    frida -U -l <script.js> <bundle_identifier>
    

    This will start the tracing process and display the output in the terminal window.

By following these steps, you can configure Frida Trace in iOS and start tracing the behavior of iOS applications in real-time.

# 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 '*[* *]'

Ottenere tutte le classi e i metodi

  • Completamento automatico: Esegui semplicemente frida -U <programma>
  • Ottenere tutte le classi disponibili (filtrate per stringa)

{% code title="/tmp/script.js" %}

// 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.");
}

{% endcode %}

  • Ottieni tutti i metodi di una classe (filtrati per stringa)

{% code title="/tmp/script.js" %}

// 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.");
}

{% endcode %}

  • Chiamare una funzione
// 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

Dalla documentazione: Stalker è il motore di tracciamento del codice di Frida. Consente di seguire i thread, catturando ogni funzione, ogni blocco e persino ogni istruzione eseguita.

Ecco un esempio che implementa Frida Stalker in https://github.com/poxyran/misc/blob/master/frida-stalker-example.py

Questo è un altro esempio per attaccare Frida Stalker ogni volta che viene chiamata una funzione:

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
}
});

{% hint style="danger" %} Questo è interessante per scopi di debug, ma per il fuzzing, essere costantemente .follow() e .unfollow() è molto inefficiente. {% endhint %}

Fpicker

fpicker è una suite di fuzzing basata su Frida che offre una varietà di modalità di fuzzing per il fuzzing in-process, come ad esempio una modalità AFL++ o una modalità di tracciamento passivo. Dovrebbe funzionare su tutte le piattaforme supportate da Frida.

# 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
  • Preparare il file system:
# 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
  • Script Fuzzer (examples/wg-log/myfuzzer.js):

{% code title="examples/wg-log/myfuzzer.js" %}

// 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;

{% endcode %}

  • Compilare il fuzzer:
# From inside fpicker clone
## Compile from "myfuzzer.js" to "harness.js"
frida-compile examples/wg-log/myfuzzer.js -o harness.js
  • Chiama il fuzzer fpicker utilizzando radamsa:

{% code overflow="wrap" %}

# 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/

{% endcode %}

{% hint style="danger" %} In questo caso non stiamo riavviando l'app o ripristinando lo stato dopo ogni payload. Quindi, se Frida trova un crash, i successivi input dopo quel payload potrebbero anche far crashare l'app (perché l'app è in uno stato instabile) anche se l'input non dovrebbe far crashare l'app.

Inoltre, Frida si aggancerà ai segnali di eccezione di iOS, quindi quando Frida trova un crash, probabilmente non verrà generato un report di crash di iOS.

Per prevenire ciò, ad esempio, potremmo riavviare l'app dopo ogni crash di Frida. {% endhint %}

Log e Crash

Puoi controllare la console di macOS o il cli log per controllare i log di macOS.
Puoi controllare anche i log da iOS utilizzando idevicesyslog.
Alcuni log ometteranno informazioni aggiungendo <private>. Per mostrare tutte le informazioni è necessario installare alcuni profili da https://developer.apple.com/bug-reporting/profiles-and-logs/ per abilitare quelle informazioni private.

Se non sai cosa fare:

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

Puoi controllare i crash in:

  • iOS
  • Impostazioni → Privacy → Analytics e miglioramenti → Dati di analisi
  • /private/var/mobile/Library/Logs/CrashReporter/
  • macOS:
  • /Library/Logs/DiagnosticReports/
  • ~/Library/Logs/DiagnosticReports

{% hint style="warning" %} iOS memorizza solo 25 crash della stessa app, quindi è necessario pulirli o iOS smetterà di crearli. {% endhint %}

Tutorial Frida Android

{% content-ref url="../android-app-pentesting/frida-tutorial/" %} frida-tutorial {% endcontent-ref %}

Riferimenti

Impara l'hacking di AWS da zero a eroe con htARTE (HackTricks AWS Red Team Expert)!

Altri modi per supportare HackTricks: