修改安卓源码:Art模式下的通用脱壳方法

*本文原创作者:x565178035,

前言

在dalvik时代有很多通用的脱壳方法,而现在的安卓版本早已不使用dalvik模式,很多方法便不再适用。那么,在 art 模式下有没有一种通用的一种方法呢?在 DEF CON 25 (2017) 黑客大会中,两位大牛 Avi Bashan 和 SlavaMakkaveev 给出了一个十分精彩的方法,他们仅在Android源码中添加了十几行代码,就干掉了主流的加壳保护工具(360,百度,梆梆加固)。然而他们给出的代码只能跑在 Android 6.0 的模拟器上,总觉得还是有点不完美(如果壳中有反模拟器的反调试手段岂不是就完了?),因此本文将介绍如何将大牛的代码稍作修改,将其编译到 LineageOS 的 Android 7.1 系统中,并且利用该代码脱壳。

0×01 基本原理

利用加壳程序的特性,因为任何加壳程序在程序运行时都会对加密的 dex 文件进行还原,因此两位大牛根据 art 模式下的 dex 加载方式,找到了两个通用的脱壳点,在加壳程序还原原始 dex 后,将内存中的数据 dump 到文件上。

修改安卓源码:Art模式下的通用脱壳方法

参考他们的 PPT,可以看到他们找到的脱壳点分别是DexFile的构造函数DexFile::DexFile(),以及OpenAndReadMagic()函数,之后我们就要在这两个函数中添加我们的脱壳代码。

更加详细的解释还请看大牛的演讲和他们的项目

0×02 修改源代码

两位大牛的源码托管在了GitHub, 但是他们用的系统还是 AOSP 的 Android 6.0.1 。由于我手头只有一个 Nexus 4, 不支持原生 Android 6.0 的源代码,就只能根据 patch 文件,在 Android 7.1.1 的 LineageOS 源码上做修改了。

首先,修改art/runtime/dex_file.cc中的DexFile对象的构造函数,具体改动如下:

*** art/runtime_bk/dex_file.cc  2018-03-19 17:23:00.587301100 +0800                               --- art/runtime/dex_file.cc     2018-03-25 20:37:34.655223300 +0800                               ***************                                                                                   *** 26,31 ****                                                                                    --- 26,32 ----                                                                                        #include <memory>                                                                                 #include <sstream>                                                                              + #include <fstream>                                                                                  #include "art_field-inl.h"                                                                        #include "art_method-inl.h"                                                                     ***************                                                                                   *** 440,445 ****                                                                                  --- 441,464 ----                                                                                          proto_ids_(reinterpret_cast<const ProtoId*>(base + header_->proto_ids_off_)),                     class_defs_(reinterpret_cast<const ClassDef*>(base + header_->class_defs_off_)),                  oat_dex_file_(oat_dex_file) {                                                             +                                                                                                 +   //------------------------------------------------------------------                          +   // DEX file unpacking                                                                         +   //------------------------------------------------------------------                          +                                                                                                 +   // let's limit processing file list                                                           +                                                                                                 +   LOG(WARNING) << "Dex File: Filename: "<< location;                                            +   if (location.find("/data/data/") != std::string::npos) {                                      +     LOG(WARNING) << "Dex File: OAT file unpacking launched";                                    +     std::ofstream dst(location + "__unpacked_oat", std::ios::binary);                           +     dst.write(reinterpret_cast<const char*>(base), size);                                       +     dst.close();                                                                                +   } else {                                                                                      +     LOG(WARNING) << "Dex File: OAT file unpacking not launched";                                +   }                                                                                             +   //------------------------------------------------------------------                          +                                                                                                     CHECK(begin_ != nullptr) << GetLocation();                                                        CHECK_GT(size_, 0U) << GetLocation();                                                             const uint8_t* lookup_data = (oat_dex_file != nullptr)  

可以看到,在加壳程序的脱壳过程运行时,会创建 DexFile 对象,它是我们的第一个脱壳点。在这里将 oat 中的 Dex 文件 dump 到应用文件夹下。

接下来修改art/runtime/base/file_magic.cc文件,修改其中的OpenAndReadMagic方法,它是我们第二个脱壳点:

*** art/runtime_bk/base/file_magic.cc   2018-02-07 12:08:22.606728900 +0800  --- art/runtime/base/file_magic.cc      2018-03-25 20:39:01.813272300 +0800  ***************  *** 19,24 ****  --- 19,26 ----    #include <fcntl.h>    #include <sys/stat.h>    #include <sys/types.h>  + #include <sys/mman.h>  + #include <unistd.h>      #include "base/logging.h"    #include "dex_file.h"  ***************  *** 33,38 ****  --- 35,71 ----        *error_msg = StringPrintf("Unable to open '%s' : %s", filename, strerror(errno));        return ScopedFd();      }  +  +   //------------------------------------------------------------------  +   // DEX file unpacking  +   //------------------------------------------------------------------  +  +   struct stat st;  +   // let's limit processing file list  +  +   LOG(WARNING) << "File_magic: Filename: "<<filename;  +   if (strstr(filename, "/data/data") != NULL) {  +     LOG(WARNING) << "File_magic: DEX file unpacking launched";  +     char* fn_out = new char[PATH_MAX];  +     strcpy(fn_out, filename);  +     strcat(fn_out, "__unpacked_dex");  +  +     int fd_out = open(fn_out, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);  +  +     if (!fstat(fd.get(), &st)) {  +       char* addr = (char*)mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd.get(), 0);  +       int ret=write(fd_out, addr, st.st_size);  +       ret+=1;  +       munmap(addr, st.st_size);  +     }  +  +     close(fd_out);  +     delete[] fn_out;  +   } else {  +     LOG(WARNING) << "File_magic: DEX file unpacking not launched";  +   }  +   //------------------------------------------------------------------  +      int n = TEMP_FAILURE_RETRY(read(fd.get(), magic, sizeof(*magic)));      if (n != sizeof(*magic)) {        *error_msg = StringPrintf("Failed to find magic in '%s'", filename);  

需要修改的地方仅此两处。

0×03 编译系统

接下来就是重新编译 Android 项目,缺乏这方面经验的读者可以参考我之前写的博文,当然英语好的同学可以直接阅读 LineageOS 的官方文档。值得注意的是,从源代码可以看到脱壳后的文件会放在/data/data的应用目录下,这一目录只有 root 和应用本身(准确的说,因为每个 android 应用都对应一个用户,这一用户对自己的应用目录有读写权限)可以访问,为了方便调试,我们需要在编译 LineageOS 时开启 root 功能,如果是其他系统的话,在刷完系统后也需要 root。

0×04 脱壳!

将编译好的文件刷到手机上后,进开发者模式,开 adb 调试和 root 许可

开启 adb:

修改安卓源码:Art模式下的通用脱壳方法

开启 Root:

修改安卓源码:Art模式下的通用脱壳方法

这里笔者准备了一个 apk,并用360进行加壳,值得注意的是,因为360在加壳程序默认有证书签名校验功能,如何绕过签名校验不在本文的说明范围,因此,如果读者是使用自己的 apk 放到360里面做加壳,请务必保证加壳后的程序签名使用的证书与加壳前的证书一致,或者直接关闭360的签名校验功能。这里展示加壳前的 apk 和加壳后的 apk:

加壳前:

修改安卓源码:Art模式下的通用脱壳方法

加壳后:

修改安卓源码:Art模式下的通用脱壳方法

大神给的自动化 bash 脚本是有一点问题的,我们手动操作一下,先把 DDMS 开着:

修改安卓源码:Art模式下的通用脱壳方法

从图中我们可以看到,我们的代码确实有运行,只不过目前还没有加壳的程序,所以不会 dump 。

接着,安装加壳后的 apk:

λ adb install goatdroid_sign_facaf6ed_enc_sign.apk  Success  

接着,打开加壳后的 apk,这里笔者选择手动打开,也可以使用命令的方式。

可以看到 DDMS 中的日志已经显示我们有文件被 dump 出来了。

修改安卓源码:Art模式下的通用脱壳方法

接下来就是用 adb 一顿操作,将 dump 的文件提取出来啦

λ adb shell  mako:/ $ su  mako:/ # cd data/data  # 这里的文件夹是原始app的包名  mako:/data/data # find . -name *__unpacked_*  ./org.owasp.goatdroid.fourgoats/.jiagu/classes.dex__unpacked_oat  ./org.owasp.goatdroid.fourgoats/.jiagu/classes2.dex__unpacked_oat  ./org.owasp.goatdroid.fourgoats/.jiagu/classes.dex__unpacked_dex  ./org.owasp.goatdroid.fourgoats/.jiagu/classes2.dex__unpacked_dex  mako:/data/data # cp -R /data/data/org.owasp.goatdroid.fourgoats/.jiagu/ /sdcard/jiagu  mako:/sdcard/jiagu # ls  classes.dex                classes2.dex  classes.dex__unpacked_dex  classes2.dex__unpacked_dex  classes.dex__unpacked_oat  classes2.dex__unpacked_oat  classes.oat                libjiagu.so  mako:/sdcard/jiagu # chmod 777 *  mako:/sdcard/jiagu # ls -al  total 3632  drwxrwx--x  2 root sdcard_rw    4096 2018-03-25 02:53 .  drwxrwx--x 12 root sdcard_rw    4096 2018-03-25 02:53 ..  -rw-rw----  1 root sdcard_rw       4 2018-03-25 02:53 .jgck  -rw-rw----  1 root sdcard_rw  264818 2018-03-25 02:53 classes.dex  -rw-rw----  1 root sdcard_rw  264818 2018-03-25 02:53 classes.dex__unpacked_dex  -rw-rw----  1 root sdcard_rw  771568 2018-03-25 02:53 classes.dex__unpacked_oat  -rw-rw----  1 root sdcard_rw 1917356 2018-03-25 02:53 classes.oat  -rw-rw----  1 root sdcard_rw       0 2018-03-25 02:53 classes2.dex  -rw-rw----  1 root sdcard_rw       0 2018-03-25 02:53 classes2.dex__unpacked_dex  -rw-rw----  1 root sdcard_rw   38856 2018-03-25 02:53 classes2.dex__unpacked_oat  -rw-rw----  1 root sdcard_rw  437056 2018-03-25 02:53 libjiagu.so  

这里我们可以看到 dump 出了两种文件:classes.dex__unpacked_dex, classes.dex__unpacked_oat,(还记得我们改代码的两个脱壳点吗?),哪个有用哪个没用是由加壳程序的还原过程决定,最简单的方法是把他们都拉出来放到JEB里面看一下。

λ adb pull /sdcard/jiagu/classes.dex__unpacked_dex  /sdcard/jiagu/classes.dex__unpacked_dex: 1 file pulled. 6.2 MB/s (264818 bytes in 0.041s)    λ adb pull /sdcard/jiagu/classes.dex__unpacked_oat  /sdcard/jiagu/classes.dex__unpacked_oat: 1 file pulled. 9.9 MB/s (771568 bytes in 0.074s)  

按经验,先看体积大的那个:

修改安卓源码:Art模式下的通用脱壳方法

LOL,成功脱壳了。

*本文原创作者:x565178035,