123下一页
返回列表 发新帖

拨云见日:安卓APP脱壳的本质以及如何快速发现ART下的脱...

[复制链接]

1

主题

2

帖子

32

积分

小学生

Rank: 2

金币
5
好评
3
贡献
0
发表于 2019-9-19 21:19:06 | 显示全部楼层 | 阅读模式
本帖最后由 hanbingle 于 2019-9-19 21:20 编辑

拨云见日:安卓APP脱壳的本质以及如何快速发现ART下的脱壳点

这个帖子我最先发在了看雪,这里也发一下吧,mt论坛最近刚注册的。
我在文章《FART:ART环境下基于主动调用的自动化脱壳方案》中简单对当前的几个脱壳方法进行了总结,然而并没有对每一种的具体原理进行分析。同时,在文章《FART正餐前甜点:ART下几个通用简单高效的dump内存中dex方法》中,通过对ART下类加载执行流程进行源码分析,又给出了几个新的ART下通用的简单高效的脱壳点。但是,我在写上述文章的时候,并没有对当前安卓平台加固APP的脱壳本质进行总结;同时,也没有总结给出快速定位发现安卓中通用脱壳点的方法。这里要感谢看雪的邀请,让我能够有幸成为看雪的一名讲师。我在看雪的线下培训班:《安卓高级研修班》课程上已经对当前网上公开的一些脱壳方法的原理进行了深入的分析,同时总结出了脱壳的本质以及如何快速发现ART环境下的脱壳点。相信听了我的《安卓高级研修班》的课程的大佬们目前也都已经发现了ART下新的通用脱壳点并付诸实际应用中,这也是我为什么说发现“海量”脱壳点的原因。这里,也相信在看懂了本文关于对脱壳的本质和快速发现ART下的脱壳点的方法后,大家也可以开始自己的“脱壳点”挖掘的工作中。这里先抛砖引玉,我在《安卓高级研修班》课程上也给出了ART下的一个未公开的通用脱壳点和脱壳镜像。关于这部分内容将在本文的第三节,作为本文的结束彩蛋送给大家。貌似MT论坛对帖子内容长度有限制,也不能贴链接,需要的就去看雪看原版吧。。。。。。

一、发现脱壳的本质
1、现有脱壳方法原理分析
首先看下Dalvik下hook dvmdexopenpartal、dexfileparse函数来实现脱壳的原理。
先看dexFileParse函数代码:
该函数主要就是对内存中的dex内容进行解析,最终返回一个DexFile结构体供虚拟机使用,函数的参数部分包含了内存中的dex文件的起始地址和大小,因此,在这里可以实现对app的脱壳。
OpenMemory函数在DexFile类中被调用,相关源码如下:
可以看到OpenMemory函数的参数中包含了内存中dex的起始位置和大小,因此,能够通过该函数进行脱壳。
在17年的DEF CON 25 黑客大会中,Avi Bashan 和 SlavaMakkaveev 提出的通过修改DexFile的构造函数DexFile::DexFile(),以及OpenAndReadMagic()函数来实现对加壳应用的内存中的dex的dump来脱壳技术,下面我们就来看这两个函数的具体代码。首先看DexFile类的构造函数的源码:
可以看到该构造函数的参数中依然是包含了内存中dex的起始位置和大小,因此,能够通过修改该函数进行脱壳。下面为添加的代码,在代码中只需要调用write将内存中的dex写入文件即完成脱壳。
接下来再看OpenAndReadMagic函数,该函数打开了dex文件并进行魔数的校验,源码如下:
因此添加如下代码,即可完成脱壳。
接下来分析在java层进行脱壳的典型案例:Fdex2的原理以及如何对Fdex2进行拓展使用。
先看Fdex2的关键代码部分:
可以看到,fdex2通过对类"java.lang.ClassLoader”中的loadClass进行hook,进而获取到该函数执行后返回的Class对象cls。对于"java.lang.ClassLoader”类中的loadClass函数是Android中加载Class流程中的一个关键函数,因此,Fdex2选择hook这个函数。本来在java中来操作指针以及dump内存是非常困难的事情,然而,Android系统源码却恰好提供了java.lang.Class类中的getDex函数,用于通过一个Class对象来获取到其所属的dex对象,以及com.android.dex.Dex类中的getBytes函数,用于通过一个dex对象来获取到该dex对象在内存中的字节流。正是有了这两个api的支持,才能够实现在java代码中实现对内存中的dex的dump。因此,便可以利用这两个api对Fdex2的脱壳原理进行拓展使用。比如,所有的app的组件信息都会在Manifest.xml文件中进行注册,那么我们就可以知道该加壳app中dex必然包含的一些类,如Activity类名、Service类名等,那么我们就可以通过使用各种hook技术完成对该app的hook,获取到这些类的Class对象,如首先通过反射获取到app最终的Classloader,然后再通过classloader的loadClass获取到某一个已知类的Class对象,然后再配合getDex和getBytes这两个api便可以实现对该类所属的dex的脱壳。
最后,再提一下根据Classloader来脱壳,看雪上也有对这个方法的介绍。通过对源码分析,可以看到app加载的所有的dex都依附在对应的Classloader中,那么,便可以通过一系列的反射最终获取到每一个dex文件的DexFile对象,具体流程可以首先通过反射,获取加固app最终的Classloader,然后再通过反射获取到BaseDexClassloader类中的DexPathList pathList对象,再然后获取pathList对象中的Element[]类型的dexElements对象,最后,便可以获取到Element类型对象中的DexFile dexFile对象,进而再获取到DexFile对象中的关键元素: mCookie,那么接下来就可以通过mCookie在C/C++层完成对dex的dump操作。
2、总结Android脱壳的本质
上面对当前的一些主流的脱壳方法进行了简单的原理分析。可以得出结论:Android APP脱壳的本质就是对内存中处于解密状态的dex的dump。首先要区分这里的脱壳和修复的区别。这里的脱壳指的是对加固apk中保护的dex的整体的dump,不管是函数抽取、dex2c还是vmp壳,首要做的就是对整体dex的dump,然后再对脱壳下来的dex进行修复。要达到对apk的脱壳,最为关键的就是准确定位内存中解密后的dex文件的起始地址和大小。那么这里要达成对apk的成功脱壳,就有两个最为关键的要素:
1、内存中dex的起始地址和大小,只有拿到这两个要素,才能够成功dump下内存中的dex
2、脱壳时机,只有正确的脱壳时机,才能够dump下明文状态的dex。否则,时机不对,及时是正确的起始地址和大小,dump下来的也可能只是密文。
其中,通过上一小节对当前的一些脱壳方法的原理分析可以看到,Dalvik下的脱壳都是围绕着一个至关重要的结构体:DexFile结构体,而到了ART以后,便演化为了DexFile类。可以说,在ART下只要获得了DexFile对象,那么我们就可以得到该dex文件在内存中的起始地址和大小,进而完成脱壳。下面是DexFile结构体的定义以及DexFile类的定义源码:
Dalvik下DexFile结构体:

ART下DexFile类,代码较长,只贴出片段吧:
可以看到,ART下DexFile类中定义了两个关键的变量: begin_、size_以及用于获取这两个变量的Begin()和Size()函数。这两个变量分别代表着当前DexFile对象对应的内存中的dex文件加载的起始位置和大小。当然也有其他的方法来获取到内存中的dex加载的起始位置和大小。可以说,只要有了这两个值,我们就可以完成对这个dex的dump。
二、ART下基于关键字的快速定位脱壳点方法
在上一小节,我对当前Android加固app脱壳的本质进行了总结。其中,ART下影响脱壳的关键的一个类就是DexFile,那么我们便可以围绕这个类,实现在Android庞大的系统源码中快速定位脱壳点,从而能够找到“海量”的脱壳点。这里再总结给出两种快速定位脱壳点的方法。
1、直接查找法
这里的直接查找法就是指以DexFile为关键字,在庞大的源码库中检索定位可能的脱壳点。如参数中出现DexFile类型的、返回值为DexFile类型的、函数流程中出现DexFile类型的源码位置。在获取到DexFile对象以后,然后再通过该对象的Begin()和Size()函数获取到该DexFile对象对应的内存中的dex的起始地址和大小即可进行dex的dump。如下图的检索过程,可以轻松定位出网上公开的基于dex2oat流程进行脱壳的脱壳点,同时,也可以看到那个脱壳点只是dex2oat流程中的一个脱壳点而已。
2、间接查找法
间接法就是指以DexFile为出发点,寻找能够间接获取到DexFile对象的。如通过ArtMethod对象的getDexFile()获取到ArtMethod所属的DexFile对象的这种一级间接法等;然后再在海量源码中以ArtMethod为关键字进行检索,检索那些参数中出现ArtMethod类型的、返回值为ArtMethod类型的、函数流程中出现ArtMethod类型的源码位置;
再比如通过Thread的getCurrentMethod()函数首先获取到ArtMethod或者通过ShadowFrame的getMethod获取到ArtMethod对象,然后再通过getDexFile获取到ArtMethod对象所属的DexFile的二级间接法以及通过ShadowFrame对象的GetMethod()函数获取到当前栈中执行的ArtMethod对象,然后再获取DexFile对象等等的二级间接法。
好了,上面已经给出了如何快速在庞大的源码库中定位可能的脱壳点的方法了,大家就可以开始自己的“挖宝”行动了!接下来,便进入了本文的彩蛋部分了。
三、彩蛋:送出一个未公开的脱壳点1、原理分析
众所周知,ART下引入了dex2oat来对dex进行编译,生成每一个java函数对应的native代码,来提高运行效率。但是,dex2oat并不是对dex中的所有函数进行编译,通过对dex2oat的源码进行分析,最终可以到达CompilerDriver类的CompileMethod函数,可以看到dex2oat对dex进行编译的过程中是按照函数粒度进行编译的。
可以看到在进行编译前进行了判断,最终可以发现,dex2oat对类的初始化函数并没有进行编译,那么也就是说类的初始化函数始终运行在ART下的inpterpreter模式,那么最终必然进入到interpreter.cc文件中的Execute函数,进而进入ART下的解释器解释执行。因此,我们便可以选择在Execute或者其他解释执行流程中的函数中进行dex的dump操作。事实上,当前一些壳通过阻断dex2oat的编译过程,导致了不只是类的初始化函数在解释模式下执行,也让类中的其他函数也运行在解释模式下。


2、实现代码
下面进入到代码部分了,最终我们只需要在Execute函数中添加一些代码即可,修改后的Execute函数代码如下:
  1. static inline JValue Execute(Thread* self, const DexFile::CodeItem* code_item,
  2.                              ShadowFrame& shadow_frame, JValue result_register) {
  3.       //addcodestart
  4.     char *dexfilepath=(char*)malloc(sizeof(char)*1000);   
  5.     if(dexfilepath!=nullptr)
  6.     {
  7.     ArtMethod* artmethod=shadow_frame.GetMethod();
  8.     const DexFile* dex_file = artmethod->GetDexFile();
  9.     const uint8_t* begin_=dex_file->Begin();  // Start of data.
  10.     size_t size_=dex_file->Size();  // Length of data.
  11.     int size_int_=(int)size_;
  12.     int fcmdline =-1;
  13.     char szCmdline[64]= {0};
  14.     char szProcName[256] = {0};
  15.     int procid = getpid();
  16.     sprintf(szCmdline,"/proc/%d/cmdline", procid);
  17.     fcmdline = open(szCmdline, O_RDONLY,0644);
  18.     if(fcmdline >0)
  19.     {
  20.         read(fcmdline, szProcName,256);
  21.         close(fcmdline);
  22.     }
  23.             
  24.     if(szProcName[0])
  25.     {
  26.             memset(dexfilepath,0,1000);               
  27.             sprintf(dexfilepath,"/sdcard/%s_%d_dexfile.dex",szProcName,size_int_);     
  28.             int dexfilefp=open(dexfilepath,O_RDONLY,0666);
  29.             if(dexfilefp>0){
  30.                                 close(dexfilefp);
  31.                                 dexfilefp=0;
  32.                                        
  33.                             }else{
  34.                                         int fp=open(dexfilepath,O_CREAT|O_APPEND|O_RDWR,0666);
  35.                                         if(fp>0)
  36.                                         {
  37.                                             write(fp,(void*)begin_,size_);
  38.                                             fsync(fp);
  39.                                             close(fp);  
  40.                                             }  
  41.                              
  42.                                 }
  43.     }

  44.     if(dexfilepath!=nullptr)
  45.     {
  46.         free(dexfilepath);
  47.         dexfilepath=nullptr;
  48.     }                        
  49.    }
  50. //addcodeend
  51.   DCHECK(!shadow_frame.GetMethod()->IsAbstract());
  52.   DCHECK(!shadow_frame.GetMethod()->IsNative());
  53.   shadow_frame.GetMethod()->GetDeclaringClass()->AssertInitializedOrInitializingInThread(self);



  54.   bool transaction_active = Runtime::Current()->IsActiveTransaction();
  55.   if (LIKELY(shadow_frame.GetMethod()->IsPreverified())) {
  56.     // Enter the "without access check" interpreter.
  57.     if (kInterpreterImplKind == kSwitchImpl) {
  58.       if (transaction_active) {
  59.         return ExecuteSwitchImpl<false, true>(self, code_item, shadow_frame, result_register);
  60.       } else {
  61.         return ExecuteSwitchImpl<false, false>(self, code_item, shadow_frame, result_register);
  62.       }
  63.     } else {
  64.       DCHECK_EQ(kInterpreterImplKind, kComputedGotoImplKind);
  65.       if (transaction_active) {
  66.         return ExecuteGotoImpl<false, true>(self, code_item, shadow_frame, result_register);
  67.       } else {
  68.         return ExecuteGotoImpl<false, false>(self, code_item, shadow_frame, result_register);
  69.       }
  70.     }
  71.   } else {
  72.     // Enter the "with access check" interpreter.
  73.     if (kInterpreterImplKind == kSwitchImpl) {
  74.       if (transaction_active) {
  75.         return ExecuteSwitchImpl<true, true>(self, code_item, shadow_frame, result_register);
  76.       } else {
  77.         return ExecuteSwitchImpl<true, false>(self, code_item, shadow_frame, result_register);
  78.       }
  79.     } else {
  80.       DCHECK_EQ(kInterpreterImplKind, kComputedGotoImplKind);
  81.       if (transaction_active) {
  82.         return ExecuteGotoImpl<true, true>(self, code_item, shadow_frame, result_register);
  83.       } else {
  84.         return ExecuteGotoImpl<true, false>(self, code_item, shadow_frame, result_register);
  85.       }
  86.     }
  87.   }
  88. }
复制代码

3、测试效果
在修改完代码并完成脱壳镜像的编译后(这里提供一个已经编译好的nexus5的6.0镜像供大家体验,就可以愉快的开始测试脱壳效果了。这里要注意,我在代码中对dex直接保存到了SD卡的根目录下,因此在安装完app后,记得到设置中授予app读写SD卡权限,不然无法写入脱壳的dex。这里我就随便选择了几个最新版的加固厂商的加固app进行测试了,下面是测试效果:
数字壳测试效果:

可以看到能够dump成功:

某梆脱壳效果:

好了,就到这里吧,大家可以下载镜像测试其他的壳。



已有3人评分好评 金币 理由
ROX + 1
梦__玩 + 1 + 1
一脸懵逼的小白 + 1 + 1 感谢分享

查看全部评分 总评分:好评 +3  金币 +2 

回复

使用道具 举报

13

主题

750

帖子

1780

积分

高中生

Rank: 4

金币
507
好评
2
贡献
1
发表于 2019-9-19 21:23:37 来自手机  | 显示全部楼层
看雪的大佬
回复

使用道具 举报

13

主题

750

帖子

1780

积分

高中生

Rank: 4

金币
507
好评
2
贡献
1
发表于 2019-9-19 21:23:54 来自手机  | 显示全部楼层
666啊
回复

使用道具 举报

13

主题

750

帖子

1780

积分

高中生

Rank: 4

金币
507
好评
2
贡献
1
发表于 2019-9-19 21:24:27 来自手机  | 显示全部楼层
有时间和哈大切磋一下吗
回复

使用道具 举报

13

主题

750

帖子

1780

积分

高中生

Rank: 4

金币
507
好评
2
贡献
1
发表于 2019-9-19 21:28:23 来自手机  | 显示全部楼层
哈大,@Hasky
回复

使用道具 举报

9

主题

351

帖子

1898

积分

高中生

Rank: 4

金币
296
好评
0
贡献
0
发表于 2019-9-19 21:35:03 来自手机  | 显示全部楼层
高手高手高高手
回复

使用道具 举报

5

主题

1654

帖子

3737

积分

大学生

Rank: 5Rank: 5

金币
2203
好评
0
贡献
0
发表于 2019-9-19 21:38:10 来自手机  | 显示全部楼层
看起来不错
回复

使用道具 举报

0

主题

506

帖子

2417

积分

大学生

Rank: 5Rank: 5

金币
839
好评
0
贡献
0

考神

发表于 2019-9-19 21:40:16 来自手机  | 显示全部楼层
大佬就是大佬
回复

使用道具 举报

6

主题

1055

帖子

2574

积分

大学生

超级无敌巨无霸黄金钻石头衔

Rank: 5Rank: 5

金币
516
好评
6
贡献
0

考神MT论坛最佳新人MT论坛帅哥

发表于 2019-9-19 22:02:21 来自手机  | 显示全部楼层
看起来就像是个大佬
回复

使用道具 举报

55

主题

747

帖子

6425

积分

硕士生

Rank: 6Rank: 6

金币
523
好评
9
贡献
0
发表于 2019-9-19 22:03:29 来自手机  | 显示全部楼层
打这么多字辛苦你了
回复

使用道具 举报

4

主题

85

帖子

490

积分

初中生

Rank: 3Rank: 3

金币
29
好评
1
贡献
0
QQ
发表于 2019-9-19 22:05:39 来自手机  | 显示全部楼层
这么深奥的帖子,估计没几个人看懂,见议MT论坛发易懂带图文,解说的帖子
回复

使用道具 举报

13

主题

651

帖子

2536

积分

大学生

Rank: 5Rank: 5

金币
827
好评
8
贡献
4
发表于 2019-9-19 22:17:43 来自手机  | 显示全部楼层
大佬辛苦了
回复

使用道具 举报

1

主题

858

帖子

2934

积分

大学生

Rank: 5Rank: 5

金币
969
好评
17
贡献
0
发表于 2019-9-19 22:18:32 来自手机  | 显示全部楼层
感谢大佬分享 学习学习
回复

使用道具 举报

6

主题

307

帖子

684

积分

初中生

Rank: 3Rank: 3

金币
152
好评
0
贡献
0
发表于 2019-9-19 22:30:25 来自手机  | 显示全部楼层
谢谢
回复

使用道具 举报

9

主题

458

帖子

4442

积分

大学生

Rank: 5Rank: 5

金币
213
好评
8
贡献
0

考神

发表于 2019-9-19 22:38:16 来自手机  | 显示全部楼层
圣火迪莫 发表于 2019-9-19 21:24
有时间和哈大切磋一下吗

比不了,不是一个层次的
回复

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表