需要学习的一下知识
此文章全部基于art,dalvik的太老了,不做分析
- frida脚本编写
- 逆向基础
- Frida工具使用
- Dex文件结构
- 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导出表中的某个函数
分为下面几步:
-
找到需要hook的函数的符号
-
使用这个符号运行时找到这个函数的地址
-
使用找到的地址对函数进行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的函数
分为以下这几步:
-
找到函数的偏移
-
找到模块基地址
-
用基地址加上偏移就是函数现在的地址
-
通过上一步的地址去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带有以下这写工具
-
frida 用来注入js的工具
-
frida-discover 用来发现一个程序的内部可以使用frida-trace跟踪的函数
-
frida-kill 一个杀死目标设备某个进程的工具
-
frida-ls-devices 列出所有的设备
-
frida-ps 显示目标设备的进程
-
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有两个重要的方法:findClass
和loadClass
,其中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.cc
的OpenDexFilesFromOat
来实现的。
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的过程如下:
-
art::ClassLinker::OpenDexFilesFromOat(char const*, char const*, std::__1::vector, std::__1::allocator >, std::__1::allocator, std::__1::allocator > > >*, std::__1::vector >*)
-
art::DexFile::GetChecksum(char const*, unsigned int*, std::__1::basic_string, std::__1::allocator >*)
-
art::DexFile::GetDexCanonicalLocation(char const*)
-
art::ClassLinker::FindOatFileContainingDexFileFromDexLocation(char const*, unsigned int const*, art::InstructionSet, std::__1::vector, std::__1::allocator >, std::__1::allocator, std::__1::allocator > > >*, bool*)
-
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 > > >*)
-
art::DexFilenameToOdexFilename(std::__1::basic_string, std::__1::allocator > const&, art::InstructionSet)
-
art::ClassLinker::FindOpenedOatFileFromOatLocation(std::__1::basic_string, std::__1::allocator > const&)
-
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 >*)
-
art::OatFile::OatFile(std::__1::basic_string, std::__1::allocator > const&, bool)
-
art::OatFile::ElfFileOpen(unix_file::FdFile*, unsigned char*, unsigned char*, bool, bool, std::__1::basic_string, std::__1::allocator >*)
-
void std::__1::vector >::__push_back_slow_path(art::DexFile const*&&)
-
art::OatFile::Setup(std::__1::basic_string, std::__1::allocator >*)
-
art::ClassLinker::VerifyOatWithDexFile(art::OatFile const*, char const*, unsigned int const*, std::__1::basic_string, std::__1::allocator >*)
-
art::ClassLinker::VerifyOatAndDexFileChecksums(art::OatFile const*, char const*, unsigned int, art::InstructionSet, std::__1::basic_string, std::__1::allocator >*)
-
art::ClassLinker::VerifyOatChecksums(art::OatFile const*, art::InstructionSet, std::__1::basic_string, std::__1::allocator >*)
-
art::OatFile::GetOatDexFile(char const*, unsigned int const*, bool) const
-
art::OatDexFile::OpenDexFile(std::__1::basic_string, std::__1::allocator >*) const
-
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 >*)
-
art::DexFile::DexFile(unsigned char const*, unsigned int, std::__1::basic_string, std::__1::allocator > const&, unsigned int, art::MemMap*, art::OatDexFile const*)
-
art::DexFile::CheckMagicAndVersion(std::__1::basic_string, std::__1::allocator >*) const
-
art::DexFile::Init(std::__1::basic_string, std::__1::allocator >*)
-
art::DexFile::GetMultiDexClassesDexName(unsigned int, char const*)
-
art::OatFile::GetOatDexFile(char const*, unsigned int const*, bool) const
目前的一些加固
目前存在的大概有四种类型的加固:
-
整体加固
-
指令抽取
-
VMP
-
java2c
这三种加固的后两种都是从第一种上面衍生出来的,整体加固的现在基本上找不到了,执行抽取的爱加密梆梆娜迦很多都用的指令抽取,vmp比较常见的就是360的加固,java2c是几维推出来的一种保护,据说是把smali代码转换成native代码。
整体加固
整体加固是将整个dex加密,在启动的时候解密然后内存加载,这是最初期的一种加固,很老版本的加固都是用的这种方式来加固的。对于这种加固,有很多种方案可以脱壳:
-
修改dex2oat,在dex2oat获取到dex的时候就把dex写到文件中
-
内存遍历,找dex,然后把dex写到文件中
-
hook从内存中加载dex的函数
OpenMemory
,然后把dex写到文件中。
第一代壳是很容易获取到完整的dex,并且也不需要做修复,丢到jeb就能看
指令抽取
指令抽取这一类壳就很蛋疼了,他在整体加固的基础上,把dex的指令全部写为空,然后在方法被调用的时候才将指令填充回去。对于这种加固,使用整体加固的脱壳方案得到的dex只包类方法等信息,并没有真正的指令。这种加固就需要先让他把指令填回去再去内存中把dex写到文件中。对于这种壳脱壳方案有:
-
强迫加载所有的类,加载完之后再从内存中dump
-
强迫加载所有类,加载完之后从运行时获取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
,针对一代壳,我们脱壳需要以下一些步骤:
-
hook
OpenMemory
函数(其中第一个参数为dex的地址,第二个参数为dex的大小。在6.0以上第一个参数是this指针,第二个为dex地址,第三个为dex大小) -
读取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关键的信息就是修复之后的。