[原创]Fart 源码攻略笔记
2023-5-3 20:37:4 Author: bbs.pediy.com(查看原文) 阅读量:8 收藏

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


文章来源: https://bbs.pediy.com/thread-277074.htm
如有侵权请联系:admin#unsafe.sh