Android第一二代壳的脱壳点分析

只讲ART下的脱壳,DVM太过时了


一、脱壳点分析

编写过Android第一二代加固壳的可以知道,无论是通过系统的类加载器加载的,还是通过自定义类加载器加载的,无一例外,最终都逃不过创建DexFile对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class DexFile {
...
// Raw header_item.
struct Header {
uint8_t magic_[8];
uint32_t checksum_; // See also location_checksum_
uint8_t signature_[kSha1DigestSize];
uint32_t file_size_; // size of entire file
uint32_t header_size_; // offset to start of next section
uint32_t endian_tag_;
uint32_t link_size_; // unused
uint32_t link_off_; // unused
uint32_t map_off_; // unused
uint32_t string_ids_size_; // number of StringIds
uint32_t string_ids_off_; // file offset of StringIds array
uint32_t type_ids_size_; // number of TypeIds, we don't support more than 65535
uint32_t type_ids_off_; // file offset of TypeIds array
uint32_t proto_ids_size_; // number of ProtoIds, we don't support more than 65535
uint32_t proto_ids_off_; // file offset of ProtoIds array
uint32_t field_ids_size_; // number of FieldIds
uint32_t field_ids_off_; // file offset of FieldIds array
uint32_t method_ids_size_; // number of MethodIds
uint32_t method_ids_off_; // file offset of MethodIds array
uint32_t class_defs_size_; // number of ClassDefs
uint32_t class_defs_off_; // file offset of ClassDef array
uint32_t data_size_; // unused
uint32_t data_off_; // unused
private:
DISALLOW_COPY_AND_ASSIGN(Header);
};
...
// 关键函数:返回DexFile对象对应的内存中的dex的起始地址
const byte* Begin() const {
return begin_;
}
// 关键函数:返回DexFile对象对应的内存中的dex的大小
size_t Size() const {
return size_;
}
...
// 关键变量:内存中的dex的起始地址
const byte* const begin_;
// 关键变量:内存中的dex的大小
const size_t size_;
}

因此,我们脱壳,也会围绕DexFile这个类(或者说是对象)来进行。

我们要明白,脱壳无非就是从内存中将解密状态下的dex整体dump下来(或者说写成文件存储下来)。而要想实现这一目的,就需要解决以下几个问题:

  1. 内存中dex的起始地址和大小,或者是DexFile对象,甚至是DexFile对象的首地址。
  2. 脱壳时机,只有正确的脱壳时机,才能够dump下解密状态下的dex,否则即使dump下来也只是加密状态下的dex。

对于问题一,我们跟踪dex文件的加载流程就可以很容易解决的。dex文件的加载流程具体见:

在链接文章中,拥有dex的起始地址和大小的有这么几个函数:

  • DexFile::Open(const uint8_t* base, size_t size, …)
  • OpenAndReadMagic(),因为该函数中会创建dex文件输入流,可以借此dump。
  • OpenCommon(const uint8_t* base, size_t size, …)
  • OpenMemory(const uint8_t* base, size_t size, …)
  • DexFile的构造函数
  • DexFileVerifier::Verify(const DexFile* dex_file, const uint8_t* begin, size_t size, …)
  • DexFileVerifier的构造函数

因此它们都可以作为脱壳点。当然了, 肯定不止这些函数,还有很多很多函数可以作为脱壳点,比如说那些返回值是DexFile类型的函数、与DexFile有关的函数、与cookie有关的函数等等。对这些函数在源码层面进行改写,就可以实现一个简单的脱壳机了。

问题二很简单,如何确定脱壳时机呢?根据脱壳程序中的代码逻辑,首先是壳程序先走一遍dex加载流程,然后再是源程序走一遍dex加载流程,虽然有的脱壳点会触发两次,即会dump两次(一次壳程序dex,一次源程序dex),但是没关系,因为dump下来保存的文件名是固定的,造成的结果就是重写覆盖,而后者就是我们想要的源程序dex。

二、脱壳方法

详见[原创]Android漏洞之战(11)——整体加壳原理和脱壳技巧详解-Android安全-看雪-安全社区|安全招聘|kanxue.com


参考:

[原创]拨云见日:安卓APP脱壳的本质以及如何快速发现ART下的脱壳点-Android安全-看雪-安全社区|安全招聘|kanxue.com

Android漏洞之战(11)——整体加壳原理和脱壳技巧详解

[原创]ART环境下dex加载流程分析及frida dump dex方案


Android第一二代壳的脱壳点分析
http://example.com/2023/12/12/Android安全/Android第一二代壳的脱壳点分析/
作者
gla2xy
发布于
2023年12月12日
许可协议