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.
- Basic iOS Hooking
- Methods
- 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}`);
|
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