上篇花了很多篇幅讲了Robust的原理,并以做题的思路去求解了这个示例ctf,其实这是一种思路的启示,当我们在不知道怎么hook动态加载的dex,jar时候,找找是否存在能够操作动态加载出来的类的方法。当然这不是重点,这篇我会重点给大家分享如何使用frida去hook DexclassLoader,怎么用反射直接调用类的方法,达到跟hook一般类一样的效果。
文章涉及内容及使用到的工具
0x00 使用到的工具
- ADT(Android Developer Tools)
- Jadx-gui
- JEB
- frida
- apktool
- android源码包
- 天天模拟器(genymotion,实体机等亦可)
0x01 涉及知识点
- Java 泛型
- Java 反射机制
- DexClassLoader 动态加载机制
- Frida 基本操作
- Frida 创建任意类型数组(Java.array)
- Frida 类型转换(Java.cast)
- Frida 方法重载(overload)
- Frida Spawn
代码分析与构造
0x00 Frida Spawn的使用
经过上篇文章我们对Robust的原理学习和对app的分析,我们知道Robust的其实就是在正常的使用DexClassLoader
去动态加载文件,随后通过反射
的方式去调用类方法或成员变量。
同时在上篇文章中,我们也知道Robust调用DexClassLoader的类是在PatchExecutor
中,而调用PatchExecutor类是在一个叫runRobust
的方法中,这个方法就在MainActivity
中,并且在onCreate
方法中调用。
现在我们明白了一点是,app动态加载dex的地方是在onCreate中,也就是说app一启动就执行了动态加载,并不是在我们点击按钮的时候。所以这个地方,我们要执行py脚本的话,需要在app执行onCreate方法之前。frida有一个功能可以为我们生成一个进程而不是将它注入到运行中的进程中,它注入到Zygote中,生成我们的进程并且等待输入。
我们可以通过-f
参数选项来实现。
frida -U -f app完整名
从上面可以看到,通过-f
参数,frida会Spawned这个应用,在这个时候启动python脚本,再执行%resume
命令,我们就可以在app执行onCreate方法前完成脚本的启动,这时候就能hook住onCreate中执行的一些方法。
0x01 DexClassLoader 动态加载机制
好,我们已经知道怎么hook住onCreate中执行的方法了,现在我们就来试试,第一个目标是能够获取到动态加载的dex中的类。在这之前我们来看看,直接去hook 动态加载的类会出现什么情况。
测试js代码如下,我们尝试获取dex中的MainActivityPatch
类。
Java.perform(function(){
console.log("test");
Java.use("cn.chaitin.geektan.crackme.MainActivityPatch");
console.log("test over");
});
完整代码:(后面的代码就只贴js_code =中的javascript代码了,因为这里面只有js_code的代码变化了)
# -*- coding: UTF-8 -*-
import frida,sys
def on_message(message, data):
if message['type'] == 'send':
print("[*] {0}".format(message['payload']))
else:
print(message)
js_code = '''
Java.perform(function(){
console.log("test");
Java.use("cn.chaitin.geektan.crackme.MainActivityPatch");
console.log("test over");
});
'''
session = frida.get_usb_device().attach("cn.chaitin.geektan.crackme")
script = session.create_script(js_code)
script.on('message',on_message)
script.load()
sys.stdin.read()
可以清晰的看到错误信息,未找到类。
java.lang.ClassNotFoundException: Didn\'t find class "cn.chaitin.geektan.crackme.MainActivityPatch
果不其然,这样去直接hook类肯定是不行的,但我们知道只要是从外部资源文件中动态加载dex,一般都是采用DexClassLoader动态加载的。学习过Java的同学应该知道,DexClassLoader动态加载的主要方法就是loadClass()
。我们从Java源码上去分析一下,这里给大家一个java开发文档的查看地址:访问
我们看到DexClassLoader的构造函数有4个参数,这里没有loadClass(),我们继续查看它的父类BaseDexClassLoader。
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
}
}
同样BaseDexClassLoader也没有loadClass(),最终在它的父类ClassLoader
中找到了loadClass()方法。
可以看到DexClassLoader加载的逻辑其实就是ClassLoader中的loadClass(),它的机制简单的了解到这里,现在我们来试试,通过这样的方式能不能hook我们想要的类。
我们先hook DexClassLoader的构造函数,看看传递进的参数值是什么。
Java.perform(function(){
//创建一个DexClassLoader的wapper
var dexclassLoader = Java.use("dalvik.system.DexClassLoader");
//hook 它的构造函数$init,我们将它的四个参数打印出来看看。
dexclassLoader.$init.implementation = function(dexPath,optimizedDirectory,librarySearchPath,parent){
console.log("dexPath:"+dexPath);
console.log("optimizedDirectory:"+optimizedDirectory);
console.log("librarySearchPath:"+librarySearchPath);
console.log("parent:"+parent);
//不破换它原本的逻辑,我们调用它原本的构造函数。
this.$init(dexPath,optimizedDirectory,librarySearchPath,parent);
}
console.log("down!");
});
执行看看:
我们获取到了构造函数的参数,简单看一下。
dexPath:/data/data/cn.chaitin.geektan.crackme/cache/GeekTan/patch_temp.jar
optimizedDirectory:/data/data/cn.chaitin.geektan.crackme/cache
librarySearchPath:null
parent:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/cn.chaitin.geektan.crackme-2.apk"],nativeLibraryDirectories=[/data/app-lib/cn.chaitin.geektan.crackme-2, /system/lib, /system/lib/arm]]]
0x02 Frida 方法重载(overload)
接下来,就来尝试一下获取动态加载的类。
Java.perform(function(){
var dexclassLoader = Java.