mirror of
https://github.com/feicong/rom-course.git
synced 2025-11-05 18:33:52 +00:00
第八章内容优化
This commit is contained in:
parent
fb50cffe76
commit
01963405e6
@ -1,57 +1,69 @@
|
|||||||
# 第八章 软件壳
|
# 第八章 软件壳
|
||||||
|
|
||||||
熟悉类的加载流程后可以利用加载的原理来开发辅助功能,在逆向中,比较典型的功能就是脱壳,加壳就是对类中代码的保护,而脱壳则是还原出被保护的代码,除此之外,`Art Hook`的实现也是和类的加载,函数的执行的原理息息相关的。在这一章中,将对加壳和脱壳进行详细的介绍,在最后将通过上一章中学习到的类加载原理,简单实现自动脱壳机。
|
熟悉类的加载流程后,可以利用加载的原理来开发辅助功能。在逆向工程中,比较典型的功能是脱壳。加壳是对类中代码进行保护,而脱壳则是还原出被保护的代码。此外,Art Hook(一种针对Android运行时环境的Hook技术)的实现也与类加载和函数执行的原理密切相关。
|
||||||
|
|
||||||
## 8.1 壳,加壳,脱壳
|
在这一章中,将详细介绍加壳和脱壳,并通过前面学习到的类加载原理简单实现自动脱壳机。
|
||||||
|
|
||||||
`Android`的`APK`文件实际上是一种压缩文件格式,它包含了应用程序的二进制代码、资源文件、清单文件等。在安装应用程序之前,系统会将`APK`文件解压缩并安装到设备上。在`APK`文件中,应用程序的二进制代码通常是以`DEX(Dalvik Executable)`格式存储的。`DEX`格式是一种针对移动设备优化的字节码格式,与`Java`虚拟机`(JVM)`的字节码格式有所不同。由于`DEX`格式采用了特殊的指令集和数据结构,使得反编译工具可以轻松地将其转换为可读性较高的`Java`代码。此外,许多反编译工具还可以通过反汇编和反混淆等技术来还原出源代码,因此为了防止应用程序的关键代码轻易被暴露,开发人员会采取一系列的手段来保护代码。
|
|
||||||
|
|
||||||
`Android`常规对代码保护的方案主要包括以下几种:
|
## 8.1 加壳与脱壳
|
||||||
|
|
||||||
1. 混淆(`Obfuscation`):通过重命名类、方法、变量等标识符来隐藏程序逻辑,使得反编译后的代码难以被理解和分析。
|
`Android`的`APK`文件实际上是一种压缩文件格式,其中包含了应用程序的二进制代码、资源文件和清单文件等。在安装应用程序之前,系统会将`APK`文件解压缩并安装到设备上。
|
||||||
2. 压缩(`Compression`):将应用程序的二进制代码压缩成较小的体积,防止恶意用户逆向工程和复制源代码。
|
|
||||||
3. 签名(`Signing`):在应用程序发布前,使用数字证书对应用程序进行签名,确保其完整性和来源可信。
|
|
||||||
4. 加固(`Hardening`):在应用程序内部添加额外的安全保护机制,如代码加密、反调试、反注入等,增强应用程序的抵御能力。
|
|
||||||
5. 动态加载(`Dynamic Loading`):将敏感的代码和资源文件放置在远程服务器上,在运行时动态加载到本地设备,以防止被攻击者轻易访问和修改。
|
|
||||||
|
|
||||||
### 8.1.1 什么是加壳
|
在`APK`文件中,应用程序的二进制代码通常以`DEX(Dalvik Executable)`格式存储。与`Java 虚拟机 (JVM)`的字节码格式不同,`DEX`格式是针对移动设备优化过的字节码格式。由于`DEX`格式采用了特殊的指令集和数据结构,使得反编译工具可以轻松地将其转换为可读性较高的 `Java 代码`。
|
||||||
|
|
||||||
加壳`(Packing)`就是一种应用程序加固手段之一。它将原始应用程序二进制代码嵌入到一个特殊的外壳中,通过修改程序入口和解密算法等方式,增加反调试、反逆向、防篡改等安全机制,提高应用程序的安全性。
|
然而, 为了防止应用程序关键代码被轻易暴露出来, 开发人员会采取一系列手段来保护代码. 反编译工具还可以通过反汇编和反混淆等技术来还原源代码。
|
||||||
|
|
||||||
加壳的目的是使应用程序难以被攻击者分析和修改,从而提高应用程序的抵御能力。但是,加壳也会带来一些负面影响,如增加应用程序的体积、降低应用程序运行效率、可能引入新的安全漏洞等。
|
`Android`常见的代码保护的方案主要包括以下几种:
|
||||||
|
|
||||||
常见的加壳壳包括:
|
1. 混淆(Obfuscation):通过重命名类、方法、变量等标识符来隐藏程序逻辑,使得反编译后的代码难以被理解和分析。
|
||||||
|
2. 压缩(Compression):将应用程序的二进制代码压缩成较小体积,防止恶意用户进行逆向工程和复制源代码。
|
||||||
|
3. 签名(Signing):在应用程序发布前,使用数字证书对应用程序进行签名,确保其完整性和来源可信。
|
||||||
|
4. 加固(Hardening):在应用程序内部添加额外的安全保护机制,如代码加密、反调试、反注入等,增强应用程序的抵御能力。
|
||||||
|
5. 动态加载(Dynamic Loading):将敏感的代码和资源文件放置在远程服务器上,在运行时动态加载到本地设备,以防止被攻击者轻易访问和修改。
|
||||||
|
|
||||||
|
|
||||||
1. `DexProtector`:支持`Android`和`iOS`平台,可以对`Java`代码和`NDK`库进行加固。其特点是支持多种代码混淆技术,同时还提供了反调试、防止`Hook`攻击、反模拟器等多种安全机制。
|
### 8.1.1 软件加壳技术
|
||||||
2. `360`加固:支持`Android`和`iOS`平台,采用自己研发的加固壳技术,可以对`Java`代码和`C/C++`库进行加固,同时还提供了反调试、反逆向、防篡改等多种安全机制。
|
|
||||||
3. `Bangcle`:一款国内著名的加壳工具,支持`Android`和`iOS`平台,提供了多种加固壳方案,如`DexShell、SOShell、`加密资源等,同时还支持反调试、反注入等多种安全机制。
|
|
||||||
4. `APKProtect`:一款功能强大的加壳工具,支持`Android`平台,可以对`Java`代码和`Native`库进行加固,支持多种加固方式,如代码混淆、`Resource Encryption、Anti-debugging`等,同时还提供了反反编译、反调试等多种安全机制。
|
|
||||||
|
|
||||||
这些加壳工具都有不同的特点和适用场景,开发者可以根据实际需求选择合适的加壳壳进行加固。需要注意的是,加壳只是一种安全加固手段,不能取代其他常规的安全措施,并且可能带来一些负面影响,如体积增大、运行效率下降等。
|
加壳(Packing)是应用程序加固的一种手段之一。它将原始应用程序二进制代码嵌入到一个特殊的外壳中,通过修改程序入口和解密算法等方式,增加反调试、反逆向、防篡改等安全机制,提高应用程序的安全性。
|
||||||
|
|
||||||
### 8.1.2 如何脱壳
|
加壳的目的是使应用程序难以被攻击者分析和修改,从而提高应用程序的抵御能力。然而,加壳也会带来一些负面影响,例如增加应用程序体积、降低运行效率,并可能引入新的安全漏洞。
|
||||||
|
|
||||||
加壳的本质就是对`DEX`格式的`java`字节码进行保护避免被攻击者分析和修改,而脱壳就是通过分析壳的特征和原理,将被壳保护的`java`字节码还原出来,通常用于逆向分析、恶意代码分析等领域。
|
常见的加壳壳包括:
|
||||||
|
|
||||||
脱壳常用的几个步骤如下。
|
1. DexProtector:支持Android和iOS平台,可以对Java代码和NDK库进行加固。其特点是支持多种代码混淆技术,同时还提供了反调试、防止Hook攻击、反模拟器等多种安全机制。
|
||||||
|
2. 360加固:支持Android和iOS平台,采用自己研发的加固壳技术,可以对Java代码和C/C++库进行加固,同时还提供了反调试、反逆向、防篡改等多种安全机制。
|
||||||
|
3. Bangcle:一款国内著名的加壳工具,支持Android和iOS平台,提供了多种加固壳方案,如DexShell、SOShell、加密资源等,并且还支持反调试、反注入等多种安全机制。
|
||||||
|
4. APKProtect:一款功能强大的加壳工具,支持Android平台,可以对Java代码和Native库进行加固,并且支持多种方式的加固(如代码混淆、Resource Encryption、Anti-debugging),同时还提供了反反编译、反调试等多种安全机制。
|
||||||
|
|
||||||
1. 静态分析:通过对样本进行静态分析,获取样本中的壳的特征,加密算法、解密函数等信息,为后续的动态分析做好准备。
|
这些加壳工具都有不同的特点和适用场景。开发者可以根据实际需求选择合适的加壳工具来进行应用程序保护。需要注意的是,在使用任何一种加壳工具时,都应该结合其他常规的安全措施,并且要意识到加壳可能会带来一些负面影响,如增大应用程序体积、降低运行效率等。
|
||||||
2. 动态分析:在调试器或`hook`工具的帮助下,运行加密的程序,跟踪程序的执行流程,并尝试找到解密或解压的位置,获取加密或压缩前的原始数据。
|
|
||||||
3. 重构代码:通过分析反汇编代码,重新构建可读性高且易于理解的代码,以便更好地理解样本的行为。
|
|
||||||
|
|
||||||
在脱壳的过程中,会面临开发者为保护代码而添加的各类的防护措施,例如代码混淆、反调试、`ROM`检测、`root`检测、`hook`注入检测等加固手段,而这个博弈的过程就是一种攻防对抗。而`ROM`脱壳将从另外一个层面解决部分对抗的问题。
|
|
||||||
|
|
||||||
## 8.2 壳的特征
|
### 8.1.2 脱壳方法
|
||||||
|
|
||||||
早期的`Android`应用程序很容易被反编译和修改,因此一些开发者会使用简单的壳来保护自己的应用程序。这些壳主要是基于`Java`层的代码混淆和加密,以及`Native`层的简单加密。
|
加壳的本质就是对DEX格式的Java字节码进行保护,以避免被攻击者分析和修改。而脱壳则是通过分析壳的特征和原理,将被壳保护的Java字节码还原出来,通常用于逆向分析、恶意代码分析等领域。
|
||||||
|
|
||||||
但是单纯的混淆和加密很难保障代码的安全性,第一代壳,动态加载壳就诞生了,这时的思想主要还是将整个`DEX`进行加密保护,在运行期间才会解密还原`DEX`文件,再动态加载运行原文件。但是这样依赖`Java`的动态加载机制,非常容易被攻击,直接通过加载流程就能拿到被保护的数据,这种壳的特征非常明显,当反编译解析时,只能看到壳的代码,找不到任何`Activity`相关的处理,这种情况就是动态加载壳了。
|
脱壳常用的几个步骤如下:
|
||||||
|
|
||||||
随后第二代壳,指令抽取壳就出现了,对`Java`层的代码进行函数粒度的保护,第一代的思想是将整个`DEX`保护起来,而第二代的思想就是只需要保护关键的函数即可。将原始`DEX`中需要保护的函数内部的`codeitem`进行清空,将真正的函数内容加密保护,存放在其他地方,只有当这个函数真正执行时,才通过解密函数将其还原填充回去,达到让其能正常执行的目的,有些指令抽取壳甚至会在函数执行完成后,重新将`codeitem`清空。否则执行过一次的函数指令将很容易被还原出来。这种壳的特征可以通过函数内容的特征来分辨,例如一些空的函数,查看`smali`指令发现内部有大量的`nop`空指令,这种情况就是指令抽取壳
|
1. 静态分析:通过对样本进行静态分析,获取样本中壳的特征、加密算法、解密函数等信息,为后续的动态分析做好准备。
|
||||||
|
2. 动态分析:在调试器或hook工具的帮助下,运行加密程序,并跟踪程序执行流程,并尝试找到解密或解压缩位置,以获取加密或压缩前的原始数据。
|
||||||
|
3. 重构代码:通过反汇编代码进行分析,并重新构建可读性高且易于理解的代码结构,以更好地理解样本行为。
|
||||||
|
|
||||||
随着攻防的对抗不断的升级,第二代壳也无法带来安全保障,第三代壳,指令转换壳诞生。指令转换壳的思想和指令抽取是相同的,对具体的函数进行保护,但是在第二代壳的缺陷上进行了优化,由于指令抽取壳最终依然还是一个`Java`函数的调用,最终还是要将指令回填后进行执行的。不管是如何保护,只要在获取到执行过程中的`codeitem`,就能轻易的修复为真实的`DEX`文件。而指令转换壳则是将被保护的函数转换为`native`,将函数的指令集解析成中间码,中间码会被映射到自定义的虚拟机进行解析执行。这样就不会走`Android`提供的指令执行流程了。但是这样也会导致函数执行过慢,以及一些兼容问题,这类壳的特征也非常明显,就是`native`化函数。
|
在脱壳过程中,可能会面临开发者添加各种防护措施来保护其代码。例如代码混淆、反调试、ROM检测、root检测、hook注入检测等加固手段。这个博弈过程就是一种攻防对抗。另外,ROM脱壳可以从另一个层面解决部分对抗问题。
|
||||||
|
|
||||||
### 8.3 动态加载壳的实现
|
|
||||||
|
## 8.2 软件壳特征
|
||||||
|
|
||||||
|
早期的 `Android`应用程序很容易被反编译和修改,因此一些开发者会使用简单的壳来保护自己的应用程序。这些壳主要是基于`Java`层的代码混淆和加密,以及 `Native`层的简单加密。
|
||||||
|
|
||||||
|
但是单纯的混淆和加密很难保障代码的安全性,第一代壳——动态加载壳就诞生了。这时候思想主要还是将整个DEX进行加密保护,在运行期间才会解密还原DEX文件,并动态加载运行原文件。然而,这样依赖Java的动态加载机制非常容易受到攻击,因为攻击者可以直接通过加载流程获取被保护数据。当反编译解析时,只能看到壳的代码,并找不到任何与`Activity`相关处理有关联的内容。这种情况就是所谓的动态加载壳。
|
||||||
|
|
||||||
|
随后出现了第二代指令抽取壳。它对Java层代码进行函数粒度上的保护。相较于第一代思想将整个DEX保护起来,第二代更注重只需保护关键函数即可达到目标。该方法清空原始DEX中需要保护函数内部`codeitem`部分,将真正的函数内容加密保护并存放在其他地方。只有当这些函数真正执行时,才通过解密函数将其还原填充回去,以实现正常执行。某些指令抽取壳甚至会在函数执行完成后重新清空codeitem。否则,已经执行过的函数指令很容易被还原出来。这种壳可通过查看函数内容特征进行鉴别,例如一些空的函数,在查看smali指令时会发现内部有大量nop空指令。这种情况就是所谓的指令抽取壳。
|
||||||
|
|
||||||
|
随着攻防对抗不断升级,第二代壳也无法提供足够安全保障。于是第三代——指令转换壳诞生了。它与指令抽取思想相同,对具体的函数进行保护,并优化了第二代壳存在的缺陷。由于指令抽取最终依然是一个Java函数调用,并需要回填和执行指令集合代码项(codeitem)。无论如何保护措施采用何种方式,在获取到正在执行过程中的codeitem后都能轻松修复为真实DEX文件。
|
||||||
|
而指令转换壳则将受到保护断裂处理的函数转换为native代码形式。它将函数的指令集解析为中间码,并通过自定义虚拟机进行解析和执行,而不会使用Android提供的指令执行流程。然而,这样也会导致函数执行速度变慢以及一些兼容性问题。这类壳有明显特征,即将函数"native化"。
|
||||||
|
|
||||||
|
|
||||||
|
## 8.3 动态加载壳的实现
|
||||||
|
|
||||||
动态加载壳是一种常见的代码保护技术,它通过在程序运行时动态加载壳来保护应用程序。下面是一般情况下动态加载壳的流程:
|
动态加载壳是一种常见的代码保护技术,它通过在程序运行时动态加载壳来保护应用程序。下面是一般情况下动态加载壳的流程:
|
||||||
|
|
||||||
@ -63,13 +75,14 @@
|
|||||||
6. 壳程序会根据被保护程序的程序入口点开始执行被保护程序的代码。
|
6. 壳程序会根据被保护程序的程序入口点开始执行被保护程序的代码。
|
||||||
7. 被保护程序运行时的系统调用和`DLL`库的调用等操作,都会由壳程序处理并返回结果给被保护程序。同时,壳程序可能会进行一些额外的安全检查,例如防止调试、防止反汇编、防止破解等操作。
|
7. 被保护程序运行时的系统调用和`DLL`库的调用等操作,都会由壳程序处理并返回结果给被保护程序。同时,壳程序可能会进行一些额外的安全检查,例如防止调试、防止反汇编、防止破解等操作。
|
||||||
|
|
||||||
|
|
||||||
### 8.3.1 加固过程
|
### 8.3.1 加固过程
|
||||||
|
|
||||||
接下来我们看一个简单的动态加载壳的实现的案例,下载地址:`https://github.com/zhang-hai/apkjiagu`。
|
接下来我们看一个简单的动态加载壳的实现案例,下载地址:[https://github.com/zhang-hai/apkjiagu](https://github.com/zhang-hai/apkjiagu)。
|
||||||
|
|
||||||
该案例主要分为三个部分,分别是:壳程序、待保护的程序、加壳程序。大致流程是使用加壳工具解析待保护的程序,然后将壳程序加入。最后重新生成新的`apk`,重新签名。接下来先了解如何使用该工具来保护`Android`应用。
|
该案例主要分为三个部分,分别是:壳程序、待保护的程序、加壳程序。大致流程是使用加壳工具解析待保护的程序,然后将壳程序加入。最后重新生成新的APK,并重新签名。接下来先了解如何使用该工具来保护Android应用。
|
||||||
|
|
||||||
首先准备一个需要被保护的`apk`,这里直接使用前文中测试动态加载系统内置`jar`包的`APP`作为样例,代码如下。
|
首先准备一个需要被保护的APK,这里直接使用前文中测试动态加载系统内置JAR包的APP作为样例,代码如下:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
package cn.mik.myservicedemo;
|
package cn.mik.myservicedemo;
|
||||||
@ -121,13 +134,13 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
将上面的代码编译后,输出`app-debug.apk`的结果,这个就是需要待保护的目标。然后开始准备加固的环境。
|
将上面的代码编译后,输出`app-debug.apk`的结果,这个就是需要待保护的目标。然后开始准备加固的环境。
|
||||||
|
|
||||||
在加固过程使用到的命令:`dx`、`apktool`、`zipalign`、`apksigner`。其中`dx、zipalign、apksigner`工具在`sdk`的`build-tools`目录中,例如本地的路径`Android\Sdk\build-tools\30.0.2`,将该工具目录加入环境变量`PATH`中。
|
在加固过程使用到的命令:`dx`、`apktool`、`zipalign`、`apksigner`。其中`dx、zipalign、apksigner`工具在`sdk`的`build-tools`目录中,例如本地的路径`Android\Sdk\build-tools\30.0.2`,将该工具目录加入环境变量`PATH`中。
|
||||||
|
|
||||||
而`apktool`是在项目`https://github.com/iBotPeaches/Apktool`中,下载`apktool_版本.jar`,重新命名为`apktool.jar`,并且下载`https://github.com/iBotPeaches/Apktool/tree/master/scripts/windows/apktool.bat`文件,和`apktool.jar`放在同一个目录,将该目录也加入到环境变量`PATH`中。
|
而`apktool`是在项目`https://github.com/iBotPeaches/Apktool`中,下载`apktool_版本.jar`,重新命名为`apktool.jar`,并且下载`https://github.com/iBotPeaches/Apktool/tree/master/scripts/windows/apktool.bat`文件,和`apktool.jar`放在同一个目录,将该目录也加入到环境变量`PATH`中。
|
||||||
|
|
||||||
修改`apktool.bat`文件,将最后一行尾部的`pause`去掉。修改如下。
|
修改`apktool.bat`文件,将最后一行尾部的`pause`去掉。修改如下。
|
||||||
|
|
||||||
```
|
```
|
||||||
// 修改前
|
// 修改前
|
||||||
@ -265,7 +278,7 @@ adb: failed to install ./app-debug-over.apk: Failure [INSTALL_PARSE_FAILED_NO_CE
|
|||||||
apksigner sign --ks mykeystore.keystore app-debug-over.apk
|
apksigner sign --ks mykeystore.keystore app-debug-over.apk
|
||||||
```
|
```
|
||||||
|
|
||||||
再次安装`apk`,出现了新错误。
|
再次安装`apk`,出现了新错误。
|
||||||
|
|
||||||
```
|
```
|
||||||
adb install ./app-debug-over.apk
|
adb install ./app-debug-over.apk
|
||||||
@ -304,6 +317,7 @@ adb install --no-incremental app-debug-over.apk
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
### 8.3.2 加固原理
|
### 8.3.2 加固原理
|
||||||
|
|
||||||
对加固的操作流程理解后,接下来开始分析源码,学习这个壳是如何实现的。首先我们从加壳工具开始入手。查看工程`jiiaguLib`的入口函数`main`,代码如下。
|
对加固的操作流程理解后,接下来开始分析源码,学习这个壳是如何实现的。首先我们从加壳工具开始入手。查看工程`jiiaguLib`的入口函数`main`,代码如下。
|
||||||
@ -814,15 +828,16 @@ public void onCreate() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
替换的步骤主要是修正`android.app.LoadedApk`的`mApplication`,以及`android.app.ActivityThread`的`mInitialApplication`,并且将壳的`Application`从`android.app.ActivityThread`的`mAllApplications`中移除。最后调用真实`Application`的`onCreate`函数。简单的动态加载的加固过程就完成了。
|
替换的步骤主要是修正`android.app.LoadedApk`的`mApplication`,以及`android.app.ActivityThread`的`mInitialApplication`,并且将壳的`Application`从`android.app.ActivityThread`的`mAllApplications`中移除。最后调用真实`Application`的`onCreate`函数。简单的动态加载的加固过程就完成了。
|
||||||
|
|
||||||
### 8.4 如何脱壳
|
|
||||||
|
|
||||||
在掌握了加壳的详细过程后,想要完成脱壳就非常简单了。前文中介绍的这个壳,可以通过分析被加壳应用中,壳的`Application`,找到其逻辑是如何还原真实`dex`的,就可以通过各种方式拿到被保护的`dex`了,例如通过`hook`解密的函数,或者参考壳代码的解密方式,开发一个应用调用解密的`so`,实现将`dex`提取并解密的过程。
|
## 8.4 脱壳方法
|
||||||
|
|
||||||
但是使用分析壳原理的方式并不是那么通用,每个壳都需要人工进行分析会非常费时,所以最佳的方式是找一个所有动态加载壳都会执行的流程,通过`hook`这个流程的某个节点,提取出真实的`dex`即可。而这个动态加载壳共同的特征就是,都是基于动态加载的机制,将真实的`Application`进行还原的。所以只需要在动态加载的过程中找到一个合适的调用时机,将真实`dex`写入文件即可。
|
在掌握了加壳的详细过程后,想要完成脱壳就非常简单了。前文中介绍的这个壳可以通过分析被加壳应用中的壳的`Application`来找到其逻辑是如何还原真实dex的。你可以通过各种方式拿到被保护的dex,例如通过hook解密函数或者参考壳代码的解密方式开发一个应用调用解密库(so),从而实现将dex提取并解密。
|
||||||
|
|
||||||
`dex`块被保存为一个连续的内存区域,所以想要将`dex`写入文件时,只要知道了该`dex`的起始地址和大小,就可以读取这块数据,将其写入文件了。下面回顾前文中`DexFile`的结构体如下。
|
然而,使用分析壳原理的方法并不通用,每个壳都需要进行人工分析,这可能会耗费大量时间。因此最佳方式是找到所有动态加载壳都会执行的流程,并在该流程中hook某个节点以提取出真实dex。这些动态加载壳共同具备一个特征:它们基于动态加载机制来还原真实Application。因此只需在动态加载过程中找到适合调用时机,在其中将真实dex写入文件即可。
|
||||||
|
|
||||||
|
dex块被保存为一个连续内存区域,所以只要知道该dex起始地址和大小,就能读取这一块数据并将其写入文件。下面回顾前文中DexFile结构体如下:
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
class DexFile {
|
class DexFile {
|
||||||
@ -840,11 +855,14 @@ class DexFile {
|
|||||||
|
|
||||||
在`DexFile`的结构体中,就可以拿到需要的起始位置和其大小。有了这些前提后,就可以实现一个基于修改`AOSP`源码的动态加载壳的脱壳机了。
|
在`DexFile`的结构体中,就可以拿到需要的起始位置和其大小。有了这些前提后,就可以实现一个基于修改`AOSP`源码的动态加载壳的脱壳机了。
|
||||||
|
|
||||||
|
|
||||||
## 8.5 自动脱壳机
|
## 8.5 自动脱壳机
|
||||||
|
|
||||||
在前文的动态加载壳样例中看到,解密出来的原始`dex`文件使用`DexClassLoader`对其进行加载的,而类的动态加载流程,在上一章中,非常详细的讲解了实现的原理和整个调用过程。接下来开始根据前文中学习到内容,逐步开发一个自动脱壳机。
|
在前文的动态加载壳样例中,我们看到解密出来的原始dex文件是使用DexClassLoader进行加载的。而类的动态加载流程,在上一章中已经详细讲解了实现原理和整个调用过程。接下来,我们将根据之前学习到的内容逐步开发一个自动脱壳机。
|
||||||
|
|
||||||
首先第一步,参考类加载流程,添加打桩点,输出`dex`的起始位置及大小。然后根据输出的信息和原始`dex`对比是否大小相同。确认无误后,则将该`dex`数据保存到文件。首先查看刚进入`native`层的`DexFile_defineClassNative`函数。
|
首先,第一步是参考类加载流程,在适当位置添加打桩点,并输出dex的起始位置及大小。然后,通过与原始dex进行比较确认是否大小相同。如果确认无误,则将该dex数据保存到文件中。
|
||||||
|
|
||||||
|
具体操作可以从刚进入native层的`DexFile_defineClassNative`函数开始查看。
|
||||||
|
|
||||||
```c++
|
```c++
|
||||||
static jclass DexFile_defineClassNative(JNIEnv* env,
|
static jclass DexFile_defineClassNative(JNIEnv* env,
|
||||||
@ -882,7 +900,7 @@ static jclass DexFile_defineClassNative(JNIEnv* env,
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
通过`ConvertJavaArrayToDexFiles`函数将`Java`层传递过来的`cookie`转换成了`dexfile`列表。可以在遍历该列表的循环中输出每个`dex_file`的信息,或者在`DefineClass`中输出`dex_file`的信息。以下是打桩输出日志相关代码。
|
通过`ConvertJavaArrayToDexFiles`函数将`Java`层传递过来的`cookie`转换成了`dexfile`列表。可以在遍历该列表的循环中输出每个`dex_file`的信息,或者在`DefineClass`中输出`dex_file`的信息。以下是打桩输出日志相关代码。
|
||||||
|
|
||||||
```java
|
```java
|
||||||
ObjPtr<mirror::Class> ClassLinker::DefineClass(Thread* self,
|
ObjPtr<mirror::Class> ClassLinker::DefineClass(Thread* self,
|
||||||
@ -900,13 +918,13 @@ ObjPtr<mirror::Class> ClassLinker::DefineClass(Thread* self,
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
日志添加完成后,将重新编译系统,并刷入测试机中。然后将未加固的`apk`解压,查看加固前的`dex`的大小。信息如下。
|
日志添加完成后,将重新编译系统,并刷入测试机中。然后将未加固的`apk`解压,查看加固前的`dex`的大小。信息如下。
|
||||||
|
|
||||||
```
|
```
|
||||||
-a---- 1981-01-01 1:01 9738740 classes.dex
|
-a---- 1981-01-01 1:01 9738740 classes.dex
|
||||||
```
|
```
|
||||||
|
|
||||||
未保护的`dex`大小为`9738740`,接下来将加固后的`apk`安装到测试机中,查看系统输出日志,能看到添加的打桩输出信息非常多,这是因为每次加载类时,都会触发该函数,所以触发非常频繁。当打开这个被保护的样例应用时,就能看到大小和未加固前一样大的`dex`信息,输出信息如下。
|
未保护的`dex`大小为`9738740`,接下来将加固后的`apk`安装到测试机中,查看系统输出日志,能看到添加的打桩输出信息非常多,这是因为每次加载类时,都会触发该函数,所以触发非常频繁。当打开这个被保护的样例应用时,就能看到大小和未加固前一样大的`dex`信息,输出信息如下。
|
||||||
|
|
||||||
```
|
```
|
||||||
D/k.myservicedem: mikrom DefineClass dex begin:0xc6b37000 size:9738740
|
D/k.myservicedem: mikrom DefineClass dex begin:0xc6b37000 size:9738740
|
||||||
@ -954,14 +972,23 @@ ObjPtr<mirror::Class> ClassLinker::DefineClass(Thread* self,
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
重新编译系统后,刷入手机,安装前文中加固好的样例应用,打开后在日志中成功看到下面的输出信息。
|
重新编译系统后,刷入手机,安装前文中加固好的样例应用,打开后在日志中成功看到下面的输出信息。
|
||||||
|
|
||||||
```
|
```
|
||||||
D/k.myservicedem: mikrom DefineClass write 2 /data/data/cn.mik.myservicedemo/defineClass_9738740.dex dex begin:0xbdbc5000
|
D/k.myservicedem: mikrom DefineClass write 2 /data/data/cn.mik.myservicedemo/defineClass_9738740.dex dex begin:0xbdbc5000
|
||||||
```
|
```
|
||||||
|
|
||||||
最后将这个文件传到电脑中,使用`jadx`打开看到脱壳后的结果。
|
最后将这个文件传到电脑中,使用反编译工具`jadx`打开看到脱壳后的结果。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
在这个自动脱壳的例子中,并不限于在哪个调用时机来对其保存到文件,只要是在加载过程中,能获取到`DexFile`结果的地方,大多数都是能拿到动态加载壳所保护的目标。当你不确定的情况,可以先加上打桩信息,然后逐步去排查来判断你使用的调用时机是否可用。
|
在这个自动脱壳的例子中,并不限于在哪个调用时机来对其保存到文件,只要是在加载过程中,能获取到`DexFile`结果的地方,大多数都是能拿到动态加载壳所保护的目标。当你不确定的情况,可以先加上打桩信息,然后逐步去排查来判断你使用的调用时机是否可用。
|
||||||
|
|
||||||
|
|
||||||
|
## 8.6 本章小结
|
||||||
|
|
||||||
|
本章主要介绍了软件壳的概念以及软件加密与解密技术原理。
|
||||||
|
|
||||||
|
软件壳技术属于二进制安全攻防技术,涉及的领域非常广泛,包括计算机系统底层工作原理、程序加载与链接技术、加密算法应用等。如果想深入学习这门技术,需要付出大量时间和精力去专研整个工作流程。商业级的软件壳比本章介绍的内容复杂得多,功能强大的加密软件甚至可以进行指令级别的混淆与加密保护。要实现对此类软件进行解密是非常困难的,只能结合分析经验,并借助自动化跟踪工具来局部地进行功能解密。
|
||||||
|
|
||||||
|
在软件安全攻防领域中,关于软件壳的讨论已经有很长时间了。本章所提供的内容只是为引起兴趣并开启探索之路而提供一些基础知识。如若感兴趣,请继续深入研究该领域,并不断探索新知识。
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user