Frida—HOOK 学习笔记2

Android部分

基础知识

1)安卓分层

简单提一下安卓分层,这个点知道了更好,不知道也无所谓。毕竟我们不是开发,只是为了避免下述情况:
我要学习so文件HOOK。一波百度 “HOOK so层”之后,出现了一个“native”。点进去一看,其内容是对so文件hook。

image-20220214113711807

那native是啥?这就要提一下安卓的分层。
Android采用分层的架构,分为四层,从高层到底层分为应用程序层(app+System apps),应用程序框架层(Java API Framework),系统运行库和运行环境层(Libraries + android Runtime)和Linux核心层(HAL+ Linux Kernel),如下图所示:

image-20220214113728378

细分之后如下图:

image-20220214113737629

其中,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目录下,利用解压软件查看目录结构如下:

image-20220214163117463

该目录下存在各个CPU构架的分类。不同CPU架构的android手机加载时会在libs下找自己对应的目录,从对应的目录下寻找需要的so文件。如果没有对应的目录,就会去armeabi下去寻找,如果已经有对应的目录,却没有找到对应的so文件,也不会去armeabi下去寻找了。
目前主流的Android设备主要是 armeabi-v7a 架构的,然后是 x86 和 armeabi 了。如果同时包含了 armeabi, armeabi-v7a和x86,所有设备都可以运行。这个点是为了寻找将要HOOK的so文件,armeabi-v7a构架的手机去HOOK其他构架的so文件,一句话就是“可以,但是没必要。非要改也不拦着。”这边贴一个兼容内容:

image-20220214163128336

以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)加载过程

image-20220214163155608

关注上图中存在的几个关键字。

IDA基础使用

IDA这个工具闻名遐迩,不过现在只需要其一小部分。吾爱上有其7.0版本的破解版,注意的是在安装的时候中文目录的问题,在包含中文目录时会出现40343这个错误。

本项目阶段使用Ghidra,分析相对简单,无复杂的操作细节,相较IDA会有一些小细节,故尔此处以IDA分析为例。

1.运行ida.exe(不是ida64.exe),打开so文件。

image-20220214163220415

2.常用区域为红线选择区域,网路上也存在很多ida插件来辅助进行操作,这个东西在需要的时候,度娘基本可以解决。

image-20220214163243166

Function window:so文件中包含函数的显示区域;
IDA View-A:so文件汇编语言显示区域;
Hex View-1:so文件16进制显示区域;
Imports/Exports:so文件应用/输出函数显示区域。

3、F5快捷操作

选择查看的函数,因为是原始显示为汇编语言。汇编不咋会的可使用F5快捷操作将其转化为伪C代码。
转化前:

image-20220214163322104

转化后:

image-20220214163348116

测试环境

推荐只安装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脚本其结果如下:

image-20220214164032458

函数参数返回值修改

本节开始利用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相关内容如下:

image-20220214164738799

通过查询官方文档获取到遍历所使用的相关函数,只需添加循环即可输出结果。代码里加相关筛选代码,代码如下:

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;		
        }				
    }						
});						
"""

未加判断运行结果如下:

image-20220214165013426

添加判断运行结果如下:

image-20220214165030439

未导出函数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表现图:

image-20220214165256855

执行结果如下:

image-20220214165442588

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

image-20220214165724580

依据各位对Frida的学习,我们在使用Frida是可直接编写js脚本,后利用:
frida -U -f appname -l XXXX.js --no-pause
可直接运行js脚本,至于Frida可以使用js的那些内容,可详见Frida官方的js api接口文档。在该文档中对相关接口作用做了详尽说明,脚本写法可参考上一文内容。不做过多的赘述。
接下来,我们按照上文思路开始(在已知安卓版本的前提下):

1)、 获取当前App所调用的so文件:

代码如下:

image-20220214165735760

运行结果如下:

image-20220214165744286

通过上述脚本可获取到app所使用的so文件、地址、大小、路径。方便后期复杂加壳相关so拉取。现在继续按照我们的思路继续向下走。

2)、 获取记载dex文件函数

代码如下:

image-20220214165758398

运行结果如下:

image-20220214165903862

在执行后可能会发现,多个函数名。但都不是我们提到的几个关键函数,那是因为这个函数被混淆了,在一般加壳时,app运行过程中只会调用提到的关键函数,一般情况下hook第一个函数即可。

3)、内存转储

代码如下:

image-20220214165928133

运行结果如下:

image-20220214165936921

通过以上结果,我们可以发现。该app转储下来多个dex文件,学名叫做multidex。目前个人对该技术也不是很了解,采用的方式还是一个个打开查看。

image-20220214165946637

通过打开上述转储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为例,简单串一下使用流程。因为也是自己摸索这学习,耽误了很多时间。首先,先看一下最终的效果:

image-20220412182840575

本例目的是HOOK斗鱼用户快速登录时的手机号。

前提准备

1、已经完成砸壳子的目标APP。工具:frida-ios-dump

2、目标APP相关代码》工具:class-dump

这部分没啥好讲的,百度有一堆使用教程

HOOK过程

创建项目

启动Xcode新建一个项目。

image-20220412184506996

之前,如果你已经完成了MonkeyDev的安装步骤,在你的新建项目下面会出现相关项目模版。我们这里新建一个MonkeyAPP。

image-20220412184715611

填写相关信息,Team信息需要自己准备。其他字段按字面意思填写,点击下一步将项目放在一个自己能找到的地方。

image-20220412184857157

进入项目内容,进入部分设置和文件导入。

image-20220412185739497

点击运行,一般情况下,你的项目会报错。我遇到的报错内容,已在前面提到了,按步骤操作就好。导入相关框架文件,可试着重新打开项目。

UI分析,定位函数

解决完所有报错后,运行你的项目。等待一段时间,完成文件安装后,完成如下操作。

image-20220412190926641

同时,将APP打开将要分析函数相关界面,本例即快速登录界面。

image-20220412191109087

完成上述步骤后,经过一段时间,你的Xcode界面会出现如下界面:

image-20220412191250908

圈红位置,可进行相关视图的改变。经过相关文字提示,可快速定位到下相关功能类:

image-20220412191919313

这里可能会问为什么是DYQuickMobileTextField而不是UITextFieldLabel,简单解释一下就是UITextFieldLabel算是一个公共调用,在其他类下也可使用,现在我们去分析一下DYQuickMobileTextField这个类文件。

image-20220412192437877

通过类文件可以清晰看到DYQuickMobileTextField继承的父类信息、目标函数reformatAsPhoneNumber。斗鱼开发大大目的性比较直接,也方便快速定位到目标函数。

接下来可以准备写HOOK代码来验证一下,当前函数是否是我们需要的。首先,返回项目目录,修改目标文件的类型。该xm文件即是我们要编写HOOK代码的地方。

image-20220412192915230

重新选择该文件使修改生效,删除部分内容,留下如图内容:

image-20220412193146381

编写HOOK代码

通过前面步骤,我们可以开始,相关函数HOOK代码的编写。选择类文件中函数行至DouyuDomeDylib.xm文件中,进行相关内容的变形

image-20220412193541943

接下来,就可以根据我们的需求,在函数体内写代码。这里写了一个简单的输出。

image-20220412183743329

// See http://iphonedevwiki.net/index.php/Logos

#import <UIKit/UIKit.h>

#import <Foundation/NSObjCRuntime.h>

%hook DYQuickMobileTextField

- (void)reformatAsPhoneNumber:(id)arg1{

NSLog(@“手机号:%@”,arg1);

}

%end

执行结果

image-20220412183959500

HOOK到的结果不是自己想要的结果,我们进一步进行输出优化,这一步自己在搞的时候,花了整一下午的时间。有个小建议就是,多看objc代码中相关类的代码,很多输入、输出等要求很清晰。

这个分析过程,这里大致分析过程如下:

对DYLoginPhoneInputView为相关输入的展示类,发现其没有定义自己的文件展示,选择使用父类继续。该父类是apple官类。那我们可以猜想,斗鱼对于text相关细节的展示也是使用的官类。

image-20220412194841639

APPLE相关官类为:UITextView。地址为:https://developer.apple.com/documentation/uikit/uitextview?language=objc

故尔,我们可以采用正向开发思路,将目标函数内容arg1赋给UITextView类型的 *pwd。最终优化代码如下:

image-20220412183533210

// 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

执行结果;

image-20220412182840575

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、脱壳卡住不动

image-20220217151630405

当你遇到上述情况时,不要怀疑。原因是权限不够,命令变更为:

sudo python3 dump.py 斗鱼

结果如下:

image-20220217151821263

image-20220217151837095

猜你喜欢

转载自blog.csdn.net/m0_38036918/article/details/126624641