首页 > 其他分享 >FART 脱壳机原理分析

FART 脱壳机原理分析

时间:2023-04-12 16:25:24浏览次数:33  
标签:dexfilepath item int FART 脱壳机 dumpMethodCode 原理 null method

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中

标签:dexfilepath,item,int,FART,脱壳机,dumpMethodCode,原理,null,method
From: https://www.cnblogs.com/gradyblog/p/17310185.html

相关文章

  • STP协议原理
    STP技术背景:在网络中,为了防止单点故障的出现,会给链路和设备都做冗余   冗余带来环路,环路会导致:-广播风暴-MAC地址表紊乱【不稳定的MAC地址表/MAC地址表抖动】-交换机收到重复数据帧 广播风暴  如何解决环路带来的问题?STP:既要有冗余,又要去除环路带......
  • BS结构的系统通信原理(没有涉及到java小程序)
    B/S结构的系统通信原理(没有涉及到java小程序)WEB系统的访问过程第一步:打开浏览器第二步:找到地址栏第三步:输入一个合法的网址第四步:回车第五步:在浏览器上会展示相应的结果关于域名:http://www.baidu.com/(网址)www.baidu.com是一个域名在浏览器地址上输入域名,回车之后......
  • Java中ThreadLocal的用法和原理
    用法隔离各个线程间的数据避免线程内每个方法都进行传参,线程内的所有方法都可以直接获取到ThreadLocal中管理的对象。packagecom.example.test1.service;importorg.springframework.scheduling.annotation.Async;importorg.springframework.stereotype.Component;imp......
  • Youpk 脱壳机脱壳原理分析
    Youpk是一个针对整体加固和Dex抽取加固壳的脱壳机主要是基于虚拟机的,也就是基于VA的脱壳机,相对FART出来的更晚一些,厂商针对少一些,脱壳位置相对更底层一些,还提供了Dex修复的工具,简直棒棒1.先分析整体脱壳的原理在ActivityThread的handleBindApplication中增加了代......
  • 第十二篇 手写原理代码 - 实现一个前端并发控制请求函数
    实现并发控制请求函数/***并发控制请求函数*@param{Array}urls请求的URL数组*@param{Number}max最大并发数*@param{Function}callback请求成功回调函数*/asyncfunctionconcurrentRequest(urls,max,callback){constlen=urls.length;//用......
  • 第十三篇 手写原理代码 - 实现 Promise
    当使用JavaScript进行异步编程时,我们往往需要面对回调地狱(callbackhell)、代码可读性低、错误处理困难等问题。为了解决这些问题,ECMAScript6(ES6)中引入了Promise。Promise是一种用于处理异步操作的对象,它是一个容器,保存着未来才会结束的事件(通常是一个异步操作),并提供了一组......
  • 第十一篇 手写原理代码 - 实现事件订阅中类
    javaScript中的订阅发布模式(也称为观察者模式)是一种设计模式,用于在对象之间的事件通信中。该模式由两部分构成:发布者和订阅者。发布者持有所有订阅者的引用,在某个事件发生时通知所有的订阅者,从而触发它们的相应行为。这种模式可以用于解耦发布者和订阅者之间的依赖关系,从而实......
  • 第二篇 手写原理代码 - 函数【 函数防抖 、函数节流 】
    函数防抖和函数节流都是优化高频事件处理的JavaScript技术。它们可以限制函数的调用,在一定程度上减少计算、网络请求和提高响应速度,但它们的实现方式略有不同函数防抖:延迟执行函数,只有在事件停止后才会执行最后一次事件函数节流:定期执行函数,每隔一段时间执行一次通常情况下,......
  • 第四篇 手写原理代码 - 函数 【 实现 compose 函数 】
    JavaScript中的Compose函数用于组合一些函数,使得每个函数都接收上一个函数的返回值作为参数,并返回一个新的函数。可以使用这种方式把多个函数串起来,从而实现更复杂的逻辑Comopse函数是一个非常有用的工具,它可以帮助我们简化程序逻辑,实现代码复用,提高开发效率实现compose......
  • 第六篇 手写原理代码 - 对象 【 实现 AJAX 请求 】
    AJAX是AsynchronousJavaScriptandXML的缩写,指的是通过JavaScript和XML技术在不重新加载整个页面的情况下,实现与服务器之间异步通信的技术。使用AJAX技术能够使网页更加动态和用户友好。JavaScript的AJAX技术借助于浏览器内置的XMLHttpRequest对象实现。XMLHttp......