欢迎重视微信公众号:FSA全栈举动

一、简介

Frida 是一个跨平台的轻量级 Hook 框架,支撑 MacOSLinuxWindows 操作体系,供给了精简的 Python 接口和功能丰厚的 JS 接口,除了能够运用自身的控制台交互以外,还能够运用 PythonJS 脚本注入到运转程序中,经过 Frida 能够获取程序的具体信息、阻拦和调用指定函数、注入代码、修正参数等。

Frida 源代码托管:github.com/frida

二、装置

iOS

增加软件源 https://build.frida.re/,然后查找 Frida 装置即可。

装置完结后可能经过 ps 看到 frida-server 后台程序则阐明装置成功,若没有能够重启手机后再看看

lxf-iPad:~ root# ps -ax | grep frida
26717 ??         0:00.08 /usr/sbin/frida-server
26731 ttys000    0:00.01 grep frida

MacOS

运用 pip 进行装置

pip install frida-tools # CLI tools
pip install frida       # Python bindings

后续需求升级的话,能够运用 --upgrade 参数

pip install frida-tools --upgrade
pip install frida --upgrade

假如报 command not found:pip 错误,阐明当时体系没有装置 pip,能够运用下方指令装置

brew install wget
wget https://bootstrap.pypa.io/get-pip.py
python3 get-pip.py

履行完结后会提示你将对应 python 版本的 bin 途径增加到 PATH 中,如:

export PATH=~/Library/Python/3.8/bin:$PATH

当然,假如你也有用 pyenv,则能够疏忽上述的 pip 装置流程,由于 pyenv 自带了 pip

你能够用 which 指令检查你的 pip 的装置方位。

➜  ~ which pip
/usr/local/var/pyenv/shims/pip

三、入门

除了 frida 主程序外,frida-tools 里还供给了五个实用东西,它们位于 /usr/local/bin/ 目录下

ls -al /usr/local/bin/frida-*

假如运用的是 pyenvpip 装置的 frida,则它们会被装置到 /usr/local/var/pyenv/shims/ 目录下

ls -al /usr/local/var/pyenv/shims/frida-*
-rwxr-xr-x  1 lxf  admin  180  3 19 22:05 /usr/local/var/pyenv/shims/frida-apk
-rwxr-xr-x  1 lxf  admin  180  3 19 22:05 /usr/local/var/pyenv/shims/frida-create
-rwxr-xr-x  1 lxf  admin  180  3 19 22:05 /usr/local/var/pyenv/shims/frida-discover
-rwxr-xr-x  1 lxf  admin  180  3 19 22:05 /usr/local/var/pyenv/shims/frida-join
-rwxr-xr-x  1 lxf  admin  180  3 19 22:05 /usr/local/var/pyenv/shims/frida-kill
-rwxr-xr-x  1 lxf  admin  180  3 19 22:05 /usr/local/var/pyenv/shims/frida-ls-devices
-rwxr-xr-x  1 lxf  admin  180  3 19 22:05 /usr/local/var/pyenv/shims/frida-ps
-rwxr-xr-x  1 lxf  admin  180  3 19 22:05 /usr/local/var/pyenv/shims/frida-trace

1、检查可用的设备列表

frida-ls-devices 用于获取可用的设备列表,在多设备交互的情况下会非常有用

➜  ~ frida-ls-devices
Id                                        Type    Name
----------------------------------------  ------  ------------
local                                     local   Local System
d007dc58edd70caad950ff01b41ebf73cfa49fbe  usb     iPad
socket                                    remote  Local Socket

2、获取设备的进程列表

frida-ps 用于获取进程列表信息

➜  ~ frida-ps --help
usage: frida-ps [options]
options:
  -h, --help            show this help message and exit
  -D ID, --device ID    connect to device with the given ID
  -U, --usb             connect to USB device
  -R, --remote          connect to remote frida-server
  -H HOST, --host HOST  connect to remote frida-server on HOST
  --certificate CERTIFICATE
                        speak TLS with HOST, expecting CERTIFICATE
  --origin ORIGIN       connect to remote server with “Origin” header set to
                        ORIGIN
  --token TOKEN         authenticate with HOST using TOKEN
  --keepalive-interval INTERVAL
                        set keepalive interval in seconds, or 0 to disable
                        (defaults to -1 to auto-select based on transport)
  --p2p                 establish a peer-to-peer connection with target
  --stun-server ADDRESS
                        set STUN server ADDRESS to use with --p2p
  --relay address,username,password,turn-{udp,tcp,tls}
                        add relay to use with --p2p
  -O FILE, --options-file FILE
                        text file containing additional command line options
  --version             show program's version number and exit
  -a, --applications    list only applications
  -i, --installed       include all installed applications
  -j, --json            output results as JSON

这儿阐明一下常用的指令参数

参数 描绘
-U 连接到 USB 设备
-D 假如当时有多台 USB 设备,能够运用该参数指定设备的 UDIDfrida-ls-devices 列出的那些 id
-R/-H 连接到长途 frida-server,首要用于长途调试
-a 仅显示正在运转的运用
-i 显示一切已装置的运用(包括 AppStore装置的运用和体系运用)

具体运用如下:

连接到 USB 设备检查进程列表

~ frida-ps -U
  PID  Name
-----  ---------------------------------------------------
25226   Cydia
26745   Twitter
21611   邮件
25055      AppPredictionWidget
20944      AppleCredentialManagerDaemon
 1687      AssetCacheLocatorService
23387      CMFSyncAgent
...

连接到 USB 设备检查正在运转的运用

➜  ~ frida-ps -U -a
  PID  Name         Identifier
-----  -----------  --------------------
25226   Cydia    com.saurik.Cydia
26745   Twitter  com.atebits.Tweetie2
21611   邮件       com.apple.mobilemail
➜  ~

连接到 USB 设备检查一切装置的运用

➜  ~ frida-ps -U -a -i
  PID  Name                         Identifier
-----  ---------------------------  ------------------------------------------
25226   Cydia                    com.saurik.Cydia
26745   Twitter                  com.atebits.Tweetie2
21611   邮件                       com.apple.mobilemail
    -   App Store                com.apple.AppStore
    -   FaceTime 通话              com.apple.facetime
    -   LXFProtocolTool_Example  org.cocoapods.demo.LXFProtocolTool-Example
    -   Photo Booth              com.apple.Photo-Booth
    -   Safari 浏览器               com.apple.mobilesafari
    -   Substitute               com.ex.substitute.settings
    -   SwiftyFitsize_Swift      org.cocoapods.demo.SwiftyFitsize-Swift
    -   iTunes Store             com.apple.MobileStore
    -   信息                       com.apple.MobileSMS
    -   查找 iPhone                com.apple.mobileme.fmip1
    -   设置                       com.apple.Preferences
....

连接到指定的 USB 设备检查正在运转的运用

➜  ~ frida-ps -D d007dc58edd70caad950ff01b41ebf73cfa49fbe -a
  PID  Name         Identifier
-----  -----------  --------------------
25226   Cydia    com.saurik.Cydia
26745   Twitter  com.atebits.Tweetie2
21611   邮件       com.apple.mobilemail
➜  ~

3、杀死进程

frida-kill 用来完毕设备上的指定进程

➜  ~ frida-kill --help
usage: frida-kill [options] process
options:
  -h, --help            show this help message and exit
  -D ID, --device ID    connect to device with the given ID
  -U, --usb             connect to USB device
  -R, --remote          connect to remote frida-server
  -H HOST, --host HOST  connect to remote frida-server on HOST
  --certificate CERTIFICATE
                        speak TLS with HOST, expecting CERTIFICATE
  --origin ORIGIN       connect to remote server with “Origin” header set to
                        ORIGIN
  --token TOKEN         authenticate with HOST using TOKEN
  --keepalive-interval INTERVAL
                        set keepalive interval in seconds, or 0 to disable
                        (defaults to -1 to auto-select based on transport)
  --p2p                 establish a peer-to-peer connection with target
  --stun-server ADDRESS
                        set STUN server ADDRESS to use with --p2p
  --relay address,username,password,turn-{udp,tcp,tls}
                        add relay to use with --p2p
  -O FILE, --options-file FILE
                        text file containing additional command line options
  --version             show program's version number and exit

举个例子,杀掉 PID26745Twitter

frida-kill -U 26745
frida-kill -U Twitter
frida-kill -D d007dc58edd70caad950ff01b41ebf73cfa49fbe 26745
frida-kill -D d007dc58edd70caad950ff01b41ebf73cfa49fbe Twitter

4、盯梢函数/办法的调用

frida-trace 用于盯梢函数或办法的调用。

➜  ~ frida-trace --help
usage: frida-trace [options] target
positional arguments:
  args                  extra arguments and/or target
options:
  -h, --help            show this help message and exit
  -D ID, --device ID    connect to device with the given ID
  -U, --usb             connect to USB device
  -R, --remote          connect to remote frida-server
  -H HOST, --host HOST  connect to remote frida-server on HOST
  --certificate CERTIFICATE
                        speak TLS with HOST, expecting CERTIFICATE
  --origin ORIGIN       connect to remote server with “Origin” header set to ORIGIN
  --token TOKEN         authenticate with HOST using TOKEN
  --keepalive-interval INTERVAL
                        set keepalive interval in seconds, or 0 to disable (defaults to -1 to auto-select based
                        on transport)
  --p2p                 establish a peer-to-peer connection with target
  --stun-server ADDRESS
                        set STUN server ADDRESS to use with --p2p
  --relay address,username,password,turn-{udp,tcp,tls}
                        add relay to use with --p2p
  -f TARGET, --file TARGET
                        spawn FILE
  -F, --attach-frontmost
                        attach to frontmost application
  -n NAME, --attach-name NAME
                        attach to NAME
  -p PID, --attach-pid PID
                        attach to PID
  -W PATTERN, --await PATTERN
                        await spawn matching PATTERN
  --stdio {inherit,pipe}
                        stdio behavior when spawning (defaults to “inherit”)
  --aux option          set aux option when spawning, such as “uid=(int)42” (supported types are: string, bool,
                        int)
  --realm {native,emulated}
                        realm to attach in
  --runtime {qjs,v8}    script runtime to use
  --debug               enable the Node.js compatible script debugger
  --squelch-crash       if enabled, will not dump crash report to console
  -O FILE, --options-file FILE
                        text file containing additional command line options
  --version             show program's version number and exit
  -I MODULE, --include-module MODULE
                        include MODULE
  -X MODULE, --exclude-module MODULE
                        exclude MODULE
  -i FUNCTION, --include FUNCTION
                        include [MODULE!]FUNCTION
  -x FUNCTION, --exclude FUNCTION
                        exclude [MODULE!]FUNCTION
  -a MODULE!OFFSET, --add MODULE!OFFSET
                        add MODULE!OFFSET
  -T INCLUDE_IMPORTS, --include-imports INCLUDE_IMPORTS
                        include program's imports
  -t MODULE, --include-module-imports MODULE
                        include MODULE imports
  -m OBJC_METHOD, --include-objc-method OBJC_METHOD
                        include OBJC_METHOD
  -M OBJC_METHOD, --exclude-objc-method OBJC_METHOD
                        exclude OBJC_METHOD
  -j JAVA_METHOD, --include-java-method JAVA_METHOD
                        include JAVA_METHOD
  -J JAVA_METHOD, --exclude-java-method JAVA_METHOD
                        exclude JAVA_METHOD
  -s DEBUG_SYMBOL, --include-debug-symbol DEBUG_SYMBOL
                        include DEBUG_SYMBOL
  -q, --quiet           do not format output messages
  -d, --decorate        add module name to generated onEnter log statement
  -S PATH, --init-session PATH
                        path to JavaScript file used to initialize the session
  -P PARAMETERS_JSON, --parameters PARAMETERS_JSON
                        parameters as JSON, exposed as a global named 'parameters'
  -o OUTPUT, --output OUTPUT
                        dump messages to file

4.1 盯梢函数调用

 ➜  ~ frida-trace -U -i compress -i "recv*" -x "recvmsg*" Twitter
Instrumenting...
compress: Auto-generated handler at "/Users/lxf/Desktop/LXF/reverse/Test/__handlers__/libz.1.dylib/compress.js"
recvfrom$NOCANCEL: Auto-generated handler at "/Users/lxf/Desktop/LXF/reverse/Test/__handlers__/libsystem_kernel.dylib/recvfrom_NOCANCEL.js"
recvfrom: Auto-generated handler at "/Users/lxf/Desktop/LXF/reverse/Test/__handlers__/libsystem_kernel.dylib/recvfrom.js"
recv: Auto-generated handler at "/Users/lxf/Desktop/LXF/reverse/Test/__handlers__/libsystem_c.dylib/recv.js"
recv$NOCANCEL: Auto-generated handler at "/Users/lxf/Desktop/LXF/reverse/Test/__handlers__/libsystem_c.dylib/recv_NOCANCEL.js"
Started tracing 5 functions. Press Ctrl+C to stop.

参数阐明

参数 描绘
-i 包括某个函数,支撑含糊匹配
-x 扫除某个函数,支撑含糊匹配

注:进行含糊匹配时,需求运用双引号进行包裹!

上述指令的意思:盯梢名为 compress 和以 recv 最初的函数,且扫除以 recvmsg 最初的函数。

当盯梢的函数被触发时,会输出以下日志:

           /* TID 0x1bb43 */
 36078 ms  recv$NOCANCEL()
 36078 ms     | recvfrom$NOCANCEL()
 36081 ms  recv$NOCANCEL()
 36081 ms     | recvfrom$NOCANCEL()
 36082 ms  recv$NOCANCEL()
 36082 ms     | recvfrom$NOCANCEL()
 36083 ms  recv$NOCANCEL()
 36083 ms     | recvfrom$NOCANCEL()
 36083 ms  recv$NOCANCEL()
 36083 ms     | recvfrom$NOCANCEL()

指令在履行后会在当时目录下会生成一个名为 __handlers__ 的文件夹,里面寄存的是自动生成的脚本文件

.
└── __handlers__
    ├── libsystem_c.dylib
    │ ├── recv.js
    │ └── recv_NOCANCEL.js
    ├── libsystem_kernel.dylib
    │ ├── recvfrom.js
    │ └── recvfrom_NOCANCEL.js
    └── libz.1.dylib
        └── compress.js

上述指令是在方针 App 翻开后履行的,假如咱们需求强制发动 App 来进行盯梢,能够运用 -f 运用的BundleID 参数,如:

➜  ~ frida-trace -U -i compress -i "recv*" -x "recvmsg*" -f "com.atebits.Tweetie2"
Instrumenting...
compress: Loaded handler at "/Users/lxf/Desktop/LXF/reverse/Test/__handlers__/libz.1.dylib/compress.js"
recvfrom$NOCANCEL: Loaded handler at "/Users/lxf/Desktop/LXF/reverse/Test/__handlers__/libsystem_kernel.dylib/recvfrom_NOCANCEL.js"
recvfrom: Loaded handler at "/Users/lxf/Desktop/LXF/reverse/Test/__handlers__/libsystem_kernel.dylib/recvfrom.js"
recv: Loaded handler at "/Users/lxf/Desktop/LXF/reverse/Test/__handlers__/libsystem_c.dylib/recv.js"
recv$NOCANCEL: Loaded handler at "/Users/lxf/Desktop/LXF/reverse/Test/__handlers__/libsystem_c.dylib/recv_NOCANCEL.js"
Started tracing 5 functions. Press Ctrl+C to stop.

注:frida-trace 履行时不会掩盖已有的脚本文件(即 __handlers__ 文件夹下的脚本),所以能够进行恣意修正这些 JS 文件来增加想要的功能。

4.2 盯梢 OC 办法的调用

➜  Test frida-trace -U -m "-[T1HomeTimelineItemsViewController _load*]" -M "-[T1HomeTimelineItemsViewController _loadBottomWithSource:]" Twitter
Instrumenting...
-[T1HomeTimelineItemsViewController _loadTopWithSource:]: Loaded handler at "/Users/lxf/Desktop/LXF/reverse/Test/__handlers__/T1HomeTimelineItemsViewController/_loadTopWithSource_.js"
-[T1HomeTimelineItemsViewController _loadGap:withSource:]: Loaded handler at "/Users/lxf/Desktop/LXF/reverse/Test/__handlers__/T1HomeTimelineItemsViewController/_loadGap_withSource_.js"
Started tracing 2 functions. Press Ctrl+C to stop.
           /* TID 0x303 */
 15600 ms  -[T1HomeTimelineItemsViewController _loadTopWithSource:0xc8]

参数阐明

参数 描绘
-m 包括某个办法,支撑含糊匹配
-M 扫除某个办法,支撑含糊匹配

4.3 盯梢调用栈

只需求在 JS 文件中增加如下代码片段即可盯梢某个办法的调用栈

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

想了解具体的接口阐明能够在 Frida 官网链接:frida.re/docs/javasc… 上找到。

5、交互形式

frida 供给了两种进入交互形式的方式

5.1 经过运用名或 PID 附加

运用于 App 已翻开的情况下附加的情景

frida -U 运用名
frida -U -p PID

当运用 PID 进行附加时,-p 可加可不加

举例:

frida -U Twitter
frida -U 26984
frida -U -p 26984

5.2 发动运用进入交互形式

运用于 App 未翻开的情景

➜  Test frida -U -f com.atebits.Tweetie2
     ____
    / _  |   Frida 15.1.17 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://frida.re/docs/home/
   . . . .
   . . . .   Connected to iPad (id=d007dc58edd70caad950ff01b41ebf73cfa49fbe)
Spawned `com.atebits.Tweetie2`. Use %resume to let the main thread start executing!
[iPad::com.atebits.Tweetie2 ]-> %resume

注:需求自己额定再输入 %resume,不然方针运用将一直处于暂停的状况。

假如发动运用后被强制退出或不想再额定输入 %resume,能够加上 --no-pause

frida -U -f com.atebits.Tweetie2 --no-pause

四、实战

iOS逆向 - 运行时分析(三)Frida

针对上图中的【翻译推文】,咱们来把这个标题和点击事情给修正掉

首要咱们要做的便是视图组件定位,在这个页面下,运用 FLEX 东西便可轻松定位到

iOS逆向 - 运行时分析(三)Frida

点击右侧的感叹号,能够看到该视图的特点和办法

iOS逆向 - 运行时分析(三)Frida

iOS逆向 - 运行时分析(三)Frida

然后经过如下代码,确认其是否为咱们想要 hook 的办法

if (ObjC.available) {
    var didTap = ObjC.classes.T1TranslateButton['- _didTap:forEvent:']
    var setTitle = ObjC.classes.T1TranslateButton['- setTitleText:']
    Interceptor.attach(setTitleOldImp, {
      onEnter: function(args) {
        console.log("args 0 -- ", ObjC.Object(args[0]))
        console.log("args 2 -- ", ObjC.Object(args[2]))
      }
    })
    didTap.implementation = ObjC.implement(setTitle, function(handle, selector, arg1, arg2) {
      var self = ObjC.Object(handle)
      console.log("self -- ", self) 
    })
}

翻开 Twitter 后,履行如下指令 frida -U -l Twitter.js Twitter

➜ frida -U -l Twitter.js Twitter
     ____
    / _  |   Frida 15.1.17 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://frida.re/docs/home/
   . . . .
   . . . .   Connected to iPad (id=d007dc58edd70caad950ff01b41ebf73cfa49fbe)
[iPad::Twitter ]->

Twitter 进入到指定页面后输出:

args 0 --  <T1TranslateButton: 0x122e46f80; baseClass = UIButton; frame = (0 0; 66 22); opaque = NO; layer = <CALayer: 0x28372a760>>
args 2 --  翻译推文

点一下【翻译推文】按钮输出:

[iPad::Twitter ]-> self --  <T1TranslateButton: 0x122e46f80; baseClass = UIButton; frame = (0 0; 66 22); opaque = NO; layer = <CALayer: 0x28372a760>>

看来是没错了,那接下来,咱们把标题和点击事情进行修正,完好代码如下:

if (ObjC.available) {
    const { NSString } = ObjC.classes;
    var UIAlertController = ObjC.classes.UIAlertController;
    var UIAlertAction = ObjC.classes.UIAlertAction;
    var UIApplication = ObjC.classes.UIApplication;
    // 弹窗
    function showAlert() {
      var alertHandler = new ObjC.Block({ retType: 'void', argTypes: ['object'], implementation: function () {} });
      ObjC.schedule(ObjC.mainQueue, function () {
        var alert = UIAlertController.alertControllerWithTitle_message_preferredStyle_('LinXunFeng', '欢迎重视公众号:FSA全栈举动\n博客:https://fullstackaction.com', 1);
        var defaultAction = UIAlertAction.actionWithTitle_style_handler_('OK', 0, alertHandler);
        alert.addAction_(defaultAction);
        UIApplication.sharedApplication().keyWindow().rootViewController().presentViewController_animated_completion_(alert, true, NULL);
      })
    }
    // 播映体系声响
    function playSystemSound() {
      var playSound = new NativeFunction(Module.findExportByName('AudioToolbox', 'AudioServicesPlaySystemSound'), 'void', ['int'])
      playSound(1111)
    }
    var didTap = ObjC.classes.T1TranslateButton['- _didTap:forEvent:']
    var setTitle = ObjC.classes.T1TranslateButton['- setTitleText:']
    // 保存旧完成
    var didTapOldImp = didTap.implementation
    // hook
    Interceptor.attach(setTitleOldImp, {
      onEnter: function(args) {
        args[2] = ptr(NSString.stringWithString_("Hello LinXunFeng,点击我来弹个窗和听个曲吧"))
      }
    })
    // 掩盖完成
    didTap.implementation = ObjC.implement(setTitle, function(handle, selector, arg1, arg2) {
      // 调用旧完成
      // didTapOldImp(handle, selector, arg1, arg2)
      playSystemSound()
      showAlert()
    })
}

iOS逆向 - 运行时分析(三)Frida

五、进阶

1、Python 交互

Frida 供给了 PythonJS 脚本的交互

1.1、获取设备

import frida
if __name__ == '__main__':
    deviceManager = frida.get_device_manager()
    # 枚举一切连接的设备
    print(deviceManager.enumerate_devices())
    # 根据 UDID 获取设备
    print(deviceManager.get_device("d007dc58edd70caad950ff01b41ebf73cfa49fbe"))
    # 获取当时 USB 连接的设备
    print(frida.get_usb_device())

运转成果:

[Device(id="local", name="Local System", type='local'), Device(id="socket", name="Local Socket", type='remote'), Device(id="d007dc58edd70caad950ff01b41ebf73cfa49fbe", name="iPad", type='usb')]
Device(id="d007dc58edd70caad950ff01b41ebf73cfa49fbe", name="iPad", type='usb')
Device(id="d007dc58edd70caad950ff01b41ebf73cfa49fbe", name="iPad", type='usb')

1.2、附加进程

运用 attach() 附加进程,得到 Session 实例

if __name__ == '__main__':
    device = frida.get_usb_device()
    session = device.attach("Twitter")  # 进程名
    # session = device.attach(27489)  # PID
    print(session)

输出内容:

Session(pid=27489)

1.3、发动进程

运用 spawn 能够发动进程,不过会进入挂起状况,需求合作 resume() 办法才能唤醒

if __name__ == '__main__':
    device = frida.get_usb_device()
    pid = device.spawn("com.atebits.Tweetie2")
    # session = device.attach(pid)
    device.resume(pid)

spawn 可带着参数运转

如下方代码所示,运转 Safari 并翻开 FSA全栈举动 博客: https://fullstackaction.com

pid = device.spawn("com.apple.mobilesafari", url="https://fullstackaction.com/")
device.resume(pid)

1.4、脱离进程

得到 Session 并完结一切操作后,需求运用 detach() 脱离进程

if __name__ == '__main__':
    device = frida.get_usb_device()
    pid = device.spawn("com.atebits.Tweetie2")
    session = device.attach(pid)
    device.resume(pid)
    session.detach()  # 脱离进程

1.5、注入 JS 脚本

得到 Session 实例后,就能够调用其 create_script 办法创立一个脚本对象,再调用该脚本对象的 load 办法进行脚本注入

if __name__ == '__main__':
    device = frida.get_usb_device()
    pid = device.spawn("com.atebits.Tweetie2")
    session = device.attach(pid)
    device.resume(pid)
    script = session.create_script("""
    if (ObjC.available) {
        var NSHomeDirectory = new NativeFunction(ptr(Module.findExportByName("Foundation", "NSHomeDirectory")), 'pointer', []);
        var path = new ObjC.Object(NSHomeDirectory());
        console.log(path);
    }
    """)
    script.load()
    session.detach()

JS 脚本能够保存到本地文件中再进行读取:

with codecs.open('./xxx.js', 'r', 'utf-8') as f:
    source = f.read()
script = session.create_script(source)

1.6、PythonJS 交互

JS 端传递参数,JS 端处理完结后将成果回来给 Python 端,这种场景还是很常见的,那应该怎么做呢?

示例代码如下:

import frida
import threading
g_event = threading.Event()  # 同步
def payload_message(payload):
    # print("payload_message -- ", payload)
    if "msg" in payload:
        print(payload["msg"])
    if 'status' in payload:
        if payload['status'] == 'success':
            g_event.set()
def on_message(message, data):
    # print("on_message message -- ", message)
    if message['type'] == 'send':
        payload_message(message['payload'])
    elif message['type'] == 'error':
        print(message['stack'])
SCRIPT_JS = ("""
    function handleMessage(message) {
        var cmd = message['cmd'] 
        if (cmd == 'GetDirectory') {
            var name = message['name']
            var path;
            switch (name) {
            case 'home':
                var NSHomeDirectory = new NativeFunction(ptr(Module.findExportByName("Foundation", "NSHomeDirectory")), 'pointer', []);
                path = new ObjC.Object(NSHomeDirectory());
                break;
            case 'tmp':
                var NSTemporaryDirectory = new NativeFunction(ptr(Module.findExportByName("Foundation", "NSTemporaryDirectory")), 'pointer', []);
                path = new ObjC.Object(NSTemporaryDirectory());
                break;
            default:
                path = "写的啥呀"
            }
            if (path) send({msg: path.toString()});
        }   
        send({status: 'success'});
    }
    recv(handleMessage);
""")
# 根据姓名获取对应的沙盒途径
def getDirectory(target_process, name):
    device = frida.get_usb_device()
    session = device.attach(target_process)
    script = session.create_script(SCRIPT_JS)
    script.on('message', on_message)
    script.load()
    script.post({'cmd': 'GetDirectory', 'name': name})
    g_event.wait()
    session.detach()
if __name__ == '__main__':
    # getDirectory('Twitter', 'home')
    getDirectory('Twitter', 'tmp')
  1. g_event 是为了保证同步
  2. JS 端能够设置 recv() 的回调接纳 Python 端的音讯
  3. Python 端经过 script.on() 设置回调,再运用 script.post() 将参数传递给 JS 端,然后调用 g_event.wait() 进入等待状况
  4. JS 端内部处理完结后,运用 send(){status: 'success'} 传递给 Python
  5. on_message 回调中取到 JS 端回来的数据,当识别到 statussuccess 后,调用 g_event.set() 使主线程继续履行

2、阻拦某个类的一切办法

假如想对某个类的一切办法进行批量阻拦,能够运用 ApiResolver 接口,它能够根据正则表达式获取符合条件的一切办法

var resolver = new ApiResolver('objc')
resolver.enumerateMatches('*[T1TranslateButton *]', {
    onMatch: function (match) {
        console.log(match['name'] + ":" + match['address'])
    },
    onComplete: function () {}
})

输出成果:

+[T1TranslateButton tfn_defaultShouldFlipForRightToLeftTransform]:0x101be4014
+[T1TranslateButton button]:0x101be2774
-[T1TranslateButton tapActionBlock]:0x101be4280
-[T1TranslateButton setTapActionBlock:]:0x101be4290
-[T1TranslateButton translationSource]:0x101be4250
-[T1TranslateButton setLogoTapActionBlock:]:0x101be42ac
-[T1TranslateButton setShowingTranslation:]:0x101be2c10
-[T1TranslateButton setOriginalLanguage:]:0x101be2b58
-[T1TranslateButton setTranslationSource:]:0x101be2cec
-[T1TranslateButton _didTap:forEvent:]:0x101be296c
-[T1TranslateButton _t1_didHover:]:0x101be4140
-[T1TranslateButton setSelectionPadding:]:0x101be42d8
-[T1TranslateButton touchRect]:0x101be42f8
-[T1TranslateButton _t1_isTouchingLogo:]:0x101be2a8c
-[T1TranslateButton touchLogoRect]:0x101be4328
-[T1TranslateButton _t1_buttonTitle]:0x101be2f28
-[T1TranslateButton autoTranslationExpanded]:0x101be4270
-[T1TranslateButton _t1_imageHeightOffsetForLogo:]:0x101be3164
-[T1TranslateButton _t1_titleRectWithTitleString:origin:]:0x101be32b8
-[T1TranslateButton _t1_imageRectWithOrigin:]:0x101be3394
-[T1TranslateButton _t1_drawRectFor:]:0x101be3f94
-[T1TranslateButton setTouchRect:]:0x101be4310
-[T1TranslateButton _t1_drawHighlightWithContext:andRect:]:0x101be401c
-[T1TranslateButton setTouchLogoRect:]:0x101be4340
-[T1TranslateButton setAutoTranslationExpanded:]:0x101be2cc4
-[T1TranslateButton originalLanguage]:0x101be4240
-[T1TranslateButton showingTranslation]:0x101be4260
-[T1TranslateButton logoTapActionBlock]:0x101be429c
-[T1TranslateButton selectionPadding]:0x101be42c8
-[T1TranslateButton _dynamicColorsDidReload:]:0x101be3ed0
-[T1TranslateButton titleText]:0x101be42b8
-[T1TranslateButton _titleColor]:0x101be2ea8
-[T1TranslateButton logoImage]:0x101be42e8
-[T1TranslateButton _logoImage]:0x101be2d14
-[T1TranslateButton dealloc]:0x101be28e4
-[T1TranslateButton .cxx_destruct]:0x101be4358
-[T1TranslateButton initWithFrame:]:0x101be27dc
-[T1TranslateButton sizeThatFits:]:0x101be3408
-[T1TranslateButton setHighlighted:]:0x101be2b08
-[T1TranslateButton drawRect:]:0x101be3798
-[T1TranslateButton setTitleText:]:0x101be2c38

3、替换原办法

Interceptor.attach() 能够在阻拦方针后,打印参数,修正回来值,但无法阻止原办法的履行

咱们能够给原办法的 implementation 进行赋值,然后掩盖其完成

var didTap = ObjC.classes.T1TranslateButton['- _didTap:forEvent:']
var didTapOldImp = didTap.implementation
// 掩盖完成
didTap.implementation = ObjC.implement(setTitle, function(handle, selector, arg1, arg2) {
  var self = ObjC.Object(handle)
  console.log("self -- ", self) 
  // 调用旧完成
  // didTapOldImp(handle, selector, arg1, arg2)
})

这儿需求留意的是,像 _didTap:forEvent: 这儿需求传递两个参数,则 ObjC.implement 的回调中也需求写明两个参数(arg1arg2),即需求多少参数就写多少,没有则不必写

4、RPC 调用

RPC:即 Remote Procedure Call,长途过程调用,开发人员能够将封装好的恣意函数指定为 RPC 函数,以供给给 Python 运用。

利用 rpc.exports = {} 导出 RPC 函数,多个函数以逗号分隔,留意:办法名需求全小写!

function getHomeDirectory() {
    var NSHomeDirectory = new NativeFunction(ptr(Module.findExportByName("Foundation", "NSHomeDirectory")), 'pointer', [])
    var path = new ObjC.Object(NSHomeDirectory());
    return path.toString()
}
function openUrl(url) {
    var UIApplication = ObjC.classes.UIApplication.sharedApplication()
    var toOpen = ObjC.classes.NSURL.URLWithString_(url)
    return UIApplication.openURL_(toOpen)
}
function playSystemSound() {
    var playSound = new NativeFunction(Module.findExportByName('AudioToolbox', 'AudioServicesPlaySystemSound'), 'void', ['int'])
    playSound(1111)
}
// 导出 RPC 函数
rpc.exports = {
    openurl: function (url) {
        openUrl(url)
    },
    sound: function () {
        playSystemSound()
    },
    alert: function () {
        showAlert()
    },
    homedirectory: function () { // homedirectory 必须小写
        return getHomeDirectory()
    }
}

Python 端运用 rpc.js

import codecs
import frida
if __name__ == '__main__':
    device = frida.get_usb_device()
    session = device.attach('Twitter')
    # 读取 JS 脚本 
    with codecs.open('./rpc.js', 'r', 'utf-8') as f:
        source = f.read()
    script = session.create_script(source)
    script.load()
    rpc = script.exports
    rpc.openurl("https://fullstackaction.com")
    rpc.sound()
    print(rpc.homeDirectory())
    # print(rpc)
    session.detach()

六、最后

以上代码现已上传至:github.com/LinXunFeng/…

Frida 供给的 API 接口非常丰厚,这儿只提到了常用的内容,更多内容还是需求咱们一起去阅览官方文档:frida.re/docs/javasc…

除此之外,codeshare.frida.re 上供给很多共享脚本,我们能够用来学习和引入运用

iOS逆向 - 运行时分析(三)Frida