Android部分
基础知识
1)安卓分层
简单提一下安卓分层,这个点知道了更好,不知道也无所谓。毕竟我们不是开发,只是为了避免下述情况:
我要学习so文件HOOK。一波百度 “HOOK so层”之后,出现了一个“native”。点进去一看,其内容是对so文件hook。
那native是啥?这就要提一下安卓的分层。
Android采用分层的架构,分为四层,从高层到底层分为应用程序层(app+System apps),应用程序框架层(Java API Framework),系统运行库和运行环境层(Libraries + android Runtime)和Linux核心层(HAL+ Linux Kernel),如下图所示:
细分之后如下图:
其中,Native层(本地服务)这部分为常见一些本地服务和一些链接库等。这一层的一个特点就是通过C和C++语言实现。比如我们现在要执行一个复杂运算,如果通过java代码去实现,那么效率会非常低,此时可以选择通过C或C++代码去实现,然后和我们上层的Java代码通信(这部分在android中称为jni机制,下面会提)。又比如我们的设备需要运行,那么必然要和底层的硬件驱动交互,也要通过Native层。
安卓native层,通俗来说就是对lib目录下的so文件,so文件是Android NDK动态链接库,是二进制文件,作用相当于windows下的.dll文件。
2)so 文件
so 是 shared object 的缩写,见名思义就是共享的对象,机器可以直接运行的二进制代码。so 主要存在于 Unix 和 Linux 系统中。它是 c/c++ 实现的功能函数集合,并对外提供标准的接口,外层可以通过这个接口调用c/c++的代码。
Android 系统为什么要使用.so文件呢?
Android 系统应用基本都是基于 Java 语言开发,而Java 语言是不能直接访问Android系统底层的硬件接口。而Android系统中可以通过 JNI 和 硬件访问服务去访问系统底层的硬件接口。比如:开启蓝牙、关闭蓝牙等。
安卓so文件存在与app的lib目录下,利用解压软件查看目录结构如下:
该目录下存在各个CPU构架的分类。不同CPU架构的android手机加载时会在libs下找自己对应的目录,从对应的目录下寻找需要的so文件。如果没有对应的目录,就会去armeabi下去寻找,如果已经有对应的目录,却没有找到对应的so文件,也不会去armeabi下去寻找了。
目前主流的Android设备主要是 armeabi-v7a 架构的,然后是 x86 和 armeabi 了。如果同时包含了 armeabi, armeabi-v7a和x86,所有设备都可以运行。这个点是为了寻找将要HOOK的so文件,armeabi-v7a构架的手机去HOOK其他构架的so文件,一句话就是“可以,但是没必要。非要改也不拦着。”这边贴一个兼容内容:
以armeabi-v7a设备为例,该Android设备会优先寻找libs目录下的armeabi-v7a文件夹。如果只有armeabi-v7a文件夹而没有 so文件会报错;如果找不到armeabi-v7a文件夹,则寻找armeabi文件夹,兼容运行该文件夹下的so,但是不能兼容运行x86的so。所以项目中如果只含有x86的so,在armeabi和armeabi-v7a也是无法运行的。
3)JNI
定义:Java Native Interface,即 Java本地接口
作用:使得Java 与 本地其他类型语言(如C、C++)交互,即在 Java代码里调用 C、C++等语言的代码或 C、C++代码调用 Java 代码
特别注意:
JNI是 Java 调用 Native 语言的一种特性
JNI 是属于 Java 的,与 Android 无直接关系
JNI 代码经过编译之后在Unix/Linux系统上就会生成 .so文件,通过调用Java代码调用.so中的接口方法即可实现硬件的访问。
在 Android 系统下 JNI 可以通过NDK快速实现。
我们首要目的就是要了解.so文件的作用是用来访问系统底层的接口,而Android应用基本都是Java开发,而Java不支持直接访问硬件,但是Android提供了两种方式去访问接口:JNI和硬件访问服务。JNI的方式编译后会产生.so文件。同时Android还给开发者提供了NDK这个开发工具包,开发者可以使用NDK快速实现 JNI的功能。
4)加载过程
关注上图中存在的几个关键字。
IDA基础使用
IDA这个工具闻名遐迩,不过现在只需要其一小部分。吾爱上有其7.0版本的破解版,注意的是在安装的时候中文目录的问题,在包含中文目录时会出现40343这个错误。
本项目阶段使用Ghidra,分析相对简单,无复杂的操作细节,相较IDA会有一些小细节,故尔此处以IDA分析为例。
1.运行ida.exe(不是ida64.exe),打开so文件。
2.常用区域为红线选择区域,网路上也存在很多ida插件来辅助进行操作,这个东西在需要的时候,度娘基本可以解决。
Function window:so文件中包含函数的显示区域;
IDA View-A:so文件汇编语言显示区域;
Hex View-1:so文件16进制显示区域;
Imports/Exports:so文件应用/输出函数显示区域。
3、F5快捷操作
选择查看的函数,因为是原始显示为汇编语言。汇编不咋会的可使用F5快捷操作将其转化为伪C代码。
转化前:
转化后:
测试环境
推荐只安装Frida的真机,与XP框架同时使用会存在一定蜜汁问题。
so层导出函数的Hook
先贴上基本的Hook代码:
# -*- coding: UTF-8 -*-
import frida, sys
jsCode = """
Java.perform(function(){
var nativePointer = Module.findExportByName("libhello.so", "Java_com_xiaojianbang_app_NativeHelper_add");
send("native: " + nativePointer);
Interceptor.attach(nativePointer, {
onEnter: function(args){
send(args[0]);
send(args[1]);
send(args[2].toInt32());
send(args[3].toInt32());
send(args[4].toInt32());
},
onLeave: function(retval){
send(retval.toInt32());
}
});
});
"""
def message(message, data):
if message["type"] == 'send':
print(u"[*] {0}".format(message['payload']))
else:
print(message)
process = frida.get_remote_device().attach("com.xiaojianbang.app")
script= process.create_script(jsCode)
script.on("message", message)
script.load()
sys.stdin.read()
对比上述代码与前次JAVA层代码,发现两者的变化不大,主要的改变都在jscode中。故本此只对js代码内容进行
描述。其余代码除变更外不做过多赘述。
jsCode = """
Java.perform(function(){
var nativePointer = Module.findExportByName("libhello.so", "Java_com_xiaojianbang_app_NativeHelper_add");
//代码中Module.findExportByName为Frida可使用JavaScript Api中Module中的findExportByName方法Frida可以使用的API及API中方法,在官方文档中有相应的列举但不是很全面。故而需要在以后的使用
学习过程中进行爬贴积累。
//findExportByName方法有两个参数,第一个为hook的so文件名,第二个为hook的函数名。在获取到两个值之后返回hook函数名的地址
send("native: " + nativePointer);
//打印获取到的地址值
Interceptor.attach(nativePointer, {
//对获取到地址函数进行hook,Interceptor.attach函数需要两个参数。第一个为获取到的地址值,第二个为{}中内容。
onEnter: function(args){
//onEnter,函数调用前进入,用于hook函数的函数参数的修改。args可不用更改。
send(args[0]);
send(args[1]);
send(args[2].toInt32());
send(args[3].toInt32());
send(args[4].toInt32());
//上节对so文件进行伪C代码转换后发现Hook的函数有5个参数值,故在此处输出5个。
},
onLeave: function(retval){
//onLeave,函数调用后进入,用于hook函数的返回值修改。retval可不更改
send(retval.toInt32());
}
});
});
"""
运行hook脚本其结果如下:
函数参数返回值修改
本节开始利用hook对函数相关值进行修改,过程中开始涉及一些特定的函数使用。需要对Frida官方的JavaScript API内容进行熟悉,本文所使用基本可在官方文档中找解释。今后在对其他so文件等内容进行Hook时,官方文档是个比较重要的内容,获取方式有官方英文版S和看雪的中文翻译版。
核心代码如下;
jsCode = """
Java.perform(function(){
var nativePointer = Module.findExportByName("libhello.so", "Java_com_xiaojianbang_app_NativeHelper_add");
send("native: " + nativePointer);
Interceptor.attach(nativePointer, {
onEnter: function(args){
send(args[0]);
send(args[1]);
send(args[2].toInt32());
send(args[3].toInt32());
send(args[4].toInt32());
args[4] = ptr(1000);
//修改参数内容,ptr为new NativePointer。new NativePointer(s):通过 s 创建一个新的 NativePointer 对象, 可以是一个数字也可以 是一个包含以 "0x" 开头的十六进制的数字字符串.可以使用 ptr(s) 作为这个方法的简写。
send(args[4].toInt32());
},
onLeave: function(retval){
send(retval.toInt32());
retval.replace(10000);
//修改返回值内容。onLeave: function(retval): 被拦截函数调用之后回调,其中retval表示原始函数的返回值,retval是从NativePointer继承来的,是对原始返回值的一个封装,可以使用retval.replace(1337)调用来修改返回值的内容。需要注意的一点是,retval对象只在 onLeave函数作用域范围内有效,因此如果你要保存这个对象以备后续使用的话,一定要使用深拷贝来保存对象,比如:ptr(retval.toString())。
send(retval.toInt32());
}
});
});
"""
运行结果如下:
枚举导入导出函数表
本处需要官方文档Module相关内容如下:
通过查询官方文档获取到遍历所使用的相关函数,只需添加循环即可输出结果。代码里加相关筛选代码,代码如下:
jsCode = """
Java.perform(function(){
var imports = Module.enumerateImportsSync("libhello.so");
for(i = 0; i < imports.length; i++) {
if(imports[i].name == 'strncat'){
//寻找名字为strncat的函数
send(imports[i].name + ": " + imports[i].address);
break;
}
}
var exports = Module.enumerateExportsSync("libhello.so");
for(i = 0; i < exports.length; i++) {
if(exports[i].name.indexOf('add') != -1){
//寻找名字里包含add的函数
send(exports[i].name + ": " + exports[i].address);
break;
}
}
});
"""
未加判断运行结果如下:
添加判断运行结果如下:
未导出函数HOOK
在so文件中存在部分函数不在导出列表中,偶尔也会需要HOOK该函数。而未导出函数是包含导出函数,及可用未导出函数方式去获取导出函数的地址。
代码内容如下:
jsCode = """
Java.perform(function(){
var soAddr = Module.findBaseAddress("libhello.so");
send('soAddr: ' + soAddr);
var MD5FinalAddr = soAddr.add(0x1768 + 1);
//1768为函数偏移量,表现形式见下图:
send('MD5FinalAddr: ' + MD5FinalAddr);
onEnter: function(args){
send(args[0]);
send(args[1]);
},
onLeave: function(retval){
send(retval);
}
});
});
"""
偏移量IDA表现图:
执行结果如下:
Android脱壳
本节算是Frida安卓部分的结尾,前两节介绍了Frida如何HOOK安卓Java层、native层。也相信大家通过各自的渠道也学到了不少内容。本文旨在分享Frida脱壳的常规思路,大家可依据实际情况结合IDA编写代码。
1、 加壳
加壳可理解为通过Android动态加载机制,在运行时加载dex、jar、apk文件并执行。其过程大致如下:
A、壳代码先运行,进行初始化。
B、壳代码开始解密被保护的代码。
C、壳代码加载解密后的代码。
D、壳代码将程序控制权转交保护代码。
壳的强度指的是对dex、jar等文件的加密强度,以及部分反调试、反注入的强度。市面上不同的厂商有着自己的加密方式。
2、 dex文件
脱壳最终目的是为了还原动态加载的dex文件。所以,应对该dex的结构有一定的了解。首先放上google官方文档对dex解释的链接,里面相当详细的介绍了dex的格式的组成。(https://source.android.google.cn/devices/tech/dalvik/dex-format#header-item)。
了解了dex文件结构,可帮助我们分析出壳主要对dex那些内容做了修改,然后调试在dex文件加载过程中涉及这段数据的代码,为之后的修复做准备。
3、 dex文件加载
Android平台是基于Linux系统开发的,在应用层加载的是Dalvik专用的dex文件。在native加载的是elf格式。Dalvik虚拟机运行时不是直接加载dex文件,而是执行程序字节码。其步骤大致如下:
A、将dex文件结构优化成odex文件结构。
B、将odex文件结构解析成dexfile结构。
C、 将dexfile文件结构转化成Dalvik虚拟机需要的运行时的数据结构ClassObject*字节码,后解释器执行。
4、 so文件加载过程
在Android程序安装完成后,根据Android程序的结构,我们知道最主要的是classes.dex文件和程序自身加载的链接库so文件。Java代码通过system.loadlibrary(so文件名)语句加载动态链接库。在native层的加载过程是从nativeload()函数开始。
5、 脱壳思路
Dex和jar在加载时是解密状态,可定位到App在动态加载一个文件时的入口和出口,然后通过内存转储的方式获得被加载的dex文件。
在Android应用层、动态加载dex等文件是通过Android提供的dexclassloader的类构造方法完成。dexclassloader通过调用父类的构造方法,对输入的dex文件进行校验,最终会走的dvmdexfileopenfromfd()或dvmdexfileopenpartial()这两个native()函数中的一个,后获取一个dexfile结构体。
内存转储的方法可部分脱壳,对于专业加固的壳,dex被转储后不能被正常解析,这是需要分析以获取的dex,分析其异常点。根据dex加载等过程,判断壳在哪里做了桩点,需修复dex文件。
6、 简单脱壳相关代码
代码编写的思路为通过Android加载dex时的系统函数,分析dex文件加载在内存里的初始位置和总长度,并将内存里的内容保存在本地。
Android系统版本不同,打开dex文件的函数不同:
Android 4.0:libdvm.so::dexfileparse
Android 5.0及以上:libart.so::openmemory、libart.so::opencommon
依据各位对Frida的学习,我们在使用Frida是可直接编写js脚本,后利用:
frida -U -f appname -l XXXX.js --no-pause
可直接运行js脚本,至于Frida可以使用js的那些内容,可详见Frida官方的js api接口文档。在该文档中对相关接口作用做了详尽说明,脚本写法可参考上一文内容。不做过多的赘述。
接下来,我们按照上文思路开始(在已知安卓版本的前提下):
1)、 获取当前App所调用的so文件:
代码如下:
运行结果如下:
通过上述脚本可获取到app所使用的so文件、地址、大小、路径。方便后期复杂加壳相关so拉取。现在继续按照我们的思路继续向下走。
2)、 获取记载dex文件函数
代码如下:
运行结果如下:
在执行后可能会发现,多个函数名。但都不是我们提到的几个关键函数,那是因为这个函数被混淆了,在一般加壳时,app运行过程中只会调用提到的关键函数,一般情况下hook第一个函数即可。
3)、内存转储
代码如下:
运行结果如下:
通过以上结果,我们可以发现。该app转储下来多个dex文件,学名叫做multidex。目前个人对该技术也不是很了解,采用的方式还是一个个打开查看。
通过打开上述转储dex文件,发现还是存在非正常函数名。这说明通过内存转储的方式去获取的方式不是很精准,需要结合ida方式查看。推荐一篇大佬的文章https://mp.weixin.qq.com/s/Nb1BjJ-i7v7hBWHwEQ9Log
IOS部分
通过对Android Native层的相关内容的学习,大家应该对Frida的相关使用有了一定的经验,那恭喜你IOS部分,Frida的分析过程是很相似的:通过使用hopper、Ghidra等对IPA文件的头文件进行分析。 同时,也可使用class-dump,将相关类库文件拖出来,
但是以上文件内没有详细的代码内容,这个时候就需要我们使用hopper等工具去加载IPA文件里的二进制文件,然后查看伪C代码。
一般过程为:
1、打开Hopper工具
2、找到xx.app文件
3、右击xx.app文件,选择"显示包内容"
4、找到二进制文件
5、选中二进制文件,并选中拖放到Hopper工具里
以上,获取到相关代码后。就可以开始进行分析了。本文会在MonkeyDev使用部分进行描述,在Frida部分依靠Android的经验,将简要描述。
基础知识部分
通过工作学习,大家都晓得。 IOS开发有两种语言,objecive-c和Swift。本项目阶段要求能看懂下面的代码示例,了解一定的类知识。
objective-c
//导入头文件
#import <Foundation/Foundation.h>
//入口函数
int main(int argc, const char * argv[]){
@autoreleasepool
//打印语句
NSLog(@"HELLO");
//调用方法
//格式化字符串
NSString * str = [NSString stringWithFormat;@"字符串"];
NSLog(str);
}
return 0;
}
字符串前面要加@
方法调用使用中括号,一般情况下使用.
swift
//导入模块
import Foundation
//打印语句
print("HELLO");
//调用方法
//格式化字符串
let str = String(format:"字符串")
print(str)
swift语法结构类时 Java,后面无需跟分号,也不需要指定类型,和kotlin差不多
小总结
上面给大家简要写了一个语法示例,对两种语言的样子应该有了一个较为清晰的认知,这里说一下当前阶段我们为什么以Objc为主,而不是swift。原因很简单:
1、Objc发展已经相对完善,新版本迭代很少,版本兼容性好。适用于版本更新较多、追求稳定的项目。
2、Swift版本兼容较差,Swift4写的代码不一定可以在Swift5中编译通过。
熟练使用苹果官网给文档,本人在刚开始学习时比较依赖百度,后面发现阅读相关官类的文档,解决问题比百度要节省很多时间:https://developer.apple.com/search/?q=UITextView
MonkeyDev部分
简述
MonkeyDev是一个极为方便的逆向调试平台,集众家所长
主要包含四个模块
Logos Tweak – 使用theos提供的logify.pl工具将.xm文件转成.mm文件进行编译,集成了Cydia Substrate,它的主要作用是针对OC方法、C函数以及函数地址进行HOOK操作。
CaptainHook Tweak – 使用CaptainHook提供的头文件进行OC函数的Hook以及属性的获取。
Command-line Tool – 可以直接创建运行于越狱设备的命令行工具。
MonkeyApp – 自动给第三方应用集成Reveal、Cycript和注入dylib的模块,支持调试dylib和第三方应用,只需要准备一个砸壳后的ipa或者app文件即可。
安装与踩坑
官方安装地址:https://github.com/AloneMonkey/MonkeyDev/wiki
踩过的坑:
1、Types.xcspec not found
错位内容为:
Creating symlink to Xcode templates...
Modifying Bash personal initialization file...
File /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Specifications/MacOSX Package Types.xcspec not found
解决方式
sudo ln -s /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/PrivatePlugIns/IDEOSXSupportCore.ideplugin/Contents/Resources /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Specifications
参考地址:https://github.com/AloneMonkey/MonkeyDev/issues/266
2、编译报错’Cycript/Cycript.h’ file not found
参考地址:https://github.com/AloneMonkey/MonkeyDev/issues/309
3、编译报错file not found: /usr/lib/libstdc++.dylib
参考地址:https://github.com/AloneMonkey/MonkeyDev/issues/279
4、编译报错’Executable Not Found’
执行command + shift + k,清理下build data 就可以正常运行。但是此方法不解决根本问题,每次遇到该问题需重复执行。
环境准备
1、手机连接
通过SSH连接iPhone时,有两种方式:SSH+IP地址、USB连接
日常IOS HOOK测试建议采用USB直接链接。相较于IP地址连接,减少了iPhone与mac的网络环境要求,不需要在每次调试时去查看iPhone的IP地址。
USB连接过程:
使用usbmuxd工具,将本地的2222端口转发到iOS的22端口
brew install usbmuxd
iproxy 2222 22
为啥是2222端口?是因为在MonkeyDev的相关工具套件中包含有frida-ios-dump项目,该项目可在GitHub进行单独下载。项目中dump.py文件中有如下内容:
User = 'root'
Password = 'alpine'
Host = 'localhost'
Port = 2222
2、Reveal安装与使用
3、Hopper安装(反编译,查看各个类的方法)
4、Logos语法介绍(Hook原代码)
https://iphonedev.wiki/index.php/Logos
实例
本节以斗鱼APP为例,简单串一下使用流程。因为也是自己摸索这学习,耽误了很多时间。首先,先看一下最终的效果:
本例目的是HOOK斗鱼用户快速登录时的手机号。
前提准备
1、已经完成砸壳子的目标APP。工具:frida-ios-dump
2、目标APP相关代码》工具:class-dump
这部分没啥好讲的,百度有一堆使用教程
HOOK过程
创建项目
启动Xcode新建一个项目。
之前,如果你已经完成了MonkeyDev的安装步骤,在你的新建项目下面会出现相关项目模版。我们这里新建一个MonkeyAPP。
填写相关信息,Team信息需要自己准备。其他字段按字面意思填写,点击下一步将项目放在一个自己能找到的地方。
进入项目内容,进入部分设置和文件导入。
点击运行,一般情况下,你的项目会报错。我遇到的报错内容,已在前面提到了,按步骤操作就好。导入相关框架文件,可试着重新打开项目。
UI分析,定位函数
解决完所有报错后,运行你的项目。等待一段时间,完成文件安装后,完成如下操作。
同时,将APP打开将要分析函数相关界面,本例即快速登录界面。
完成上述步骤后,经过一段时间,你的Xcode界面会出现如下界面:
圈红位置,可进行相关视图的改变。经过相关文字提示,可快速定位到下相关功能类:
这里可能会问为什么是DYQuickMobileTextField而不是UITextFieldLabel,简单解释一下就是UITextFieldLabel算是一个公共调用,在其他类下也可使用,现在我们去分析一下DYQuickMobileTextField这个类文件。
通过类文件可以清晰看到DYQuickMobileTextField继承的父类信息、目标函数reformatAsPhoneNumber。斗鱼开发大大目的性比较直接,也方便快速定位到目标函数。
接下来可以准备写HOOK代码来验证一下,当前函数是否是我们需要的。首先,返回项目目录,修改目标文件的类型。该xm文件即是我们要编写HOOK代码的地方。
重新选择该文件使修改生效,删除部分内容,留下如图内容:
编写HOOK代码
通过前面步骤,我们可以开始,相关函数HOOK代码的编写。选择类文件中函数行至DouyuDomeDylib.xm文件中,进行相关内容的变形
接下来,就可以根据我们的需求,在函数体内写代码。这里写了一个简单的输出。
// See http://iphonedevwiki.net/index.php/Logos
#import <UIKit/UIKit.h>
#import <Foundation/NSObjCRuntime.h>
%hook DYQuickMobileTextField
- (void)reformatAsPhoneNumber:(id)arg1{
NSLog(@“手机号:%@”,arg1);
}
%end
执行结果
HOOK到的结果不是自己想要的结果,我们进一步进行输出优化,这一步自己在搞的时候,花了整一下午的时间。有个小建议就是,多看objc代码中相关类的代码,很多输入、输出等要求很清晰。
这个分析过程,这里大致分析过程如下:
对DYLoginPhoneInputView为相关输入的展示类,发现其没有定义自己的文件展示,选择使用父类继续。该父类是apple官类。那我们可以猜想,斗鱼对于text相关细节的展示也是使用的官类。
APPLE相关官类为:UITextView。地址为:https://developer.apple.com/documentation/uikit/uitextview?language=objc
故尔,我们可以采用正向开发思路,将目标函数内容arg1赋给UITextView类型的 *pwd。最终优化代码如下:
// See http://iphonedevwiki.net/index.php/Logos
#import <UIKit/UIKit.h>
#import <Foundation/NSObjCRuntime.h>
%hook DYQuickMobileTextField
- (void)reformatAsPhoneNumber:(id)arg1{
UITextView * pwd = arg1;
NSLog(@“手机号:%@”,pwd.text);
}
%end
执行结果;
Frida部分
Frida基础HOOK
Python基础脚本
Hook.py
# _*_ coding: utf-8 _*_
import logging
import frida
import sys
logging.basicConfig(level=logging.DEBUG)
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
with open("ios_hook.js", 'r', encoding='utf-8') as f:
sta = ''.join(f.readlines())
rdev = frida.get_device(id ="设备id")
print("设备连接成功")
session = rdev.attach('app包名') #app包名
print("连接成功")
print(session)
script = session.create_script(sta)
print(script)
def show(message,data):
print(message)
script.on("message",show)
# 加载脚本
script.load()
sys.stdin.read()
HOOK方法入参
Hook.js
function hook_function_input(){
var PDDURLRequestSetHeadersHook = eval('ObjC.classes.类名["- 方法名"]') #xxx["- xxx:xx:xxx:xx:xxxx:"]'
Interceptor.attach(PDDURLRequestSetHeadersHook.implementation, {
onEnter: function(args) {
// args[0] is self
// args[1] is selector (SEL "sendMessageWithText:")
// args[2] holds the first function argument, an NSString
console.log(`2----${
ObjC.Object(args[2])}`)
console.log(`3----${
ObjC.Object(args[3])}`)
console.log(`4----${
ObjC.Object(args[4])}`)
console.log(`5----${
ObjC.Object(args[5])}`)
console.log(`6----${
ObjC.Object(args[6])}`)
//logBacktrace(this.context, "operationFromDict:")
}, onLeave: function(retval) {
console.log(`factorSign-result:${
ObjC.Object(retval)}\n`);
}
});
}
说明:
在进行特定方法相关hook时,编写脚本时要在其名称前加"- "。
越狱检测绕过
Hook.js
function bypassJailbreakDetection() {
try {
var className = "JailbreakDetection";
var funcName = "+ isJail";
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);
retval.replace(0x0);
console.log("\t[-] Type of return value: " + typeof retval);
console.log("\t[-] Return Value: " + retval);
}
});
} catch(err) {
console.log("[-] Error: " + err.message);
}
}
寻找所有类
Hook.js
function show_classes_of_app()
{
console.log("[*] Started: Find Classes")
var count = 0
for (var className in ObjC.classes)
{
if (ObjC.classes.hasOwnProperty(className))
{
console.log(className);
count = count + 1
}
}
console.log("\n[*] Classes found: " + count);
console.log("[*] Completed: Find Classes")
}
寻找所有类所有方法
Hook.js
find-all-methods-all-classes()
{
console.log("[*] Started: Find Methods")
if (ObjC.available)
{
for (var className in ObjC.classes)
{
if (ObjC.classes.hasOwnProperty(className))
{
console.log("[+] Class: " + className);
var methods = eval('ObjC.classes.' + className + '.$methods');
for (var i = 0; i < methods.length; i++)
{
console.log("\t[-] Method: "+methods[i]);
}
}
}
}
else
{
console.log("Objective-C Runtime is not available!");
}
console.log("[*] Completed: Find Methods")
}
寻找某一特定方法
Hook.js
find-specific-method()
{
console.log("[*] Started: Find Specific Method");
if (ObjC.available)
{
for (var className in ObjC.classes)
{
try
{
if (ObjC.classes.hasOwnProperty(className))
{
try
{
var methods = eval('ObjC.classes.' + className + '.$methods');
for (var i = 0; i < methods.length; i++)
{
try
{
//填写方法名
if(methods[i].includes("方法名"))
{
console.log("[+] Class: " + className);
console.log("\t[-] Method: "+methods[i]);
}
}
catch(err)
{
console.log("[!] Exception3: " + err.message);
}
}
}
catch(err)
{
console.log("[!] Exception2: " + err.message);
}
}
}
catch(err)
{
console.log("[!] Exception1: " + err.message);
}
}
}
else
{
console.log("Objective-C Runtime is not available!");
}
console.log("[*] Completed: Find Specific Method");
}
寻找某一特定类下的全部方法
Hook.js
hook-all-methods-of-specific-class()
{
console.log("[*] Started: Hook all methods of a specific class");
if (ObjC.available)
{
try
{
//Your class name here
var className = "YOUR_CLASS_NAME_HERE";
var methods = eval('ObjC.classes.' + className + '.$methods');
for (var i = 0; i < methods.length; i++)
{
try
{
console.log("[-] "+methods[i]);
try
{
console.log("\t[*] Hooking into implementation");
//eval('var className2 = "'+className+'"; var funcName2 = "'+methods[i]+'"; var hook = eval(\'ObjC.classes.\'+className2+\'["\'+funcName2+\'"]\'); Interceptor.attach(hook.implementation, { onEnter: function(args) { console.log("[*] Detected call to: " + className2 + " -> " + funcName2); } });');
var className2 = className;
var funcName2 = methods[i];
var hook = eval('ObjC.classes.'+className2+'["'+funcName2+'"]');
Interceptor.attach(hook.implementation, {
onEnter: function(args) {
console.log("[*] Detected call to: " + className2 + " -> " + funcName2);
}
});
console.log("\t[*] Hooking successful");
}
catch(err)
{
console.log("\t[!] Hooking failed: " + err.message);
}
}
catch(err)
{
console.log("[!] Exception1: " + err.message);
}
}
}
catch(err)
{
console.log("[!] Exception2: " + err.message);
}
}
else
{
console.log("Objective-C Runtime is not available!");
}
console.log("[*] Completed: Hook all methods of a specific class");
}
改变方法入参
hook.js
function show_modify_function_args()
{
var hook = ObjC.classes["类名"]["方法名"];
Interceptor.attach(hook.implementation, {
onEnter: function(args) {
// args[0] is self
// args[1] is selector (SEL "sendMessageWithText:")
// args[2] holds the first function argument, an NSString
console.log("\n[*] Detected call to: " + className + " -> " + funcName);
console.log("\t[-] Argument Value: "+args[2]);
//替换原有参数值
var newargval = ptr("0x0")
args[2] = newargval
console.log("\t[-] New Argument Value: " + args[2])
}
});
}
改变方法输出
hook.js
function show_modify_function_return_value()
{
var hook = ObjC.classes["类名"]["方法名"];
Interceptor.attach(hook.implementation, {
onLeave: function(retval) {
console.log("\n[*] Class Name: " + className);
console.log("[*] Method Name: " + funcName);
console.log("\t[-] Type of return value: " + typeof retval);
//console.log(retval.toString());
console.log("\t[-] Return Value: " + retval);
//替换原有输出结果
var newretval = ptr("0x0") //新的结果赋值
retval.replace(newretval)
console.log("\t[-] New Return Value: " + newretval)
}
});
}
脱壳
GitHub项目:frida-iOS-dump
这里提一下,苹果对上架的应用会在审核后加一个官方的壳子。
踩坑
1、脱壳卡住不动
当你遇到上述情况时,不要怀疑。原因是权限不够,命令变更为:
sudo python3 dump.py 斗鱼
结果如下: