首页
社区
课程
招聘
[翻译]基于 Frida 和 RPC,从 Python 中直接调用 iOS 的 Native 方法
2019-6-25 22:27 16685

[翻译]基于 Frida 和 RPC,从 Python 中直接调用 iOS 的 Native 方法

2019-6-25 22:27
16685

今天我们将学习怎样在Python中使用Frida的'NativeFunction'创建并调用iOS本地函数,如果被创建的函数是本地的Python函数的话,接下来再使用RPC调用这一iOS函数,就好像它们是本地python函数一样。
图片描述

 

这篇文章的灵感来自于前段时间@CodeColorist的一篇推文。
图片描述

 

如果你还没运行过上述语句,请运行一下试试。打开Frida REPL并输入:

new NativeFunction(Module.findExportByName('AudioToolbox', 'AudioServicesPlaySystemSound'), 'void', ['int'])(1007)

单行语句剖析

那么,在运行这条语句的时候,到底发生了什么呢?这条语句调用了AudioToolbox框架中的AudioServicesPlaySystemSound函数

Audio Toolbox framework提供了记录,回放和流分析的功能。这一框架中的系统音频服务为iOS设备的短音频播放和振动功能提供了一个C接口。

 

在Frida中:

  • 框架 -> 一个模块对象
  • 函数 -> 导出模块方法的本地函数对象

现在我们可以把这条单行代码拆分成两行,方便进行分析:

var address = Module.findExportByName('AudioToolbox', 'AudioServicesPlaySystemSound')
var play_sound = new NativeFunction(address, 'void', ['int'])
play_sound(1007)

这样的话,每一步的功能就很直观了:

  • 获取模块中导出函数的绝对地址
  • 使用正确的返回值和输入参数构建一个本地函数对象
  • 使用指定参数调用本地函数

接下来我们对其中每一步的意义进行详细分析。

获取导出函数的绝对地址

我们需要的函数是AudioServicesPlaySystemSound。这一函数由AudioToolbox模块导出。你可以采用下述方式对它进行验证:

[iPhone::Telegram]-> Module.enumerateExportsSync('AudioToolbox')
...
{
    "address": "0x186cf4e88",
    "name": "AudioServicesPlaySystemSound",
    "type": "function"
},
{
    "address": "0x186cf4e70",
    "name": "AudioServicesPlaySystemSoundWithCompletion",
    "type": "function"
},
{
    "address": "0x186cf3f58",
    "name": "AudioServicesPlaySystemSoundWithOptions",
    "type": "function"
},
...

通过Module.findExportByName(moduleName, exportName)(FridaModule的API)可以获取导出函数的绝对地址:

var address = Module.findExportByName('AudioToolbox', 'AudioServicesPlaySystemSound')

在这里获取到的地址,如无意外的话会和我们上面获取到的(0x186cf4e88)相同。为了方便我们不再使用Module.enumerateExportsSync获取地址,而是采用这一方法进行手动搜索。

创建一个本地函数

要根据地址调用本地函数,我们还需要用Frida的NativeFunction创建一个本地函数对象,这一函数的结构是这样的:new NativeFunction(address, returnType, argTypes),其中

  • address是绝对地址
  • returnType指定返回类型
  • argTypes是指定参数类型的数组

    当使用Frida进行播放的时候,我推荐你多看看官方的API和例子:https://www.frida.re/docs/home/

现在,我们已经拿到address了,接下来的工作就是要获取函数的returnTypeargTypes,也就是要获取函数签名。这里不需要什么黑客技巧,假设你是一个开发者,现在你想要使用这个方法,你会从哪寻找自己需要的信息呢?没错,就是Apple docs :)

void AudioServicesPlaySystemSound(SystemSoundID inSystemSoundID);
  • 接收一个UInt32的SystemSoundID -> 在Frida中是int或者uint32
  • 返回void -> Frida中是void

因此我们的代码是这样写的:

var play_sound = new NativeFunction(address, 'void', ['int'])

请注意NativeFunction的第2个参数是返回值类型,第3个参数是输入类型的数组

调用本地函数

现在我们已经把NativeFunction存储在了play_sound变量里,像调用普通函数一样调用play_sound()并给它一个(int)作为输入参数:play_sound(1007)

 

把所有这些放在一起:

var address = Module.findExportByName('AudioToolbox', 'AudioServicesPlaySystemSound')
var play_sound = new NativeFunction(address, 'void', ['int'])
play_sound(1007)

也可以把上述语句重构成:

var play_sound = new NativeFunction(Module.findExportByName('AudioToolbox', 'AudioServicesPlaySystemSound'), 'void', ['int'])
play_sound(1007)

也就是相当于:

new NativeFunction(Module.findExportByName('AudioToolbox', 'AudioServicesPlaySystemSound'), 'void', ['int'])(1007)

这样我们又得到了文章开头的那条单行代码 :)

播放更多的音乐

通过搜索我们可以找到更多的用于播放音频的代码:
http://iphonedevwiki.net/index.php/AudioServices

 

音频文件存储在/System/Library/Audio/UISounds/:

iPhone:~ root# ls /System/Library/Audio/UISounds/

ReceivedMessage.caf
RingerChanged.caf
SIMToolkitSMS.caf
SentMessage.caf
Swish.caf
Tink.caf
...

但如果只是要下载播放文件的话未免太无聊了。现在我们想要使用之前的单行代码构建一个Frida脚本(audiobox.js):

// audiobox.js

console.log('Tags: sms, email, lock, photo')

function play(tag) {
  switch(tag) {
    case 'sms':
    _play(1007)
    break;
    case 'email':
    _play(1000)
    break;
    case 'lock':
    _play(1100)
    break;
    case 'photo':
    _play(1108)
    break;
  }

}

function _play(code) {
  new NativeFunction(Module.findExportByName('AudioToolbox', 'AudioServicesPlaySystemSound'), 'void', ['int'])(code)
}

在使用frida -U Telegram -l audiobox.js将这段代码加载进Frida REPL后,我们就可以简单地通过play('sms')play('lock')等语句来播放所有的音频。

注意:在我们的例子中,我们把Frida的调试器附加到了Telegram这个app上,事实上,附加到哪个app并不重要,因为我们调用的是系统函数,如果你想要调用某个app的本地函数,那么你就需要附加到对应的app。

使用Frida的RPC

Frida提供了通过RPC调用函数的功能,比如说通过Python调用远程代码。这也就意味着我们可以像调用普通Python方法一样调用app中的方法。很棒吧?我们接下来对我们的Frida脚本进行重写,比如说audiobox_rpc.js:

// audiobox_rpc.js

function _play(code) {
  new NativeFunction(Module.findExportByName('AudioToolbox', 'AudioServicesPlaySystemSound'), 'void', ['int'])(code)
}

rpc.exports = {
    sms: function () {
        return _play(1007);
    },
    email: function () {
        return _play(1000);
    },
    lock: function () {
        return _play(1100);
    },
    photo: function () {
        return _play(1108);
    },
};

写一个Python脚本的主要步骤如下:

  • 附加到通过USB连接的设备上的目标app
  • 从文件中读取Frida脚本
  • 把脚本分配到会话中
  • 启动(加载)脚本
  • 访问脚本中的所有RPC方法
  • 取消附加并关闭会话

Python代码如下(frida_rpc_player.py):

# frida_rpc_player.py

import codecs
import frida
from time import sleep

session = frida.get_usb_device().attach('Telegram')

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

script = session.create_script(source)
script.load()

rpc = script.exports

rpc.sms()
sleep(1)
rpc.email()
sleep(1)
rpc.lock()
sleep(1)
rpc.photo()

session.detach()

你可以通过在终端上输入python3 frida_rpc_player.py运行它。
图片描述

注意:为了在音频播放时显示一个警告,我添加了一个额外的函数到Frida脚本中。为了简化说明,在这里我没有进行详细的介绍,但是我推荐你看一看并分析一下脚本audiobox_rpc_alert.js中的所有步骤。在其中你可以看到自动触发以及消除警告的方法。

 

(译者注:这里有一个视频,因为markdown不支持直接放视频,所以我把链接贴上来了)

 


看起来很简单,对吧?我知道你想说:“太棒了,这样我就能去骚扰别人,让别人以为自己收到了消息。”

 

然而,这一技术只在你想要测试app,破解一些代码或是让app帮你自动完成一些任务时才有用(译者注:前面写脚本的步骤中提到需要通过usb连接手机,才能实现这些功能)。例如,如果app进行了一些加密/解密,并正确地实现了crypto,想要提取加密密钥几乎是不可能的,因为密钥被保存在Secure Enclave中。但是,细想一下,在app中已经提供了encrypt()/decrypt()函数的前提下,为什么一定要费尽心思地去提取密钥并复现加密算法呢。

 

请记住,这些并不是NativeFunction所特有的,你可以通过RPC使用你喜欢的任意代码。例如,你可以封装一个Objective-C函数,并以同样的方式使用它。

 

例如,我们可以写一个Frida脚本(openurl_rpc.js)来调用我之前的博客中写的函数:

// openurl_rpc.js

function openURL(url) {
   var UIApplication = ObjC.classes.UIApplication.sharedApplication();
   var toOpen = ObjC.classes.NSURL.URLWithString_(url);
   return UIApplication.openURL_(toOpen);
}

rpc.exports = {
    openurl: function (url) {
        send('called openurl: ' + url);
        openURL(url);
    }
};

现在你可以直接在Python中实现这个(见frida_rpc_openurl.py):

import codecs
import frida
from time import sleep

session = frida.get_usb_device().attach('Telegram')

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

script = session.create_script(source)
script.load()

open_twitter_about = script.exports.openurl("https://twitter.com/about")
print(f'Result: {open_twitter_about}') # Will show True/False

session.detach()

图片描述

 

根据函数返回值我们可以决定下一步怎么做,正如你看到的,这取决于你的想象力和创造力。

最后的注释

这一播放iOS系统音频的实例在现实生活中并不能用于骚扰他人。然而,这一技术可以帮助你解决在分析app时遇到的问题。

 

在学习的过程中,深度理解这一技术的原理是关键。如果你只是简单的复制一些你从网上找到的脚本的话,它只能解决你短期的问题,从长远来看,对你并没有帮助。最后的建议,我希望你能在今后的日子里一直保持学习新知识。

如果你有好的建议,反馈或是问题,请联系我的Twitter :)

 

@grepharder

 

原文链接:https://grepharder.github.io/blog/0x04_calling_ios_native_functions_from_python_using_frida_and_rpc.html

 

翻译:看雪翻译小组 梦野间

 

校对:看雪翻译小组 lumou


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
点赞3
打赏
分享
最新回复 (6)
雪    币: 36
活跃值: (976)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
芃杉 2019-6-26 09:09
2
0
mark,不错,辛苦了
雪    币: 1176
活跃值: (1219)
能力值: ( LV12,RANK:380 )
在线值:
发帖
回帖
粉丝
Tennn 5 2019-6-26 12:01
3
0
Frida用起来还是挺方便的
雪    币: 121
活跃值: (1517)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xxRea 2019-6-26 16:49
4
0
mark~~感谢分享
雪    币: 55
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Jmdebugger 2019-7-25 14:30
5
0
可以用objection哈
雪    币: 244
活跃值: (65)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Jennifor 2019-11-19 17:19
6
0
强势强势
雪    币: 359
活跃值: (2299)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
cydian 2019-12-26 18:12
7
0
frida mark
游客
登录 | 注册 方可回帖
返回