Coding is a way of life

Code Repeats, Humans Compete

How to Use Frida to Find Block Parameters

How to Use Frida to Find Objective-C Block Parameters

Have you ever wanted to peek inside an app on your iPhone or iPad to see exactly what data it’s processing, especially to understand what information it’s sending or receiving? Today, I’ll introduce you to a simple yet powerful method using Frida, which enables you to dynamically detect the parameters of a special piece of code called a “block” within iOS applications.

First: What Exactly is a Block?

In iOS programming, a block is a small, self-contained piece of code that you can pass around your app to be executed later. Imagine it as giving your phone number (the block) to a friend who can then call you when something important happens.

Sometimes, you might want to observe precisely what data these blocks are receiving (inputs) and producing (outputs). Frida helps you achieve this dynamically, without altering the app’s original code.

Detailed Structure of a Block in Memory

Understanding the internal structure of a block helps you accurately retrieve data about its signature and arguments. Here’s a clear depiction of a block’s memory layout:

struct Block_literal_1 {
    void *isa; // Pointer to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
    int flags; // Flags indicating block type and properties
    int reserved; // Reserved for future use
    void (*invoke)(struct Block_literal_1 *, ...); // Function pointer to the block implementation
    struct Block_descriptor_1 {
        unsigned long reserved;     // Reserved, usually NULL
        unsigned long size;         // Size of Block_literal_1 structure
        // Optional helper functions
        void (*copy_helper)(void *dst, void *src);     // Present if flag (1<<25) is set
        void (*dispose_helper)(void *src);             // Present if flag (1<<25) is set
        // Required for blocks containing a signature
        const char *signature;                         // Present if flag (1<<30) is set
    } *descriptor;
    // Captured variables (if any)
};

The signature within the block provides essential details such as return types and parameter types, crucial for dynamic analysis.

What is Frida?

“Inject your scripts into black-box processes. Hook functions, spy on crypto APIs, or trace private application logic, all without needing source code.”

Frida is a powerful dynamic instrumentation toolkit that lets you observe and interact with applications at runtime. Think of Frida as a magical microscope that lets you see what’s happening inside apps in real-time.

Using Frida to Discover Block Parameters

Let’s walk through finding parameters dynamically within a hypothetical iOS app.

Step 1: Connecting Frida to Your App

Connect your device to your computer and run the following command in your terminal:

frida -U -n Safari

This attaches Frida to the Safari app on your iPhone.

Step 2: Printing the Block Dynamically

Assume we’re interested in a block named addSecurityCheckBlock within MMNetworkManager. Fetch and print the block dynamically:

const blk =
  ObjC.classes.MMNetworkManager.sharedInstance().addSecurityCheckBlock();
console.log("Block Address:", blk);

This outputs the memory address of the block.

Step 3: Interpreting the Block Signature

Blocks have an internal signature that describes their inputs and outputs. Here’s how to dynamically print the signature:

const block = new ObjC.Block(blk);
const signaturePtr = block.handle
  .add(0x18)
  .readPointer() // descriptor pointer
  .add(0x10)
  .readPointer(); // signature pointer

const signature = signaturePtr.readCString();
console.log("Signature:", signature);

A typical signature looks like:

Signature: @"NSDictionary"24@?0@"NSURL"8@"NSDictionary"16

The numbers represent byte offsets within the stack frame when calling the block:

Signature PartMeaningOffset (Bytes)
@"NSDictionary" (return type)Returns an NSDictionary *24 bytes total
@?0Block itself (@?), the hidden first param0 bytes
@"NSURL"8First visible parameter: NSURL *8 bytes
@"NSDictionary"16Second visible parameter: NSDictionary *16 bytes

Step 4: Decoding the Signature Clearly

Simplify the signature decoding with the following script:

const sigClean = signature.replace(/\d+/g, ""); // Remove digits
const types = sigClean.match(/@\?|@".*?"|[@#:vBcCiIlLqQfd]/g);

function decodeType(type) {
  if (type === "@?") return "block";
  if (type.startsWith('@"')) return type.slice(2, -1) + " *";
  switch (type) {
    case "v":
      return "void";
    case "@":
      return "id";
    case "#":
      return "Class";
    default:
      return "unknown";
  }
}

console.log("Return →", decodeType(types[0]));
for (let i = 2; i < types.length; i++) {
  console.log("Arg", i - 1, "", decodeType(types[i]));
}

This produces a clear, readable result:

Return → NSDictionary *
Arg 1 → NSURL *
Arg 2 → NSDictionary *

Summary

With Frida, you can dynamically explore and understand the parameters passed to blocks in Objective-C apps. By clearly understanding block memory structures, signatures, and offsets, you become adept at analyzing any app’s internal logic without altering its codebase.

References