【胖虎的逆向之路】04——脱壳(一代壳)原理&脱壳相关概念详解

【胖虎的逆向之路】04——脱壳(一代壳)原理&脱壳相关概念详解

【胖虎的逆向之路】01——动态加载和类加载机制详解
【胖虎的逆向之路】02——Android整体加壳原理详解&实现
【胖虎的逆向之路】03——Android一代壳脱壳办法&实操



前言

提示:这里可以添加本文要记录的大概内容:

在上文中,我们讲解了关于Android脱壳的基本办法和实际操作,现在我们来针对脱壳(一代壳)的原理和脱壳相关的基础知识介绍,由于作者能力有限,会尽力的详细描述 一代壳脱壳 的流程及原理,如本文中有任何错误,烦请指正,感谢~


一、Dex加载流程

在日常分析脱壳点过程中,Dex加载的基本流程也是要明白熟悉的
在这里插入图片描述

DexPathList:该类主要用来查找Dex、SO库的路径,并这些路径整体呈一个数组
Element:根据多路径的分隔符“;”将dexPath转换成File列表,记录所有的dexFile
DexFile:用来描述Dex文件,Dex的加载以及Class的查找都是由该类调用它的native方法完成的

我们依次来分析这个过程中的源码

DexPathList

/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
public DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory) {
**********************      
   this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                         suppressedExceptions, definingContext);    
**********************  
            }

makeDexElements

private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
          List<IOException> suppressedExceptions, ClassLoader loader) {
**********************            
       DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements);    
**********************         
          }

loadDexFile

private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,
                                       Element[] elements)
            throws IOException {
        if (optimizedDirectory == null) {
            return new DexFile(file, loader, elements);
        } else {
           String optimizedPath = optimizedPathFor(file, optimizedDirectory);
            return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
        }
    }

loadDex

static DexFile loadDex(String sourcePathName, String outputPathName,
      int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
      return new DexFile(sourcePathName, outputPathName, flags, loader, elements);
  }

DexFile

/libcore/dalvik/src/main/java/dalvik/system/DexFile.java
DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
        mCookie = openDexFile(fileName, null, 0, loader, elements);
        mInternalCookie = mCookie;
        mFileName = fileName;
        //System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName);
    }

这里出现的mCookie,mCookie在C/C++层中是DexFile的指针,我们在下面详细讲解

openDexFile

private static Object openDexFile(String sourceName, String outputName, int flags,
        ClassLoader loader, DexPathList.Element[] elements) throws IOException {
       // Use absolute paths to enable the use of relative paths when testing on host.
        return openDexFileNative(new File(sourceName).getAbsolutePath(),
                                 (outputName == null)
                                    ? null
                                   : new File(outputName).getAbsolutePath(),
                               	   flags,
                                   loader,
                                   elements);
    }

这里就进入了C/C++层

openDexFileNative

在这里插入图片描述

为了节约篇幅,我们快速分析,中间再经过一些函数

OpenDexFilesFromOat()
MakeUpToDate()
GenerateOatFileNoChecks()
Dex2Oat()

最后进入了Dex2Oat,这就进入了Dex2Oat的编译流程

反之如果我们在下面Dex2Oat的流程中通过Hook相关方法或execv或execve导致dex2oat失败,我们就会返回到OpenDexFilesFromOat

OpenDexFilesFromOat

在这里插入图片描述

会先在HasOriginalDexFiles里尝试加载我们的Dex,也就是说,倘若我们的壳阻断了dex2oat的编译流程,然后又调用了DexFile的Open函数。

DexFile::Open

在这里插入图片描述

校验dex的魔术字字段,然后调用DexFile::OpenFile

DexFile::OpenFile

/art/runtime/dex_file.cc
std::unique_ptr<const DexFile> DexFile::OpenFile(int fd,
                                                const std::string& location,
                                                bool verify,
                                                bool verify_checksum,
                                                std::string* error_msg) {
 **************************************
 std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(),
                                                map->Size(),
                                                location,
                                                dex_header->checksum_,
                                                kNoOatDexFile,
                                                verify,
                                                verify_checksum,
                                                error_msg);   
  **************************************
                                                
                                                }

OpenCommon
在这里插入图片描述

最后又再次回到DexFile类,这里我们的dex文件加载基本流程分析完毕

二、Dex2Oat编译流程

Dex2oat是google公司为了提高编译效率的一种机制,从Android8.0开始实施,一些加壳厂商实现抽取壳往往会禁用Dex2oat,而针对整体加壳没有禁用的Dex2Oat也成为了脱壳点

在这里插入图片描述
Exec

/art/runtime/exec_utils.cc
bool Exec(std::vector<std::string>& arg_vector, std::string* error_msg) {
  int status = ExecAndReturnCode(arg_vector, error_msg);
  if (status != 0) {
    const std::string command_line(android::base::Join(arg_vector, ' '));
    *error_msg = StringPrintf("Failed execv(%s) because non-0 exit status",
                              command_line.c_str());
    return false;
  }
  return true;
}

ExecAndReturnCode

在这里插入图片描述

而我们就可以通过Hook execv或execve来禁用Dex2Oat,而如果我们不禁用dex2oat,execve函数是用来调用dex2oat的二进制程序实现对dex文件的加载,我们这时候找到dex2oat.cc这个文件,找到main函数

/art/dex2oat/dex2oat.cc
 int main(int argc, char** argv) {
  int result = static_cast<int>(art::Dex2oat(argc, argv));
  if (!art::kIsDebugBuild && (RUNNING_ON_MEMORY_TOOL == 0)) {
    _exit(result);
  }
  return result;

这里我们调用了Dex2oat

Dex2Oat

/art/dex2oat/dex2oat.cc
static dex2oat::ReturnCode Dex2oat(int argc, char** argv) {
   **************************************
   dex2oat::ReturnCode setup_code = dex2oat->Setup();
    dex2oat::ReturnCode result;
  if (dex2oat->IsImage()) {
    result = CompileImage(*dex2oat);
  } else {
    result = CompileApp(*dex2oat);
 }
   **************************************
}

Dex2oat中会对dex文件进行逐个类逐个函数的编译,setup()函数完成对dex的加载

然后顺序执行,就会进入CompileApp

编译过程中会按照逐个函数进行编译,就会进入CompileMethod

在这里插入图片描述

到这里Dex2oat的基本流程就分析完毕

三、类加载流程

要理解DexFile为什么如此重要,首先我们要清除Android APP的类加载流程。Android的类加载一般分为两类隐式加载和显式加载

1.隐式加载:
    (1)创建类的实例,也就是new一个对象
    (2)访问某个类或接口的静态变量,或者对该静态变量赋值
    (3)调用类的静态方法
    (4)反射Class.forName("android.app.ActivityThread")
    (5)初始化一个类的子类(会首先初始化子类的父类)
2.显示加载:
    (1)使用LoadClass()加载
    (2)使用forName()加载

我们详细看一下显示加载:

Class.forName 和 ClassLoader.loadClass加载有何不同:
(1ClassLoader.loadClass也能加载一个类,但是不会触发类的初始化(也就是说不会对类的静态变量,静态代码块进行初始化操作)2Class.forName这种方式,不但会加载一个类,还会触发类的初始化阶段,也能够为这个类的静态变量,静态代码块进行初始化操作

我们在详细来看一下在类加载过程中的流程:

java层

在这里插入图片描述

我们可以发现类加载中关键的DexFile,该类用来描述Dex文件,所以我们的脱壳对象就是DexFile

这里从DexFile进入Native层中,还有一个关键的字段就是mCookie

在这里插入图片描述
后面我们详细的介绍mCookie的作用

我们进一步分析,进入Native层

Native层

/art/runtime/native/[dalvik_system_DexFile.cc

在这里插入图片描述

ConvertJavaArrayToDexFiles对cookie进行了处理

在这里插入图片描述
通过这里的分析,我们可以知道mCooike转换为C/C++层指针后,就是dexfile的索引

我们继续分析DefineClass

art/runtime/class_linker.cc
mirror::Class* ClassLinker::DefineClass(Thread* self,
                                      const char* descriptor,
                                        size_t hash,
                                       Handle<mirror::ClassLoader> class_loader,
                                        const DexFile& dex_file,
                                        const DexFile::ClassDef& dex_class_def) {
***************
LoadClass(self, *new_dex_file, *new_class_def, klass);
***************
}

LoadClass

art/runtime/class_linker.cc
void ClassLinker::LoadClass(Thread* self,
3120                            const DexFile& dex_file,
3121                            const DexFile::ClassDef& dex_class_def,
3122                            Handle<mirror::Class> klass) {
3123  const uint8_t* class_data = dex_file.GetClassData(dex_class_def);
3124  if (class_data == nullptr) {
3125    return;  // no fields or methods - for example a marker interface
3126  }
3127  LoadClassMembers(self, dex_file, class_data, klass);
3128}

LoadClassMembers

art/runtime/class_linker.cc
void ClassLinker::LoadClassMembers(Thread* self,
                                   const DexFile& dex_file,
                                   const uint8_t* class_data,
                                   Handle<mirror::Class> klass) {
***************
      LoadMethod(dex_file, it, klass, method);
      LinkCode(this, method, oat_class_ptr, class_def_method_index);
***************
}

LoadMethod

art/runtime/class_linker.cc
void ClassLinker::LoadMethod(const DexFile& dex_file,
                           const ClassDataItemIterator& it,
                            Handle<mirror::Class> klass,
                             ArtMethod* dst) {
}

LinkCode

在这里插入图片描述

我们可以发现这里就进入了从linkcode后就进入了解释器中,并对是否进行dex2oat进行了判断,我们直接进入解释器中继续分析

我们知道Art解释器分为两种:解释模式下和quick模式下,而我们又知道Android8.0开始进行dex2oat

如果壳没有禁用dex2oat,那类中的初始化函数运行在解释器模式下 
如果壳禁用dex2oat,dex文件中的所有函数都运行在解释器模式下
则类的初始化函数运行在解释器模式下

所以一般的加壳厂商会禁用掉dex2oat,这样可以是所有的函数都运行在解释模式下,所以一些脱壳点选在dex2oat流程中,可能针对禁用dex2oat的情况并不使用,我们这里主要针对整体加壳,就不展开讲述,最后我们得知解释器中会运行在Execute下

Execute

art/runtime/interpreter/interpreter.cc
static inline JValue Execute(
    Thread* self,
    const DexFile::CodeItem* code_item,
    ShadowFrame& shadow_frame,
    JValue result_register,
    bool stay_in_interpreter = false) REQUIRES_SHARED(Locks::mutator_lock_){

***************
      ArtMethod *method = shadow_frame.GetMethod();
***************
    
    }

这里我们大致分析完成了类加载的思路

四、DexFile详解

前面我们分析了很多,对dex加载、类加载等都已经有了一个很详细的了解,而最终一切的核心就是DexFile,DexFile就是我们脱壳所关注的重点,寒冰大佬在拨云见日:安卓APP脱壳的本质以及如何快速发现ART下的脱壳点中提到,在ART下只要获得了DexFile对象,那么我们就可以得到该dex文件在内存中的起始地址和大小,进而完成脱壳。

我们先查看一些DexFile的结构体

在这里插入图片描述
只要我们能获得起始地址begin和大小size,就可以成功的将dex文件脱取下来,这里我们记得DexFile含有虚函数表,所以根据C++布局,要偏移一个指针
在这里插入图片描述
而DexFile类还给我们提供了方便的API
在这里插入图片描述

这样只要我们找到函数中有DexFile对象,就可以通过调用API来进一步dump dex文件,由此按照寒冰大佬的思想,大量的脱壳点由此产生

(1)直接查找法

我们通过直接在Android源码中搜索DexFile,就可以获得海量的脱壳点

在这里插入图片描述
我们通过在IDA中搜索libart.so导出的DexFile,同样可以获得大量的脱壳点

在这里插入图片描述

(2)间接查找法

这里就是寒冰大佬在文章中提到的通过ArtMethod对象的getDexFile()获取到ArtMethod所属的DexFile对象的这种一级间接法,通过Thread的getCurrentMethod()函数首先获取到ArtMethod或者通过ShadowFrame的getMethod获取到ArtMethod对象,然后再通过getDexFile获取到ArtMethod对象所属的DexFile的二级间接法

getDexFile()
getMethod()

五、ArtMethod详解

上面我们已经详细分析了DexFile的文件结构,我们知道通过ArtMethod可以获得DexFile,那么为啥又要单独提ArtMethod呢,因为ArtMethod在抽取壳和VMP等壳中扮演了重要的角色

ArtMethod结构体
在这里插入图片描述

我们通过ArtMethod可以获得codeitem的偏移和方法索引,熟悉dex结构的朋友知道codeitem就是代码实际的值,而codeitem则再后续加壳技术扮演了至关重要的地址,而且ArtMethod还有非常丰富的方法,可以帮助大家实现很多功能,所以在脱壳工作中也是十分重要的

总结

以上就是今天要讲的内容,主要借鉴了 随风而行 大佬的一些思路及文章,将文章在复写一遍目的是为了加深记忆,在以后的学习过程中可以很方便的查看~

参考文献

https://security-kitchen.com/2022/12/04/Packer5/
https://bbs.kanxue.com/thread-254555.htm#msg_header_h2_2

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值