Android加壳与脱壳(7)——Fdex2源码解析和frida版本实现

admin 2024年7月4日16:14:13评论3 views字数 8093阅读26分58秒阅读模式

一、前言

本实验主要是解析Fdex2源码解析和实现frida版本的Fdex2,Fdex2是一个针对第一代壳整体加壳的脱壳框架,不需要通过定位so层中的地址,仅仅只需要在java层就可以实现对第一代壳的脱去,主要是利用两个函数getBytes()getDex(),目前仅仅针对于Android 7.0及以下版本,缺点:如果一些壳不经过loadClass这个流程,就无法脱下来

本文实验分为两个版本:Android7.0及以下使用frida_fdex2、Android8.0及以上使用dumpDexByCookie.js来实现

本文收集的参考博客:

app的加壳与脱壳——此博客主要讲述看雪2w班的相关知识,十分的详细,建议仔细详细读

Android万能脱壳机 ——此博客主要提出两种主流脱壳思路,第一种思路为本文mCooike实现

Frida写抽取壳——用Frida写抽取壳的学习

frida hook so文件笔记总结——看雪2w班frida hook的学习笔记很详细,建议阅读

RDex——和本文实现思路一致

FART脱壳机流程分析——对FART脱壳机流程的解析

Dex脱壳格式详解——对Dex格式解析很清楚

二、Fdex2原理解析

1.原理解析

android中的java.lang.Class类拥有一个方法public native Dex getDex();,这意味着我们能通过Class对象的getDex方法获取到Dex对象,Dex类中有一个方法public byte[] getBytes(),我们能通过此方法获取获取该class对象关联的dex数据。这里采用的是Xposed去hook应用的ClassLoader.loadClass方法区dump解密后的dex数据

2.getBytes()和getDex()作用

(1)getDex()

Android加壳与脱壳(7)——Fdex2源码解析和frida版本实现

Android加壳与脱壳(7)——Fdex2源码解析和frida版本实现

通过getDex()来反射调用获得Dex类

(2)getBytes()

Android加壳与脱壳(7)——Fdex2源码解析和frida版本实现

然后通过该类对象去调用getBytes(),来获取数据信息

三、Fdex2实现

1.配置Xposed环境

2.编写Fdex2源码

(1)获取getBytes()和getDex()的方法引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Class Dex;
Method Dex_getBytes;
Method getDex;
String packagename;

...
public void initRefect(){
try {
// public byte[] getBytes()
Dex = Class.forName("com.android.dex.Dex");
Dex_getBytes = Dex.getDeclaredMethod("getBytes",null);
// public Dex getDex()
getDex = Class.forName("java.lang.Class").getDeclaredMethod("getDex",null);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}

(2)编写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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) throws Throwable {
initRefect();
XposedBridge.log("目标包名:"+ lpparam.packageName);
String str = "java.lang.ClassLoader";
String str2 = "loadClass";
final String packagename = "com.jiongji.andriod.card";

// protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException
if(lpparam.packageName.equals(packagename)){
// public static Unhook findAndHookMethod(String var0, ClassLoader var1, String var2, Object... var3)
XposedHelpers.findAndHookMethod(str, lpparam.classLoader, str2, String.class, Boolean.TYPE, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
super.afterHookedMethod(param);
//获取hook函数返回类
Class cls = (Class) param.getResult();
if(cls == null){
return;
}
String name = cls.getName();
XposedBridge.log("当前类名:"+name);
byte[] bArr = (byte[]) Dex_getBytes.invoke(getDex.invoke(cls,null)); //Class.getDex().getBytes()
if(bArr == null) {
XposedBridge.log("数据为空,返回");
return;

}
XposedBridge.log("开始写数据");
String dex_path = "/data/data/"+packagename+"/"+packagename+"_"+bArr.length+".dex";
XposedBridge.log(dex_path);
File file = new File(dex_path);
if(file.exists()){
return;
}
writeByte(bArr,file.getAbsolutePath());
}

@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
}
});
}

}

图示如下:

Android加壳与脱壳(7)——Fdex2源码解析和frida版本实现

Android加壳与脱壳(7)——Fdex2源码解析和frida版本实现

(3)保存数据

1
2
3
4
5
6
7
8
9
10
public void writeByte(byte[] bArr,String str){
try {
OutputStream outputStream = new FileOutputStream(str);
outputStream.write(bArr);
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
XposedBridge.log("文件写出失败");
}
}

3.运行及结果演示

(1)编译安装xposed模块,然后重启

(2)打开目标应用,运行

Android加壳与脱壳(7)——Fdex2源码解析和frida版本实现

Android加壳与脱壳(7)——Fdex2源码解析和frida版本实现

四、Frida版本修改(针对android7.0)

1.frida实现getDex()和getBytes()

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
function fdex2() {
Java.perform(function () {
// java.lang.Class.getDex
// com.android.dex.Dex.getBytes

// var Class = Java.use("java.lang.Class");
// console.log(Class);
// var getDex = Class.getDex.overloads;
// if(getDex !=null){
// console.log(getDex);
// }
// var Dex = Java.use("com.android.dex.Dex");
// var getBytes = Dex.getBytes.overloads;
// if(getBytes != null){
// console.log(getBytes);
// }

Java.enumerateClassLoadersSync().forEach(function (loader) {
try {
var Class = loader.loadClass("java.lang.Class");
var methods= Class.getDeclaredMethods();
methods.forEach(function (method) {
console.log(method);
})
}catch (e){

}
try{
var Dex = loader.loadClass("com.android.dex.Dex");
var methods= Dex.getDeclaredMethods();
methods.forEach(function (method) {
console.log(method);
})
}catch (e){

}

})
})
}

function main(){
fdex2();
}

setImmediate(main)

Android加壳与脱壳(7)——Fdex2源码解析和frida版本实现

Android加壳与脱壳(7)——Fdex2源码解析和frida版本实现

2.fdex2 frida版本的实现

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
// public static void savedex(byte[] content,String filepath) throws IOException {
// FileOutputStream fileOutputStream = new FileOutputStream(new File(filepath));
// fileOutputStream.write(content);
// fileOutputStream.close();
// }
function savedex(dexbytes,dexpath) {
Java.perform(function () {
var File = Java.use("java.io.File");
var FileOutputStream = Java.use("java.io.FileOutputStream");
var fileobj = File.$new(dexpath);
var fileOutputStreamobj = FileOutputStream.$new(fileobj);
fileOutputStreamobj.write(dexbytes);
fileOutputStreamobj.close();
console.warn("[dumpdex]"+dexpath);
})
}

function fdex2(classname) {
Java.perform(function () {
Java.enumerateClassLoadersSync().forEach(function (loader) {
try{
var ThisClass = loader.loadClass(classname);
//var Class = Java.use("java.lang.Class");
var dexobj = ThisClass.getDex();
var dexbytearray = dexobj.getBytes();
var savedexpath = "/sdcard/"+classname + ".dex";
savedex(dexbytearray,savedexpath);
}catch (e){

}
})
})
}


使用方法:

(1)附加脚本

1
frida -FU -l Fdex2.js --no-pause //attach方式启动APP 不需要setImmediate()

(2)脱取指定的Activity

Android加壳与脱壳(7)——Fdex2源码解析和frida版本实现

3.fdex2脱壳时机点

(1)onCreate函数执行完

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//时机点
function main() {
Java.perform(function () {
var StubApp = Java.use("com.stub.StubApp");

//时机点1
StubApp.onCreate.implementation = function () {
var result = this.onCreate();
console.log("StubAPP.onCreate called over!");
fdex2("com.touchtv.module_live.view.activity.PreviewListActivity");
return result;
}
})

}
setImmediate(main)

执行命令:

可以直接对包名进行进行附加:

1
frida -U -f com.touchtv.touchtv -l Fdex2.js --no-pause  //spwan模式在APP启动之前就开始附加 需要setImmediate()

Android加壳与脱壳(7)——Fdex2源码解析和frida版本实现

(2)attchBaseContext方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//时机点
function main() {
Java.perform(function () {
var StubApp = Java.use("com.stub.StubApp");
//时机点2
StubApp.attachBaseContext.implementation = function (arg0) {
var result = this.attachBaseContext(arg0);
console.log("StubAPP.attachBaseContext called over!");
fdex2("com.touchtv.module_live.view.activity.PreviewListActivity");
return result;
}
})

}
setImmediate(main)

执行命令同上

Android加壳与脱壳(7)——Fdex2源码解析和frida版本实现

我们验证时机点在attchBaseContext()之前,发现无法hook,说明dex文件在attachBaseContext()之后才解密

4.fdex2的扩展使用

1
2
3
4
1.使用frida枚举所有Classloader 
2.确定正确的ClassLoader并获取目标类的Class对象
3.通过Class对象获取得到dex对象
4.通过dex对象获取内存字节流并保存

五、高版本的类似fdex2脱壳的实现(android 8.0以上)

1.高版本脱壳实现原理图

Android加壳与脱壳(7)——Fdex2源码解析和frida版本实现

获取最终的mCookie

(1)mCookie脱壳原理解析

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
核心思路:反射 + mCookie(其实脱壳的点太多了,这是其中一个)
步骤:
1、找到加固apk的任一class,一般选择主Application或Activity
2、通过该类找到对应的Classloader
3、通过该Classloader找到BaseDexClassLoader
4、通过BaseDexClassLoader找到其字段DexPathList
5、通过DexPathList找到其变量Element数组dexElements
6、迭代该数组,该数组内部包含DexFile结构
7、通过DexFile获取其变量mCookie和mFileName(这个名字没什么鸟用)

至此我们已经获取了mCookie

对该mCookie的解释(有些现在记不太清楚了):
#14.4以下好像,mCookie对应的是一个int值,该值是指向native层内存中的dexfile的指针
#25.0是一个long值,该值指向native层std::vector<const DexFile*>* 指针,注意这里有多个dex,你需要找到你要的
#3、我还测试了8.0手机,该值也是一个long型的值,指向底层vector,但是vector下标0是oat文件,从1开始是dex文件
// 至于你手机是那个版本,如果没有落入我上面描述的,你需要自己看看代码

8、根据mCookie对应的值做转换,最终你能找到dexfile内存指针
9、把该指针转换为dexfile结构,通过findClassDef来匹配你所寻找的dex是你要的dex
10、dump写文件


代码说明(代码包括java层和native层,但java层只需定义一个native函数即可):
1、代码核心部分为dump_dex.h 和 dump_dex.cpp,里面涉及你需要自行实现的部分(我测试用的5.0.2 moto手机,
如果你的手机版本或者手机型号不同,你可能需要修改我表明的地方)
2、代码相对简单,你可以自行阅读

坑:
此方法思路相对简单,但是操作相对繁琐
1、你需要重打包apk
2、如果遇到签名校验,你同时需要在重打包中加入hook签名代码

详细参考博客:Android万能脱壳机

2.获取mCookie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function dumpDexBymCookie() {
Java.perform(function () {
Java.choose("dalvik.system.DexFile",{
onMatch:function (dexfile) {
var mCookie = dexfile.mCookie.value;
console.log(mCookie.$className);
var Array = Java.use("java.lang.reflect.Array");
var size = Array.getLength(mCookie);
var i = 0;
for(i=0;i<size;i++){
console.log(i+"->"+Array.getLong(mCookie,i));
}
},onComplete:function () {
console.warn("Search DexFile over!");
}
})
})
}

Android加壳与脱壳(7)——Fdex2源码解析和frida版本实现

3.通过mCookie获取dexFile起始地址和大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var savepath = "/sdcard/"
function savedexfile(dexfileptr) {
try {
//将mCookie转换为对应的dexfile内存指针
var dexfilebegin = ptr(dexfileptr).add(Process.pointerSize*1).readPointer(); //dex的初始指针
var dexfilesize = ptr(dexfileptr).add(Process.pointerSize*2).readU32(); //dex的大小
var dex = new File(savepath+"_"+dexfilesize+".dex","a");
if(dex!=null){
var content = ptr(dexfilebegin).readByteArray(dexfilesize);
dex.write(content);
dex.flush();
dex.close();
console.warn("[dumpdex]"+savepath+"_"+dexfilesize+".dex");
}
} catch (e) {

}
}

Android加壳与脱壳(7)——Fdex2源码解析和frida版本实现

注意:在Android8.0即更高版本上使用保存,很可能是frida_server版本过低导致出错

六、实验总结

本文主要仿造fdex2思想,针对于第一代的整体加固,开发支持android全版本的自动化脱壳脚本

- source:security-kitchen.com

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年7月4日16:14:13
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Android加壳与脱壳(7)——Fdex2源码解析和frida版本实现http://cn-sec.com/archives/2919122.html

发表评论

匿名网友 填写信息