介绍

我们知道抽取加固的脱壳是主动调用函数,然后把函数体填充之后dump出来

FART主动调用的组件源码的大概流程如下

1
2
3
4
5
6
遍历ClassLoader
遍历ClassLoader中的类的所有函数
完成对所有函数的主动调用,但不能影响app运行
函数执行过程中dump函数体数据 DexCode或者CodeItem
CodeItem起始地址的获取:artMethod->GetCodeItem()
CodeItem长度的计算:dex_file->GetCodeItemSize(const CodeItem& code)

在系统中主动调用函数的方法

1
2
3
无法使用Hook框架的主动调用
Java层可以利用反射
jni层可以参阅CallVoidMethod:artMethod->invoke(...)

遍历ClassLoader

在ActivityThread.java中定义了一个开启子线程的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void fartthread() {
new Thread(new Runnable() {

@Override
public void run() {
// TODO Auto-generated method stub
try {
Log.e("ActivityThread", "start sleep......");
Thread.sleep(1 * 60 * 1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Log.e("ActivityThread", "sleep over and start fart");
fart();
Log.e("ActivityThread", "fart run over");

}
}).start();
}

在这里会在子线程中调用fart()方法,也就是说遍历ClassLoader的操作是在子线程中完成的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void fart() {
ClassLoader appClassloader = getClassloader();
ClassLoader tmpClassloader=appClassloader;
ClassLoader parentClassloader=appClassloader.getParent();
if(appClassloader.toString().indexOf("java.lang.BootClassLoader")==-1)
{
fartwithClassloader(appClassloader);
}
while(parentClassloader!=null){
if(parentClassloader.toString().indexOf("java.lang.BootClassLoader")==-1)
{
fartwithClassloader(parentClassloader);
}
tmpClassloader=parentClassloader;
parentClassloader=parentClassloader.getParent();
}
}

跟进getClassloader()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static ClassLoader getClassloader() {
ClassLoader resultClassloader = null;
// 调用android.app.ActivityThread的currentActivityThread方法
//
Object currentActivityThread = invokeStaticMethod(
"android.app.ActivityThread", "currentActivityThread",
new Class[]{}, new Object[]{});
Object mBoundApplication = getFieldOjbect(
"android.app.ActivityThread", currentActivityThread,
"mBoundApplication");
Application mInitialApplication = (Application) getFieldOjbect("android.app.ActivityThread",
currentActivityThread, "mInitialApplication");
Object loadedApkInfo = getFieldOjbect(
"android.app.ActivityThread$AppBindData",
mBoundApplication, "info");
Application mApplication = (Application) getFieldOjbect("android.app.LoadedApk", loadedApkInfo, "mApplication");
// 最后得到Application的类加载器
resultClassloader = mApplication.getClassLoader();
return resultClassloader;
}

其实就是获取到类加载器,为什么要获取类加载器?我们知道app加载类都是通过mClassLoader指向的classloader去加载类,其修正有两种情况,目的就是获取到真正加载解密之后的dex的那个ClassLoader

1
2
3
插入ClassLoader,可以理解为把mClassLoader的值给设置成DexClassLoader,而DexClassLoader的父加载器是PathClassLoader,所以可以正常运行,对于这种情况,frida其实不需要做任何处理包括枚举所有loader,正常hook就行

替换ClassLoader,也就是把DexClassLoader的父加载器设置为BootClassLoader,然后把PathClassLoader的父加载器设置为DexClassLoader,根据双亲委派机制,仍然可以正常运行

那么这段代码就好理解了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 判断classloader是否为BootClassLoader,如果不是就主动调用
if(appClassloader.toString().indexOf("java.lang.BootClassLoader")==-1)
{
// 这个逻辑其实是针对插入ClassLoader的修正方法的
// 因为在这种情况下,mClassLoader就是加载真正业务dex的ClassLoader
// 此时可以通过获取到的ClassLoader找到业务逻辑类
fartwithClassloader(appClassloader);
}
// 这个逻辑是针对替换ClassLoader的情况去处理的
// 替换ClassLoader的情况下,mClassLoader仍然指向加载壳的dex的Loader,也就是PathClassLoader,还需要往上遍历一次才能找到DexClassLoader
while(parentClassloader!=null){
if(parentClassloader.toString().indexOf("java.lang.BootClassLoader")==-1)
{
fartwithClassloader(parentClassloader);
}
tmpClassloader=parentClassloader;
parentClassloader=parentClassloader.getParent();
}

遍历ClassLoader中的类的所有函数

跟进fartwithClassloader()方法,通过获取BaseDexClassLoaderPathList属性(dex文件的路径都会存储在这个属性中),然后获取DexPathListdexElements属性,这个属性是一个数组,每个数组都是一个dex文件

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
public static void fartwithClassloader(ClassLoader appClassloader) {
List<Object> dexFilesArray = new ArrayList<Object>();
Field pathList_Field = (Field) getClassField(appClassloader, "dalvik.system.BaseDexClassLoader", "pathList");
Object pathList_object = getFieldOjbect("dalvik.system.BaseDexClassLoader", appClassloader, "pathList");
Object[] ElementsArray = (Object[]) getFieldOjbect("dalvik.system.DexPathList", pathList_object, "dexElements");
Field dexFile_fileField = null;
try {
// Element是DexPathList的内部类,dexFile是Element的属性
dexFile_fileField = (Field) getClassField(appClassloader, "dalvik.system.DexPathList$Element", "dexFile");
} catch (Exception e) {
e.printStackTrace();
} catch (Error e) {
e.printStackTrace();
}
Class DexFileClazz = null;
try {
// 获取DexFile类
DexFileClazz = appClassloader.loadClass("dalvik.system.DexFile");
} catch (Exception e) {
e.printStackTrace();
} catch (Error e) {
e.printStackTrace();
}
// 通过DexFile类来获取类中的特定方法
// 因为要用到DexFile类的getClassNameList方法来获取所有方法
Method getClassNameList_method = null;
Method defineClass_method = null;
Method dumpDexFile_method = null;
Method dumpMethodCode_method = null;

for (Method field : DexFileClazz.getDeclaredMethods()) {
if (field.getName().equals("getClassNameList")) {
getClassNameList_method = field;
getClassNameList_method.setAccessible(true);
}
if (field.getName().equals("defineClassNative")) {
defineClass_method = field;
defineClass_method.setAccessible(true);
}
if (field.getName().equals("dumpDexFile")) {
dumpDexFile_method = field;
dumpDexFile_method.setAccessible(true);
}
if (field.getName().equals("dumpMethodCode")) {
dumpMethodCode_method = field;
dumpMethodCode_method.setAccessible(true);
}
}
Field mCookiefield = getClassField(appClassloader, "dalvik.system.DexFile", "mCookie");
Log.v("ActivityThread->methods", "dalvik.system.DexPathList.ElementsArray.length:" + ElementsArray.length);//5个
for (int j = 0; j < ElementsArray.length; j++) {
Object element = ElementsArray[j];
Object dexfile = null;
try {
dexfile = (Object) dexFile_fileField.get(element);
} catch (Exception e) {
e.printStackTrace();
} catch (Error e) {
e.printStackTrace();
}
if (dexfile == null) {
Log.e("ActivityThread", "dexfile is null");
continue;
}
if (dexfile != null) {
dexFilesArray.add(dexfile);
Object mcookie = getClassFieldObject(appClassloader, "dalvik.system.DexFile", dexfile, "mCookie");
if (mcookie == null) {
Object mInternalCookie = getClassFieldObject(appClassloader, "dalvik.system.DexFile", dexfile, "mInternalCookie");
if(mInternalCookie!=null)
{
mcookie=mInternalCookie;
}else{
Log.v("ActivityThread->err", "get mInternalCookie is null");
continue;
}

}
String[] classnames = null;
try {
classnames = (String[]) getClassNameList_method.invoke(dexfile, mcookie);
} catch (Exception e) {
e.printStackTrace();
continue;
} catch (Error e) {
e.printStackTrace();
continue;
}
if (classnames != null) {
for (String eachclassname : classnames) {
// 遍历ClassLoader中的所有dex中的所有类,并执行类中的所有函数
loadClassAndInvoke(appClassloader, eachclassname, dumpMethodCode_method);
}
}

}
}
return;
}

完成对类中所有函数的主动调用

跟进loadClassAndInvoke()方法,该方法实现了调用类中的所有函数的过程

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
52
53
54
55
56
57
58
59
60
61
62
public static void loadClassAndInvoke(ClassLoader appClassloader, String eachclassname, Method dumpMethodCode_method) {
Class resultclass = null;
Log.i("ActivityThread", "go into loadClassAndInvoke->" + "classname:" + eachclassname);
try {
resultclass = appClassloader.loadClass(eachclassname);
} catch (Exception e) {
e.printStackTrace();
return;
} catch (Error e) {
e.printStackTrace();
return;
}
if (resultclass != null) {
try {
Constructor<?> cons[] = resultclass.getDeclaredConstructors();
for (Constructor<?> constructor : cons) {
if (dumpMethodCode_method != null) {
try {
dumpMethodCode_method.invoke(null, constructor);
} catch (Exception e) {
e.printStackTrace();
continue;
} catch (Error e) {
e.printStackTrace();
continue;
}
} else {
Log.e("ActivityThread", "dumpMethodCode_method is null ");
}

}
} catch (Exception e) {
e.printStackTrace();
} catch (Error e) {
e.printStackTrace();
}
try {
Method[] methods = resultclass.getDeclaredMethods();
if (methods != null) {
for (Method m : methods) {
if (dumpMethodCode_method != null) {
try {
dumpMethodCode_method.invoke(null, m);
} catch (Exception e) {
e.printStackTrace();
continue;
} catch (Error e) {
e.printStackTrace();
continue;
}
} else {
Log.e("ActivityThread", "dumpMethodCode_method is null ");
}
}
}
} catch (Exception e) {
e.printStackTrace();
} catch (Error e) {
e.printStackTrace();
}
}
}

注意加载类用的是resultclass = appClassloader.loadClass(eachclassname)而不是class.forname,为什么?因为在一些加固中,会丢入一些垃圾类,在这些垃圾类中定义一些静态方法,当类被加载的时候会执行导致程序退出,而loadClass不会触发类的静态方法,从而避免这种情况,后面的代码就很好理解了,就是反射,重点在这一句dumpMethodCode_method.invoke(null, m);,分析dumpMethodCode_method()函数

这个函数是在fartwithClassLoader()方法中使用的

1
2
3
4
if (field.getName().equals("dumpMethodCode")) {
dumpMethodCode_method = field;
dumpMethodCode_method.setAccessible(true);
}

field来自于DexFile类,所以dumpMethodCodeDexFile类中的字段,在DexFile.java中的定义如下

1
private static native void dumpMethodCode(Object m);

所以这个方法是c层实现的

1
2
3
4
5
6
7
8
9
static void DexFile_dumpMethodCode(JNIEnv* env, jclass,jobject method) {
if(method!=nullptr)
{
ArtMethod* proxy_method = jobject2ArtMethod(env, method);
myfartInvoke(proxy_method);
}

return;
}

在c层怎么主动调用java层的方法?需要把java的method转换成art的method,然后调用artMethod->invoke调用art方法

跟进myfartInvoke()方法

1
2
3
4
5
6
7
8
extern "C" void myfartInvoke(ArtMethod* artmethod)  REQUIRES_SHARED(Locks::mutator_lock_) {
JValue *result=nullptr;
Thread *self=nullptr;
uint32_t temp=6;
uint32_t* args=&temp;
uint32_t args_size=6;
artmethod->Invoke(self, args, args_size, result, "fart");
}

可以看到调用了artMethod,而且传参是随便构造的,因为也不知道要传什么参数,就随便传了

继续跟进artmethod->Invoke()方法,可以看到会判断参数是否为瞎构造的,如果是,就保存方法体的代码,然后直接退出,不去执行方法,不然app可能会因为参数的问题而退出

1
2
3
4
5
6
7
8
void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result,
const char* shorty) {

if (self== nullptr) {
dumpArtMethod(this);
return;
}
...

上面的不执行函数而保存函数体之后退出会导致什么问题?很明显,对于那些只有在函数运行时才解密填充方法体的加固方案,这样做是无效的,因为函数压根没执行,也就是说上面的方法的脱壳时机早了

dump函数体数据

要保存函数体代码,肯定要知道在字节形式下,函数体的起始地址和大小,跟进dumpArtMethod()方法,该方法中会再次进行一次脱壳,然后才是保存函数体,只看保存函数体的代码

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
extern "C" void dumpArtMethod(ArtMethod* artmethod)  REQUIRES_SHARED(Locks::mutator_lock_) {
...;
// 获取dex_code_item_offset字段值,该字段值指向函数的起始地址
const DexFile::CodeItem* code_item = artmethod->GetCodeItem();
if (LIKELY(code_item != nullptr))
{
int code_item_len = 0;
uint8_t *item=(uint8_t *) code_item;
// 计算函数体的长度,也就是codeItemSize
// 其实可以调用code_item->GetCodeItemSize()方法来获取长度,这个方法在高版本Android上才有
if (code_item->tries_size_>0) {
const uint8_t *handler_data = (const uint8_t *)(DexFile::GetTryItems(*code_item, code_item->tries_size_));
uint8_t * tail = codeitem_end(&handler_data);
code_item_len = (int)(tail - item);
}else{
code_item_len = 16+code_item->insns_size_in_code_units_*2;
}
memset(dexfilepath,0,1000);
int size_int=(int)dex_file->Size();
// 10.0下用的是artmethod->GetDexMethodIndex()
uint32_t method_idx=artmethod->GetDexMethodIndexUnchecked();
sprintf(dexfilepath,"/sdcard/fart/%s/%d_ins_%d.bin",szProcName,size_int,(int)gettidv1());
int fp2=open(dexfilepath,O_CREAT|O_APPEND|O_RDWR,0666);
// 把函数内容写入文件
if(fp2>0){
lseek(fp2,0,SEEK_END);
memset(dexfilepath,0,1000);
int offset=(int)(item - begin_);
sprintf(dexfilepath,"{name:%s,method_idx:%d,offset:%d,code_item_len:%d,ins:",artmethod->PrettyMethod().c_str(),method_idx,offset,code_item_len);
int contentlength=0;
while(dexfilepath[contentlength]!=0) contentlength++;
result=write(fp2,(void*)dexfilepath,contentlength);
if(result<0)
{
LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,write ins file error";
}
long outlen=0;
// 函数体会做base64
char* base64result=base64_encode((char*)item,(long)code_item_len,&outlen);
result=write(fp2,base64result,outlen);
if(result<0)
{
LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,write ins file error";
}
result=write(fp2,"};",2);
if(result<0)
{
LOG(ERROR) << "ArtMethod::dumpdexfilebyArtMethod,write ins file error";
}
fsync(fp2);
close(fp2);
if(base64result!=nullptr){
free(base64result);
base64result=nullptr;
}
}
}
}

if(dexfilepath!=nullptr)
{
free(dexfilepath);
dexfilepath=nullptr;
}

}

魔改和迁移到Android 10

原先的代码中handleBindApplication()方法是程序的入口,脱壳应该在壳代码加载完毕之后也就是app = data.info.makeApplication(data.restrictedBackupMode, null);执行完毕之后,这里在之后执行了一个方法,启动了新的线程,在线程中调用脱壳的方法,这里其实没改,java层唯一改动的就是加了对类的判断,如果是系统类就不进行操作

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
for (int j = 0; j < ElementsArray.length; j++) {
Object element = ElementsArray[j];
Object dexfile = null;
try {
dexfile = (Object) dexFileField.get(element);
} catch (Exception e) {
e.printStackTrace();
}
if (dexfile == null) {
Log.e("JohnDemo", "JohnDemoWithClassloader dexfile is null");
continue;
} else {
dexFilesArray.add(dexfile);
Object _mCookie = getClassFieldObject(appClassloader, "dalvik.system.DexFile", dexfile, "mCookie");
if (_mCookie == null) {
Object _mInternalCookie = getClassFieldObject(appClassloader, "dalvik.system.DexFile", dexfile, "mInternalCookie");
if(_mInternalCookie != null)
{
_mCookie = _mInternalCookie;
}else{
Log.v("JohnDemo", "JohnDemoWithClassloader get _mInternalCookie is null");
continue;
}
}
String[] classNamesArray = null;
try {
classNamesArray = (String[]) _getClassNameList.invoke(dexfile, _mCookie);
} catch (Exception e) {
e.printStackTrace();
continue;
}
if (classNamesArray != null) {
for (String eachClassName : classNamesArray) {
//对类名进行判断
if (eachClassName.startsWith("android")) continue;
loadClassAndInvoke(appClassloader, eachClassName, _saveMethodCodeItem);
}
}
}
}

so层代码的话,在DexFile.java中定义了native方法

1
private static native void saveMethodCodeItem(Object m);

实现如下

1
2
3
4
5
6
static void DexFile_saveMethodCodeItem(JNIEnv* env, jclass, jobject method) {
if(method != nullptr) {
ArtMethod* proxyMethod = jobject2ArtMethod(env, method);
JohnDemoFunctionInvoke(proxyMethod);
}
}

so层主要改动的点是saveArtMethod方法,也就是保存函数体的代码,前面整体脱壳的代码不变,后面改了一些方法的调用,有些方法在Android 10上用不了

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
extern "C" void saveArtMethod(ArtMethod * artMethod) REQUIRES_SHARED(Locks::mutator_lock_) {
char * dexFilePath = (char *)malloc(sizeof(char) * 1000);
if (dexFilePath == nullptr) {
LOG(ERROR) << "ArtMethod::saveArtMethod, methodName:" << artMethod->PrettyMethod().c_str() << "malloc 1000 byte failed";
return;
} else {
LOG(ERROR) << "ArtMethod::saveArtMethod, malloc 1000 byte success";
}
int result = 0;
int fcmdline = -1;
char szCmdline[64] = {0};
char szProcName[256] = {0};
int procid = getpid();
sprintf(szCmdline, "/proc/%d/cmdline", procid);
fcmdline = open(szCmdline, O_RDONLY | O_CREAT, 0644);
if (fcmdline > 0) {
LOG(ERROR) << "ArtMethod::saveArtMethod, open cmdline file success";
result = read(fcmdline, szProcName, 256);
if (result < 0) {
LOG(ERROR) << "ArtMethod::saveArtMethod, read cmdline file error";
} else {
LOG(ERROR) << "ArtMethod::saveArtMethod, read cmdline file success";
}
close(fcmdline);
} else {
LOG(ERROR) << "ArtMethod::saveArtMethod, open cmdline file error";
}

if (szProcName[0]) {
const DexFile* dex_file = artMethod->GetDexFile();
const uint8_t* begin_ = dex_file->Begin(); // Start of data.
size_t size_ = dex_file->Size(); // Length of data.
int size_int_ = (int)size_;

memset(dexFilePath, 0, 1000);
sprintf(dexFilePath, "/data/data/%s/JohnDemo", szProcName);
mkdir(dexFilePath, 0777);

memset(dexFilePath, 0, 1000);
sprintf(dexFilePath, "/data/data/%s/JohnDemo/%d_dexfile_artMethod.dex", szProcName, size_int_);

if((access(dexFilePath,F_OK)) != -1) {
LOG(ERROR) << "ArtMethod::saveArtMethod, dex is exist";
} else {
int fp = open(dexFilePath, O_CREAT | O_APPEND | O_RDWR, 0666);
if (fp > 0) {
LOG(ERROR) << "ArtMethod::saveArtMethod, open dexfile success";
result = write(fp, (void * )begin_, size_);
if (result < 0) {
LOG(ERROR) << "ArtMethod::saveArtMethod, write dexfile error";
}
fsync(fp);
close(fp);
memset(dexFilePath, 0, 1000);
sprintf(dexFilePath, "/data/data/%s/JohnDemo/%d_classlist_artMethod.txt", szProcName, size_int_);
int classlistFile = open(dexFilePath, O_CREAT | O_APPEND | O_RDWR, 0666);
if (classlistFile > 0) {
LOG(ERROR) << "ArtMethod::saveArtMethod, open classlistFile success";
for (size_t ii = 0; ii < dex_file->NumClassDefs(); ++ii) {
const dex::ClassDef& class_def = dex_file->GetClassDef(ii);
const char* descriptor = dex_file->GetClassDescriptor(class_def);
result = write(classlistFile, (void*)descriptor, strlen(descriptor));
if (result < 0) {
LOG(ERROR) << "ArtMethod::saveArtMethod, write classlistFile file error1";
} else {
LOG(ERROR) << "ArtMethod::saveArtMethod, write classlistFile file success1";
}
const char* temp = "\n";
result = write(classlistFile, (void*)temp, 1);
if (result < 0) {
LOG(ERROR) << "ArtMethod::saveArtMethod, write classlistFile file error2";
} else {
LOG(ERROR) << "ArtMethod::saveArtMethod, write classlistFile file success2";
}
}
fsync(classlistFile);
close(classlistFile);
} else {
LOG(ERROR) << "ArtMethod::saveArtMethod, open classlistFile error";
}
} else {
LOG(ERROR) << "ArtMethod::saveArtMethod, open dexfile error";
}
}

const dex::CodeItem* code_item = artMethod->GetCodeItem();
if (code_item != nullptr) {
memset(dexFilePath, 0, 1000);
sprintf(dexFilePath, "/data/data/%s/JohnDemo/%d_ins_%d.bin", szProcName , size_int_, (int)gettidv1());
int fp2 = open(dexFilePath, O_CREAT | O_APPEND | O_RDWR, 0666);
if(fp2 > 0){
lseek(fp2, 0, SEEK_END);
uint32_t method_idx = artMethod->GetDexMethodIndex();
int offset = (int)artMethod->GetCodeItemOffset();
int code_item_len = dex_file->GetCodeItemSize(*code_item);
memset(dexFilePath, 0, 1000);
sprintf(dexFilePath, "{name: '%s', method_idx: %d, offset: %d, code_item_len: %d, ins: '", artMethod->PrettyMethod().c_str(), method_idx, offset, code_item_len);
int contentLength = 0;
while(dexFilePath[contentLength] != 0) contentLength++;
result = write(fp2, (void*)dexFilePath, contentLength);
if(result < 0) {
LOG(ERROR) << "ArtMethod::saveArtMethod, write ins file error";
}
long outlen = 0;
char* base64Result = base64_encode((char*)code_item, (long)code_item_len, &outlen);
result = write(fp2, base64Result, outlen);
if(result < 0) {
LOG(ERROR) << "ArtMethod::saveArtMethod, write ins file error";
}
result = write(fp2, "'};", 3);
if(result < 0) {
LOG(ERROR) << "ArtMethod::saveArtMethod, write ins file error";
}
fsync(fp2);
close(fp2);
if(base64Result != nullptr){
free(base64Result);
base64Result = nullptr;
}
}
}
}

if (dexFilePath != nullptr) {
free(dexFilePath);
dexFilePath = nullptr;
}
}

dex重构

脱壳之后的数据要把bin文件中的方法体填充回dex文件

使用dexfixer工具,https://github.com/dqzg12300/dexfixer

Eclipse工程,用IDEA打开,需要运行的代码在/src/com.android/dx/unpacker/DexFixer.java

在源代码的lib目录下的.jar库右键选择add as library

然后在IDEA中设置启动参数,运行即可

打包成jar包的话,选file->Project Structure->Artifacts->点击+,选择JAR-> 选择From modules with dependencies->选择main class为DexFixer->上方菜单栏选build ->build artifacts

不足和改进方案

FART存在的问题

1
2
3
4
调用链深度不够,有些壳将原有函数体替换为解密代码,运行时才解密执行
有些壳设置一些垃圾类,当该类被初始化的时候退出程序
有些壳设置一些垃圾类,实时检测这些类是否被加载
动态加载的dex文件,如果没有修正ClassLoader,不会出现在双亲委派关系中,也不会被FART遍历到,因为动态加载的dex可以指定父classloader,加载动态dex的classloader和mclassloader是兄弟关系,那么肯定不会被FART遍历到

改进方法

1
2
3
4
5
参照youpk的调用链深度
不进行类的初始化
设置配置文件,类似白名单,对指定的类进行主动调用,比如利用frida主动调用FART的函数,对指定类进行脱壳
利用Frida枚举所有的ClassLoader,再主动调用FART的函数进行脱壳
Execute脱壳点对于动态加载的dex也可以脱。除非这个dex没有初始化函数(静态方法)

Frida和FART结合增强脱壳功能

利用frida主动调用FART的函数,对指定类进行脱壳,我们知道通过ClassLoader进行脱壳的函数是fartWithClassloader,就是找到对应 的classloader之后主动调用fartWithClassloader方法

1
public static void fartWithClassloader(ClassLoader appClassloader)

handleBindApplication方法里makeApplication之后的开启线程的函数给注释掉,只把方法接口留在系统里,便于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
Java.perform(function () {

// Java.choose("dalvik.system.DexClassLoader", {
// onMatch: function (instanse) {
// console.log(instanse);
// var activityThread = Java.use("android.app.ActivityThread");
// activityThread.JohnDemoWithClassloader(instanse);
// }, onComplete: function () {
//
// }
// });

var activityThread = Java.use("android.app.ActivityThread");
var classloader = activityThread.getClassloader();
console.log("classloader: ", classloader);

var dexFile = Java.use("dalvik.system.DexFile");
var params = [Java.use("java.lang.Object").class];
var saveMethodCodeItem = dexFile.class.getDeclaredMethod("saveMethodCodeItem", params);
console.log("saveMethodCodeItem: ", saveMethodCodeItem);
saveMethodCodeItem.setAccessible(true);
activityThread.loadClassAndInvoke(classloader, "com.JohnDemo.encrypt.DES", saveMethodCodeItem);

});