最近在学习心心念的 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
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;
}
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
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,还记得我在笔记中多次提到了高版本的问题,至于大佬嘛……大佬们当然是可以点赞、收藏并投币啦!!!