今天我们将学习怎样在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
,也就是要获取函数签名。这里不需要什么黑客技巧,假设你是一个开发者,现在你想要使用这个方法,你会从哪寻找自己需要的信息呢?没错,就是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虚拟机自动化脱壳的方法