0. 写在前面
本篇是在frida hook的基础上继续往下开拓。
上篇文章中只研究了frida hook
的环境搭建和基本使用方法,存在许多缺陷:
1. 只解决了基本问题,不能应对现在越来越复杂的ctf android题目。需要熟练掌握frida的其他用法
2. 对于frida的运行原理不了解,知其然不知其所以然
所以本篇主要还是聚焦frida hook的实际功能和其基本原理
1. 环境搭建
其实环境搭建可以参考frida hook中提到的真机环境和模拟器环境
这里为了方便只介绍模拟器环境(其实是换电脑了需要新搭建一个环境)。
注意,大部分模拟器只能解决x86_64架构,若需在arm架构上解题/实践,最好还是来个真机
1.1 夜神模拟器
虽然网上绝大多数人都在推荐雷电模拟器,但以我个人经验,夜神模拟器能cover住绝大多数场景,所以我没有换的打算。
夜神官网
下载好后,记得添加环境变量,因为夜神的目录中自带了adb
添加环境变量后,记得重启terminal,以便环境变量刷新
1.2 frida
pip install frida
pip install frida-tools
注意你安装的frida版本,前往https://github.com/frida/frida/releases选择相应版本的frida-server
我安装时的版本为frida 16.6.6
frida是在你的本地pc上运行的,frida-server是在你的安卓机器上运行的,两者会建立一条通道供你调试。
根据你手机/虚拟机的架构,选择合适的frida-server。
1.3 frida-server上传并运行
如先前所说,需要把frida-server上传到真机/虚拟机上。
我的frida版本是16.6.6
因此我的frida-server版本也为16.6.6
,虚拟机安卓架构为x86_64,所以上传的frida-server最终为frida-server-16.6.6-android-x86_64
因为我是上传到虚拟机,这里提供两个思路
1.3.1 思路一: adb
adb devices # 查看设备名称
# -s 后跟着的是设备名称 push表示从本地上传文件到虚拟机,后续为路径
adb -s 127.0.0.1:62025 push .\frida-server-16.6.6-android-x86_64 /data/local/tmp/frida-server
adb -s 127.0.0.1:62025 shell # 进入虚拟机交互界面
cd /data/local/tmp/
chmod 777 frida-server
./frida-server &
如果运行正常,在本地启一个terminal,运行
frida-ps -U
能看到进程,说明成功安装。
1.3.2 思路二:直接拖拽frida-server到虚拟机
某些严格场景下,你可能无法adb push(会被警告,但是直接拖拽不会出现这个问题)
接下来将
adb shell
进入后,将frida-server路径移动到/data/local/tmp/
并赋予权限执行即可
1.4 frida js代码补全工具(非必需)
这一步并不是实践必须的一步,做不做均可,对于我而言,代码补全是很重要的,能够节省大量的开发时间,所以我会安装它。
为了完成frida js的代码补全,1. 首先需要安装Node
https://nodejs.org/zh-cn
安装完成后,直接在windows terminal上执行npm命令时会提示你禁止运行脚本。
这时,你需要以管理员权限打开windows terminal,执行set-executionpolicy remotesigned
,选项选Y
这样,你就能以普通用户成功执行npm命令了。
2. 在你写js脚本的目录下执行命令:npm i @types/frida-gum
,安装完成后,你会发现你的目录下多了以下几个内容
接下来你编写frida js代码时就可以补全了。
2. frida用法
2.1 使用python代码导入js脚本到目标进程
import frida
def on_message(message, data): # 打印报错函数,方便查看错误
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
device = frida.get_usb_device(timeout=10) # 获取设备,设置10s防止卡死
print(device) # Device(id="127.0.0.1:62025", name="SM-S9110", type='usb')
session = device.attach(3132) # 通过frida-ps -U 找到对应进程id,attach上去
# 打开js脚本并读取内容,在进程中创建这个脚本并加载执行
with open(r"C:\Users\wsxk\Desktop\CTF\Mobile\script.js",encoding='UTF-8') as f:
script = session.create_script(f.read())
script.on('message',on_message) # 添加一个signal handler,方便查看报错
script.load()
# 脚本会持续运行等待输入
input()
通常情况下,python脚本只要写成这样就行了,重点在于js的编写,所以上述的代码挺通用的(个人感觉)
读入的脚本当中,就有我们希望apk做的事情的代码了
接下来我会介绍完成某个特定行为所需要写的js代码是哪些。
2.2 附加调用Java.perform
Java.perform(fn)
是frida js编写中使用到的核心功能,其原理是:将当前线程附加到Java VM中,并调用fn方法(函数)
所以它的frida js中的必用函数,非常重要,所有对android应用程序的hook,都需要在java.perform中执行。
2.3 Java.available和Java.andriodVersion
这两个是常用的检测JavaVM是否可用,一个用于检测android版本
console.log("test begin!"); //打印日志
Java.perform( () => {
if(Java.available){
console.log("Android Version:",Java.androidVersion)
}else{
console.log("Java VM unavailable!");
}
});
2.4 枚举类Java.enumerateLoadedClasses和枚举类加载器Java.enumerateClassLoaders
Java.enumerateLoadedClasses
用于获取Java VM
中加载的类,Java.enumerateClassLoaders
用于加载Java VM
中的类加载器。
两个函数在加壳场景中会有奇效
console.log("test begin!"); //打印日志
Java.perform( () => {
if(Java.available){
console.log("Android Version:",Java.androidVersion)
Java.enumerateLoadedClasses({ //枚举类
onMatch: function(className){ //每枚举一个类名称就调用一次
console.log("",className);
},
onComplete: function(){ //枚举完毕后调用
console.log("enumerate Classes end!");
}
});
Java.enumerateClassLoaders({
onMatch: function(loader){
console.log("",loader);
},
onComplete: function(){
console.log("enumerate Loaders end!")
}
})
}else{
console.log("Java VM unavailable!");
}
});
2.5 获取类定义Java.use和获取类实例Java.choose
Java.use用于获取类的定义,Java.choose用于获取类在Java VM 中的实例化对象
要想灵活使用Java.use和Java.choose
,关键在于理解java中的类的 静态变量/实例变量,静态函数/实例函数
1. 静态变量:
静态变量在类的定义中,由static标识
静态变量在java类对象的所有实例中共享,即A类中有静态变量B,A的实例C设置B的值为1后,A的实例D使用B的值也为1.
2. 实例变量:
类定义中,没有static表示的变量均为实例变量。
实例变量每个java类对象的实例均有自己的独立副本。
3. 静态函数:
静态函数在类的定义中,也由static标识
静态方法可以通过 类名 直接调用,而不需要创建该类的实例。也可以通过类的实例来调用,但推荐通过类名调用。
静态方法只能访问类的静态变量和静态方法。它不能直接访问实例变量和实例方法,因为静态方法是在类加载时加载的,而实例变量和实例方法依赖于特定的实例。
4. 实例函数:
实例方法没有 static 关键字
实例方法可以访问静态变量、静态方法、实例变量和实例方法
实例方法需要通过 类的实例 来调用,必须先创建类的对象,然后通过该对象调用实例方法。
在理解了这些定义之后,我们可以理解Java.use和Java.choose
的区别
1. Java.use: 获取android应用中类的定义。
这只是获得了类的定义,并没有获得类的实例对象;
因此在这种情况下能做只有直接调用静态方法,修改静态变量,以及对类定义的方法进行implementation hook。
2. Java.choose: 获取android应用中已有的类的实例对象.
得到一个类的实例对象,可以直接调用静态方法,修改静态变量,调用动态方法,修改动态变量。
2.6 hook native层函数Interceptor.attach
有些android应用的函数是通过native层调用,通常都是c代码。Interceptor
的核心原理是通过内存地址来对函数进行hook,通常都需要和Module
模块联合使用
后续就留到什么时候用到了就记录什么吧~