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

*本文原创作者:x565178035,本文属FreeBuf原创奖励计划,未经许可禁止转载

前言

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

0x01 基本原理

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

ppt.png

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

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

0x02 修改源代码

两位大牛的源码托管在了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);

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

0x03 编译系统

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

0x04 脱壳!

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

开启 adb:

adb.png

开启 Root:

root.png

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

加壳前:

before_pack.png

加壳后:

after_pack.png

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

ddms.png

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

接着,安装加壳后的 apk:

λ adb install goatdroid_sign_facaf6ed_enc_sign.apk
Success

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

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

ddms_unpacking.png

接下来就是用 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)

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

unpacked.png

LOL,成功脱壳了。

*本文原创作者:x565178035,本文属FreeBuf原创奖励计划,未经许可禁止转载

来源:freebuf.com 2018-03-30 10:00:02 by: x565178035

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论