Fart 源码攻略笔记




工具类方法分析


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

getClassField


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


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。

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


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。

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


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


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 执行的起始点。

} 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


// 创建了一个延时 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


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


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


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


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


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


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


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


// 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.7zhttp://gofile.me/6J4EF/ppVNyElgq



Fart 源码攻略笔记


看雪ID:简单的简单

https://bbs.kanxue.com/user-home-950902.htm

*本文为看雪论坛优秀文章,由 简单的简单 原创,转载请注明来自看雪社区

Fart 源码攻略笔记

# 往期推荐

1、记一次基于unidbg模拟执行的去除ollvm混淆
2、火绒剑杀死进程分析
3、QEMU tcg源码分析与unicorn原理
4、腾讯游戏安全技术竞赛2023 安卓客户端初赛题解
5、2023南极动物厂高校决赛之决赛附加题

Fart 源码攻略笔记


Fart 源码攻略笔记

球分享

Fart 源码攻略笔记

球点赞

Fart 源码攻略笔记

球在看

原文始发于微信公众号(看雪学苑):Fart 源码攻略笔记

版权声明:admin 发表于 2023年5月26日 下午6:07。
转载请注明:Fart 源码攻略笔记 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...