越狱手机怎么安装ipa样安装Frida

如何使用Frida绕过iOS应用的越狱检测
本文是翻译文章,文章原作者,文章来源:attify.com
原文地址:
译文仅供参考,具体内容表达以及含义原文为准
预估稿费:180RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
在这篇文章中,我们将向读者介绍的一些相关知识。Frida是移动应用安全分析中的一个非常有趣的工具。
即使你从来没有用过Frida,你也可以将这篇文章作为使用Frida开展移动应用安全研究和探索的指南。
这篇文章主要包含以下内容:
1、Frida简介
2、如何在iOS上配置Frida
3、如何使用Frida连接到某个iOS进程
4、如何导出类和方法信息
5、如何使用Frida操控正在运行的iOS应用
二、Frida简介
Frida是一个跨平台的动态代码注入工具集,你可以使用它hook应用程序,插入自己的JavaSciprt代码,同时也能获取应用的内存和函数的完全访问权限。
Frida由Ole André V. Ravn?s()所开发,有个非常活跃的IRC(Internet Relay Chat,互联网中继聊天)频道与之相关,你可以在这个频道中与其他Frida爱好者探讨思路、问题以及工具的新功能。IRC频道为irc.freenode.net上的#frida频道。
根据具体的使用需求,Frida的一些应用场景为:
1、Hook某个函数,修改返回值。
2、分析自定义协议,嗅探或解密实时流量。
3、调试自己的应用程序。
4、导出iOS应用的类和方法信息。
5、其他应用场景。
我之所以提到“5、其他应用场景”,是因为我们可以将Frida用于各种用途。Frida提供的API功能非常强大,是构建自己的安全或分析工具的首选方案。现在已经有几种工具以Frida为基础,比如以及等。
Firda的其他应用场景中就包括它可以在非越狱设备上工作,这一点非常有用。为了在非越狱设备上运行Frida调试应用程序,我们需要使用诸如Swizzler2的工具修改应用程序,将FridaGadget dylib添加到应用程序中。
三、在iOS上配置Frida
只需要简单几步,我们就能配置Frida分析iOS应用的安全性。我们需要在iOS设备以及主机上进行配置。
为了在iOS设备上安装Frida,我们需要按照以下步骤操作:
1、在iOS设备上打开Cydia应用。
2、添加源,地址为“https://build.frida.re/”
3、进入已添加的源,搜索“Frida”,点击“Modify”然后安装。
要在主机上安装Frida,我们需要启动控制台,输入“pip install frida”命令,安装Frida程序。
四、使用Frida连接iOS进程
Frida安装完毕后,现在我们已经准备就绪,可以开始使用Frida评估iOS应用的安全性的可利用性。
我们的目标应用是Prateek Gianchandani开发的DVIA(Damn Vulnerable Ios Application,这个应用专门用于iOS平台的渗透测试)iOS应用。本文中用到的脚本大多数都可以在Interference Security的Github代码库中找到。
我们将要分析的是DVIA提供的越狱检测功能,如下所示,该应用提示设备目前处于越狱状态:
我们可以先看一下目标设备当前运行的进程列表,命令如下:
frida-ps –U
从上图中,我们可以了解到iOS设备当前正在运行的所有进程。
我们可以使用“frida –U process-name”命令,将Frida附加到任意一个进程上,命令执行成功后我们将会跳转到Frida控制台中,可以访问目标进程的所有属性、内存内容以及函数功能。
我们可以使用Frida的shell与目标进程交互,也可以编写自己的JavaScrpt代码,获取需要分析的数据。
五、导出iOS应用的类和方法信息
对于DVIA的越狱检测功能而言,我们的目标是确定哪个ViewController和函数负责检测设备的越狱状态。
首先,我们可以写个基本的Frida脚本,导出目标应用中的所有类和方法,从中找到与越狱有关的所有内容,以便在Frida的帮助下绕过应用的越狱检测。
我们的工作流程如下所示:
六、使用Frida找出DVIA中负责越狱检测的类
首先我们先找出应用中的所有类。
for (var className in ObjC.classes)
if (ObjC.classes.hasOwnProperty(className))
console.log(className);
运行脚本,将Frida附加到目标进程中(如下图所示),我们可以从运行结果中,找到目标进程的所有类。
我们最好通过grep命令搜索目标类,在本文的案例中,我们可以使用“Jailbreak”这个关键词。
通过关键词搜索,我们定位到一个名为“JailbreakDetectionVC”的类,如下图所示:
找到所有实例后,你可能会看到一个错误提示,忽略这个提示即可。
现在我们已经找到目标类,接下来我们可以探索这个类中的关键函数。
七、使用Frida找出DVIA中负责越狱检测的方法
为了查找类方法,我们需要使用Frida提供的“ObjC.classes.class-name.$methods”方法。在本文案例中,我们只需要查找“JailbreakDetectionVC”类中的方法即可。
console.log("[*] Started: Find All Methods of a Specific Class");
if (ObjC.available)
var className = "JailbreakDetectionVC";
var methods = eval('ObjC.classes.' + className + '.$methods');
for (var i = 0; i & methods. i++)
console.log("[-] "+methods[i]);
catch(err)
console.log("[!] Exception1: " + err.message);
catch(err)
console.log("[!] Exception2: " + err.message);
console.log("Objective-C Runtime is not available!");
console.log("[*] Completed: Find All Methods of a Specific Class");
运行这个脚本,使用grep命令查找类似“Jailbreak”、“Jailbroken”、“Detection”之类的关键词,如下图所示:
根据这些关键词,我们找到了三个方法,分别为“isJailbroken”、“jailbreakTest1Tapped:”以及“jailbreakTest2Tapped:”。
对于本文案例,“isJailbroken”看上去更像是检测设备是否越狱的方法。
八、使用Frida修改DVIA中负责越狱检测方法的返回值
现在我们可以分析“isJailbroken”方法究竟发送了什么类型的返回值。
if (ObjC.available)
var className = "JailbreakDetectionVC";
var funcName = "- isJailbroken";
var hook = eval('ObjC.classes.' + className + '["' + funcName + '"]');
Interceptor.attach(hook.implementation, {
onLeave: function(retval) {
console.log("[*] Class Name: " + className);
console.log("[*] Method Name: " + funcName);
console.log("t[-] Type of return value: " + typeof retval);
console.log("t[-] Return Value: " + retval);
catch(err)
console.log("[!] Exception2: " + err.message);
console.log("Objective-C Runtime is not available!");
运行这个脚本,点击iOS应用中的“Jailbreak Test 1”按钮,之后我们就可以在Frida控制台中看到函数的返回值。
由于我们的设备已越狱,我们获得的返回值为0x1,表明函数的返回结果为True。
接下来我们的任务是覆盖这个返回值,修改这个方法,使得不管什么时候我们点击应用中的“Jailbreak Test 1”按钮,函数的返回值始终为false(即0x0)。
我们只需要添加一行代码,就可以修改这个函数的返回值。
我们可以通过如下代码,修改函数的返回值,并将返回值反馈到控制台中:
newretval = ptr("0x0")
retval.replace(newretval)
console.log("t[-] New Return Value: " + newretval)
最终的脚本如下所示:
if (ObjC.available)
var className = "JailbreakDetectionVC";
var funcName = "- isJailbroken";
var hook = eval('ObjC.classes.' + className + '["' + funcName + '"]');
Interceptor.attach(hook.implementation, {
onLeave: function(retval) {
console.log("[*] Class Name: " + className);
console.log("[*] Method Name: " + funcName);
console.log("t[-] Type of return value: " + typeof retval);
console.log("t[-] Original Return Value: " + retval);
newretval = ptr("0x0")
retval.replace(newretval)
console.log("t[-] New Return Value: " + newretval)
catch(err)
console.log("[!] Exception2: " + err.message);
console.log("Objective-C Runtime is not available!");
运行这个脚本,我们可以在控制台中看到函数的返回值已经被成功修改:
回头看看这个iOS应用,你会发现应用提示设备处于未越狱状态,如下所示:
这就是本文的全部内容。在接下来的文章中,我们会向大家介绍Frida脚本语言的详细知识,以及如何利用Frida的API函数和其他工具对iOS和Android应用的安全性进行评估。
肥叶好香锅,孜孜不倦如何在iOS应用程序中用Frida来绕过“越狱检测”? - FreeBuf互联网安全新媒体平台 | 关注黑客与极客
如何在iOS应用程序中用Frida来绕过“越狱检测”?
共351227人围观
,发现 3 个不明物体
本文我将为大家展示,如何在iOS应用程序中使用Frida来绕过越狱检测。在正式开始之前,让我们先来简单了解下本文的具体流程。
以下是本文将要介绍的内容:
Frida框架介绍
Frida在iOS上的设置
将Frida连接到一个iOS进程
dump类和方法信息
使用Frida进行iOS应用程序的运行时操作
Frida是一款基于python + javascript 的hook与调试框架。它允许你将 JavaScript 的部分代码或者你自己的库注入到 windows、macos、linux、iOS、Android,以及 QNX 的原生应用中,同时能完全访问内存和功能。
该工具由OleAndréV.Ravn?s()开发,并且还有一个非常活跃的IRC频道,在这里你可以与其他许多同样热衷于Frida的技术人员探讨交流。你可以通过irc.freenode.net上的#frida加入IRC。
Frida的一些实际用例(根据自身使用的目的而定)–
hook特定函数并更改返回值
分析定制协议,并迅速嗅探/解密流量
对自己的应用程序进行调试
从iOS应用程序中dump类和方法信息
除以上提到的作用之外,Frida 还提供了一系列的 API 以及方法。你可以使用命令行窗口或者像 frida-trace 的记录 low-level 函数(例如 libc.so 中的’open’调用)的工具来快速运行。你可以使用C,NodeJs或者Python绑定来完成更加复杂的工作。因此,Frida 也是我强烈推荐大家使用的安全或分析工具的首选。目前,已经有好几种工具都建立在了Frida上,包括&和。
Frida的另一大优势就是,可以在非越狱的设备上正常工作。为了更好的运行Frida来调试非越狱设备上的应用程序,你可以使用Swizzler2等工具来修改应用程序,以便在应用程序中添加FridaGadget&dylib。
Frida在iOS上的设置
Frida在ios上的设置也非常的简单,只需要在你的iOS设备以及主机上执行以下操作。
要在你的iOS设备上安装Frida服务器,请参照以下步骤。
1.在你的iOS设备上打开Cydia应用程序。
2.添加一个源,URL为:
3.打开Source或搜索Frida,单击Modify,然后单击Install。
为了在你的系统上安装Frida的Python绑定,你需要启动erminal并输入pip install frida来进行安装。
将Frida连接到一个iOS进程
现在我们已经安装了Frida。下面我们就要正式开始使用Frida,对我们的iOS应用程序进行安全评估和开发了!
在本案例中,我们将使用Damn Vulnerable iOS App(DVIA)这款包含大量安全漏洞的app来进行测试,你可以从下载到它。以下大部分所使用的ios app&Frida测试脚本你可以在获取到。
我们将分析DVIA的越狱检测行为,目前该设备显示已越狱。
让我们先来查看下,目标设备上所有正在运行的进程有哪些:
frida-ps –U
从上面的截图我们可以看到,所有当前正在运行的进程。
下面让我们来attach一个进程。你可以通过 ‘frida -U 进程名’ 的格式来attach某个进程。成功attach后,我们将进入到frida的控制台界面,在该控制台我们可以访问到目标进程的所有不同属性,内存内容和功能。
我们可以在Frida的shell中工作,并与我们的进程进行交互,或者我们还可以通过编写自己的JavaScript,来获取我们想要的数据。
dump类和方法信息
这项工作的目的是为了确定在DVIA的越狱检测中,负责验证我们的设备是否越狱的ViewController和function是哪个。
我们先来写一个基本的Frida脚本,来转储目标应用程序中存在的所有类和方法。在这里,我们将寻找与越狱相关所有的内容,以便我们能够在Frida的帮助下绕过越狱检测。
基本操作流程如下:
使用Frida查找DVIA中的越狱检测类
我们先来看看,应用程序中的类都有哪些。
for (var className in ObjC.classes)
&&&&&&& if (ObjC.classes.hasOwnProperty(className))
&&&&&&&&&&& console.log(className);
一旦运行它,你会看到Frida成功attach到目标进程(如下图所示),随后它将为我们显示目标进程中的所有类。
这里我们可以通过grep命令来筛查出包含Jailbreak字样的类。这里我们看到一个叫JailbreakDetectionVC的类,如下所示。
找到所有实例后,在这里你可能会遇到一个可忽略的错误语句。
在完成了目标类的查找之后,接下来让我们将目光转向类中的方法。
使用Frida查找DVIA中越狱检测类的方法
为了找到方法,我们需要使用&ObjC.classes.class-name.$methods。在这里我们将只查找JailbreakDetectionVC类中的方法。
console.log("[*] Started: Find All Methods of a Specific Class");
if (ObjC.available)
var className = "JailbreakDetectionVC";
var methods = eval('ObjC.classes.' + className + '.$methods');
for (var i = 0; i & methods. i++)
console.log("[-] "+methods[i]);
catch(err)
console.log("[!] Exception1: " + err.message);
catch(err)
console.log("[!] Exception2: " + err.message);
console.log("Objective-C Runtime is not available!");
console.log("[*] Completed: Find All Methods of a Specific Class");
让我们继续运行它,并继续使用grep命令来检测那些带有Jailbreak&,&Jailbroken& 和&Detection字符串的内容,如下所示。
我们发现该类中其中有三个方法,包含我们的查找关键字,它们分别为Jailbroken,jailbreakTest1Tapped:和&jailbreakTest2Tapped:。
在我们的案例中,isjailbroken&是最有可能被用于检测是否越狱,并发送返回值的函数。
使用Frida修改DVIA越狱检测类中方法的返回值
所以让我们继续看看,Jailbroken都发送了什么类型的返回值。
if (ObjC.available)
var className = "JailbreakDetectionVC";
var funcName = "- isJailbroken";
var hook = eval('ObjC.classes.' + className + '["' + funcName + '"]');
Interceptor.attach(hook.implementation, {
onLeave: function(retval) {
console.log("[*] Class Name: " + className);
console.log("[*] Method Name: " + funcName);
console.log("\t[-] Type of return value: " + typeof retval);
console.log("\t[-] Return Value: " + retval);
catch(err)
console.log("[!] Exception2: " + err.message);
console.log("Objective-C Runtime is not available!");
运行此脚本后,请在iOS应用程序中按 Jailbreak Test 1,你将看到Frida控制台中显示的返回值。
由于我们的设备已经越狱,所以它的返回值为0×1。
接下来我们需要做的就是覆盖此返回值并修补该方法,以便每次在应用程序中按下Jailbreak Test 1按钮时,它将返回false或0×0。
让我们添加以下代码,来更改这个特定函数的返回值并记录到控制台:
newretval = ptr("0x0")
retval.replace(newretval)
console.log("\t[-] New Return Value: " + newretval)
完整脚本如下。
if (ObjC.available)
var className = "JailbreakDetectionVC";
var funcName = "- isJailbroken";
var hook = eval('ObjC.classes.' + className + '["' + funcName + '"]');
Interceptor.attach(hook.implementation, {
onLeave: function(retval) {
console.log("[*] Class Name: " + className);
console.log("[*] Method Name: " + funcName);
console.log("\t[-] Type of return value: " + typeof retval);
console.log("\t[-] Original Return Value: " + retval);
newretval = ptr("0x0")
retval.replace(newretval)
console.log("\t[-] New Return Value: " + newretval)
catch(err)
console.log("[!] Exception2: " + err.message);
console.log("Objective-C Runtime is not available!");
运行该脚本后,我们可以看到返回值已经被修改,如下所示。
此时当你再次查看你的iOS应用程序时你会发现,应用程序将提示你的设备还未越狱(如下所示)。
通过本案例,我们已经学会了Frida的基本使用方法。在后续的文章中我将带大家更深入的了解Frida脚本以及如何利用Frida的API和其它工具,来执行iOS和Android应用程序的安全性评估工作。
*参考来源:,FB小编 secist 编译,转载请注明来自FreeBuf(FreeBuf.COM)
试下看能绕过支付宝的越狱检测不?
必须您当前尚未登录。
必须(保密)
每个人的心中都有一个梦。。
关注我们 分享每日精选文章
可以给我们打个分吗?在 Android 手机上安装 Frida 框架,篡改 SSLContext-ZAKER新闻
嘶吼RoarTalk
本文我会想向你展示一个相当简单的方法,该方法能绕过所有 Android 移动目标的 " 证书锁定 " ( certificate pinning ) 。本文描述的方法是基于的研究文章和他开发的脚本。如果需要在 Android 手机上绕过认证,都要涉及 SSLContext。本文会讲解一种通过调用 Frida 并操纵 SSLContext 来构建一个能够在 Android 上普遍绕过证书锁定的脚本。简单地说,脚本的运行分以下 3 步:1. 它会从文件系统(Burpsuite 的 CA 证书)中加载一个流氓证书;2. 它会创建一个包含用户信任的 CA 密钥库(Keystore);3. 最后,它会创建一个 TrustManager,它会信任用户的密钥库中的 CA。每当应用程序初始化其 SSL Contex 时,Frida 脚本都会劫持 sslcontext.init ( ) 方法,当它被调用时,第二个参数即应用程序 TrustManager 会出现。在 Android 上,SSLContext 函数调用又被称为 SSLContext.init(KeyManager,TrustManager,SecuRandom)。对于初学者来说,要理解 Piergiovanni Cipolloni 的方法会显得很困难,因此为了让这部分读者能读懂,我特意加了一些基本知识在里面。本文会包含以下 7 方面内容:1. 如何将 Burp 证书安装到你的设备上;2. 如何为移动设备配置 Burpsuite;3. 如何在你的 Android 设备上安装 Frida;4. 如何远程运行 Frida 服务器;5. 如何在你的服务器上安装 Frida;6. 如何使用应用程序调用脚本;7. 为什么要进行证书锁定。通过证书锁定,开发人员可以确保其应用程序不接受实际由官方认证机构签名的假证书。 由于 Android 系统本身只检查证书的层次结构是否正确以及是否在所谓的 " 信任证书存储库 " 中体现 CA 的标识,所以如果攻击者将伪造的 CA 标识嵌入到信任存储库,那后果不堪设想。如果发生以上的攻击,攻击者基本上就能够拦截应用程序的整个 HTTPS 传输。为此,开发人员才会进一步,在应用程序中编译真实证书,并确保应用程序只使用该证书。构建运行环境:1. 你需要一台 Android 虚拟机或真实设备;2. 一些空闲时间;3. 安装并配置 ADB / Android 工具。安装 Burp 证书使用 Burpsuite 运行浏览器(默认值:127.0.0.1:8080),然后单击 CA 认证。这时一个名为 cacert.der 的文件会被下载,不过下载后该文件会被重命名为 cacert.cer,关于 cacert.der 和 cacert.cer 的区别,简而言之,就是一种替代关系,如要详细了解,请点。如果你的运行设备是 Mac,而且默认下载目录就是 " 下载 ",就可以执行以下操作:1.cd / Downloads,将你的目录更改为 " 下载 ";2.mv cacert.der cacert.cer,重命名证书;3.adb push cacert.cer / mnt / sdcard / DCIM /,将证书复制到设备的 SD 卡。接着,在设备上(无论是虚拟的还是实际的),你必须安装证书才能将其放入 Android 的信任证书存储库中。具体操作步骤是这样的:点击菜单按钮→进入设置→一直向下滚动到安全→选择从 SD 卡安装。点击 cacert.cer 并命名证书,例如命名为 Burp。如果一切正常,你应该在设置→安全→可信凭证→用户下看到以下显示。这样,你就成功地将自己的 CA 安装到系统中。配置 Burpsuite现在你已经在系统上安装了 PortSwigger CA,为此需要先设置代理,以便拦截应用程序服务器的流量。设置你的 Android 设备再次进入设置→点击 Wi-Fi →点击 WiredSSID →点击修改网络 →点击代理→选择手动设置,这样屏幕就会出现以下信息列表。你需要填写的内容如下:1. 你的 Burp Suite 应用程序的 IP 地址是代理主机名吗;2. 端口是否启动了 Burp Suite(默认为 8080)。设置 Burp在默认情况下,Burp 会在主机端口 8080 上打开一个本地代理,以拦截你的移动流量。为此,你必须设置 Burp 来监听外部 IP 地址。启动 Burpsuite 并转到代理项,然后选项当前配置(如下图所示),最后进行编辑。现在选择特定地址选项并选择你的本地 IP 地址,比如我的就是 192.168.0.193,而对于读者来说,这取决于你的 DHCP 服务器如何分配地址。如果你不确定你的 IP 地址是哪个,以 OSX 为例,请打开终端并输入 ifconfig en0 | grep inet 命令进行查找。如果你按着我说的步骤下来,那么你现在就可以无需证书锁定来拦截应用程序流量了。在你的 Android 设备上安装 Frida这一步要进行的就是绕过证书锁定了,为此你需要在你的 Android 设备上安装一个 Frida-Server 的副本。首先,你需要下载最新版本的 Frida-Server,进行下载。不过,问题来了你需要知道下载的版本,是 x86 还是 x86_64,在此,我告你一个简单方法:只需要输入 adb shell getprop ro.product.cpu.abi 即可找出正确的版本,比如我就是 x86。所以对我来说,正确的版本是 frida-server-10.6.15-android-x86.xz。你可能会会注意到 .xz 扩展名是一个档案文件,所以,在使用二进制文件之前,我们首先要对它进行解析。对于资深用户来说,你只需输入 tar -xJf frida-server-10.6.15-android-x86 命令,然后使用 MAC 解压神器即可。在解析该二进制文件以后,你就需要使用终端了。1.mv frida-server-10.6.15-android-x86 frida-server,将其重命名为 frida-server;2.adb root,确保你的环境能够以 root 身份运行命令;3. adb push frida-server /data/local/tmp/,将 frida-server 二进制文件复制到设备;4.adb shell"chmod 755 / data / local / tmp / frida-server",为二进制文件系统提供正确的权限;5.adb shell"/ data / local / tmp / frida-server&" – 在后台将 frida-server 作为一个服务运行;5.1. 如果要在外部 IP 上运行 frida-server 就要输入 adh shell "/data/local/tmp/frida-server – listen 0.0.0.0 & 命令。在你的设备上安装 Frida不管你用的是什么设备,你都需要执行 sudo pip 来安装 frida。为验证你是否安装了 Frida(不管是远程还是本地),你都需要执行 frida-ps。此时会显示一个 ps 命令,该命令将在你的设备上生成当前正在运行的进程列表。如果通过 USB 连接则会显示 frida-ps U;如果你在外部地址使用 frida ,那无论你手机的地址是什么,都会显示 frida-ps -H 192.*.*.*。如果你看到类似以下的输出:那证明你可以准备绕过 " 证书锁定 " 了。为了证明我的判断,我将在这里使用一个随机应用程序,如果你想要看更多的实践案例,可以在,,或上找到。为了绕过证书锁定,我需要通过 Piergiovanni Cipolloni 编写的 Android SSL Re-pinning Frida 脚本来实现,该脚本可以在找到。使用 frida 绕过证书锁定首先,我需要在设备上安装我的目标,这可以通过多种方式完成:1. 从 Google 应用商店中下载安装应用程序;2. 使用 Apkpure 或 apk-dl 下载应用程序。下载完以后,打开终端并使用 adb install com.company.whatever.apk 安装应用程序。下一步是从应用程序中选择你的目标,就像我之前所讲的那样,可以使用 frida-ps -H 192.*.*.* 用于远程 Frida 服务器,如果你是通过 USB 连接设备的,请使用 frida-ps -U。接下来,我需要做的就是创建一个以前生成的 cacert.cer 的副本,如果你完全是按着我的步骤来做的,那此时 cacert.cer 的副本仍然位于 " 下载 " 文件夹中。最后打开终端,进行以下 5 步操作:1. 通过 cd Downloads 命令,把 "cacert.cer" 移动到 " 下载 " 文件夹;2. 通过 mv cacert.cer burpca-cert-der.crt 命令来进行重命名,以匹配 Frida 脚本中的文件;3. 通过 wget https://techblog.mediaservice.net/wp-content/uploads/2017/07/frida-android-repinning_sa-1.js 命令下载 Frida 脚本;4. 通过 adb push burpca-cert-der.crt /data/local/tmp/cert-der.crt 命令将流氓证书推送到设备;5. 通过 frida -U -f it.app.mobile -l frida-android-repinning_sa-1.js – no-pause 命令实现绕过。如果看到以下输出,就意味着你可以绕过所有 Android 移动目标的 " 证书锁定 "。
相关标签:
原网页已经由 ZAKER 转码排版
中关村在线4小时前
凤凰科技13小时前
腾讯科技昨天
少数派昨天
IT之家2小时前
钛媒体刚刚
TechWeb52分钟前
互联网新闻昨天
App精选昨天
凤凰科技10小时前
凤凰科技15小时前
差评17小时前
新芽NEWSEED20小时前
驱动之家2小时前
凤凰科技昨天[原创] 如何构建一款像 frida 一样的框架
一般来说可以分为以下几个模块
内存分配 模块
指令写 模块
指令读 模块
指令修复 模块
调度器 模块
1. 内存分配 模块
需要分配部分内存用于写入指令, 这里需要关注两个函数都是关于内存属性相关的. 1. 如何使内存 可写 2. 如何使内存 可执行 3. 如何分配相近的内存来达到 near jump
这一部分与具体的操作系统有关. 比如 darwin 下分配内存使用 mmap 实际使用的是 mach_vm_allocate. .
在 lldb 中可以通过 memory region address 查看地址的内存属性.
当然这里也存在一个巨大的坑, IOS 下无法分配 rwx 属性的内存页. 这导致 inlinehook 无法在非越狱系统上使用, 并且只有 MobileSafari 才有 VM_FLAGS_MAP_JIT 权限. 具体解释请参下方 [坑 - rwx 与 codesigning].
另一个坑就是如何在 hook 目标周围分配内存, 如果可以分配到周围的内存, 可以直接使用 b 指令进行相对地址跳(near jump), 从而可以可以实现单指令的 hook.
举个例子比如 b label, 在 armv8 中的可以想在 +-128MB 范围内进行 near jump, 具体可以参考 ARM Architecture Reference Manual ARMv8, for ARMv8-A architecture profile Page: C6-550.
这里可以有三个尝试.
使用 mmap 的 MAP_FIXED 尝试在周围地址分配内存页, 成功几率小.
尝试使用 vm_region_recurse_64 搜索 protection 为 PROT_EXEC & PROT_READ 的 code cave. (通常用来暴力查找 dyld 的地址)
尝试搜索内存空洞(code cave), 搜索 __text 这个 section 其实更准确来说是搜索 __TEXT 这个 segment. 由于内存页对齐的原因以及其他原因很容易出现 code cave. 所以只需要搜索这个区间内的 00 即可, 00 本身就是无效指令, 所以可以判断该位置无指令使用.
当然还可以有强制相对跳(double jump), 直接对 +-128MB 内选一个地址强制 code patch 并修复.
// 第一次绝对地址跳, 跳转到修复模块, 执行正常流程
&ldr x17, #0x8\n&
&b #0xc\n&
// double jump, 跳转到 on_enter_trampoline
&ldr x17, #0x8\n&
&b #0xc\n&
2. 指令写 模块
先说坑, &非越狱状态下不允许设置 rw- 为 r-x, 或者 &设置 r-x 为 rx-. 具体解释请参考下方坑 [坑-rwx 与 codesigning].
其实这里的指令写有种简单的方法, 就是在本地生成指令的16进制串, 之后直接写即可. 但这种应该是属于 hardcode.
这里使用 frida-gum 和 CydiaSubstrace 都用的方法, 把需要用到的指令都写成一个小函数.
// frida-gum/gum/arch-arm64/gumarm64writer.c
gum_arm64_writer_put_ldr_reg_address (GumArm64Writer * self,
arm64_reg reg,
GumAddress address)
gum_arm64_writer_put_ldr_reg_u64 (self, reg, (guint64) address);
gum_arm64_writer_put_ldr_reg_u64 (GumArm64Writer * self,
arm64_reg reg,
guint64 val)
GumArm64RegI
gum_arm64_writer_describe_reg (self, reg, &ri);
g_assert_cmpuint (ri.width, ==, 64);
gum_arm64_writer_add_literal_reference_here (self, val);
gum_arm64_writer_put_instruction (self,
(ri.is_integer ? 0x : 0x5c000000) | ri.index);
其实有另外一个小思路, &有一点小不足, 就是确定指令片段的长度, 但其实也有解决方法, 可以放几条特殊指令作为结尾标记.
先使用内联汇编写一个函数.
__attribute__((__naked__)) static void ctx_save() {
__asm__ volatile(
/* reserve space for next_hop */
&sub sp, sp, #(2*8)\n&
/* save {q0-q7} */
&sub sp, sp, #(8*16)\n&
&stp q6, q7, [sp, #(6*16)]\n&
&stp q4, q5, [sp, #(4*16)]\n&
&stp q2, q3, [sp, #(2*16)]\n&
&stp q0, q1, [sp, #(0*16)]\n&
/* save {x1-x30} */
&sub sp, sp, #(30*8)\n&
&stp fp, lr, [sp, #(28*8)]\n&
&stp x27, x28, [sp, #(26*8)]\n&
&stp x25, x26, [sp, #(24*8)]\n&
&stp x23, x24, [sp, #(22*8)]\n&
&stp x21, x22, [sp, #(20*8)]\n&
&stp x19, x20, [sp, #(18*8)]\n&
&stp x17, x18, [sp, #(16*8)]\n&
&stp x15, x16, [sp, #(14*8)]\n&
&stp x13, x14, [sp, #(12*8)]\n&
&stp x11, x12, [sp, #(10*8)]\n&
&stp x9, x10, [sp, #(8*8)]\n&
&stp x7, x8, [sp, #(6*8)]\n&
&stp x5, x6, [sp, #(4*8)]\n&
&stp x3, x4, [sp, #(2*8)]\n&
&stp x1, x2, [sp, #(0*8)]\n&
/* save sp, x0 */
&sub sp, sp, #(2*8)\n&
&add x1, sp, #(2*8 + 8*16 + 30*8 + 2*8)\n&
&stp x1, x0, [sp, #(0*8)]\n&
/* alignment padding + dummy PC */
&sub sp, sp, #(2*8)\n&);
之后直接复制这块函数内存数据即可, 这一般适合那种指令片段堆.
void ZzThunkerBuildEnterThunk(ZzWriter *writer)
// pop x17
writer_put_ldr_reg_reg_offset(writer, ARM64_REG_X17, ARM64_REG_SP, 0);
writer_put_add_reg_reg_imm(writer, ARM64_REG_SP, ARM64_REG_SP, 16);
writer_put_bytes(writer, (void *)ctx_save, 26 * 4);
// call `function_context_begin_invocation`
writer_put_bytes(writer, (void *)pass_enter_func_args, 4 * 4);
writer_put_ldr_reg_address(
writer, ARM64_REG_X17,
(zaddr)(zpointer)function_context_begin_invocation);
writer_put_blr_reg(writer, ARM64_REG_X17);
writer_put_bytes(writer, (void *)ctx_restore, 23 * 4);
3. 指令读 模块
这一部分实际上就是 disassembler, 这一部分可以直接使用 capstone, 这里需要把 capstone 编译成多种架构.
4. 指令修复 模块
这里的指令修复主要是发生在 hook 函数头几条指令, 由于备份指令到另一个地址, 这就需要对所有 PC(IP) 相关指令进行修复. 对于确定的哪些指令需要修复可以参考 .
大致的思路就是: 判断 capstone 读取到的指令 ID, 针对特定指令写一个小函数进行修复.
例如在 frida-gum 中:
frida-gum/gum/arch-arm64/gumarm64relocator.c
static gboolean
gum_arm64_relocator_rewrite_b (GumArm64Relocator * self,
GumCodeGenCtx * ctx)
const cs_arm64_op * target = &ctx-&detail-&operands[0];
gum_arm64_writer_put_ldr_reg_address (ctx-&output, ARM64_REG_X16,
target-&imm);
gum_arm64_writer_put_br_reg (ctx-&output, ARM64_REG_X16);
return TRUE;
5. 跳板 模块
跳板模块的设计是希望各个模块的实现更浅的耦合, 跳板函数主要作用就是进行跳转, 并准备 跳转目标 需要的参数. 举个例子, 被 hook 的函数经过入口跳板(enter_trampoline), 跳转到调度函数(enter_chunk), 需要被 hook 的函数相关信息等, 这个就需要在构造跳板时完成.
6. 调度 模块
可以理解为所有被 hook 的函数都必须经过的函数, 类似于 objc_msgSend, 在这里通过栈返回值来控制函数(replace_call, pre_call, half_call, post_call)调用顺序.
本质有些类似于 objc_msgSend 所有的被 hook 的函数都在经过 enter_trampoline 跳板后, 跳转到 enter_thunk, 在此进行下一步的跳转判断决定, 并不是直接跳转到 replace_call.
如果希望在 pre_call 和 post_call &使用同一个局部变量, 就想在同一个函数内一样. 在 frida-js 中也就是 this 这个关键字. 这就需要自建函数栈, 模拟栈的行为. 同时还要避免线程冲突, 所以需要使用 thread local variable, 为每一个线程中的每一个 hook-entry 添加线程栈, 同时为每一次调用添加函数栈. 所以这里存在两种栈. 1. 线程栈(保存了该 hook-entry 的所有当前函数调用栈) 2. 函数调用栈(本次函数调用时的栈)
在进行指令修复时, 需要需要将 PC 相关的地址转换为绝对地址, 其中涉及到保存地址到寄存器. 一般来说是使用指令 ldr. 也就是说如何完成该函数 writer_put_ldr_reg_address(relocate_writer, ARM64_REG_X17, target_addr);
frida-gum 的实现原理是, 有一个相对地址表, 在整体一段写完后进行修复.
gum_arm64_writer_put_ldr_reg_u64 (GumArm64Writer * self,
arm64_reg reg,
guint64 val)
GumArm64RegI
gum_arm64_writer_describe_reg (self, reg, &ri);
g_assert_cmpuint (ri.width, ==, 64);
gum_arm64_writer_add_literal_reference_here (self, val);
gum_arm64_writer_put_instruction (self,
(ri.is_integer ? 0x : 0x5c000000) | ri.index);
在 HookZz 中的实现, 直接将地址写在指令后, 之后使用 b 到正常的下一条指令, 从而实现将地址保存到寄存器.
void writer_put_ldr_reg_address(ZzWriter *self, arm64_reg reg, zaddr address)
writer_put_ldr_reg_imm(self, reg, (zuint)0x8);
writer_put_b_imm(self, (zaddr)0xc);
writer_put_bytes(self, (zpointer)&address, sizeof(address));
也就是下面的样子.
&ldr x17, #0x8\n&
&b #0xc\n&
寄存器污染
在进行 inlinehook 需要进行各种跳转, 通常会以以下模板进行跳转.
ldr x16, 8;
问题在于这会造成 x16 寄存器被污染(arm64 中 svc #0x80 使用 x16 传递系统调用号) 所以这里有两种思路解决这个问题.
在使用寄存器之前进行 push, 跳转后 pop, 这里存在一个问题就是在原地址的几条指令进行 patch code 时一定会污染一个寄存器(也不能说一定, 如果这时进行压栈, 在之后的 invoke_trampline 会导致函数栈发生改变, 此时有个解决方法可以 pop 出来, 由 hook-entry 或者其他变量暂时保存, 但这时需要处理锁的问题. )
挑选合适的寄存器, 不考虑污染问题. 这时可以参考, 下面的资料, 选择 x16 or x17, 或者自己做一个实验 otool -tv ~/Downloads/DiSpecialDriver64 & ~/Downloads/DiSpecialDriver64.txt 通过 dump 一个 arm64 程序的指令, 来判断哪个寄存器用的最少, 但是不要使用 x18 寄存器, 你对该寄存器的修改是无效的.
Tips: 之前还想过为对每一个寄存器都做适配, 用户可以选择当前的 hook-entry 选择哪一个寄存器作为临时寄存器.
Programmer’s Guide for ARMv8-A
9.1 Register use in the AArch64 Procedure Call Standard
9.1.1 Parameters in general-purpose registers
这里也有一个问题, &这也是 frida-gum 中遇到一个问题, 就是对于 svc #0x80 类系统调用, 系统调用号(syscall number)的传递是利用 x16 寄存器进行传递的, 所以本框架使用 x17 寄存器, 并且在传递参数时使用 push & pop, 在跳转后恢复 x17, 避免了一个寄存器的使用.
rwx 与 codesigning
对于非越狱, 不能分配可执行内存, 不能进行 code patch.
两篇原理讲解 codesign 的原理
https://papers.put.as/papers/ios/2011/syscan11_breaking_ios_code_signing.pdf
http://www.newosxbook.com/articles/CodeSigning.pdf
以及源码分析如下:
crash 异常如下, 其中 0x4000 是 mmap 分配的页.
Exception Type:
EXC_BAD_ACCESS (SIGKILL - CODESIGNING)
Exception Subtype: unknown at 0x4000
Termination Reason: Namespace CODESIGNING, Code 0x2
Triggered by Thread:
寻找对应的错误码
xnu-/bsd/sys/reason.h
* codesigning exit reasons
#define CODESIGNING_EXIT_REASON_TASKGATED_INVALID_SIG 1
#define CODESIGNING_EXIT_REASON_INVALID_PAGE
#define CODESIGNING_EXIT_REASON_TASK_ACCESS_PORT
找到对应处理函数, 请仔细阅读注释里内容, 不做解释了.
# xnu-/osfmk/vm/vm_fault.c:2632
/* If the map is switched, and is switch-protected, we must protect
* some pages from being write-faulted: immutable pages because by
* definition they may not be written, and executable pages because that
* would provide a way to inject unsigned code.
* If the page is immutable, we can simply return. However, we can't
* immediately determine whether a page is executable anywhere. But,
* we can disconnect it everywhere and remove the executable protection
* from the current map. We do that below right before we do the
* PMAP_ENTER.
cs_enforcement_enabled = cs_enforcement(NULL);
if(cs_enforcement_enabled && map_is_switched &&
map_is_switch_protected && page_immutable(m, prot) &&
(prot & VM_PROT_WRITE))
return KERN_CODESIGN_ERROR;
if (cs_enforcement_enabled && page_nx(m) && (prot & VM_PROT_EXECUTE)) {
if (cs_debug)
printf(&page marked to be NX, not letting it be mapped EXEC\n&);
return KERN_CODESIGN_ERROR;
if (cs_enforcement_enabled &&
!m-&cs_validated &&
(prot & VM_PROT_EXECUTE) &&
!(caller_prot & VM_PROT_EXECUTE)) {
* FOURK PAGER:
* This page has not been validated and will not be
* allowed to be mapped for &execute&.
* But the caller did not request &execute& access for this
* fault, so we should not raise a code-signing violation
* (and possibly kill the process) below.
* Instead, let's just remove the &execute& access request.
* This can happen on devices with a 4K page size if a 16K
* page contains a mix of signed&executable and
* unsigned&non-executable 4K pages, making the whole 16K
* mapping &executable&.
prot &= ~VM_PROT_EXECUTE;
/* A page could be tainted, or pose a risk of being tainted later.
* Check whether the receiving process wants it, and make it feel
* the consequences (that hapens in cs_invalid_page()).
* For CS Enforcement, two other conditions will
* cause that page to be tainted as well:
* - pmapping an unsigned page executable - this
* - writeable mapping of a validated page - the content of that page
can be changed without the kernel noticing, therefore unsigned
code can be created
if (!cs_bypass &&
(m-&cs_tainted ||
(cs_enforcement_enabled &&
(/* The page is unsigned and wants to be executable */
(!m-&cs_validated && (prot & VM_PROT_EXECUTE))
/* The page should be immutable, but is in danger of being modified
* This is the case where we want policy from the code directory -
* is the page immutable or not? For now we have to assume that
* code pages will be immutable, data pages not.
* We'll assume a page is a code page if it has a code directory
* and we fault for execution.
* That is good enough since if we faulted the code page for
* writing in another map before, if we fault
* it for writing in this map later it will also be faulted for executing
* and if we fault for writing in another map
* later, we will disconnect it from this pmap so we'll notice
* the change.
(page_immutable(m, prot) && ((prot & VM_PROT_WRITE) || m-&wpmapped))
Later on, whenever a page fault occurs the vm_fault function in vm_fault.c is called. During the page fault the signature is validated if necessary. The signature will need to be validated if the page is mapped in user space, if the page belongs to a code-signed object, if the page will be writable or simply if it has not previously been validated. Validation happens in the vm_page_validate_cs function inside vm_fault.c (the validation process and how it is enforced continually and not only at load time is interesting, see Charlie Miller’s book for more details).
If for some reason the page cannot be validated, the kernel checks whether the CS_KILL flag has been set and kills the process if necessary. There is a major distinction between iOS and OS X regarding this flag. All iOS processes have this flag set whereas on OS X, although code signing is checked it is not set and thus not enforced.
In our case we can safely assume that the (missing) code signature couldn’t be verified leading to the kernel killing the process.
支付方式:
最新回复 (2)
& 感谢分享
1.请先关注公众号。
2.点击菜单"更多"。
3.选择获取下载码。

我要回帖

更多关于 越狱手机怎么安装软件 的文章

 

随机推荐