脱壳入门

需要学习的一下知识

此文章全部基于art,dalvik的太老了,不做分析

  1. frida脚本编写
  2. 逆向基础
  3. Frida工具使用
  4. Dex文件结构
  5. Dex加载流程

逆向基础

IDA使用

把可执行文件拖进去就可以,接下来都是图形化操作。

导入表导出表

导入表就是自身引用其他库的函数列表,导出表就是自身提供了哪些函数给其他库调用。导入表对于ida的Imports,导出表对于ida的Exports。

字符串表

字符串表是用来存放字符串的。对于ida里面的Strings。点击view->open subview->strings:

什么是hook?

hook是逆向中常用的一种手段,其目的是为了去劫持一些函数的流程,来做一些自己的操作。hook大概有三种类型:导入表hook;inline hook;异常hook。

参考: GOT表和PLT表知识详解, Android中GOT表HOOK手动实现, Android Inline Hook, Android平台基于异常的Hook实现

为什么有些应用在模拟器上跑不起来?

Android这个系统设计的时候就支持多种架构的cpu。arm,arm64,x86,x86_64,mips,mips64。不同架构的cpu对应不同的指令集,而在Android应用编译的时候可以设置abi-filter来限制编译哪些架构的so。比如我只编译arm架构的so,然后把这个apk直接装到x86的模拟器上,高版本的Android直接安装失败,低版本的Android打开会崩溃,这就是因为这个应用不支持x86架构的cpu,所以跑不起来。

如何hook导出表中的某个函数

分为下面几步:

  1. 找到需要hook的函数的符号

  2. 使用这个符号运行时找到这个函数的地址

  3. 使用找到的地址对函数进行hook操作

一个简单的demo如下(hook libart.so的art::Dexfile::OpenMemory):

var openmemory = Module.findExportByName("libart.so", "_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_");
if(openmemory != undefined) {
  console.log("openmemory at " + openmemory);
  Interceptor.attach(openmemory, {
    onEnter: function (args) {
      console.log("loaddex: " + args[0] + "size: " + args[1]);
    },
    onLeave: function (retval) {
      console.log("dexfile: " + retval);
    }
  });
}

其中找到的符号为_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_

找符号的方法为,在ida的函数窗口中搜索需要hook的函数,然后双击,在ida view里面查看汇编,你可以看到ida显示的是以=============== S U B R O U T I N E =======================================开始,然后下一行是注释的函数名,再下面一行EXPORT ...这EXPORT后面跟着的就是到处的符号,下面一行写了一行一样的。

如何hook ida中为sub_xxx的函数

分为以下这几步:

  1. 找到函数的偏移

  2. 找到模块基地址

  3. 用基地址加上偏移就是函数现在的地址

  4. 通过上一步的地址去hook

一个demo如下:

var editor = Process.findModuleByName("010 Editor");
if(editor != undefined) {
  var modulebase = editor.base;
  var offset = 0xD8180;
  var sub_1000D8180 = modulebase.add(offset);
  var buf = Memory.readByteArray(sub_1000D8180, 64);
  console.log(hexdump(buf, {
    offset: 0,
    length: 64,
    header: true,
    ansi: true
  }));
  Interceptor.attach(sub_1000D8180, {
    onEnter: function (args) {

    },
    onLeave: function (retval) {
      console.log("retval = " + retval.toInt32());
      retval.replace(219);
    }
  });
}

Frida工具使用

frida带有以下这写工具

  1. frida 用来注入js的工具

  2. frida-discover 用来发现一个程序的内部可以使用frida-trace跟踪的函数

  3. frida-kill 一个杀死目标设备某个进程的工具

  4. frida-ls-devices 列出所有的设备

  5. frida-ps 显示目标设备的进程

  6. frida-trace 跟踪目标程序的函数调用

主要了解frida和frida-trace的使用

frida

frida可以用来注入js,必须学会如何使用,其官方帮助如下:

Usage: frida [options] target

Options:
  --version             show program's version number and exit
  -h, --help            show this help message and exit
  -D ID, --device=ID    connect to device with the given ID
  -U, --usb             connect to USB device
  -R, --remote          connect to remote frida-server
  -H HOST, --host=HOST  connect to remote frida-server on HOST
  -f FILE, --file=FILE  spawn FILE
  -n NAME, --attach-name=NAME
                        attach to NAME
  -p PID, --attach-pid=PID
                        attach to PID
  --debug               enable the Node.js compatible script debugger
  --enable-jit          enable JIT
  -l SCRIPT, --load=SCRIPT
                        load SCRIPT
  -c CODESHARE_URI, --codeshare=CODESHARE_URI
                        load CODESHARE_URI
  -e CODE, --eval=CODE  evaluate CODE
  -q                    quiet mode (no prompt) and quit after -l and -e
  --no-pause            automatically start main thread after startup
  -o LOGFILE, --output=LOGFILE
                        output to log file

假如你需要在应用打开的时候就开始跟踪,那么加上-f选项

注入一个js脚本(重新打开应用):

frida -R -f {app package name} -l {js file} --no-pause

注入一个js脚本(附加到某个进程)

frida-ps -R | grep {app package name}  # 使用上一步得到的进程  firda -R -p {pid} -l {js file}

frida-trace

frida-trace能跟踪函数调用,极大的减少逆向工作量,其官方帮助如下:

Usage: frida-trace [options] target

Options:
  --version             show program's version number and exit
  -h, --help            show this help message and exit
  -D ID, --device=ID    connect to device with the given ID
  -U, --usb             connect to USB device
  -R, --remote          connect to remote frida-server
  -H HOST, --host=HOST  connect to remote frida-server on HOST
  -f FILE, --file=FILE  spawn FILE
  -n NAME, --attach-name=NAME
                        attach to NAME
  -p PID, --attach-pid=PID
                        attach to PID
  --debug               enable the Node.js compatible script debugger
  --enable-jit          enable JIT
  -I MODULE, --include-module=MODULE
                        include MODULE
  -X MODULE, --exclude-module=MODULE
                        exclude MODULE
  -i FUNCTION, --include=FUNCTION
                        include FUNCTION
  -x FUNCTION, --exclude=FUNCTION
                        exclude FUNCTION
  -a MODULE!OFFSET, --add=MODULE!OFFSET
                        add MODULE!OFFSET
  -T, --include-imports
                        include program's imports
  -t MODULE, --include-module-imports=MODULE
                        include MODULE imports
  -m OBJC_METHOD, --include-objc-method=OBJC_METHOD
                        include OBJC_METHOD
  -M OBJC_METHOD, --exclude-objc-method=OBJC_METHOD
                        exclude OBJC_METHOD
  -s DEBUG_SYMBOL, --include-debug-symbol=DEBUG_SYMBOL
                        include DEBUG_SYMBOL
  -q, --quiet           do not format agent's output

跟踪libart.so的所以导出函数的例子:

frida-trace -R -f {app package name} -I "*libart*"

跟踪所有带有DexFile的方法

frida-trace -R -f {app package name} -i "*DexFile*"

Dex文件结构

关于dex文件可以参考: Dex文件格式详解

对于指令抽取这种方式加固的,我们了解dex文件格式是很有必要的,Android在加载类的时候不会一次性把所有的都加载到内存中,而是用到什么就加载什么。所以加固完全可以把dex的指令部分加密或者全部置位0,然后在加载的是hook相关的函数,把指令填回去。

Dex加载流程(基于Android5.1源码)

DexClassloader

我们要动态加载一个dex文件,需要用到的是DexClassloader,而DexClassloader是BaseDexClassLoader的子类,BaseDexClassLoader还有另外一个子类PathClassloader,PathClassloader是系统默认用来加载apk文件dex的类,而我们要动态加载一个dex的话,需要使用DexClassloader来加载。使用很简单,直接new一个DexClassloader对象就行了。其代码如下(源码):

public DexClassLoader(String dexPath, String optimizedDirectory,
        String libraryPath, ClassLoader parent) {
    super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}

DexClassloader的代码就只有下面短短的几行,直接调用的父类的构造方法。

BaseClassloader

BaseClassloader就是DexClassloader的父类,在这里面实现了dex的加载逻辑。BaseClassloader是Classloader的子类,Classloader有两个重要的方法:findClassloadClass,其中findClass是用来实现类加载的逻辑,而loadClass是先从父Classloader里面去寻找,如果找不到就调用自己的findClass来找。也就是说当前加载dex的class是由loadClass来实现的。BaseClassloader的源码请看这里,在BaseClassloader里面我们需要关注的有两个方法,其一是构造方法,另外一个是findClass方法。请自己参看源码看下面的解释。

在构造方法中,将自己的pathList这个成员变量赋值,其值是一个新创建的DexPathList对象。在findClass方法中是调用的pathList里面的findClass方法,所以dex加载的逻辑应该是在DexPathList里面实现的。

DexPathList

DexPathList的源码请看这里,下面对照源码讲解其核心逻辑。

在DexPathList里面主要需要关注三个函数:构造方法、findClass和makeDexElements。我们先看findClass,这个方法是用来找类的,其代码里面是从自己的dexElements去找的类。然后我们看正好是在其构造方法中调用makeDexElements这个方法类给dexElements来赋值的。在makeDexElements这个类里面,就是将apk或者zip或者jar或者裸的dex加载起来,放到Elements对象里面。dexFile就是放到这个element里面,在makeDexElements里面是调用的自身的loadDexFile来加载dex,在loadDexFile里面判断了文件是否是zip或者apk jar,如果是就调用构造方法来加载dex,否则就使用loadDex方法来加载dex。

DexFile

DexFile的源码请看这里,下面对照源码看dexfile的主要逻辑。

DexFile这个类就是java层最终加载dex的类,在其构造方法中调用的openDexFile方法来加载dex,而openDexFile是调用的openDexFileNative方法来加载dex,这个方法是一个native方法。其实现在dalvik_system_DexFile.cc中。

dalvik_system_DexFile.cc

DexFile.java中调用的openDexFileNative的实现就在dalvik_system_DexFile.cc中的DexFile_openDexFileNative函数里面,源码请看这里

在这里是由class_inker.ccOpenDexFilesFromOat来实现的。

class_linker.cc

源码请看这里,这里的代码很长,就不一一解释,大概就是查看当前dex文件是否被解析成oat,如果被解析成了oat文件,就直接下一步,如果没有被解析成oat就先调用dex2oat把dex解析成oat文件。

使用frida-trace跟踪dex加载流程

由于加固这种不会按照正常的流程去加载dex,所以我们需要借助frida来看看大概是个什么加载流程。

我们需要分析的大部分函数都是OatFile或者DexFile相关的,由于C++函数编译出来的符号会带有类名命名空间等,所以我们使用下面的命令来跟踪dex加载流程:

frida-trace -R -f cn.com.xib.xibpb.v3.xiben -i "*OatFile*" -i "*DexFile*" > trace.txt

这里的cn.com.xib.xibpb.v3.xiben是我们的一个测试应用的包名。

我们把结果重定向到一个txt里面方便查看。

其中追踪到的一段和打开dex相关的日志如下:

876 ms     | _ZN3art11ClassLinker20FindOpenedOatDexFileEPKcS2_PKj()
   876 ms     |    | _ZNK3art7OatFile13GetOatDexFileEPKcPKjb()
   876 ms     |    |    | _ZNKSt3__16__treeINS_12__value_typeIN3art11StringPieceEPKNS2_10OatDexFileEEENS_19__map_value_compareIS3_S7_NS_4lessIS3_EELb1EEENS_9allocatorIS7_EEE4findIS3_EENS_21__tree_const_iteratorIS7_PNS_11__tree_nodeIS7_PvEEiEERKT_()
   877 ms     |    |    | _ZN3art7DexFile23GetDexCanonicalLocationEPKc()
   877 ms     |    |    | _ZNSt3__13mapIN3art11StringPieceEPKNS1_10OatDexFileENS_4lessIS2_EENS1_17TrackingAllocatorINS_4pairIS2_S5_EELNS1_12AllocatorTagE19EEEE12emplace_hintIJRKS2_RKS5_EEENS_14__map_iteratorINS_15__tree_iteratorINS_12__value_typeIS2_S5_EEPNS_11__tree_nodeISM_PvEEiEEEENS_20__map_const_iteratorINS_21__tree_const_iteratorISM_SQ_iEEEEDpOT_()
   877 ms     |    | _ZNK3art7OatFile13GetOatDexFileEPKcPKjb()
   878 ms     |    |    | _ZNKSt3__16__treeINS_12__value_typeIN3art11StringPieceEPKNS2_10OatDexFileEEENS_19__map_value_compareIS3_S7_NS_4lessIS3_EELb1EEENS_9allocatorIS7_EEE4findIS3_EENS_21__tree_const_iteratorIS7_PNS_11__tree_nodeIS7_PvEEiEERKT_()
   878 ms     |    |    | _ZN3art7DexFile23GetDexCanonicalLocationEPKc()
   878 ms     |    |    | _ZNSt3__13mapIN3art11StringPieceEPKNS1_10OatDexFileENS_4lessIS2_EENS1_17TrackingAllocatorINS_4pairIS2_S5_EELNS1_12AllocatorTagE19EEEE12emplace_hintIJRKS2_RKS5_EEENS_14__map_iteratorINS_15__tree_iteratorINS_12__value_typeIS2_S5_EEPNS_11__tree_nodeISM_PvEEiEEEENS_20__map_const_iteratorINS_21__tree_const_iteratorISM_SQ_iEEEEDpOT_()
   878 ms     |    | _ZNK3art7OatFile13GetOatDexFileEPKcPKjb()
   878 ms     |    |    | _ZNKSt3__16__treeINS_12__value_typeIN3art11StringPieceEPKNS2_10OatDexFileEEENS_19__map_value_compareIS3_S7_NS_4lessIS3_EELb1EEENS_9allocatorIS7_EEE4findIS3_EENS_21__tree_const_iteratorIS7_PNS_11__tree_nodeIS7_PvEEiEERKT_()
   878 ms     |    |    | _ZN3art7DexFile23GetDexCanonicalLocationEPKc()
   878 ms     |    |    | _ZNSt3__13mapIN3art11StringPieceEPKNS1_10OatDexFileENS_4lessIS2_EENS1_17TrackingAllocatorINS_4pairIS2_S5_EELNS1_12AllocatorTagE19EEEE12emplace_hintIJRKS2_RKS5_EEENS_14__map_iteratorINS_15__tree_iteratorINS_12__value_typeIS2_S5_EEPNS_11__tree_nodeISM_PvEEiEEEENS_20__map_const_iteratorINS_21__tree_const_iteratorISM_SQ_iEEEEDpOT_()
   878 ms     |    | _ZNK3art7OatFile13GetOatDexFileEPKcPKjb()
   879 ms     |    |    | _ZNKSt3__16__treeINS_12__value_typeIN3art11StringPieceEPKNS2_10OatDexFileEEENS_19__map_value_compareIS3_S7_NS_4lessIS3_EELb1EEENS_9allocatorIS7_EEE4findIS3_EENS_21__tree_const_iteratorIS7_PNS_11__tree_nodeIS7_PvEEiEERKT_()
   879 ms     |    |    | _ZN3art7DexFile23GetDexCanonicalLocationEPKc()
   879 ms     |    |    | _ZNSt3__13mapIN3art11StringPieceEPKNS1_10OatDexFileENS_4lessIS2_EENS1_17TrackingAllocatorINS_4pairIS2_S5_EELNS1_12AllocatorTagE19EEEE12emplace_hintIJRKS2_RKS5_EEENS_14__map_iteratorINS_15__tree_iteratorINS_12__value_typeIS2_S5_EEPNS_11__tree_nodeISM_PvEEiEEEENS_20__map_const_iteratorINS_21__tree_const_iteratorISM_SQ_iEEEEDpOT_()
   879 ms     | _ZN3art11ClassLinker43FindOatFileContainingDexFileFromDexLocationEPKcPKjNS_14InstructionSetEPNSt3__16vectorINS6_12basic_stringIcNS6_11char_traitsIcEENS6_9allocatorIcEEEENSB_ISD_EEEEPb()
   879 ms     |    | _ZN3art11ClassLinker26OpenOatFileFromDexLocationERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEENS_14InstructionSetEPbSB_PNS1_6vectorIS7_NS5_IS7_EEEE()
   879 ms     |    |    | _ZN3art25DexFilenameToOdexFilenameERKNSt3__112basic_stringIcNS0_11char_traitsIcEENS0_9allocatorIcEEEENS_14InstructionSetE()
   879 ms     |    |    | _ZN3art11ClassLinker32FindOpenedOatFileFromOatLocationERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE()
   879 ms     |    |    | _ZN3art11ClassLinker32FindOpenedOatFileFromOatLocationERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE()
   879 ms     |    |    | _ZN3art7OatFile4OpenERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEES9_PhSA_bPS7_()
   880 ms     |    |    | _ZN3art7OatFile4OpenERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEES9_PhSA_bPS7_()
   880 ms     |    |    |    | _ZN3art7OatFileC1ERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEb()
   880 ms     |    |    |    | _ZN3art7OatFile11ElfFileOpenEPN9unix_file6FdFileEPhS4_bbPNSt3__112basic_stringIcNS5_11char_traitsIcEENS5_9allocatorIcEEEE()
   880 ms     |    |    |    |    | _ZNSt3__16vectorIPKN3art7DexFileENS_9allocatorIS4_EEE21__push_back_slow_pathIS4_EEvOT_()
   880 ms     |    |    |    |    | _ZNSt3__16vectorIPKN3art7DexFileENS_9allocatorIS4_EEE21__push_back_slow_pathIS4_EEvOT_()
   880 ms     |    |    |    |    | _ZNSt3__16vectorIPKN3art7DexFileENS_9allocatorIS4_EEE21__push_back_slow_pathIS4_EEvOT_()
   881 ms     |    |    |    |    | _ZN3art7OatFile5SetupEPNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE()
   881 ms     |    |    |    |    |    | _ZNSt3__16vectorIPKN3art10OatDexFileENS_9allocatorIS4_EEE7reserveEj()
   881 ms     |    |    |    |    |    | _ZN3art7DexFile12IsMagicValidEPKh()
   881 ms     |    |    |    |    |    | _ZN3art7DexFile14IsVersionValidEPKh()
   881 ms     |    |    |    |    |    | _ZN3art7DexFile23GetDexCanonicalLocationEPKc()
   881 ms     |    |    |    |    |    | _ZNSt3__13mapIN3art11StringPieceEPKNS1_10OatDexFileENS_4lessIS2_EENS1_17TrackingAllocatorINS_4pairIS2_S5_EELNS1_12AllocatorTagE19EEEE7emplaceIIRKS2_RKS5_EEENS9_INS_14__map_iteratorINS_15__tree_iteratorINS_12__value_typeIS2_S5_EEPNS_11__tree_nodeISM_PvEEiEEEEbEEDpOT_()
   881 ms     |    |    |    |    |    |    | _ZNSt3__16__treeINS_12__value_typeIN3art11StringPieceEPKNS2_10OatDexFileEEENS_19__map_value_compareIS3_S7_NS_4lessIS3_EELb1EEENS_9allocatorIS7_EEE12__find_equalIS7_EERPNS_16__tree_node_baseIPvEESK_RKT_()
   882 ms     |    |    | _ZN3art11ClassLinker12CheckOatFileEPKNS_7RuntimeEPKNS_7OatFileENS_14InstructionSetEPbPNSt3__112basic_stringIcNS9_11char_traitsIcEENS9_9allocatorIcEEEE()
   882 ms     |    |    |    | _ZNK3art7OatFile5BeginEv()
   882 ms     |    |    |    | _ZNK3art7OatFile5IsPicEv()
   882 ms     |    | _ZN3art11ClassLinker20VerifyOatWithDexFileEPKNS_7OatFileEPKcPKjPNSt3__112basic_stringIcNS8_11char_traitsIcEENS8_9allocatorIcEEEE()
   882 ms     |    |    | _ZN3art11ClassLinker28VerifyOatAndDexFileChecksumsEPKNS_7OatFileEPKcjNS_14InstructionSetEPNSt3__112basic_stringIcNS7_11char_traitsIcEENS7_9allocatorIcEEEE()
   882 ms     |    |    |    | _ZN3art11ClassLinker18VerifyOatChecksumsEPKNS_7OatFileENS_14InstructionSetEPNSt3__112basic_stringIcNS5_11char_traitsIcEENS5_9allocatorIcEEEE()
   882 ms     |    |    |    |    | _ZNK3art7OatFile5BeginEv()
   882 ms     |    |    |    |    | _ZNK3art7OatFile5IsPicEv()
   883 ms     |    |    |    | _ZNK3art7OatFile13GetOatDexFileEPKcPKjb()
   883 ms     |    |    |    |    | _ZNKSt3__16__treeINS_12__value_typeIN3art11StringPieceEPKNS2_10OatDexFileEEENS_19__map_value_compareIS3_S7_NS_4lessIS3_EELb1EEENS_9allocatorIS7_EEE4findIS3_EENS_21__tree_const_iteratorIS7_PNS_11__tree_nodeIS7_PvEEiEERKT_()
   883 ms     |    |    | _ZNK3art7OatFile13GetOatDexFileEPKcPKjb()
   883 ms     |    |    |    | _ZNKSt3__16__treeINS_12__value_typeIN3art11StringPieceEPKNS2_10OatDexFileEEENS_19__map_value_compareIS3_S7_NS_4lessIS3_EELb1EEENS_9allocatorIS7_EEE4findIS3_EENS_21__tree_const_iteratorIS7_PNS_11__tree_nodeIS7_PvEEiEERKT_()
   883 ms     |    |    | _ZNK3art10OatDexFile11OpenDexFileEPNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE()
   883 ms     |    |    |    | _ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_()
   883 ms     |    |    |    |    | _ZN3art7DexFileC1EPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileE()
   883 ms     |    |    |    |    | _ZNK3art7DexFile20CheckMagicAndVersionEPNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE()
   883 ms     |    |    |    |    | _ZN3art7DexFile4InitEPNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE()
   883 ms     |    |    | _ZN3art7DexFileD0Ev()
   884 ms     | _ZN3art7DexFile25GetMultiDexClassesDexNameEjPKc()
   884 ms     | _ZNK3art7OatFile13GetOatDexFileEPKcPKjb()
   884 ms     |    | _ZNKSt3__16__treeINS_12__value_typeIN3art11StringPieceEPKNS2_10OatDexFileEEENS_19__map_value_compareIS3_S7_NS_4lessIS3_EELb1EEENS_9allocatorIS7_EEE4findIS3_EENS_21__tree_const_iteratorIS7_PNS_11__tree_nodeIS7_PvEEiEERKT_()
   884 ms     | _ZNK3art10OatDexFile11OpenDexFileEPNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE()
   884 ms     |    | _ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_()
   884 ms     |    |    | _ZN3art7DexFileC1EPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileE()
   884 ms     |    |    | _ZNK3art7DexFile20CheckMagicAndVersionEPNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE()
   884 ms     |    |    | _ZN3art7DexFile4InitEPNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE()
   884 ms     | _ZNSt3__16vectorIPKN3art7DexFileENS_9allocatorIS4_EEE21__push_back_slow_pathIS4_EEvOT_()
   884 ms     | _ZN3art7DexFile25GetMultiDexClassesDexNameEjPKc()
   885 ms     | _ZN3art7DexFile11GetChecksumEPKcPjPNSt3__112basic_stringIcNS4_11char_traitsIcEENS4_9allocatorIcEEEE()
   885 ms     | _ZNK3art7OatFile13GetOatDexFileEPKcPKjb()
   885 ms     |    | _ZNKSt3__16__treeINS_12__value_typeIN3art11StringPieceEPKNS2_10OatDexFileEEENS_19__map_value_compareIS3_S7_NS_4lessIS3_EELb1EEENS_9allocatorIS7_EEE4findIS3_EENS_21__tree_const_iteratorIS7_PNS_11__tree_nodeIS7_PvEEiEERKT_()
   885 ms     |    | _ZN3art7DexFile23GetDexCanonicalLocationEPKc()
   886 ms     |    | _ZNSt3__13mapIN3art11StringPieceEPKNS1_10OatDexFileENS_4lessIS2_EENS1_17TrackingAllocatorINS_4pairIS2_S5_EELNS1_12AllocatorTagE19EEEE12emplace_hintIJRKS2_RKS5_EEENS_14__map_iteratorINS_15__tree_iteratorINS_12__value_typeIS2_S5_EEPNS_11__tree_nodeISM_PvEEiEEEENS_20__map_const_iteratorINS_21__tree_const_iteratorISM_SQ_iEEEEDpOT_()
   886 ms     | _ZN3art11ClassLinker15RegisterOatFileEPKNS_7OatFileE()
   886 ms     |    | _ZNSt3__16vectorIPKN3art7DexFileENS_9allocatorIS4_EEE21__push_back_slow_pathIS4_EEvOT_()
   886 ms  _ZN3art11ClassLinker13ResolveMethodERKNS_7DexFileEjNS_6HandleINS_6mirror8DexCacheEEENS4_INS5_11ClassLoaderEEENS4_INS5_9ArtMethodEEENS_10InvokeTypeE()
   886 ms     | _ZN3art11ClassLinker11ResolveTypeERKNS_7DexFileEtNS_6HandleINS_6mirror8DexCacheEEENS4_INS5_11ClassLoaderEEE()
   886 ms  _ZN3art11ClassLinker13ResolveStringERKNS_7DexFileEjNS_6HandleINS_6mirror8DexCacheEEE()
   887 ms  _ZN3art11ClassLinker13ResolveMethodERKNS_7DexFileEjNS_6HandleINS_6mirror8DexCacheEEENS4_INS5_11ClassLoaderEEENS4_INS5_9ArtMethodEEENS_10InvokeTypeE()
   887 ms     | _ZN3art11ClassLinker11ResolveTypeERKNS_7DexFileEtNS_6HandleINS_6mirror8DexCacheEEENS4_INS5_11ClassLoaderEEE()
   887 ms     |    | _ZNK3art7DexFile12FindClassDefEPKcj()
   887 ms  _ZN3art12MethodHelper32FindDexMethodIndexInOtherDexFileERKNS_7DexFileEj()
   887 ms     | _ZNK3art7DexFile12FindStringIdEPKc()
   887 ms     | _ZNK3art7DexFile10FindTypeIdEj()
   887 ms     | _ZNK3art7DexFile12FindMethodIdERKNS0_6TypeIdERKNS0_8StringIdERKNS0_7ProtoIdE()
   887 ms  _ZN3art11ClassLinker13ResolveStringERKNS_7DexFileEjNS_6HandleINS_6mirror8DexCacheEEE()

使用c++filt工具可以把符号还原成函数名,从追踪来看,加固之后加载dex的过程如下:

  1. art::ClassLinker::OpenDexFilesFromOat(char const*, char const*, std::__1::vector, std::__1::allocator >, std::__1::allocator, std::__1::allocator > > >*, std::__1::vector >*)

  2. art::DexFile::GetChecksum(char const*, unsigned int*, std::__1::basic_string, std::__1::allocator >*)

  3. art::DexFile::GetDexCanonicalLocation(char const*)

  4. art::ClassLinker::FindOatFileContainingDexFileFromDexLocation(char const*, unsigned int const*, art::InstructionSet, std::__1::vector, std::__1::allocator >, std::__1::allocator, std::__1::allocator > > >*, bool*)

  5. art::ClassLinker::OpenOatFileFromDexLocation(std::__1::basic_string, std::__1::allocator > const&, art::InstructionSet, bool*, bool*, std::__1::vector, std::__1::allocator >, std::__1::allocator, std::__1::allocator > > >*)

  6. art::DexFilenameToOdexFilename(std::__1::basic_string, std::__1::allocator > const&, art::InstructionSet)

  7. art::ClassLinker::FindOpenedOatFileFromOatLocation(std::__1::basic_string, std::__1::allocator > const&)

  8. art::OatFile::Open(std::__1::basic_string, std::__1::allocator > const&, std::__1::basic_string, std::__1::allocator > const&, unsigned char*, unsigned char*, bool, std::__1::basic_string, std::__1::allocator >*)

  9. art::OatFile::OatFile(std::__1::basic_string, std::__1::allocator > const&, bool)

  10. art::OatFile::ElfFileOpen(unix_file::FdFile*, unsigned char*, unsigned char*, bool, bool, std::__1::basic_string, std::__1::allocator >*)

  11. void std::__1::vector >::__push_back_slow_path(art::DexFile const*&&)

  12. art::OatFile::Setup(std::__1::basic_string, std::__1::allocator >*)

  13. art::ClassLinker::VerifyOatWithDexFile(art::OatFile const*, char const*, unsigned int const*, std::__1::basic_string, std::__1::allocator >*)

  14. art::ClassLinker::VerifyOatAndDexFileChecksums(art::OatFile const*, char const*, unsigned int, art::InstructionSet, std::__1::basic_string, std::__1::allocator >*)

  15. art::ClassLinker::VerifyOatChecksums(art::OatFile const*, art::InstructionSet, std::__1::basic_string, std::__1::allocator >*)

  16. art::OatFile::GetOatDexFile(char const*, unsigned int const*, bool) const

  17. art::OatDexFile::OpenDexFile(std::__1::basic_string, std::__1::allocator >*) const

  18. art::DexFile::OpenMemory(unsigned char const*, unsigned int, std::__1::basic_string, std::__1::allocator > const&, unsigned int, art::MemMap*, art::OatDexFile const*, std::__1::basic_string, std::__1::allocator >*)

  19. art::DexFile::DexFile(unsigned char const*, unsigned int, std::__1::basic_string, std::__1::allocator > const&, unsigned int, art::MemMap*, art::OatDexFile const*)

  20. art::DexFile::CheckMagicAndVersion(std::__1::basic_string, std::__1::allocator >*) const

  21. art::DexFile::Init(std::__1::basic_string, std::__1::allocator >*)

  22. art::DexFile::GetMultiDexClassesDexName(unsigned int, char const*)

  23. art::OatFile::GetOatDexFile(char const*, unsigned int const*, bool) const

目前的一些加固

目前存在的大概有四种类型的加固:

  1. 整体加固

  2. 指令抽取

  3. VMP

  4. java2c

这三种加固的后两种都是从第一种上面衍生出来的,整体加固的现在基本上找不到了,执行抽取的爱加密梆梆娜迦很多都用的指令抽取,vmp比较常见的就是360的加固,java2c是几维推出来的一种保护,据说是把smali代码转换成native代码。

整体加固

整体加固是将整个dex加密,在启动的时候解密然后内存加载,这是最初期的一种加固,很老版本的加固都是用的这种方式来加固的。对于这种加固,有很多种方案可以脱壳:

  1. 修改dex2oat,在dex2oat获取到dex的时候就把dex写到文件中

  2. 内存遍历,找dex,然后把dex写到文件中

  3. hook从内存中加载dex的函数OpenMemory,然后把dex写到文件中。

第一代壳是很容易获取到完整的dex,并且也不需要做修复,丢到jeb就能看

指令抽取

指令抽取这一类壳就很蛋疼了,他在整体加固的基础上,把dex的指令全部写为空,然后在方法被调用的时候才将指令填充回去。对于这种加固,使用整体加固的脱壳方案得到的dex只包类方法等信息,并没有真正的指令。这种加固就需要先让他把指令填回去再去内存中把dex写到文件中。对于这种壳脱壳方案有:

  1. 强迫加载所有的类,加载完之后再从内存中dump

  2. 强迫加载所有类,加载完之后从运行时获取dexheader、stringData、typeList、classData、code等,然后再拼接成一个新的dex

对于指令抽取这种壳,比较完善的就是dexhunter那个方案。但是dexhunter需要编译源码,动静太大。我们目前有两种方式来脱壳:

第一种:使用virtualApp,改写的VirtualApp其原理就是:hook打开dex的函数,记录加载dex的地址,然后拿到他的Classloader,然后在java层加载所有的dex,然后再从记录的dex地址中将dex写到文件。这种方式可以脱一部分指令抽取,但是一部分应用在加载所有类的时候会闪退,这种就不能脱下来

第二种:半自动,使用frida。这种原理也是一样,hook他打开dex的函数,然后打印出地址,然后手动去点他的业务,在关键业务执行了之后再把dex从内存中写到文件里面(讲解的时候会演示如何处理)。

VMP

vmp这种虚拟指令的壳,自动脱壳是很难实现,手动脱壳耗费时间比较多。比较典型的就是360的壳,抽取onCreate函数。对于这种壳就是找到他的指令替换表,然后动态调试,把它指令还原,复杂度很高,需要有耐心。

java2c

只听过,没有见过样本。

使用frida进行简单脱壳的例子

我们使用frida跟踪到的dex加载点是DexFile::OpenMemory,针对一代壳,我们脱壳需要以下一些步骤:

  1. hook OpenMemory函数(其中第一个参数为dex的地址,第二个参数为dex的大小。在6.0以上第一个参数是this指针,第二个为dex地址,第三个为dex大小)

  2. 读取dex这一段内存,然后写到文件中

实现代码如下:

var DEX_MAGIC = 0x0A786564;
var dexrec = [];

dumpdexmemory = function(addr) {
    var dex_len = Memory.readU32(addr.add(0x20));
    var buf = Memory.readByteArray(addr, 64);
    console.log(hexdump(buf, {
      offset: 0,
      length: 64,
      header: true,
      ansi: true
    }));
    var dumppath = "/sdcard/xxunpack/" + dex_len.toString(0x10) + ".dex";
    console.log(dumppath);
    var dumpdexfile = new File(dumppath, "wb");
    dumpdexfile.write(Memory.readByteArray(addr, dex_len));
    dumpdexfile.close();
    console.log("write file to " + dumppath);
}

var openmemory = Module.findExportByName("libart.so", "_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_");
if(openmemory != undefined) {
    console.log("openmemory at " + openmemory);
    Interceptor.attach(openmemory, {
        onEnter: function (args) {
            console.log("loaddex: " + args[0] + "size: " + args[1]);
            if(Memory.readU32(args[0]) == DEX_MAGIC) {
                dexrec.push(args[0]);
                dumpdexmemory(args[0]);
            }
        },
        onLeave: function (retval) {
            console.log("dexfile: " + retval);
        }
    });
}

对于指令抽取的壳,在加载dex的时候需要去还原指令,而这个一般是在class_linker.cc的DefineClass执行的时候完成的。代码如下:

var DEX_MAGIC = 0x0A786564;
var dexrec = [];

dumpdexmemory = function(addr) {
    var dex_len = Memory.readU32(addr.add(0x20));
    var buf = Memory.readByteArray(addr, 64);
    // console.log(hexdump(buf, {
    //   offset: 0,
    //   length: 64,
    //   header: true,
    //   ansi: true
    // }));
    var dumppath = "/sdcard/xxunpack/" + dex_len.toString(0x10) + ".dex";
    console.log(dumppath);
    var dumpdexfile = new File(dumppath, "wb");
    dumpdexfile.write(Memory.readByteArray(addr, dex_len));
    dumpdexfile.close();
    console.log("write file to " + dumppath);
}

var openmemory = Module.findExportByName("libart.so", "_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_10OatDexFileEPS9_");
if(openmemory != undefined) {
    console.log("openmemory at " + openmemory);
    Interceptor.attach(openmemory, {
        onEnter: function (args) {
            console.error("loaddex: " + args[0] + ", size: " + args[1]);
            if(Memory.readU32(args[0]) == DEX_MAGIC) {
                dexrec.push(args[0]);
                dumpdexmemory(args[0]);
            }
        },
        onLeave: function (retval) {
            console.log("dexfile: " + retval);
        }
    });
}

var DefineClass = Module.findExportByName("libart.so", "_ZN3art11ClassLinker11DefineClassEPNS_6ThreadEPKcjNS_6HandleINS_6mirror11ClassLoaderEEERKNS_7DexFileERKNS9_8ClassDefE");
if(DefineClass != undefined) {
    console.log("DefineClass at " + DefineClass);
    Interceptor.attach(DefineClass, {
        onEnter: function (args) {
            var descriptor = Memory.readUtf8String(args[2]);
            console.log("dexfile: " + args[5] + ", classdef: " + args[6] + ", descriptor: " + descriptor);
        },
        onLeave: function (retval) {
        }
    });
}

当我们看到一些关键类加载的时候,可以在frida的控制台手动调用我们自己写的dumpdexmemory方法,并且把打印出来的地址传进去,这样脱下来的dex关键的信息就是修复之后的。

1 Like