首页
社区
课程
招聘
[原创]Fart 源码攻略笔记
2023-5-3 12:10 30056

[原创]Fart 源码攻略笔记

2023-5-3 12:10
30056

前言

最近在学习心心念的 Fart,研究了几天也翻阅了许多文章,但还是发现在有许多的问题不易理解(大佬们的笔记总是惜墨如金),最后决定把每一行代码的分析记录下来,便于日后温习,我始终坚信好记性不如烂笔头,分析再明白的的东西,时间久了也难免生疏,并且本篇笔记分析的 Fart 的源码也会放一份在最后,方便大家下载对比阅读本篇笔记,最后感谢 Fart 的作者 hanbingle 将这么优秀的框架开源供大家学习。

工具类方法分析

Fart 的作者封装了一些工具类方法,想要彻底理解 Fart 的源码,对这些工具类方法的分析就是基础,而这些工具类方法的分析其实只是是对 Java 反射机制的一个温习,和 Fart 主体的流程关系并不大,所以本篇笔记将分为工具类分析和主体流程分析两部分。

getClassField

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;
}

getClassFieldObject

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();
        }
    }
}

invokeStaticMethod

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);
        }
    }
}

getFieldOjbect

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;
}

getClassloader

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;
}

主体流程分析

performLaunchActivity

找到 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;
}

fartthread

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();
}

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
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
}

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;
}

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
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();
        }
    }
}

DexFile_dumpMethodCode

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;
}

jobject2ArtMethod

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;
}

myfartInvoke

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");
}

ArtMethod::Invoke

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;
    }
}

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
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_aosp8.0.7z


[培训]《安卓高级研修班(网课)》月薪三万计划,掌 握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2023-9-24 12:32 被简单的简单编辑 ,原因: 文末的链接挂了,补一下
收藏
点赞10
打赏
分享
最新回复 (7)
雪    币: 19803
活跃值: (29410)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-5-10 09:12
2
1
感谢分享
雪    币: 122
活跃值: (1465)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
koflfy 1 2023-5-10 10:11
3
0
mark
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_pnlabahd 2023-6-6 16:51
4
0
8.0哪个版本
雪    币: 5673
活跃值: (3968)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
简单的简单 2023-6-7 10:04
5
0
mb_pnlabahd 8.0哪个版本
还有很多版本么,不太清楚,不过我分析的版本放在帖子最下面了
雪    币: 2055
活跃值: (3616)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_bppcorlj 2023-9-24 02:03
6
0
请问fart8的源码在哪下载呢
雪    币: 5673
活跃值: (3968)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
简单的简单 2023-9-24 12:33
7
0
mb_bppcorlj 请问fart8的源码在哪下载呢
放在文末了,可以自取哈
雪    币: 2055
活跃值: (3616)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_bppcorlj 2023-9-25 23:56
8
0
简单的简单 放在文末了,可以自取哈
谢谢你,祝你开心每一天
游客
登录 | 注册 方可回帖
返回