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 Part | Meaning | Offset (Bytes) |
---|---|---|
@"NSDictionary" (return type) | Returns an NSDictionary * | 24 bytes total |
@?0 | Block itself (@? ), the hidden first param | 0 bytes |
@"NSURL"8 | First visible parameter: NSURL * | 8 bytes |
@"NSDictionary"16 | Second 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.