mirror of
https://github.com/feicong/rom-course.git
synced 2025-08-29 02:35:20 +00:00
第8章
This commit is contained in:
parent
9920a486c5
commit
c4154bb8b3
@ -1,18 +1,18 @@
|
|||||||
# 第八章 脱壳
|
# 第八章 脱壳
|
||||||
|
|
||||||
熟悉类的加载流程后可以做什么功能呢,最典型的功能就是脱壳,因为加壳就是对类中代码的保护,而脱壳就是还原出被保护的代码,除此之外,`Art Hook`的实现也是和类的加载,函数的执行的原理息息相关的。在这一章中,将对加壳和脱壳进行详细的介绍,在最后将通过上一章中学习到的类加载原理,简单实现自动脱壳机。
|
熟悉类的加载流程后可以利用加载的原理来开发辅助功能,在逆向中,比较典型的功能就是脱壳,加壳就是对类中代码的保护,而脱壳则是还原出被保护的代码,除此之外,`Art Hook`的实现也是和类的加载,函数的执行的原理息息相关的。在这一章中,将对加壳和脱壳进行详细的介绍,在最后将通过上一章中学习到的类加载原理,简单实现自动脱壳机。
|
||||||
|
|
||||||
## 8.1 壳,加壳,脱壳
|
## 8.1 壳,加壳,脱壳
|
||||||
|
|
||||||
`Android`的`APK`文件实际上是一种压缩文件格式,它包含了应用程序的二进制代码、资源文件、清单文件等。在安装应用程序之前,系统会将`APK`文件解压缩并安装到设备上。在`APK`文件中,应用程序的二进制代码通常是以`DEX(Dalvik Executable)`格式存储的。`DEX`格式是一种针对移动设备优化的字节码格式,与`Java`虚拟机`(JVM)`的字节码格式有所不同。由于`DEX`格式采用了特殊的指令集和数据结构,使得反编译工具可以轻松地将其转换为可读性较高的`Java`代码。此外,许多反编译工具还可以通过反汇编和反混淆等技术来还原出源代码,因此为了防止应用程序的关键代码轻易被暴露,开发人员会采取一系列的手段来保护代码。
|
`Android`的`APK`文件实际上是一种压缩文件格式,它包含了应用程序的二进制代码、资源文件、清单文件等。在安装应用程序之前,系统会将`APK`文件解压缩并安装到设备上。在`APK`文件中,应用程序的二进制代码通常是以`DEX(Dalvik Executable)`格式存储的。`DEX`格式是一种针对移动设备优化的字节码格式,与`Java`虚拟机`(JVM)`的字节码格式有所不同。由于`DEX`格式采用了特殊的指令集和数据结构,使得反编译工具可以轻松地将其转换为可读性较高的`Java`代码。此外,许多反编译工具还可以通过反汇编和反混淆等技术来还原出源代码,因此为了防止应用程序的关键代码轻易被暴露,开发人员会采取一系列的手段来保护代码。
|
||||||
|
|
||||||
Android常规对代码保护的方案主要包括以下几种:
|
`Android`常规对代码保护的方案主要包括以下几种:
|
||||||
|
|
||||||
1. 混淆(Obfuscation):通过重命名类、方法、变量等标识符来隐藏程序逻辑,使得反编译后的代码难以被理解和分析。
|
1. 混淆(`Obfuscation`):通过重命名类、方法、变量等标识符来隐藏程序逻辑,使得反编译后的代码难以被理解和分析。
|
||||||
2. 压缩(Compression):将应用程序的二进制代码压缩成较小的体积,防止恶意用户逆向工程和复制源代码。
|
2. 压缩(`Compression`):将应用程序的二进制代码压缩成较小的体积,防止恶意用户逆向工程和复制源代码。
|
||||||
3. 签名(Signing):在应用程序发布前,使用数字证书对应用程序进行签名,确保其完整性和来源可信。
|
3. 签名(`Signing`):在应用程序发布前,使用数字证书对应用程序进行签名,确保其完整性和来源可信。
|
||||||
4. 加固(Hardening):在应用程序内部添加额外的安全保护机制,如代码加密、反调试、反注入等,增强应用程序的抵御能力。
|
4. 加固(`Hardening`):在应用程序内部添加额外的安全保护机制,如代码加密、反调试、反注入等,增强应用程序的抵御能力。
|
||||||
5. 动态加载(Dynamic Loading):将敏感的代码和资源文件放置在远程服务器上,在运行时动态加载到本地设备,以防止被攻击者轻易访问和修改。
|
5. 动态加载(`Dynamic Loading`):将敏感的代码和资源文件放置在远程服务器上,在运行时动态加载到本地设备,以防止被攻击者轻易访问和修改。
|
||||||
|
|
||||||
### 8.1.1 什么是加壳
|
### 8.1.1 什么是加壳
|
||||||
|
|
||||||
@ -22,8 +22,8 @@
|
|||||||
|
|
||||||
常见的加壳壳包括:
|
常见的加壳壳包括:
|
||||||
|
|
||||||
1. `DexProtector`:一款商业化的加壳工具,支持`Android`和`iOS`平台,可以对`Java`代码和`NDK`库进行加固。其特点是支持多种代码混淆技术,同时还提供了反调试、防止`Hook`攻击、反模拟器等多种安全机制。
|
1. `DexProtector`:支持`Android`和`iOS`平台,可以对`Java`代码和`NDK`库进行加固。其特点是支持多种代码混淆技术,同时还提供了反调试、防止`Hook`攻击、反模拟器等多种安全机制。
|
||||||
2. `Qihoo360`加固保:一款免费的加壳工具,支持`Android`和`iOS`平台,采用自己研发的加固壳技术,可以对`Java`代码和`C/C++`库进行加固,同时还提供了反调试、反逆向、防篡改等多种安全机制。
|
2. `360`加固:支持`Android`和`iOS`平台,采用自己研发的加固壳技术,可以对`Java`代码和`C/C++`库进行加固,同时还提供了反调试、反逆向、防篡改等多种安全机制。
|
||||||
3. `Bangcle`:一款国内著名的加壳工具,支持`Android`和`iOS`平台,提供了多种加固壳方案,如`DexShell、SOShell、`加密资源等,同时还支持反调试、反注入等多种安全机制。
|
3. `Bangcle`:一款国内著名的加壳工具,支持`Android`和`iOS`平台,提供了多种加固壳方案,如`DexShell、SOShell、`加密资源等,同时还支持反调试、反注入等多种安全机制。
|
||||||
4. `APKProtect`:一款功能强大的加壳工具,支持`Android`平台,可以对`Java`代码和`Native`库进行加固,支持多种加固方式,如代码混淆、`Resource Encryption、Anti-debugging`等,同时还提供了反反编译、反调试等多种安全机制。
|
4. `APKProtect`:一款功能强大的加壳工具,支持`Android`平台,可以对`Java`代码和`Native`库进行加固,支持多种加固方式,如代码混淆、`Resource Encryption、Anti-debugging`等,同时还提供了反反编译、反调试等多种安全机制。
|
||||||
|
|
||||||
@ -31,25 +31,25 @@
|
|||||||
|
|
||||||
### 8.1.2 如何脱壳
|
### 8.1.2 如何脱壳
|
||||||
|
|
||||||
加壳的本质就是对DEX格式的java字节码进行保护避免被攻击者分析和修改,而脱壳就是通过分析壳的特征和原理,将被壳保护的java字节码还原出来,通常用于逆向分析、恶意代码分析等领域。
|
加壳的本质就是对`DEX`格式的`java`字节码进行保护避免被攻击者分析和修改,而脱壳就是通过分析壳的特征和原理,将被壳保护的`java`字节码还原出来,通常用于逆向分析、恶意代码分析等领域。
|
||||||
|
|
||||||
脱壳常用的几个步骤如下。
|
脱壳常用的几个步骤如下。
|
||||||
|
|
||||||
1. 静态分析:通过对样本进行静态分析,获取样本中的壳的特征,加密算法、解密函数等信息,为后续的动态分析做好准备。
|
1. 静态分析:通过对样本进行静态分析,获取样本中的壳的特征,加密算法、解密函数等信息,为后续的动态分析做好准备。
|
||||||
2. 动态分析:在调试器或hook工具的帮助下,运行加密的程序,跟踪程序的执行流程,并尝试找到解密或解压的位置,获取加密或压缩前的原始数据。
|
2. 动态分析:在调试器或`hook`工具的帮助下,运行加密的程序,跟踪程序的执行流程,并尝试找到解密或解压的位置,获取加密或压缩前的原始数据。
|
||||||
3. 重构代码:通过分析反汇编代码,重新构建可读性高且易于理解的代码,以便更好地理解样本的行为。
|
3. 重构代码:通过分析反汇编代码,重新构建可读性高且易于理解的代码,以便更好地理解样本的行为。
|
||||||
|
|
||||||
在脱壳的过程中,会面临开发者为保护代码而添加的各类的防护措施,例如代码混淆、反调试、ROM检测、root检测、hook注入检测等加固手段,而这个博弈的过程就是一种攻防对抗。而ROM脱壳将从另外一个层面解决一部分对抗的问题。
|
在脱壳的过程中,会面临开发者为保护代码而添加的各类的防护措施,例如代码混淆、反调试、`ROM`检测、`root`检测、`hook`注入检测等加固手段,而这个博弈的过程就是一种攻防对抗。而`ROM`脱壳将从另外一个层面解决部分对抗的问题。
|
||||||
|
|
||||||
## 8.2 壳的特征
|
## 8.2 壳的特征
|
||||||
|
|
||||||
早期的Android应用程序很容易被反编译和修改,因此一些开发者会使用简单的壳来保护自己的应用程序。这些壳主要是基于Java层的代码混淆和加密,以及Native层的简单加密。
|
早期的`Android`应用程序很容易被反编译和修改,因此一些开发者会使用简单的壳来保护自己的应用程序。这些壳主要是基于`Java`层的代码混淆和加密,以及`Native`层的简单加密。
|
||||||
|
|
||||||
但是单纯的混淆和加密很难保障代码的安全性,第一代壳,动态加载壳就诞生了,这时的思想主要还是将整个DEX进行加密保护,在运行期间才会解密还原DEX文件,再动态加载运行原文件。但是这样依赖Java的动态加载机制,非常容易被攻击,直接通过加载流程就能拿到被保护的数据,这种壳的特征非常明显,当反编译解析时,只能看到壳的代码,找不到任何Activity相关的处理,这种情况就是动态加载壳了。
|
但是单纯的混淆和加密很难保障代码的安全性,第一代壳,动态加载壳就诞生了,这时的思想主要还是将整个`DEX`进行加密保护,在运行期间才会解密还原`DEX`文件,再动态加载运行原文件。但是这样依赖`Java`的动态加载机制,非常容易被攻击,直接通过加载流程就能拿到被保护的数据,这种壳的特征非常明显,当反编译解析时,只能看到壳的代码,找不到任何`Activity`相关的处理,这种情况就是动态加载壳了。
|
||||||
|
|
||||||
随后第二代壳,指令抽取壳就出现了,对Java层的代码进行函数粒度的保护,第一代的思想是将整个DEX保护起来,而第二代的思想就是只需要保护关键的函数即可。将原始DEX中需要保护的函数内部的codeitem进行清空,将真正的函数内容加密保护存放在其他地方,只有当这个函数真正执行时,才通过解密函数将其还原填充回去,达到让其能正常执行的目的,有些指令抽取壳甚至会在函数执行完成后,重新将codeitem清空。否则执行过一次的函数指令将很容易被还原出来。这种壳的特征可以通过函数内容的特征来分辨,例如一些空的函数,查看smali指令发现内部有大量的nop空指令,这种情况就时指令抽取壳
|
随后第二代壳,指令抽取壳就出现了,对`Java`层的代码进行函数粒度的保护,第一代的思想是将整个`DEX`保护起来,而第二代的思想就是只需要保护关键的函数即可。将原始`DEX`中需要保护的函数内部的`codeitem`进行清空,将真正的函数内容加密保护,存放在其他地方,只有当这个函数真正执行时,才通过解密函数将其还原填充回去,达到让其能正常执行的目的,有些指令抽取壳甚至会在函数执行完成后,重新将`codeitem`清空。否则执行过一次的函数指令将很容易被还原出来。这种壳的特征可以通过函数内容的特征来分辨,例如一些空的函数,查看`smali`指令发现内部有大量的`nop`空指令,这种情况就是指令抽取壳
|
||||||
|
|
||||||
随着攻防的对抗不断的升级,第二代壳也无法带来安全保障,第三代壳,指令转换壳诞生了。指令转换壳的思想和指令抽取是相同的,对具体的函数进行保护,但是在第二代壳的缺陷上进行了优化,由于指令抽取壳最终依然还是一个Java函数的调用,最终还是要将指令回填后进行执行的。不管是如何保护,只要在获取到执行过程中的`codeitem`,就能轻易的修复为真实的`DEX`文件。而指令转换壳则是将被保护的函数转换为native,将函数的指令集解析成中间码,中间码会被映射到自定义的虚拟机进行解析执行。这样就不会走Android提供的指令解析执行流程了。但是这样也会导致函数执行过慢,以及一些兼容问题,这类壳的特征也非常明显,就是native化一些函数,并且可能会包含大量密集的虚拟指令。
|
随着攻防的对抗不断的升级,第二代壳也无法带来安全保障,第三代壳,指令转换壳诞生。指令转换壳的思想和指令抽取是相同的,对具体的函数进行保护,但是在第二代壳的缺陷上进行了优化,由于指令抽取壳最终依然还是一个`Java`函数的调用,最终还是要将指令回填后进行执行的。不管是如何保护,只要在获取到执行过程中的`codeitem`,就能轻易的修复为真实的`DEX`文件。而指令转换壳则是将被保护的函数转换为`native`,将函数的指令集解析成中间码,中间码会被映射到自定义的虚拟机进行解析执行。这样就不会走`Android`提供的指令执行流程了。但是这样也会导致函数执行过慢,以及一些兼容问题,这类壳的特征也非常明显,就是`native`化函数。
|
||||||
|
|
||||||
### 8.3 动态加载壳的实现
|
### 8.3 动态加载壳的实现
|
||||||
|
|
||||||
@ -61,15 +61,15 @@
|
|||||||
4. 然后,壳程序会将被保护程序从加密状态中解密出来。
|
4. 然后,壳程序会将被保护程序从加密状态中解密出来。
|
||||||
5. 接着,壳程序会在内存中为被保护程序申请一块连续的内存区域,将被保护程序的代码和数据映射到该内存区域中。
|
5. 接着,壳程序会在内存中为被保护程序申请一块连续的内存区域,将被保护程序的代码和数据映射到该内存区域中。
|
||||||
6. 壳程序会根据被保护程序的程序入口点开始执行被保护程序的代码。
|
6. 壳程序会根据被保护程序的程序入口点开始执行被保护程序的代码。
|
||||||
7. 被保护程序运行时的系统调用和DLL库的调用等操作,都会由壳程序处理并返回结果给被保护程序。同时,壳程序可能会进行一些额外的安全检查,例如防止调试、防止反汇编、防止破解等操作。
|
7. 被保护程序运行时的系统调用和`DLL`库的调用等操作,都会由壳程序处理并返回结果给被保护程序。同时,壳程序可能会进行一些额外的安全检查,例如防止调试、防止反汇编、防止破解等操作。
|
||||||
|
|
||||||
### 8.3.1 加固过程
|
### 8.3.1 加固过程
|
||||||
|
|
||||||
接下来我们看一个简单的动态加载壳的实现的案例,下载地址:`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;
|
||||||
@ -123,9 +123,7 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
将上面的代码编译后,输出`app-debug.apk`的结果,这个就是需要待保护的目标。然后开始准备加固的环境。
|
将上面的代码编译后,输出`app-debug.apk`的结果,这个就是需要待保护的目标。然后开始准备加固的环境。
|
||||||
|
|
||||||
使用的加固工具在加固过程使用到的命令:`dx`、`apktool`、`zipalign`、`apksigner`。
|
在加固过程使用到的命令:`dx`、`apktool`、`zipalign`、`apksigner`。其中`dx、zipalign、apksigner`工具在`sdk`的`build-tools`目录中,例如本地的路径`Android\Sdk\build-tools\30.0.2`,将该工具目录加入环境变量`PATH`中。
|
||||||
|
|
||||||
`dx、zipalign、apksigner`工具在`sdk`的`build-tools`目录中,例如本地的路径`C:\Users\king\AppData\Local\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`中。
|
||||||
|
|
||||||
@ -145,7 +143,7 @@ for /f "tokens=2" %%# in ("%cmdcmdline%") do if /i "%%#" equ "/c"
|
|||||||
keytool -genkeypair -alias myalias -keyalg RSA -keysize 2048 -validity 9125 -keystore mykeystore.keystore
|
keytool -genkeypair -alias myalias -keyalg RSA -keysize 2048 -validity 9125 -keystore mykeystore.keystore
|
||||||
```
|
```
|
||||||
|
|
||||||
输入口令以及各项信息后,得到一个证书文件`mykeystore.keystore`。接下来可以编译加固的项目了,将壳程序,以及加固程序编译出来,步骤如下。
|
输入口令以及各项信息后,得到一个证书文件`mykeystore.keystore`。接下来可以编译此加固的项目了,将壳程序,以及加固程序编译出来,步骤如下。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -261,13 +259,13 @@ adb install ./app-debug-over.apk
|
|||||||
adb: failed to install ./app-debug-over.apk: Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Scanning Failed.: No signature found in package of version 2 or newer for package cn.mik.myservicedemo]
|
adb: failed to install ./app-debug-over.apk: Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Scanning Failed.: No signature found in package of version 2 or newer for package cn.mik.myservicedemo]
|
||||||
```
|
```
|
||||||
|
|
||||||
这是因为当`targetSdkVersion`版本号,只要大于30时,需要使用v2进行签名,签名方式如下。
|
这是因为当`targetSdkVersion`版本号,只要大于`30`时,需要使用`v2`进行签名,签名方式如下。
|
||||||
|
|
||||||
```
|
```
|
||||||
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
|
||||||
@ -316,6 +314,7 @@ public static void main(String[] args) {
|
|||||||
ROOT = "jiaguLib/";
|
ROOT = "jiaguLib/";
|
||||||
OUT_TMP = ROOT+"temp/";
|
OUT_TMP = ROOT+"temp/";
|
||||||
JiaGuMain jiagu = new JiaGuMain();
|
JiaGuMain jiagu = new JiaGuMain();
|
||||||
|
// 开始加固的入口
|
||||||
jiagu.beginJiaGu();
|
jiagu.beginJiaGu();
|
||||||
}else {
|
}else {
|
||||||
...
|
...
|
||||||
@ -819,9 +818,9 @@ public void onCreate() {
|
|||||||
|
|
||||||
### 8.4 如何脱壳
|
### 8.4 如何脱壳
|
||||||
|
|
||||||
在掌握了加壳的详细过程后,想要完成脱壳就非常简单了。前文中介绍的这个壳,可以通过分析被加壳应用中,壳的`Application`,找到其逻辑是如何还原真实`dex`的,就可以通过各种方式拿到被保护的`dex`了,例如通过`hook`解密的函数,或者参考壳代码的解密方式,开发一个应用调用解密的so,实现将`dex`提取并解密的过程。
|
在掌握了加壳的详细过程后,想要完成脱壳就非常简单了。前文中介绍的这个壳,可以通过分析被加壳应用中,壳的`Application`,找到其逻辑是如何还原真实`dex`的,就可以通过各种方式拿到被保护的`dex`了,例如通过`hook`解密的函数,或者参考壳代码的解密方式,开发一个应用调用解密的`so`,实现将`dex`提取并解密的过程。
|
||||||
|
|
||||||
但是使用分析壳原理的方式并不是那么通用,每个壳都需要人工进行分析会非常费时,所以最佳的方式是找一个所有动态加载壳都会执行的流程,通过hook这个流程的某个节点,提取出真实的`dex`即可。而这个动态加载壳共同的特征就是,都是基于动态加载的机制,将真实的`Application`进行还原的。所以只需要在动态加载的过程中找到一个合适的调用时机,将真实`dex`写入文件即可。
|
但是使用分析壳原理的方式并不是那么通用,每个壳都需要人工进行分析会非常费时,所以最佳的方式是找一个所有动态加载壳都会执行的流程,通过`hook`这个流程的某个节点,提取出真实的`dex`即可。而这个动态加载壳共同的特征就是,都是基于动态加载的机制,将真实的`Application`进行还原的。所以只需要在动态加载的过程中找到一个合适的调用时机,将真实`dex`写入文件即可。
|
||||||
|
|
||||||
`dex`块被保存为一个连续的内存区域,所以想要将`dex`写入文件时,只要知道了该`dex`的起始地址和大小,就可以读取这块数据,将其写入文件了。下面回顾前文中`DexFile`的结构体如下。
|
`dex`块被保存为一个连续的内存区域,所以想要将`dex`写入文件时,只要知道了该`dex`的起始地址和大小,就可以读取这块数据,将其写入文件了。下面回顾前文中`DexFile`的结构体如下。
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user