DDCTF2018的Android题目 Hello Baby Dex. 题解有很多,这里用的是frida的hook的方法.
POINT
- Robust热修复框架原理
- Frida的各种数据格式的转换
- DexClassLoader loadclass动态加载
LINK
- Robust项目 (Robust是美团出的一款Android热修复框架)
- dex Hook上https://bbs.pediy.com/thread-229597.htm
- dex Hook下https://bbs.pediy.com/thread-229657.htm
Description
具体的分析在上面的两个看雪论坛的链接中有,而且讲得很详细。具体的过程就不赘述了.这里就是在自己复现的时候总结一下两种方法,还有遇到的一些问题吧。
另外,这道题目可以通过动态调试断在字符串比较判断的地方,也可以获取flag,这里费劲心思用两种方法或许是为了掌握一些hook动态加载的class的方法吧。
(建议先看完上面两篇的分析再看看下面的PS即可)
方法1 HOOK Robust中的invokeReflectMethod
Robust中,通过反射得到类的实例及方法,最终通过invokeReflectMethod
代入参数执行方法。也就是说,新动态加载进来的类,它的函数的执行依赖于原本加载好的EnhancedRobustUtils
类中的invokeReflectMethod,通过给这个函数传入实例化对象和函数参数数组来执行对应的函数(也就是依附于EnhancedRobustUtils) .
这里我们发现这个EnhancedRobustUtils 是Robust自带的类,并不是动态加载的。这样我们就能很方便地hook了。
hookinvokeReflectMethod
,对传入的函数名参数检查, hook出Joseph函数的返回结果
js_code='''
Java.perform(function(){
//get EnhancedRobustUtils
var robust = Java.use("com.meituan.robust.utils.EnhancedRobustUtils");
//hook invokeReflectMethod of EnhancedRobustUtils
robust.invokeReflectMethod.implementation = function(v1,v2,v3,v4,v5){
//get Joseph,equals result
var result = this.invokeReflectMethod(v1,v2,v3,v4,v5);
if(v1=="Joseph"){
console.log("functionName:"+v1);
console.log("functionArg3:"+v3);
console.log("functionArg4:"+v4);
send(v4);
console.log("return:"+result);
console.log("-----------------------------------------------------")
}
else if(v1=="equals"){
console.log("functionName:"+v1);
console.log("functionArg3:"+v3);
console.log("functionArg4:"+v4);
send(v4);
console.log("return:"+result);
}
return result;
}
});
'''
运行:
F:\Python3\python.exe E:/PythonProject/frida_test.py
functionName:Joseph
functionArg3:5,6
functionArg4:int,int
[*] [{'$handle': '0x100bce', '$weakRef': 47}, {'$handle': '0x100bd2', '$weakRef': 48}]
return:29360128
-----------------------------------------------------
functionName:Joseph
functionArg3:7,8
functionArg4:int,int
[*] [{'$handle': '0x100f56', '$weakRef': 275}, {'$handle': '0x100f5a', '$weakRef': 276}]
return:29362176
-----------------------------------------------------
functionName:equals
functionArg3:DDCTF{2936012829362176}
functionArg4:class java.lang.Object
[*] [{'$handle': '0x1012f2', '$weakRef': 510}]
return:false
方法一由于实质上还是hook本身加载好的EnhancedRobustUtils
类,所以稳定性很好.
方法2 HOOK dexclassLoader
DexClassLoader可以用于加载任意路径的zip,jar或者apk文件,也是进行安卓动态加载的基础.
通过hook dexclassLoader
中的loadClass方法
我们对传入loadClass的名字参数进行过滤检测,如果是待加载的目标classcn.chaitin.geektan.crackme.MainActivityPatch
,我们获取loadclass后的返回值,一个Class<?>
类型的变量,我们cast才能获得这个类。
注意,这里记得cast,因为Class<?>
代表所有类型的类,我们使用frida提供的cast的方法Java.cast
var ClassUse = Java.use("java.lang.Class");
dexclassLoader.loadClass.overload('java.lang.String').implementation = function(name){
...
var hookClass= this.loadClass(name,false);
var hookClassCast = Java.cast(hookClass,ClassUse);
然后我们要用到反射机制。(一篇构造器反射机制的笔记)
总体思路是,我们通过一个已经拿到的对象获取其类的构造器,然后通过这个构造器去实例化出我们想要的对象。这其中,最关键的就是构造器的参数的传递。
frida中通过java.array()
来创建数组。格式如下
Java.array('type',[value1,value2,....]);
其中type
的类型(官方源码)有
1. Z -- boolean
2. B -- byte
3. C -- char
4. S -- short
5. I -- int
6. J -- long
7. F -- float
8. D -- double
9. V -- void
这里写的语法与smali类似,例如
var objectclass= Java.use("java.lang.Object");
var ConstructorParam =Java.array('Ljava.lang.Object;',[objectclass.class]);
首先我们要通过对象获取构造器,这个函数的原型是
public Constructor<T> getDeclaredtConstructor(Class<?>... parameterTypes)
先构造好参数数组,然后传入给hookClassCast去拿到对应参数的类构造器
var objectclass= Java.use("java.lang.Object");
var ConstructorParam =Java.array('Ljava.lang.Object;',[objectclass.class]);
var Constructor = hookClassCast.getDeclaredConstructor(ConstructorParam);
然后用这个构造器的构造方法(按照之前的构造参数)去创建对象instance
var instance = Constructor.newInstance([mainAc]);
在后面的invoke中, 我们需要两个参数,一个是执行这个方法的实例对象(此处即instance ),一个是参数数组(辨识调用哪个函数的)。返回值是Object。
然后我们手动调用Joseph方法即可
var num1 = Integerclass.$new(5);
var num2 = Integerclass.$new(6);
var numArr1 = Java.array('Ljava.lang.Object;',[num1,num2]);
var rtn1 = hookClassCast.getDeclaredMethods()[0].invoke(instance,numArr1);
PS1
文中通过
frida -U -f yourappname
来spawn(即创建进程后挂起)一个app。
这样每次调试很麻烦,还要在控制台手动%resume
。再去开python脚本。
我们可以把脚本中的attach
部分改为如下:
device = frida.get_usb_device()
pid = device.spawn(["cn.chaitin.geektan.crackme"])
session = device.attach(pid)
script = session.create_script(js_code)
script.on('message', on_message)
script.load()
device.resume(pid)
sys.stdin.read()
这样就能通过脚本直接启动app并挂起恢复,调试能方便些。
PS2
方法二的脚本在某些(不少)Android版本中失去效果,有的Android(测试为5.1.1)中无法hook到dexclassLoader中的loadclass,有的Android(夜神的7.0版本)无法invoke成功(invoke后一直获取不到返回结果)。还没弄清原因😭😭😭.
网友评论