最近在学习心心念的 Fart,研究了几天也翻阅了许多文章,但还是发现在有许多的问题不易理解(大佬们的笔记总是惜墨如金),最后决定把每一行代码的分析记录下来,便于日后温习,我始终坚信好记性不如烂笔头,分析再明白的的东西,时间久了也难免生疏,并且本篇笔记分析的 Fart 的源码也会放一份在最后,方便大家下载对比阅读本篇笔记,最后感谢 Fart 的作者 hanbingle 将这么优秀的框架开源供大家学习。
Fart 的作者封装了一些工具类方法,想要彻底理解 Fart 的源码,对这些工具类方法的分析就是基础,而这些工具类方法的分析其实只是是对 Java 反射机制的一个温习,和 Fart 主体的流程关系并不大,所以本篇笔记将分为工具类分析和主体流程分析两部分。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
public static Field getClassField(ClassLoader classloader, String class_name, String filedName) {
/
/
该函数通过传入的 classloader、class_name、filedName 获取到反射的对象
try
{
/
/
在 classloader 中通过类名获取到类对象
Class obj_class
=
classloader.loadClass(class_name);
/
/
Class.forName(class_name);
/
/
在类对象中通过反射名称获取到反射对象
Field field
=
obj_class.getDeclaredField(filedName);
/
/
将反射对象设置为可访问权限
field.setAccessible(true);
/
/
返回得到的反射对象
return
field;
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return
null;
}
|
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 static
Object
getClassFieldObject(
ClassLoader classloader, String class_name,
Object
obj, String filedName) {
/
/
其实就是通过反射的方法获取对象的属性
try
{
/
/
在 classloader 中通过类名获取到类对象
Class obj_class
=
classloader.loadClass(class_name);
/
/
在类对象中通过反射名称获取到反射对象
Field field
=
obj_class.getDeclaredField(filedName);
/
/
将反射对象设置为可访问权限
field.setAccessible(true);
/
/
声明一个局部变量 result 作为返回结果
Object
result
=
null;
/
/
调用反射对象的 get 方法获取 obj 对象的属性值
result
=
field.get(obj);
/
/
将属性值返回
return
result;
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return
null;
}
|
为了方便理解这个函数,这里写了个 demo
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
|
/
/
Person.java
package com.example.demo3;
public
class
Person {
private
int
age
=
22
;
public
int
get_age()
{
return
age
*
2
;
}
}
/
/
MainActivity.java
public
class
MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Person person
=
new Person();
try
{
ClassLoader classLoader
=
getClassLoader();
Class classObj
=
classLoader.loadClass(
"com.example.demo3.Person"
);
Field field
=
classObj.getDeclaredField(
"age"
);
field.setAccessible(true);
Object
myage
=
field.get(person);
Log.d(
"lxz"
, String.valueOf(myage));
/
/
2023
-
05
-
02
14
:
19
:
33.054
29940
-
29940
lxz com.example.demo3 D
22
} catch (Exception e) {
e.printStackTrace();
}
}
}
|
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
|
public static
Object
invokeStaticMethod(String class_name, String method_name, Class[] pareTyple,
Object
[] pareVaules) {
/
/
通过 类名、方法名、方法参数类型 找到静态的方法,传入参数执行
try
{
/
/
通过类名从 Class 中获取到类对象
Class obj_class
=
Class.forName(class_name);
/
/
在类对象中通过方法名和函数的参数类型,找到静态方法
/
/
pareTyple 函数的参数类型 (有点类似方法签名,避免重载时方法名相同问题)
Method method
=
obj_class.getMethod(method_name, pareTyple);
/
/
执行找到的方法,第一个参数传类对象,如果是静态方法传 null 就可以
/
/
pareVaules 调用函数时传入的参数
return
method.invoke(null, pareVaules);
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return
null;
}
|
为了方便理解这个函数,这里写了个 demo
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
|
/
/
Person.java
public
class
Person {
private static
int
age
=
22
;
public static
int
get_age(
int
a ,
int
b)
{
return
age
+
a
+
b;
}
}
/
/
MainActivity.java
public
class
MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Person person
=
new Person();
try
{
Class obj_class
=
Class.forName(
"com.example.demo3.Person"
);
Method method
=
obj_class.getMethod(
"get_age"
,new Class[]{
int
.
class
,
int
.
class
});
Object
myage
=
method.invoke(null, new
Object
[]{
1
,
2
});
Log.d(
"lxz"
,
"myage is "
+
myage.toString());
/
/
2023
-
05
-
02
15
:
17
:
47.493
6942
-
6942
lxz com.example.demo3 D myage
is
25
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
|
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
|
public static
Object
getFieldOjbect(String class_name,
Object
obj, String filedName) {
try
{
/
/
通过类名从 Class 中获取到类对象
/
/
这里发现和上边的 getClassFieldObject 基本一样
/
/
所以这里查了一下 Class.forName 和 classloader.loadClass加载类时区别
/
/
Class.forName 加载类时将类进了初始化
/
/
classloader.loadClass 并没有对类进行初始化,只是把类加载到了虚拟机中
/
/
据说作者之所以搞出来两个是因为有的壳为了对抗主动调用会在一些垃圾方法中在静态代码块中
/
/
调用结束进程的指令,如果使用 Class.forName 那显然就掉进壳设置的陷阱了
Class obj_class
=
Class.forName(class_name);
Field field
=
obj_class.getDeclaredField(filedName);
field.setAccessible(true);
return
field.get(obj);
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NullPointerException e) {
e.printStackTrace();
}
return
null;
}
|
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
|
public static ClassLoader getClassloader() {
ClassLoader resultClassloader
=
null;
/
/
调用静态方法 currentActivityThread 得到 scurrentActivityThread
/
/
简单搜索一下就可以知道 sCurrentActivityThread 就是 this 指针
/
/
public static ActivityThread currentActivityThread() {
/
/
return
sCurrentActivityThread;
/
/
}
/
/
private void attach(boolean system) {
/
/
sCurrentActivityThread
=
this;
/
/
mSystemThread
=
system;
Object
currentActivityThread
=
invokeStaticMethod(
"android.app.ActivityThread"
,
"currentActivityThread"
,
new Class[]{}, new
Object
[]{});
/
/
传入 this 指针获取当前类中的 mBoundApplication 的值
/
/
简单搜索一下就可以知道有如下关系
/
/
mBoundApplication
=
data;
/
/
AppBindData data
=
(AppBindData)msg.obj;
/
/
handleBindApplication(data);
/
/
笔者对这边的数据结构不是特别熟悉,到这里还看不出什么,但先继续往下跟
Object
mBoundApplication
=
getFieldOjbect(
"android.app.ActivityThread"
, currentActivityThread,
"mBoundApplication"
);
/
/
这里和上边如出一辙,获取了 mInitialApplication 属性,不过据说这个属性没用到,可以忽略删掉
Application mInitialApplication
=
(Application) getFieldOjbect(
"android.app.ActivityThread"
,
currentActivityThread,
"mInitialApplication"
);
/
/
这里获取了内部类 AppBindData 的 info 属性的值
/
/
这里选择看一这个内部类,info 的类型是 LoadedApk
/
/
看到这里也算是图穷匕见了,众所周知 LoadedApk 里面有 mClassLoader 嘛
/
/
static final
class
AppBindData {
/
/
LoadedApk info;
/
/
String processName;
/
/
ApplicationInfo appInfo;
Object
loadedApkInfo
=
getFieldOjbect(
"android.app.ActivityThread$AppBindData"
,
mBoundApplication,
"info"
);
/
/
看到这就挺奇怪的,作者先获取了 mApplication 然后调用 getClassLoader 方法获取 classloader
/
/
这里不是特别能理解为什么不直接获取 mClassLoader,从源码来看 mClassLoader 和 mApplication 的定义代码就是紧挨着
Application mApplication
=
(Application) getFieldOjbect(
"android.app.LoadedApk"
, loadedApkInfo,
"mApplication"
);
resultClassloader
=
mApplication.getClassLoader();
return
resultClassloader;
}
|
找到 fart 执行的起始点
1
2
3
4
5
6
7
8
9
10
11
12
|
} catch (Exception e) {
if
(!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to start activity "
+
component
+
": "
+
e.toString(), e);
}
}
/
/
add
fartthread();
/
/
add
return
activity;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/
/
创建了一个延时
60
秒后再开始的线程
public static void fartthread() {new Thread(new Runnable() {
@Override
public void run() {
/
/
TODO Auto
-
generated method stub
try
{
Log.e(
"ActivityThread"
,
"start sleep......"
);
/
/
休眠
60
秒
Thread.sleep(
1
*
60
*
1000
);
} catch (InterruptedException e) {
/
/
TODO Auto
-
generated catch block
e.printStackTrace();
}
Log.e(
"ActivityThread"
,
"sleep over and start fart"
);
/
/
调用函数 fart
fart();
Log.e(
"ActivityThread"
,
"fart run over"
);
}
}).start();
}
|
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
|
public static void fart() {
/
/
获取 Classloader
ClassLoader appClassloader
=
getClassloader();
ClassLoader tmpClassloader
=
appClassloader;
/
/
获取父 Classloader
ClassLoader parentClassloader
=
appClassloader.getParent();
/
/
如果 appClassloader 不是 java.lang.BootClassLoader
if
(appClassloader.toString().indexOf(
"java.lang.BootClassLoader"
)
=
=
-
1
)
{
/
/
暂时不知道做什么的函数,等下分析
fartwithClassloader(appClassloader);
}
while
(parentClassloader!
=
null){
/
/
如果 parentClassloader 不是 java.lang.BootClassLoader
if
(parentClassloader.toString().indexOf(
"java.lang.BootClassLoader"
)
=
=
-
1
)
{
/
/
暂时不知道做什么的函数,等下分析
fartwithClassloader(parentClassloader);
}
tmpClassloader
=
parentClassloader;
/
/
继续向上找 parentClassloader,直到 parentClassloader 为空
parentClassloader
=
parentClassloader.getParent();
}
/
/
小结:不停的向上找 parentClassloader,将找到所有的 Classloader
/
/
都作为参数传给函数 fartwithClassloader
}
|
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
|
public static void fartwithClassloader(ClassLoader appClassloader) {
/
/
定义了一个列表,从名字来看应该是要用来存放 dex 对象
List
<
Object
> dexFilesArray
=
new ArrayList<
Object
>();
/
/
获取 dalvik.system.BaseDexClassLoader 中 pathList 的反射,不过好像没用到
Field pathList_Field
=
(Field) getClassField(appClassloader,
"dalvik.system.BaseDexClassLoader"
,
"pathList"
);
/
/
通过反射获取 dalvik.system.BaseDexClassLoader 中 pathList 对象
Object
pathList_object
=
getFieldOjbect(
"dalvik.system.BaseDexClassLoader"
, appClassloader,
"pathList"
);
/
/
获取 dalvik.system.DexPathList 中 dexElements 对象
Object
[] ElementsArray
=
(
Object
[]) getFieldOjbect(
"dalvik.system.DexPathList"
, pathList_object,
"dexElements"
);
Field dexFile_fileField
=
null;
try
{
/
/
获取 dalvik.system.DexPathList$Element 中 dexFile 的反射
dexFile_fileField
=
(Field) getClassField(appClassloader,
"dalvik.system.DexPathList$Element"
,
"dexFile"
);
} catch (Exception e) {
e.printStackTrace();
} catch (Error e) {
e.printStackTrace();
}
Class DexFileClazz
=
null;
try
{
/
/
加载了 dalvik.system.DexFile 类,注意这是 native 层的类
DexFileClazz
=
appClassloader.loadClass(
"dalvik.system.DexFile"
);
} catch (Exception e) {
e.printStackTrace();
} catch (Error e) {
e.printStackTrace();
}
Method getClassNameList_method
=
null;
Method defineClass_method
=
null;
Method dumpDexFile_method
=
null;
Method dumpMethodCode_method
=
null;
/
/
遍历 dalvik.system.DexFile 中所有的方法
for
(Method field : DexFileClazz.getDeclaredMethods()) {
/
/
getClassNameList 是系统源码原本就有的方法
if
(field.getName().equals(
"getClassNameList"
)) {
getClassNameList_method
=
field;
getClassNameList_method.setAccessible(true);
}
/
/
defineClassNative 是系统源码原本就有的方法
if
(field.getName().equals(
"defineClassNative"
)) {
defineClass_method
=
field;
defineClass_method.setAccessible(true);
}
/
/
这个方法没找到,应该是冗余的代码忘记删了
if
(field.getName().equals(
"dumpDexFile"
)) {
dumpDexFile_method
=
field;
dumpDexFile_method.setAccessible(true);
}
/
/
重点看这个方法,它是在 dalvik.system.DexFile
/
/
中定义的,是个 native 层的函数,你可以在 DexFile.java 中
/
/
找到它的声明,在 dalvik_system_DexFile.cc 中找到它的实现,
/
/
相信我,请务必记住这个方法的名字
if
(field.getName().equals(
"dumpMethodCode"
)) {
dumpMethodCode_method
=
field;
dumpMethodCode_method.setAccessible(true);
}
}
/
/
获取 dalvik.system.DexFile 中的 mCookie 反射,不过好像没用到
Field mCookiefield
=
getClassField(appClassloader,
"dalvik.system.DexFile"
,
"mCookie"
);
Log.v(
"ActivityThread->methods"
,
"dalvik.system.DexPathList.ElementsArray.length:"
+
ElementsArray.length);
/
/
遍历 ElementsArray 这里面存放着 DexPathList 里记录的 dex 列表
for
(
int
j
=
0
; j < ElementsArray.length; j
+
+
) {
Object
element
=
ElementsArray[j];
Object
dexfile
=
null;
try
{
/
/
通过反射对象 dexFile_fileField 的 get 方法获取到 dex
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) {
/
/
将 dexfile 添加到 dexFilesArray 中
dexFilesArray.add(dexfile);
/
/
获取 mcookie 的对象
Object
mcookie
=
getClassFieldObject(appClassloader,
"dalvik.system.DexFile"
, dexfile,
"mCookie"
);
if
(mcookie
=
=
null) {
/
/
如果 mcookie 没获取到,就获取 mInternalCookie
/
/
查阅源码可以知道这两个值是相等的,所以部分加固厂商会抹去 mcookie
/
/
这为了避免加固厂商的骚操作两个都获取了一下
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
{
/
/
获取 dex 的类名列表
classnames
=
(String[]) getClassNameList_method.invoke(dexfile, mcookie);
} catch (Exception e) {
e.printStackTrace();
continue
;
} catch (Error e) {
e.printStackTrace();
continue
;
}
if
(classnames !
=
null) {
/
/
遍历类名列表,执行 loadClassAndInvoke
for
(String eachclassname : classnames) {
loadClassAndInvoke(appClassloader, eachclassname, dumpMethodCode_method);
}
}
}
}
return
;
}
|
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
|
public static void loadClassAndInvoke(ClassLoader appClassloader, String eachclassname, Method dumpMethodCode_method) {
Class resultclass
=
null;
Log.i(
"ActivityThread"
,
"go into loadClassAndInvoke->"
+
"classname:"
+
eachclassname);
try
{
/
/
通过 appClassloader 加载 eachclassname
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 方法
/
/
正常来讲第一个参数传对象(要想执行一个非静态方法总得有个对象吧)
/
/
第二个参数传方法的参数,类型为
Object
[] args
/
/
分析到这里发现有点解释不通了,这里姑且作为第一次分析
/
/
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
第二次分析分割线
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/
/
还记得我上边让你记住的那个方法名称么
/
/
这其实是将构造函数传递给了 native 层的 DexFile_dumpMethodCode
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 方法
/
/
正常来讲第一个参数传对象(要想执行一个非静态方法总得有个对象吧)
/
/
第二个参数传方法的参数,类型为
Object
[] args
/
/
分析到这里发现有点解释不通了,这里姑且作为第一次分析
/
/
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
第二次分析分割线
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/
/
这其实是将遍历到的方法 m 传递给了 native 层
/
/
的 DexFile_dumpMethodCode 函数
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();
}
}
}
|
1
2
3
4
5
6
7
8
9
10
11
|
static void DexFile_dumpMethodCode(JNIEnv
*
env, jclass, jobject method) {
if
(method!
=
nullptr)
{
/
/
将参数 method 传给 jobject2ArtMethod
ArtMethod
*
proxy_method
=
jobject2ArtMethod(env, method);
/
/
将 proxy_method 传给 myfartInvoke
/
/
这里还是看不出什么,只能继续分析 jobject2ArtMethod 和 myfartInvoke
myfartInvoke(proxy_method);
}
return
;
}
|
1
2
3
4
5
6
7
8
|
extern
"C"
ArtMethod
*
jobject2ArtMethod(JNIEnv
*
env, jobject javaMethod) {
/
/
这个东西还是头一回见到,百度了一下,大概是可以实现更快的从Java层调用Native层函数
ScopedFastNativeObjectAccess soa(env);
/
/
这行代码也是百度一下,根据我的理解是将 java 的方法转换为 Native 层代码
ArtMethod
*
method
=
ArtMethod::FromReflectedMethod(soa, javaMethod);
/
/
总的来讲就是 java 方法转换为 native 函数
return
method;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
extern
"C"
void myfartInvoke(ArtMethod
*
artmethod) REQUIRES_SHARED(Locks::mutator_lock_) {
JValue
*
result
=
nullptr;
/
/
注意这里的
self
被赋值为 nullptr
Thread
*
self
=
nullptr;
uint32_t temp
=
6
;
uint32_t
*
args
=
&temp;
uint32_t args_size
=
6
;
/
/
直接就执行了???
/
/
没有什么特别的处理,打了个 tag 就直接执行了???
/
/
这里也是让人困惑的一匹,java 层的 invoke 非要传到 Native 层执行???
/
/
其实是 fart 的作者修改了 ArtMethod::Invoke 函数
/
/
我们接下来去看看 fart 作者都做了什么事情
artmethod
-
>Invoke(
self
, args, args_size, result,
"fart"
);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
void ArtMethod::Invoke(Thread
*
self
, uint32_t
*
args, uint32_t args_size, JValue
*
result,const char
*
shorty) {
/
/
add
if
(
self
=
=
nullptr) {
/
/
当
self
为 nullptr 时执行 dumpArtMethod,这是为了区别正常执行的函数
/
/
-
-
-
-
-
-
-
-
-
-
-
-
-
通篇分析后的回顾分割线
-
-
-
-
-
-
-
-
-
-
-
-
-
/
/
感觉 dump 放在这里不是特别合适,万一有闲的蛋疼的厂商对 ArtMethod::Invoke
/
/
这种系统函数做 CRC 校验呢,感觉这种函数历经多个版本的更新都不会怎么变
dumpArtMethod(this);
return
;
}
/
/
add
if
(UNLIKELY(__builtin_frame_address(
0
) <
self
-
>GetStackEnd())) {
ThrowStackOverflowError(
self
);
return
;
}
}
|
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
|
/
/
REQUIRES_SHARED(Locks::mutator_lock_) 在该函数执行时,给程序加锁,应该是避免 cpu 切片时出现问题
/
/
学过汇编的都知道,多线程非原子操作不加锁会出问题
extern
"C"
void dumpArtMethod(ArtMethod
*
artmethod) REQUIRES_SHARED(Locks::mutator_lock_) {
char
*
dexfilepath
=
(char
*
)malloc(sizeof(char)
*
1000
);
if
(dexfilepath
=
=
nullptr)
{
LOG(ERROR) <<
"ArtMethod::dumpArtMethodinvoked,methodname:"
<<artmethod
-
>PrettyMethod().c_str()<<
"malloc 1000 byte failed"
;
return
;
}
int
result
=
0
;
int
fcmdline
=
-
1
;
char szCmdline[
64
]
=
{
0
};
char szProcName[
256
]
=
{
0
};
/
/
获取进程 pid
int
procid
=
getpid();
/
/
拼接 cmdline 路径
sprintf(szCmdline,
"/proc/%d/cmdline"
, procid);
fcmdline
=
open
(szCmdline, O_RDONLY,
0644
);
if
(fcmdline >
0
)
{
/
/
读取进程名称
result
=
read(fcmdline, szProcName,
256
);
if
(result<
0
)
{
LOG(ERROR) <<
"ArtMethod::dumpdexfilebyArtMethod,open cmdline file file error"
;
}
close(fcmdline);
}
if
(szProcName[
0
])
{
/
/
通过 artmethod 获取 dex
const DexFile
*
dex_file
=
artmethod
-
>GetDexFile();
/
/
得到 dex 的起始地址
const uint8_t
*
begin_
=
dex_file
-
>Begin();
/
/
Start of data.
/
/
得到 dex 的大小
size_t size_
=
dex_file
-
>Size();
/
/
Length of data.
memset(dexfilepath,
0
,
1000
);
int
size_int_
=
(
int
)size_;
memset(dexfilepath,
0
,
1000
);
sprintf(dexfilepath,
"%s"
,
"/sdcard/fart"
);
/
/
创建
/
sdcard
/
fart 文件夹
mkdir(dexfilepath,
0777
);
memset(dexfilepath,
0
,
1000
);
sprintf(dexfilepath,
"/sdcard/fart/%s"
,szProcName);
/
/
创建
/
sdcard
/
fart
/
进程名 文件夹
mkdir(dexfilepath,
0777
);
memset(dexfilepath,
0
,
1000
);
/
/
拼接 dex 路径
+
文件名
sprintf(dexfilepath,
"/sdcard/fart/%s/%d_dexfile.dex"
,szProcName,size_int_);
/
/
只读方式打开 dexfilepath,主要是为了判断 dex 是否存在
/
/
已经找到的 dex 就不需要重复创建了,注意高版本的 Android 系统已经不能
/
/
使用这种方法判断文件是否存在了,换成 access(dexfilepath,F_OK) 是个好主意
int
dexfilefp
=
open
(dexfilepath,O_RDONLY,
0666
);
if
(dexfilefp>
0
){
close(dexfilefp);
dexfilefp
=
0
;
}
else
{
int
fp
=
open
(dexfilepath,O_CREAT|O_APPEND|O_RDWR,
0666
);
if
(fp>
0
)
{
/
/
此处进行 dex 整体 dump
result
=
write(fp,(void
*
)begin_,size_);
if
(result<
0
)
{
LOG(ERROR) <<
"ArtMethod::dumpdexfilebyArtMethod,open dexfilepath file error"
;
}
fsync(fp);
close(fp);
memset(dexfilepath,
0
,
1000
);
/
/
从拼接的名字可看出,这是把所有的类都记录在了一个列表里
sprintf(dexfilepath,
"/sdcard/fart/%s/%d_classlist.txt"
,szProcName,size_int_);
int
classlistfile
=
open
(dexfilepath,O_CREAT|O_APPEND|O_RDWR,
0666
);
if
(classlistfile>
0
)
{
/
/
遍历 dex 中的所有类
for
(size_t ii
=
0
; ii< dex_file
-
>NumClassDefs();
+
+
ii)
{
const DexFile::ClassDef& class_def
=
dex_file
-
>GetClassDef(ii);
const char
*
descriptor
=
dex_file
-
>GetClassDescriptor(class_def);
/
/
将遍历到的类记录在 classlist.txt 中
result
=
write(classlistfile,(void
*
)descriptor,strlen(descriptor));
if
(result<
0
)
{
LOG(ERROR) <<
"ArtMethod::dumpdexfilebyArtMethod,write classlistfile file error"
;
}
const char
*
temp
=
"\n"
;
result
=
write(classlistfile,(void
*
)temp,
1
);
if
(result<
0
)
{
LOG(ERROR) <<
"ArtMethod::dumpdexfilebyArtMethod,write classlistfile file error"
;
}
}
fsync(classlistfile);
close(classlistfile);
}
}
}
/
/
获取 code_item
const DexFile::CodeItem
*
code_item
=
artmethod
-
>GetCodeItem();
/
/
LIKELY 是偏向执行的意思,这和 CPU 的预执行理机制有关系,
/
/
比较有名的幽灵融毁漏洞就是利用这个机制,但总的来讲可以提高代码执行速度
if
(LIKELY(code_item !
=
nullptr))
{
int
code_item_len
=
0
;
/
/
将 code_item 强转为指针,应该是转为指针就是 code 的起始地址
/
/
不禁感慨,在 C语言 中,指针就是这么灵活
uint8_t
*
item
=
(uint8_t
*
) code_item;
/
/
code_item 中是否含有 tryItem, 其大小的计算方式不同,这里不展开分析
/
/
(显然需要分析 code_item 的数据结构,估计都可以单独写篇文章了)
/
/
总的来讲在此处获取了 code_item 的长度
/
/
PS:在高版本 Android 中可以用 dex_file
-
>GetCodeItemSize(
*
code_item)
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();
/
/
获取 method 在 dex 中的
id
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_);
/
/
拼接方法的基本信息,名称、
id
、偏移、大小
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
+
+
;
/
/
将方法的基本信息记录在
bin
文件中
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);
/
/
将方法的代码记录在
bin
文件中
result
=
write(fp2,base64result,outlen);
if
(result<
0
)
{
LOG(ERROR) <<
"ArtMethod::dumpdexfilebyArtMethod,write ins file error"
;
}
/
/
收个尾,函数粒度的 dump 就完成了!!!
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;
}
}
|
本来是想做个流程图的,但我梳理了一下通篇笔记的流程,感觉 Fart 的流程还是清晰明了并不复杂,跟寻我的分析思路,基本上是一条主线,没什么分支,所以这里就偷个懒了。
说一下本篇笔记的意义,对于笔者来讲,当然是温故而知新,可以为师矣,对于小白来讲,可以温习反射机制,熟悉 Fart 的机制,并且还可以移植魔改 Fart,还记得我在笔记中多次提到了高版本的问题,至于大佬嘛……大佬们当然是可以点赞、收藏并投币啦!!!
更多【Fart 源码攻略笔记】相关视频教程:www.yxfzedu.com