赞
踩
FART脱壳的步骤主要分为三步:
**1.内存中DexFile结构体完整dex的dump
2.主动调用类中的每一个方法,并实现对应CodeItem的dump
3.通过主动调用dump下来的方法的CodeItem进行dex中被抽取的方法的修复**
下面分别对每一步的实现原理进行介绍。
1. 内存中DexFile结构体完整dex的dump
该步同Avi Bashan 和 SlavaMakkaveev 在DefCon 2017上提出的通过修改DexFile的构造函数DexFile::DexFile(),以及OpenAndReadMagic()函数来实现对加壳应用的内存中的dex的dump来脱壳的原理类似。不同之处在于Avi Bashan 和 SlavaMakkaveev是通过修改系统中DexFile中提供的相关函数来实现dump,实际上壳完全可以自实现一套Dex文件的内存加载机制从而绕过这种dump方法。本文提出的是通过选择合适的时机点获取到应用解密后的dex文件最终依附的Classloader,进而通过java的反射机制最终获取到对应的DexFile的结构体,并完成dex的dump。接下来主要介绍具体实现细节。
首先,对于获取Classloader的时机点的选择。在第一节的App启动流程以及第三节中APP加壳原理和执行流程的介绍中,可以看到,APP中的Application类中的attachBaseContext和onCreate函数是app中最先执行的方法。壳都是通过替换APP的Application类并自己实现这两个函数,并在这两个函数中实现dex的解密加载,hook系统中Class和method加载执行流程中的关键函数,最后通过反射完成关键变量如最终的Classloader,Application等的替换从而完成执行权的交付。因此,我们可以选在任意一个在Application的onCreate函数执行之后才开始被调用的任意一个函数中。众所周知,对于一个正常的应用来说,最终都要由一个个的Activity来展示应用的界面并和用户完成交互,那么我们就可以选择在ActivityThread中的performLaunchActivity函数作为时机,来获取最终的应用的Classloader。选择该函数还有一个好处在于该函数和应用的最终的application同在ActivityThread类中,可以很方便获取到该类的成员。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
…
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
//下面通过application的getClassLoader()获取最终的Classloader,并开启线程,在新线程中完成内存中的dex的dump以及主动调用过程,由于该过程相对耗时,为了防止应用出现ANR,从而开启新线程,在新线程中进行,主要的工作都在getDexFilesByClassLoader_23
//addstart
packagename=r.packageInfo.getPackageName();
//mInitialApplication
//final java.lang.ClassLoader finalcl=cl
if(mInitialApplication!=null){
final java.lang.ClassLoader finalcl=mInitialApplication.getClassLoader();
new Thread(new Runnable() {
@Override
public void run() {
getDexFilesByClassLoader_23(finalcl);
}
}).start();
}
//addend
}
}
getDexFilesByClassLoader_23()函数的主要流程就是通过一系列的反射,最终获取到当前Classloader中的mCookie,即Native层中的DexFile。为了在C/C++中完成对dex的dump操作。这里我们在framework层的DexFile类中添加两个Native函数供调用:
在文件libcore/dalvik/src/main/java/dalvik/system/DexFile.java中
private static native void dumpDexFile(String dexfilepath,Object cookie);
private static native void dumpMethodCode(String eachclassname, String methodname,Object cookie, Object method);
这两个函数分别用于完成内存中dex的dump以及构造主动调用链,完成方法体的dump
在对应的c++文件中添加这两个Native函数的实现并完成注册:
art/runtime/native/dalvik_system_DexFile.cc文件中
static void DexFile_dumpDexFile(JNIEnv* env, jclass, jstring filepath,jobject cookie) {
std::unique_ptr<std::vector<const DexFile*>> dex_files = ConvertJavaArrayToNative(env, cookie);
if (dex_files.get() == nullptr) {
DCHECK(env->ExceptionCheck());
return;
}
int dexnum=0;
char dexfilepath[1000];
for (auto& dex_file : *dex_files) {
const uint8_t* begin_=dex_file->getbegin(); // Start of data.
size_t size_=dex_file->getsize(); // Length of data.
int dexfilesize=(int)size_;
const char *filepathcstr = env->GetStringUTFChars(filepath, nullptr);
memset(dexfilepath,0,1000);
sprintf(dexfilepath,“%s_%d_%d”,filepathcstr,dexfilesize,dexnum);
dexnum++;
//由于部分壳通过hook libc中的关键文件读写函数来防止dump,这里直接使用系统调用完成dex文件的dump
int fp=open(dexfilepath,O_CREAT|O_APPEND|O_RDWR,0666);
write(fp,(void*)begin_,size_);
fsync(fp);
close(fp);
}
}
上面实现了对Classloader中加载的dex的dump,那么如何实现对类中函数的主动调用来实现函数粒度的脱壳呢?下面开始介绍主动调用的设计
2. 类函数的主动调用设计实现
对类函数的主动调用链的构造我们或许可以从JNI提供的相关函数的源码可以得出参考。JNI提供了一系列java层函数与Native层函数交互的接口。当需要在Native层中的c/c++函数中调用位于java层的函数时,需要先获取到该函数的jmethodid然后再通过诸如jni中提供的call开头的一系列函数来完成对java层中函数的调用。我们以jni中的CallObjectMethod函数为例,进行分析。下面开始源码分析:
static jobject CallObjectMethod(JNIEnv* env, jobject obj, jmethodID mid, …) {
va_list ap;
va_start(ap, mid);
CHECK_NON_NULL_ARGUMENT(obj);
CHECK_NON_NULL_ARGUMENT(mid);
ScopedObjectAccess soa(env);
JValue result(InvokeVirtualOrInterfaceWithVarArgs(soa, obj, mid, ap));
va_end(ap);
return soa.AddLocalReference(result.GetL());
}
该函数中通过(InvokeVirtualOrInterfaceWithVarArgs(soa, obj, mid, ap)函数来完成调用,下面看该函数内容:
该函数首先对jmethodid进行了转换,转换成ArtMethod对象指针,进而通过函数InvokeWithArgArray完成调用,下面再看InvokeWithArgArray函数内容
JValue InvokeVirtualOrInterfaceWithVarArgs(const ScopedObjectAccessAlreadyRunnable& soa,
jobject obj, jmethodID mid, va_list args) {
…
ArtMethod* method = FindVirtualMethod(receiver, soa.DecodeMethod(mid));
…
InvokeWithArgArray(soa, method, &arg_array, &result, shorty);
…
}
下面看 InvokeWithArgArray函数:
static void InvokeWithArgArray(const ScopedObjectAccessAlreadyRunnable& soa,
ArtMethod* method, ArgArray* arg_array, JValue* result,
const char* shorty)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
uint32_t* args = arg_array->GetArray();
if (UNLIKELY(soa.Env()->check_jni)) {
CheckMethodArguments(soa.Vm(), method->GetInterfaceMethodIfProxy(sizeof(void*)), args);
}
method->Invoke(soa.Self(), args, arg_array->GetNumBytes(), result, shorty);
}
该函数最终通过调用ArtMethod类中的Invoke函数完成对java层中的函数的调用。由此,我们可以看到ArtMethod类中的Invoke方法在jni中扮演着至关重要的地位。于是,我们可以构造出自己的invoke函数,在该函数中再调用ArtMethod的Invoke方法从而完成主动调用,并在ArtMethod的Invoke函数中首先进行判断,当发现是我们自己的主动调用时就进行方法体的dump并直接返回,从而完成对壳的欺骗,达到方法体的dump。下面开始代码部分。在libcore/dalvik/src/main/java/dalvik/system/DexFile.java的另一个Native函数DexFile_dumpMethodCode中
static void DexFile_dumpMethodCode(JNIEnv* env, jclass, jstring eachclassname, jstring methodname,jobject cookie,jobject method) {
ScopedFastNativeObjectAccess soa(env);
ArtMethod* called_method = ArtMethod::FromReflectedMethod(soa, method);
method ->myfartInvoke(method );
return;
}`
可以看到代码非常简洁,首先是对Java层传来的Method结构体进行了类型转换,转成Native层的ArtMethod对象,接下来就是调用ArtMethod类中myfartInvoke实现虚拟调用,并完成方法体的dump。下面看ArtMethod.cc中添加的函数myfartInvoke的实现主动调用的代码部分:
void ArtMethod::myfartInvoke(ArtMethod* artmethod)
{ 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”);
}
这里代码依然很简洁,只是对ArtMethod类中的Invoke的一个调用包装,不同的是在参数方面,我们直接给Thread*传递了一个nullptr,作为对主动调用的标识。下面看ArtMethod类中的Invoke函数:
void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result,
const char* shorty) {
// unsigned int tempresult=(unsigned int)self;
if (self== nullptr) {
LOG(INFO) <<“art_method.cc::Invoke is invoked by myfartinvoke”;
dumpArtMethod(this);
return;
}
…
该函数只是在最开头添加了对Thread*参数的判断,当发现该参数为nullptr时,即表示是我们自己构造的主动调用链到达,则此时调用dumpArtMethod()函数完成对该ArtMethod的CodeItem的dump,这部分代码和fupk3一样直接采用dexhunter里的,这里不再赘述。到这里,我们就完成了内存中DexFile结构体中的dex的整体dump以及主动调用完成对每一个类中的函数体的dump,下面就是修复被抽取的函数部分。
3. 抽取类函数的修复
壳在完成对内存中加载的dex的解密后,该dex的索引区即stringid,typeid,methodid,classdef和对应的data区中的string列表并未加密。而对于classdef中类函数的CodeItem部分可能被加密存储或者直接指向内存中另一块区域。这里我们只需要使用dump下来的method的CodeItem来解析对应的被抽取的方法即可,这里我提供了一个用python实现的修复脚本。
下面进入到最激动人心的实验验证部分了,也顺便说一下Fart脱壳工具的使用方法和流程:
Fart的使用流程主要包含四步:
① 编写fart工具配置文件并push到/data/fart,并添加所有用户可读写权限。
如,我要脱壳的应用包名为com.example.dexcode,则此时fart的文件为第一行为包名,第二行为该应用安装后的私有目录,这里是/data/data/com.example.dexcode
② 安装应用,并点击启动应用,进入脱壳阶段。
在应用进入主页面Activity时开始正式脱壳阶段,会在应用私有目录下生成dump下来的dex文件以及函数体文件。该过程较为耗时,再次建议该过程喝杯茶。
③ 将脱壳dump下来的dex和函数体文件pull到电脑上fart目录下
待脱壳完成后,在应用私有目录下会生成相关dump文件,将这些文件拷贝到电脑fart目录即可。例如这里dump下来的是以_data_app开头的dex文件和722044_ins.bin文件。其中前者dump下来的dex文件大小为722044,和函数体文件722044_ins.bin文件一一对应关系。
④ 在fart目录下运行Python修复脚本fart.py,输出即为反编译后的修复函数的smali代码。参数依次为dump下来的dex文件以及函数体文件。如:python fart.py -d dumpdex -i ins_file>>repired.txt。这里dump的dex和函数体文件按照dump下来的dex文件大小一一对应。比如,如果dump下来的dex文件文件大小为1000,则对应的函数体文件则为1000_ins.bin。整个修复过程视要修复的dex文件大小决定,当文件较大时,修复过程较漫长,建议先喝杯茶。待修复完成后,打开repired.txt,即可看到对应的被抽空的函数的真正方法体信息。如下图,可以看到被抽空的函数已经完成了修复。
文章最后附上github:https://github.com/hanbinglengyue/FART
其中提供了nexus5和arm模拟器镜像,以及供修复的fart.py脚本,建议使用nexus5镜像,脱壳更快。
原文链接:https://bbs.pediy.com/thread-252630.htm
参考链接:
https://github.com/zyq8709/DexHunter
https://github.com/F8LEFT/FUPK3
https://www.jianshu.com/p/a1f40b39b3de
http://gityuan.com/2016/03/26/app-process-create/
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
简历首选内推方式,速度快,效率高啊!然后可以在拉钩,boss,脉脉,大街上看看。简历上写道熟悉什么技术就一定要去熟悉它,不然被问到不会很尴尬!做过什么项目,即使项目体量不大,但也一定要熟悉实现原理!不是你负责的部分,也可以看看同事是怎么实现的,换你来做你会怎么做?做过什么,会什么是广度问题,取决于项目内容。但做过什么,达到怎样一个境界,这是深度问题,和个人学习能力和解决问题的态度有关了。大公司看深度,小公司看广度。大公司面试你会的,小公司面试他们用到的你会不会,也就是岗位匹配度。
选定你想去的几家公司后,先去一些小的公司练练,学习下面试技巧,总结下,也算是熟悉下面试氛围,平时和同事或者产品PK时可以讲得头头是道,思路清晰至极,到了现场真的不一样,怎么描述你所做的一切,这绝对是个学术性问题!
面试过程一定要有礼貌!即使你觉得面试官不尊重你,经常打断你的讲解,或者你觉得他不如你,问的问题缺乏专业水平,你也一定要尊重他,谁叫现在是他选择你,等你拿到offer后就是你选择他了。
金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
重他,谁叫现在是他选择你,等你拿到offer后就是你选择他了。
金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-mfNU8Jnl-1713305747965)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。