Post

Frida for iOS

Introductory guide on how to use Frida to analyse iOS applications at runtime to perform actions such as search for methods, hook methods, view & modify instructions, and view & modify registers. This guide already assumes you have frida installed and have frida-server installed on your iOS device.

For the below code examples, Snapchat was used as the target application.

  1. Basic iOS Hooking
  2. Methods
  3. Working with Instructions


Basic iOS Hooking

Running Scripts

Get Running Applications:

1
2
3
4
5
root@kali:~# frida-ps -Ua
PID Name Identifier
----- -------- -----------------------
1285 Camera com.apple.camera
14968 Snapchat com.toyopagroup.picaboo

Run Frida script against target application:

1
root@kali:~# frida -U -f com.toyopagroup.picaboo -l script.js --no-pause
  • -U USB mode
  • -f specify target application
  • -l specify frida script
  • --no-pause don’t pause the application during startup

Get and Search Classes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// List of Strings to search for
let toSearchFor = [
	'rawframe',
	'jpeg',
];

let matches = [];

// Iterate over all the ObjC classes
for (let className in ObjC.classes){ 
	if (!ObjC.classes.hasOwnProperty(className)) continue;

	let lowerCaseName = className.toLowerCase();

	let found = false;

	// Check if the name of the Class contains any of the target Strings
	for (let i = 0; i < toSearchFor.length; ++i) {
		if (lowerCaseName.includes(toSearchFor[i])) matches.push(`"${className}"`);
	}
}

matches.sort();

console.log();
console.log(matches.join('\n'));
console.log(`\n[+] Finished Search: ${matches.length} result(s) found\n`);
1
2
3
4
5
6
7
8
9
10
"BWIntermediateJPEGCompressedBufferAssociatedSemaphore"
"BWIntermediateJPEGCompressor"
"FFJpegFrame"
"FFOptimizedRawFrame"
"FFRawFrame"
"MFHardwareJPEGScaler"
"NUImageExportFormatJPEG"
"PLJPEGThumbnailDecode"

[+] Finished Search: 8 result(s) found

Get Class Methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let className = 'FFJpegFrame';

// Iterate over all the ObjC classes
for (let className in ObjC.classes){ 
	if (ObjC.classes.hasOwnProperty(className)) continue;

	let lowerCaseName = className.toLowerCase();

	let found = false;

	// Check if the name of the Class contains any of the target Strings
	for (let i = 0; i < toSearchFor.length; ++i) {
		if (lowerCaseName.includes(toSearchFor[i])) matches.push(`"${className}"`);
	}
}

Get Memory Address of Class Method

1
2
3
4
5
6
let className = `FFJpegFrame`;
let methodName = `- setData:`;

let address = ObjC.classes[className][methodName].implementation;

console.log(`Address: ${address}`);
1
Address: 0x102a9305c

Hexdump Area of Memory

1
2
3
4
5
6
7
8
let address = ObjC.classes[className][methodToHook].implementation;

let hexData = hexdump(address, {
	offset: 0,
	length: 64,
});

console.log(hexData);
1
2
3
4
5
           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F 0123456789ABCDEF
10102b05c e1 03 02 aa 00 20 00 91 d2 4c 78 15 00 20 00 91 ..... ...Lx.. ..
10102b06c 01 00 80 d2 cf 4c 78 15 ff 43 01 d1 e9 23 01 6d .....Lx..C...#.m
10102b07c f6 57 02 a9 f4 4f 03 a9 fd 7b 04 a9 fd 03 01 91 .W...O...{......
10102b08c f5 03 02 aa f4 03 00 aa e0 03 02 aa a4 4c 78 95 .............Lx.

Create Pointer to Memory Address

1
2
3
4
5
6
7
8
9
10
// Create a Pointer to a specific Memory Address
let pointer1 = new NativePointer(0x10010318);
let pointer2 = ptr(0x10010318); // This is short-hand for the above statement

// Add offset to Address
let pointer3 = pointer2.add(0x08); // 0x10010320

console.log(`pointer1: ${pointer1}`);
console.log(`pointer2: ${pointer2}`);
console.log(`pointer3: ${pointer3}`);
1
2
3
pointer1: 0x10010318
pointer2: 0x10010318
pointer3: 0x10010320

Monitor File Access

1
2
3
4
5
Interceptor.attach(ObjC.classes.NSFileManager['- fileExistsAtPath:'].implementation, {
	onEnter: function (args) {
		console.log('open' , ObjC.Object(args[2]).toString());
	}
});


Methods

Hooking a Method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
let className = `SCDiscoverFeedRanker`;
let methodName = `- addListener:`;

let address = ObjC.classes[className][methodName].implementation;

Interceptor.attach(address, {
	onEnter: function(args) {
		console.log();
		console.log(`Function Called`);
	},
	onLeave: function(returnValue) {
		console.log(`\nReturn Value: ${returnValue}`);
	}
});
1
2
3
Function Called

Return Value: 0x1

View Method Arguments

1
2
3
4
5
6
7
8
9
10
11
12
let className = `SCDiscoverFeedRanker`;
let methodName = `- addListener:`;

let address = ObjC.classes[className][methodName].implementation;

Interceptor.attach(address, {
	onEnter: function(args) {
		console.log('arg[0]:', args[0]);
		console.log('arg[1]:', args[1]);
		console.log('arg[2]:', args[2]);
	}
});
1
2
3
arg[0]: 0x2817ac060
arg[1]: 0x1e43ee192
arg[2]: 0x119d79700

View and Modify Registers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let className = `SCDiscoverFeedRanker`;
let methodName = `- addListener:`;

let address = ObjC.classes[className][methodName].implementation;

Interceptor.attach(address, {
	onEnter: function(args) {
		// Print ALL Registers
		console.log(JSON.stringify(this.context, null, 4), '\n');

		// View Register Value
		console.log(`Register (x14): ${this.context.x14}`);
		console.log(`Register (x14): ${this.context.x14.toInt32()}\n`);

		// Update Register Value
		this.context.x14 = 64;
		this.context.x14 = 0x44; // Same as the previous line

		// View Register Value
		console.log(`Register (x14): ${this.context.x14}`);
		console.log(`Register (x14): ${this.context.x14.toInt32()}`);
	},
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
	"pc": "0x102d91d34",
	"sp": "0x16d3f42d0",
	"x0": "0x2831e8a80",
	"x1": "0x1e43ee192",
	...
	"x13": "0x0",
	"x14": "0x18",
	"x15": "0x1",
	...
	"x28": "0x2815d3f20",
	"fp": "0x16d3f4440",
	"lr": "0x102d90638"
}

Register (x14): 0x18
Register (x14): 24

Register (x14): 0x44
Register (x14): 68


Working with Instructions

Get Instruction at Memory Address

1
2
3
4
5
6
7
8
9
let className = `FFJpegFrame`;
let methodName = `- setData:`;

let address = ObjC.classes[className][methodName].implementation;

console.log(`Instruction at ${address}: ${Instruction.parse(address)}`);
console.log();
console.log('Instruction (mnemonic):', Instruction.parse(address).mnemonic);
console.log('Instruction (opStr):', Instruction.parse(address).opStr);
1
2
3
4
Instruction at 0x1053818cc: mov x1, x2

Instruction (mnemonic): mov
Instruction (opStr): x1, x2

Overwrite Instruction at Memory Address

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let className = `FFJpegFrame`;
let methodName = `- setData:`;

let address = ObjC.classes[className][methodName].implementation;
let addressToOverwrite = address.add(0x0c);

console.log(`Instruction at ${addressToOverwrite} (BEFORE): ${Instruction.parse(addressToOverwrite)}`);

Memory.patchCode(addressToOverwrite, 4, code => {
     let cs = new Arm64Writer(code, { pc: code });
     cs.putNop();
     cs.flush();
});

console.log(`Instruction at ${addressToOverwrite} (AFTER): ${Instruction.parse(addressToOverwrite)}`);
  • Depending on the arch, you may need to use Arm64Writer, ArmWriter, X86Writer, MipsWriter.
1
2
Instruction at 0x1035158dc (BEFORE): add x0, x0, #8
Instruction at 0x1035158dc (AFTER): nop


References

This post is licensed under CC BY 4.0 by the author.