*本文原创作者: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,可以看到他们找到的脱壳点分别是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:
开启 Root:
这里笔者准备了一个 apk,并用360进行加壳,值得注意的是,因为360在加壳程序默认有证书签名校验功能,如何绕过签名校验不在本文的说明范围,因此,如果读者是使用自己的 apk 放到360里面做加壳,请务必保证加壳后的程序签名使用的证书与加壳前的证书一致,或者直接关闭360的签名校验功能。这里展示加壳前的 apk 和加壳后的 apk:
加壳前:
加壳后:
大神给的自动化 bash 脚本是有一点问题的,我们手动操作一下,先把 DDMS 开着:
从图中我们可以看到,我们的代码确实有运行,只不过目前还没有加壳的程序,所以不会 dump 。
接着,安装加壳后的 apk:
λ adb install goatdroid_sign_facaf6ed_enc_sign.apk
Success
接着,打开加壳后的 apk,这里笔者选择手动打开,也可以使用命令的方式。
可以看到 DDMS 中的日志已经显示我们有文件被 dump 出来了。
接下来就是用 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)
按经验,先看体积大的那个:
LOL,成功脱壳了。
*本文原创作者:x565178035,本文属FreeBuf原创奖励计划,未经许可禁止转载