使用frida Hook动态加载Dex

复现了2018DDCTF 的一道安卓题HelloBabyDex,参考了网上的很多资料,学到了很多知识,记录一下使用frida Hook动态加载Dex去解这道题过程中学到的东西,期间还补充了Java关于反射机制的知识…..

POINT

  1. Hook动态加载的Dex的类方法
  2. Hook重载的方法
  3. 使用Java.cast进行类型转换
  4. 使用Java.array创建数组
  5. java反射机制中的实例化对象

PROLOGUE

热修复其实是使用DexClassLoader方法去动态加载文件,然后通过反射机制去调用类方法或成员变量,而DexClassLoader方法真正使用的是其父类的父类中的loadClass方法,那就可以去Hook DexClassLoader这个类下的loadClass方法。

Hook重载的方法

但是loadClass有2个重载方法,在frida中使用overload指定Hook的重载方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
......
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}

if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
return c;
}

Hook只有一个参数为String类型的方法:

1
2
var dexclassLoader = Java.use("dalvik.system.DexClassLoader");
dexclassLoader.loadClass.overload('java.lang.String').implementation = function(name)

类型转换

这个name就是动态加载的类名,即 cn.chaitin.geektan.crackme.MainActivityPatch,当name匹配成功时,返回一个类对象class cn.chaitin.geektan.crackme.MainActivityPatch,按理说利用反射机制,可以使用这个类对象去实例化对象,但是尝试后不行,需要进行类型转换

1
2
var ClassUse = Java.use("java.lang.Class");
var hookClassCast = Java.cast(hookClass,ClassUse);

这样就获得了一个指向MainActivityPatch的类对象

通过类对象实例化对象

有了类对象,就可以通过类对象的 getDeclaredConstructor(Class<?>...ParametersType)方法获得指定参数的构造器,即构造函数对象。函数的参数是类型为类对象的数组。查看动态加载类中MainActivityPatch的构造函数,参数是Object类型。

1
2
3
public MainActivityPatch(Object obj) {
this.originClass = (MainActivity) obj;
}

通过frida的Java.array方法去构造Object类型的数组,语法是

1
Java.array('type',[value1,value2,....]);

构造语句,这个type的写法和smali语法一样

1
2
var objectclass= Java.use("java.lang.Object");
var ConstructorParam =Java.array('Ljava.lang.Object;',[objectclass.class]);

然后调用getDeclaredConstructor方法获得构造器

1
var a = hookClassCast.getDeclaredConstructor(ConstructorParam);

这样通过指定构造函数的参数,返回了一个指定的构造函数对象,Constructor对象中有一个方法newInstance(Object ... initargs),这里的initargs即为要传给构造函数的参数。使用newInstance就获取了MainActivityPatch类的实例化对象

1
2
3
var orininclass = Java.use("cn.chaitin.geektan.crackme.MainActivity");
var mainAc = orininclass.$new();
var instance = Constructor.newInstance([mainAc]);

调用实例化对象的方法

得到实例化的对象后,就可以通过getDeclaredMethods方法获取所有这个类的类方法,查看getDeclaredMethods的原型

1
2
3
public Method[] getDeclaredMethods() throws SecurityException {
......
}

通过原型可以知道该方法返回的是Method数组,Joseph函数在数组中第一个。

1
2
var func = hookClassCast.getDeclaredMethods();
var f = func[0];

知道目标函数的位置后,就可以使用Method.invoke方法调用该方法,看下invoke方法的参数,第一个参数是执行这个方法的对象实例,第二个参数是带入的实际值数组

1
public native Object invoke(Object obj, Object... args)

所以执行Joseph(5,6)的js语句是

1
2
3
4
5
var Integerclass = Java.use("java.lang.Integer");
var num1 = Integerclass.$new(5);
var num2 = Integerclass.$new(6);
var numArr1 = Java.array('Ljava.lang.Object;',[num1,num2]);
var rtn1 = f.invoke(instance,numArr1);

最终的EXP如下(参考看雪论坛ghostmazeW

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
Java.perform(function(){
var hookClass = undefined;
var ClassUse = Java.use("java.lang.Class");
var objectclass= Java.use("java.lang.Object");
var dexclassLoader = Java.use("dalvik.system.DexClassLoader");
var orininclass = Java.use("cn.chaitin.geektan.crackme.MainActivity");
var Integerclass = Java.use("java.lang.Integer");
var mainAc = orininclass.$new();


dexclassLoader.loadClass.overload('java.lang.String').implementation = function(name){
var hookname = "cn.chaitin.geektan.crackme.MainActivityPatch";
var result = this.loadClass(name,false);
if(name == hookname){
var hookClass = result;
var hookClassCast = Java.cast(hookClass,ClassUse);
console.log("-----------------------------GET Constructor-------------------------------------");
var ConstructorParam =Java.array('Ljava.lang.Object;',[objectclass.class]);
var Constructor = hookClassCast.getDeclaredConstructor(ConstructorParam);
console.log("Constructor:"+Constructor);
console.log("orinin:"+mainAc);
var instance = Constructor.newInstance([mainAc]);
console.log("patchAc:"+instance);
send(instance);

console.log("-----------------------------GET Methods----------------------------");
var func = hookClassCast.getDeclaredMethods();
console.log(func);
console.log("--------------------------GET Joseph Function---------------------------");
console.log(func[0]);
var f = func[0];
var num1 = Integerclass.$new(5);
var num2 = Integerclass.$new(6);
var numArr1 = Java.array('Ljava.lang.Object;',[num1,num2]);
var num3 = Integerclass.$new(7);
var num4 = Integerclass.$new(8);
var numArr2 = Java.array('Ljava.lang.Object;',[num3,num4]);
console.log("-----------------------------GET Array------------------------------");
console.log(numArr1);
console.log(numArr2);
var rtn1 = f.invoke(instance,numArr1);
var rtn2 = f.invoke(instance,numArr2);
console.log("--------------------------------FLAG---------------------------------");
console.log("DDCTF{"+rtn1+rtn2+"}");
console.log("--------------------------------OVER--------------------------------");
return result;

}
return result;
}
});

经测这个方法在Genymotion模拟器的android4.4和androdi6.0均测试失败…Hook不到loadClass方法。