Android Frida
Frida实在太常用了,所以结合网上的资料以及自己平时的使用总结一份Frida文档,也方便自己查,主要参考Sakura的这篇文章,后面有新的再总结
Frida Android hook | Sakuraのblog (eternalsakura13.com)
frida环境
直接下载r0Pan /r0env
R0env,里面有配置好的pyenv以及预装的frida
pyenv
1 | 下载一个3.8.2,下载真的很慢,要慢慢等 |
临时禁用pyenv
1 | if command -v pyenv 1>/dev/null 2>&1; then |
Frida安装
自己手动安装frida,如果直接按下述安装则会直接安装frida和frida-tools的最新版本。
1 | pip install frida-tools |
指定frida的版本去安装
1 | pyenv install 3.7.7 |
老版本frida和对应关系
对应关系很好找
objection
objection是基于frida的,所以objection的版本要跟frida对应上,找版本间的对应关系是去objection的release处,根据发布时间找最接近的
sensepost/objection: 📱 objection - runtime mobile exploration (github.com)
frida-server
下载frida-server并解压,在这里下载frida-server-12.8.0
先adb shell,然后切换到root权限,把之前push进来的frida server改个名字叫fs
然后运行frida
Android手机一般push的通用目录就是/data/local/tmp
如果要监听端口(因为app可能反frida会检测默认端口),就
1 | ./fs -l 0.0.0.0:8888 |
frida开发环境
1 | git clone https://github.com/oleavr/frida-agent-example.git |
下载上面整个项目,使用vscode打开此工程,在agent文件夹下编写js,会有智能提示。
npm run watch
会监控代码修改自动编译生成js文件
python脚本形式的frida脚本注入
1 | import time |
Objection操作
Objection也很常用
Objection有个缺陷就是 watch class 的时候回watch很多 但是唯独漏掉了构造函数
手动watch构造函数
1 | android hooking watch class_method java.io.File.$init --dump-args --dump-return |
Frida Java Hook
打印参数、返回值/设置返回值/主动调用
1 | public class LoginActivity extends AppCompatActivity { |
LoginActivity.a(obj, obj).equals(obj2)
分析之后可得obj2来自password,由从username得来的obj,经过a函数运算之后得到一个值,这两个值相等则登录成功。
所以这里关键是hook a函数的参数,脚本如下。
1 | //打印参数、返回值 |
观察输入和输出,这里也可以直接主动调用。
1 | function login() { |
1 | ... |
主动调用静态/非静态函数 以及 设置静态/非静态成员变量的值
主要就是下面三点
- 静态函数直接use class然后调用方法,非静态函数需要先choose实例然后调用
- 设置成员变量的值,写法是
xx.value = yy
,其他方面和函数一样。 - 如果有一个成员变量和成员函数的名字相同,则在其前面加一个
_
,如_xx.value = yy
Hook内部类,枚举类的函数并Hook
1 | public class FridaActivity4 extends BaseFridaActivity { |
这一关的关键是让if (!InnerClasses.check1() || !InnerClasses.check2() || !InnerClasses.check3() || !InnerClasses.check4() || !InnerClasses.check5() || !InnerClasses.check6())
中的所有check全部返回true。
其实这里唯一的问题就是寻找内部类InnerClasses
,对于内部类的hook,通过类名$内部类名
去use。
手动Hook所有的函数
1 | function ch4() { |
批量HOOK用getDeclareMethods
1 | var inner_classes = Java.use("com.example.androiddemo.Activity.FridaActivity4$InnerClasses") |
Hook构造函数
用$init
来hook构造函数
1 | Java.use("com.tlamb96.kgbmessenger.b.a").$init.implementation = function (i, str1, str2, z) { |
打印栈回溯
用的就是Java的Exception类的getStackTrace方法
1 | function printStack(name) { |
Spwan/Attach
frida注入时-f参数代表spawn启动,spawn适用于一些APP在刚启动时就进行的一些操作
1 | /* access modifiers changed from: protected */ |
因为这个check是在onCreate
里,所以app刚启动就自动检查,所以这里需要用spawn的方式去启动frida脚本hook,而不是attach。
这里有两个检查,一个是检查property的值,一个是检查str的值。
分别从System.getProperty
和System.getenv
里获取,hook住这两个函数就行。
1 | function main() { |
获取类名
1 | function getRealClassName(对象) { |
逻辑就是先用getClass获取类,再用getName获取类名,只不过这里写法是apply来反向绑定对象
注入dex
实例可以参考gson
总结:
编译出dex之后,通过Java.openClassFile("xxx.dex").load()
加载,这样我们就可以正常通过Java.use
调用里面的方法了。
制作dex的话,一个是可以直接把class文件转成dex,比较懒就是直接写成apk,然后解压提取classes.dex
枚举activity
从objection中得到的灵感
Hook所有的event(按钮事件)
objection-plugins/agent.js at master · hluwa/objection-plugins (github.com)
葫芦娃大佬的脚本,这个脚本非常显功底,hook event首先就得找到要监控的Onclick函数所处的类 ,一般我们都是传一个new的对象给注册的按钮事件
这里脚本中首先截获setOnclickListener得到注册的对象,然后再用getobjClassName获取对象的类名,再监控器OnClick事件,我们平时要用的话,这里可以加上打印调用栈相关的信息
1 | /* |
打印和参数构造
frida本来就是置于Java虚拟机的,所以各种复杂的打印只要取食于Java本身就行
- 数组/(字符串)对象数组/gson/Java.array
- 对象/多态、强转Java.cast/接口Java.register
- 泛型、List、Map、Set、迭代打印
- non-ascii 、 child-gating、rpc 上传到PC上打印
frida 对object、[object,object]的打印
碰运气
- 对象.toString()
- JSON.stringify(对象)
- gson.$new().toJson(对象)
Frida
打印[object]解决gson
包重名的问题-Android安全-看雪论坛-安全社区|安全招聘|bbs.pediy.com
靠实力
先获取类名
1
2
3
4function getRealClassName(对象) {
const objClass = Java.use("java.lang.Object").getClass.apply(对象);
return Java.use("java.lang.Class").getName.apply(objClass)
}用wallBreaker查看该类的输出函数 hluwa/Wallbreaker: 🔨 Break Java Reverse Engineering form Memory World! (github.com)
主动调用该类的输出函数进行输出
gson
1 | Java.openClassFile("/data/local/tmp/r0gson.dex").load(); |
gson的用法也是用frida注入自己的dex的案例
java Array构造
打印hashmap
1 | Java.perform(function(){ |
去内存里遍历HashMap,toString之后再过滤关键字
更细的操作
Frida Native Hook
Java层的Native函数的hook和主动调用
跟普通Java函数没什么区别,因为Native的函数要在Java层使用,是会首先声明在java层中,只不过加了个native
Hook导出函数
除了Hook直接在Java层注册的Native函数,SO中导出的函数也可以Hook
首先是找到是否so被加载,通过
Process.enumerateModules()
,这个API可以枚举被加载到内存的modules。然后通过
Module.findBaseAddress(module name)
来查找要hook的函数所在的so的基地址,如果找不到就返回null。然后可以通过
findExportByName(moduleName: string, exportName: string): NativePointer
来查找导出函数的绝对地址。如果不知道moduleName是什么,可以传入一个null进入,但是会花费一些时间遍历所有的module。如果找不到就返回null。找到地址之后,就可以拦截function/instruction的执行。通过
Interceptor.attach
。使用方法见下代码。- 另外为了将jstring的值打印出来,可以使用jenv的函数getStringUtfChars,就像正常的写native程序一样。
Java.vm.getEnv().getStringUtfChars(args[2], null).readCString()
1 | extern "C" |
hook脚本
1 | function hook_native() { |
getStringUtfChars是得到jstring的c指针,c指针里是字符串的内容,readCString是从一个NativePointer中读取字符串内容
readCString([size = -1])
,readUtf8String([size = -1])
,readUtf16String([length = -1])
,readAnsiString([size = -1])
: reads the bytes at this memory location as an ASCII, UTF-8, UTF-16, or ANSI string. Supply the optionalsize
argument if you know the size of the string in bytes, or omit it or specify -1 if the string is NUL-terminated. Likewise you may supply the optionallength
argument if you know the length of the string in characters.A JavaScript exception will be thrown if any of the
size
/length
bytes read from the address isn’t readable.Note that
readAnsiString()
is only available (and relevant) on Windows.
Hook Libc
方法是一样的,找到libc.so的模块,再枚举符号就行了
1 | function hook_libc(){ |
Native 调用栈打印
frida提供了接口
1 | Interceptor.attach(f, { |
打印的效果
1 | [Google Pixel::myapplication.example.com.ndk_demo]-> RegisterNatives called from: |
这是官方文档就这样写的
Thread.backtrace([context, backtracer])
: generate a backtrace for the current thread, returned as an array ofNativePointer
objects.If you call this from Interceptor’s
onEnter
oronLeave
callbacks you should providethis.context
for the optionalcontext
argument, as it will give you a more accurate backtrace. Omittingcontext
means the backtrace will be generated from the current stack location, which may not give you a very good backtrace due to the JavaScript VM’s stack frames. The optionalbacktracer
argument specifies the kind of backtracer to use, and must be eitherBacktracer.FUZZY
orBacktracer.ACCURATE
, where the latter is the default if not specified. The accurate kind of backtracers rely on debugger-friendly binaries or presence of debug information to do a good job, whereas the fuzzy backtracers perform forensics on the stack in order to guess the return addresses, which means you will get false positives, but it will work on any binary.
Native 方法替换
直接替换掉整个Native方法的话,用的是Interceptor的replace,而不是attach
也是在文档里有对标的
1 | const openPtr = Module.getExportByName('libc.so', 'open'); |
inline Hook
inline hook就是任意地址hook,前面介绍的interceptor的常规方法都是函数头hook
一下面ARM64代码为例
1 | .text:000000000000772C ; __unwind { |
在775C时,x0里保存的是一个指向一个字符串的指针。hook这个指令,然后Memory.readCString(this.context.x0);
打印出来,hook脚本如下
1 | function inline_hook() { |
其实看这里就知道,Interceptor.attach是可以用任意地址的,target只要是个NativePointer就行
这里进去之后可以用context获取上下文寄存器,this对象及context如下
1 | /** |
Native主动调用未导出函数
例子如下
1 | extern "C" |
根据NativePointer创建一个NativeFunction就可以直接调用
1 | function main() { |
主动加载so并调用函数(SO注入)
Frida反调试
这一节的主要内容就是关于反调试的原理和如何破解反调试,重要内容还是看文章理解即可。
因为我并不需要做反调试相关的工作,所以部分内容略过。
Frida反调试与反反调试基本思路
(Java层API、Native层API、Syscall)
####