在iOS上使用Frida

准备Frida环境

  • 越狱设备
    1.在你的iOS设备上打开Cydia应用程序。
    2.添加一个源,URL为:https://build.frida.re
    3.打开Source或搜索Frida,单击Modify,然后单击Install。
  • 非越狱设备
    需要将frida-garget打包到app中,参考之前的文章《iOS应用安全- 非越狱下使用Frida》

frida用法

1.注入到进程

注入进程有两种方式:

  • 通过frida-tools的REPL

    1
    frida -U Safari -l NSURL_openURL_hook.js
  • 通过frida-bindings
    以python为例:

    1
    2
    3
    4
    5
    6
    7
    8
    jscode='''
    console.log("add js code here");
    '''
    process = frida.get_usb_device().attach("Safari")
    script = process.create_script(jscode)
    script.on('message', on_message)
    print('[*] dump all class into obclass.txt')
    script.load()

2.列出所有运行的进程

1
frida-ps -U

frida-ps -Uai 以pid、名称、进程名称的格式输出
upload successful

3.打印调用栈

在hook到的函数中加入以下代码:

1
2
3
console.log('\tBacktrace:\n\t' + Thread.backtrace(this.context,
Backtracer.ACCURATE).map(DebugSymbol.fromAddress)
.join('\n\t'));

upload successful

4.调用native函数

1
2
3
var address = Module.findExportByName('libsqlite3.dylib', 'sqlite3_sql');
var sql = new NativeFunction(address, 'char', ['pointer']);
sql(statement);

5.数据类型和转换

如果对一个变量的类型不确定,可以使用如下代码确定其类型:

1
console.log("Type of args[2] -> " + new ObjC.Object(args[2]).$className)

一些常用的数据类型转换:

  • NSData转String
    1
    2
    var data = new ObjC.Object(args[2]);
    Memory.readUtf8String(data.bytes(), data.length());

如果为null的不需要第二个参数

  • NSData转二进制数据

    1
    2
    var data = new ObjC.Object(args[2]);
    Memory.readByteArray(data.bytes(), data.length());
  • 遍历NSArray

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var array = new ObjC.Object(args[2]);
    /*
    * Be sure to use valueOf() as NSUInteger is a Number in
    * 32-bit processes, and UInt64 in 64-bit processes. This
    * coerces it into a Number in the latter case.
    */
    var count = array.count().valueOf();
    for (var i = 0; i !== count; i++) {
    var element = array.objectAtIndex_(i);
    }
  • 遍历NSDictionary

    1
    2
    3
    4
    5
    6
    var dict = new ObjC.Object(args[2]);
    var enumerator = dict.keyEnumerator();
    var key;
    while ((key = enumerator.nextObject()) !== null) {
    var value = dict.objectForKey_(key);
    }
  • NSKeyedArchiver

    1
    var parsedValue = ObjC.classes.NSKeyedUnarchiver.unarchiveObjectWithData_(value);
  • 读一个结构体

    1
    Memory.readU32(args[0].add(4));

常用脚本

1.枚举所有的类

1
2
3
4
5
6
7
for (var className in ObjC.classes)
{
if (ObjC.classes.hasOwnProperty(className))
{
send(className);
}
}

2.枚举一个类的所有method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if (ObjC.available)
{
try
{
var className = "NSURL";
var methods = eval('ObjC.classes.' + className + '.$methods');
for (var i = 0; i < methods.length; i++)
{
try
{
if (methods[i].indexOf("fileURLWithPath") > -1)
console.log("[-] "+methods[i]);
}
catch(err)
{
console.log("[!] Exception1: " + err.message);
}
}
}
catch(err)
{
console.log("[!] Exception2: " + err.message);
}
}

3. Hook一个method

打印参数时需要注意:

  • args[0]:self
  • args[1]:The selector (openURL:)
  • args[2]:The first param
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
if (ObjC.available)
{
try
{
var className = "JailbreakDetectionVC";
var funcName = "- isJailbroken";
var hook = eval('ObjC.classes.' + className + '["' + funcName + '"]');
console.log("[*] Class Name: " + className);
console.log("[*] Method Name: " + funcName);
Interceptor.attach(hook.implementation, {
onEnter: function(args) {
console.log("aaaa");
console.log("param:"+args[2]+" type:"+typeof args[2]);
},
onLeave: function(retval) {
console.log("Return value-> (type:"+typeof retval+",value:"+retval+")");
newretval = ptr("0x0")
retval.replace(newretval)
}
});
}
catch(err)
{
console.log("[!] Exception2: " + err.message);
}
}

4.调用函数

  • 函数名以”+”开头的,如:“+ URLWithString:”,可以直接通过类名调用方法,相当于java中的static函数
  • 函数名以“-”开头的需要找到一个实例化的对象,然后再调用方法

    • 如果内存中没有这样的对象
      这种情况需要手动生成一个实例,用法为ObjC.classes.类名.alloc()
    • 如果内存中存在实例化后的对象
      这种情况需要先找出一个类的实例,使用var tmp=ObjC.chooseSync(ObjC.classes.类名),例如:

      1
      ObjC.chooseSync(ObjC.classes.PARSHealthPedometer10thHomeViewController)[0]

      其中[0]表示取找到的实例中的第一个实例,可根据实际情况换成其他的实例。
      调用函数时,以my_obj“- requestUploadWithSure”的函数,如果有参数直接附在括号中。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      if (ObjC.available)
      {
      try
      {
      //var my_obj=ObjC.chooseSync(ObjC.classes.PARSHealthPedometer10thHomeViewController)[0]
      var my_obj=ObjC.classes.PARSHealthPedometer10thHomeViewController.alloc()
      my_obj["- requestUploadWithSure:"](1)
      }
      catch(err)
      {
      console.log("[!] Exception2: " + err.message);
      }
      }
      else
      {
      console.log("Objective-C Runtime is not available!");
      }