FART 脱壳机原理分析

发布时间 2023-04-12 16:15:04作者: 明月照江江

FART是一个基于Android 源码修改的脱壳机

可以脱整体壳和抽取壳

FART脱壳的步骤主要分为三步:
1.内存中DexFile结构体完整dex的dump
2.主动调用类中的每一个方法,并实现对应CodeItem的dump
3.通过主动调用dump下来的方法的CodeItem进行dex中被抽取的方法的修复

1. 整体壳脱壳分析

这里利用的是dex2oat过程中,如果是构造函数将不会native化,还是走java解释器执行,于是在interpreter.cc中添加了如下代码

static inline JValue Execute(Thread* self, const DexFile::CodeItem* code_item,
                             ShadowFrame& shadow_frame, JValue result_register) {

  if(strstr(PrettyMethod(shadow_frame.GetMethod()).c_str(),"<clinit>")!=nullptr)
  {
    dumpDexFileByExecute(shadow_frame.GetMethod());
  }

  ......
  }

也就是说,一旦发现方法是构造函数,那么就立刻执行脱壳操作
dumpDexFileByExecute在 art_method.cc 中

extern "C" void dumpDexFileByExecute(ArtMethod * artmethod)
	 SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
		char *dexfilepath = (char *) malloc(sizeof(char) * 2000);
		if (dexfilepath == nullptr) {
			LOG(INFO) <<
			    "ArtMethod::dumpDexFileByExecute,methodname:"
			    << PrettyMethod(artmethod).
			    c_str() << "malloc 2000 byte failed";
			return;
		}
		int fcmdline = -1;
		char szCmdline[64] = { 0 };
		char szProcName[256] = { 0 };
		int procid = getpid();
		sprintf(szCmdline, "/proc/%d/cmdline", procid);
		fcmdline = open(szCmdline, O_RDONLY, 0644);
		if (fcmdline > 0) {
			read(fcmdline, szProcName, 256);
			close(fcmdline);
		}

		if (szProcName[0]) {

			const DexFile *dex_file = artmethod->GetDexFile();
			const uint8_t *begin_ = dex_file->Begin();	// Start of data.
			size_t size_ = dex_file->Size();	// Length of data.

			memset(dexfilepath, 0, 2000);
			int size_int_ = (int) size_;

			memset(dexfilepath, 0, 2000);
			sprintf(dexfilepath, "%s", "/sdcard/fart");
			mkdir(dexfilepath, 0777);

			memset(dexfilepath, 0, 2000);
			sprintf(dexfilepath, "/sdcard/fart/%s",
				szProcName);
			mkdir(dexfilepath, 0777);

			memset(dexfilepath, 0, 2000);
			sprintf(dexfilepath,
				"/sdcard/fart/%s/%d_dexfile_execute.dex",
				szProcName, size_int_);
			int dexfilefp = open(dexfilepath, O_RDONLY, 0666);
			if (dexfilefp > 0) {
				close(dexfilefp);
				dexfilefp = 0;

			} else {
				dexfilefp =
				    open(dexfilepath, O_CREAT | O_RDWR,
					 0666);
				if (dexfilefp > 0) {
					write(dexfilefp, (void *) begin_,
					      size_);
					fsync(dexfilefp);
					close(dexfilefp);
				}


			}


		}

		if (dexfilepath != nullptr) {
			free(dexfilepath);
			dexfilepath = nullptr;
		}

	}

脱下来的dex 会写入到xxx_dexfile_execute.dex 文件中, 这里的原理是 artMethod 本身是持有对应的DexFile的指针的,那么就有Dex在文件中的偏移和大小,就可以dump下来
( const DexFile *dex_file = artmethod->GetDexFile(); )

2. 脱抽取壳

在APP启动流程中会执行performLaunchActivity方法,在这里的末尾 FART 添加了一些代码

fartthread();



    public static void fartthread() {
        new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    Log.e("ActivityThread", "start sleep,wait for fartthread start......");
                    Thread.sleep(1 * 60 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.e("ActivityThread", "sleep over and start fartthread");
                fart();
                Log.e("ActivityThread", "fart run over");

            }
        }).start();
    }

从这里可以看出,FART 会先休眠1分钟,然后开始干活,但只干一次, youpk 每十秒就干一次

public static void fart() {
        ClassLoader appClassloader = getClassloader();
        List<Object> dexFilesArray = new ArrayList<Object>();
        Field pathList_Field = (Field) getClassField(appClassloader, "dalvik.system.BaseDexClassLoader", "pathList");
        Object pathList_object = getFieldOjbect("dalvik.system.BaseDexClassLoader", appClassloader, "pathList");
        Object[] ElementsArray = (Object[]) getFieldOjbect("dalvik.system.DexPathList", pathList_object, "dexElements");
        Field dexFile_fileField = null;
        try {
            dexFile_fileField = (Field) getClassField(appClassloader, "dalvik.system.DexPathList$Element", "dexFile");
        } catch (Exception e) {
            e.printStackTrace();
        }
        Class DexFileClazz = null;
        try {
            DexFileClazz = appClassloader.loadClass("dalvik.system.DexFile");
        } catch (Exception e) {
            e.printStackTrace();
        }
        Method getClassNameList_method = null;
        Method defineClass_method = null;
        Method dumpDexFile_method = null;
        Method dumpMethodCode_method = null;

        for (Method field : DexFileClazz.getDeclaredMethods()) {
            if (field.getName().equals("getClassNameList")) {
                getClassNameList_method = field;
                getClassNameList_method.setAccessible(true);
            }
            if (field.getName().equals("defineClassNative")) {
                defineClass_method = field;
                defineClass_method.setAccessible(true);
            }
            if (field.getName().equals("dumpMethodCode")) {
                dumpMethodCode_method = field;
                dumpMethodCode_method.setAccessible(true);
            }
        }
        Field mCookiefield = getClassField(appClassloader, "dalvik.system.DexFile", "mCookie");
        for (int j = 0; j < ElementsArray.length; j++) {
            Object element = ElementsArray[j];
            Object dexfile = null;
            try {
                dexfile = (Object) dexFile_fileField.get(element);
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (dexfile == null) {
                continue;
            }
            if (dexfile != null) {
                dexFilesArray.add(dexfile);
                Object mcookie = getClassFieldObject(appClassloader, "dalvik.system.DexFile", dexfile, "mCookie");
                if (mcookie == null) {
                    continue;
                }
                String[] classnames = null;
                try {
                    classnames = (String[]) getClassNameList_method.invoke(dexfile, mcookie);
                } catch (Exception e) {
                    e.printStackTrace();
                    continue;
                } catch (Error e) {
                    e.printStackTrace();
                    continue;
                }
                if (classnames != null) {
                    for (String eachclassname : classnames) {
                        loadClassAndInvoke(appClassloader, eachclassname, dumpMethodCode_method);
                    }
                }

            }
        }
        return;
    }

一 通过classLoader 拿到DexFile 的clazz对象

 try {
            DexFileClazz = appClassloader.loadClass("dalvik.system.DexFile");
        } catch (Exception e) {
            e.printStackTrace();
        }
        Method getClassNameList_method = null;
        Method defineClass_method = null;
        Method dumpDexFile_method = null;
        Method dumpMethodCode_method = null;

        for (Method field : DexFileClazz.getDeclaredMethods()) {
            if (field.getName().equals("getClassNameList")) {
                getClassNameList_method = field;
                getClassNameList_method.setAccessible(true);
            }
            if (field.getName().equals("defineClassNative")) {
                defineClass_method = field;
                defineClass_method.setAccessible(true);
            }
            if (field.getName().equals("dumpMethodCode")) {
                dumpMethodCode_method = field;
                dumpMethodCode_method.setAccessible(true);
            }
        }

这里的defineClassNative和dumpMethodCode 为Fart 添加的函数

二 再通过classLoader 反射拿到对应的pathList 的 dexElements

        ClassLoader appClassloader = getClassloader();
        List<Object> dexFilesArray = new ArrayList<Object>();
        Field pathList_Field = (Field) getClassField(appClassloader, "dalvik.system.BaseDexClassLoader", "pathList");
        Object pathList_object = getFieldOjbect("dalvik.system.BaseDexClassLoader", appClassloader, "pathList");
        Object[] ElementsArray = (Object[]) getFieldOjbect("dalvik.system.DexPathList", pathList_object, "dexElements");

dexElements 的元素就是一个个DexFile的引用
开始遍历dexElements

for (int j = 0; j < ElementsArray.length; j++) {
            Object element = ElementsArray[j];
            Object dexfile = null;
            try {
                dexfile = (Object) dexFile_fileField.get(element);
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (dexfile == null) {
                continue;
            }
            if (dexfile != null) {
                dexFilesArray.add(dexfile);
                Object mcookie = getClassFieldObject(appClassloader, "dalvik.system.DexFile", dexfile, "mCookie");
                if (mcookie == null) {
                    continue;
                }
                String[] classnames = null;
                try {
                    classnames = (String[]) getClassNameList_method.invoke(dexfile, mcookie);
                } catch (Exception e) {
                    e.printStackTrace();
                    continue;
                } catch (Error e) {
                    e.printStackTrace();
                    continue;
                }
                if (classnames != null) {
                    for (String eachclassname : classnames) {
                        loadClassAndInvoke(appClassloader, eachclassname, dumpMethodCode_method);
                    }
                }

            }
        }

最终会遍历Dex中的class 执行loadClassAndInvoke(appClassloader, eachclassname, dumpMethodCode_method);

public static void loadClassAndInvoke(ClassLoader appClassloader, String eachclassname, Method dumpMethodCode_method) {
        Log.i("ActivityThread", "go into loadClassAndInvoke->" + "classname:" + eachclassname);
        Class resultclass = null;
        try {
            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.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.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();
            } 
        }
    }

在这里会通过classloader 和类名称,来找到对应的clazz对象,并分别执行它的构造方法和普通方法

dumpMethodCode_method.invoke(null, constructor);
...
dumpMethodCode_method.invoke(null, m);

dumpMethodCode_method 对应的native 方法在dalvik_system_DexFile.cc中

static void DexFile_dumpMethodCode(JNIEnv* env, jclass,jobject method) {
ScopedFastNativeObjectAccess soa(env);
  if(method!=nullptr)
  {
		  ArtMethod* artmethod = ArtMethod::FromReflectedMethod(soa, method);
		  myfartInvoke(artmethod);
	  }	  


  return;
}

这里将method对应转换为artmethod,然后执行myfartInvoke
art_method.cc

	extern "C" void myfartInvoke(ArtMethod * artmethod)
	 SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
		JValue *result = nullptr;
		Thread *self = nullptr;
		uint32_t temp = 6;
		uint32_t *args = &temp;
		uint32_t args_size = 6;
		artmethod->Invoke(self, args, args_size, result, "fart");
	}

最终会执行到Invoke函数,在Invoke中fart做了一些修改

	void ArtMethod::Invoke(Thread * self, uint32_t * args,
			       uint32_t args_size, JValue * result,
			       const char *shorty) {


		if (self == nullptr) {
			dumpArtMethod(this);
			return;
		}
		.....
	}

只要发现self值null(这里是fart故意埋下的特征),就执行脱壳操作,并不往下执行,达到欺骗的效果

	extern "C" void dumpArtMethod(ArtMethod * artmethod)
	 SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
		char *dexfilepath = (char *) malloc(sizeof(char) * 2000);
		if (dexfilepath == nullptr) {
			LOG(INFO) <<
			    "ArtMethod::dumpArtMethodinvoked,methodname:"
			    << PrettyMethod(artmethod).
			    c_str() << "malloc 2000 byte failed";
			return;
		}
		int fcmdline = -1;
		char szCmdline[64] = { 0 };
		char szProcName[256] = { 0 };
		int procid = getpid();
		sprintf(szCmdline, "/proc/%d/cmdline", procid);
		fcmdline = open(szCmdline, O_RDONLY, 0644);
		if (fcmdline > 0) {
			read(fcmdline, szProcName, 256);
			close(fcmdline);
		}

		if (szProcName[0]) {

			const DexFile *dex_file = artmethod->GetDexFile();
			const char *methodname =
			    PrettyMethod(artmethod).c_str();
			const uint8_t *begin_ = dex_file->Begin();
			size_t size_ = dex_file->Size();

			memset(dexfilepath, 0, 2000);
			int size_int_ = (int) size_;

			memset(dexfilepath, 0, 2000);
			sprintf(dexfilepath, "%s", "/sdcard/fart");
			mkdir(dexfilepath, 0777);

			memset(dexfilepath, 0, 2000);
			sprintf(dexfilepath, "/sdcard/fart/%s",
				szProcName);
			mkdir(dexfilepath, 0777);

			memset(dexfilepath, 0, 2000);
			sprintf(dexfilepath,
				"/sdcard/fart/%s/%d_dexfile.dex",
				szProcName, size_int_);
			int dexfilefp = open(dexfilepath, O_RDONLY, 0666);
			if (dexfilefp > 0) {
				close(dexfilefp);
				dexfilefp = 0;

			} else {
				dexfilefp =
				    open(dexfilepath, O_CREAT | O_RDWR,
					 0666);
				if (dexfilefp > 0) {
					write(dexfilefp, (void *) begin_,
					      size_);
					fsync(dexfilefp);
					close(dexfilefp);
				}


			}
			const DexFile::CodeItem * code_item =
			    artmethod->GetCodeItem();
			if (LIKELY(code_item != nullptr)) {
				int code_item_len = 0;
				uint8_t *item = (uint8_t *) 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, 2000);
				int size_int = (int) dex_file->Size();	// Length of data
				uint32_t method_idx =
				    artmethod->get_method_idx();
				sprintf(dexfilepath,
					"/sdcard/fart/%s/%d_%ld.bin",
					szProcName, size_int, gettidv1());
				int fp2 =
				    open(dexfilepath,
					 O_CREAT | O_APPEND | O_RDWR,
					 0666);
				if (fp2 > 0) {
					lseek(fp2, 0, SEEK_END);
					memset(dexfilepath, 0, 2000);
					int offset = (int) (item - begin_);
					sprintf(dexfilepath,
						"{name:%s,method_idx:%d,offset:%d,code_item_len:%d,ins:",
						methodname, method_idx,
						offset, code_item_len);
					int contentlength = 0;
					while (dexfilepath[contentlength]
					       != 0)
						contentlength++;
					write(fp2, (void *) dexfilepath,
					      contentlength);
					long outlen = 0;
					char *base64result =
					    base64_encode((char *) item,
							  (long)
							  code_item_len,
							  &outlen);
					write(fp2, base64result, outlen);
					write(fp2, "};", 2);
					fsync(fp2);
					close(fp2);
					if (base64result != nullptr) {
						free(base64result);
						base64result = nullptr;
					}
				}

			}


		}

		if (dexfilepath != nullptr) {
			free(dexfilepath);
			dexfilepath = nullptr;
		}

	}

这里有两步
一 整体dump
const DexFile *dex_file = artmethod->GetDexFile()
拿到dex_file 整体dump到xxx_dexfile.dex
二 dump code_item
const DexFile::CodeItem * code_item = artmethod->GetCodeItem();
按照一个 json格式写入到.bin文件中
json格式为
{name:%s,method_idx:%d,offset:%d,code_item_len:%d,ins:%s}
ins为base64编码

到这里脱壳逻辑就完成了

FART还提供了 一个fart.py, 但好像执行生成一个合并前后的对比文件,没有真正将数据写入进dex中