安卓加壳脱壳原理-简单概要
# 安卓加壳脱壳原理大白话#### 踏麻的,遇到加壳的app怎么解决,我刚入行就遇到了加壳的app,马上就去论坛搜索:apk加壳了怎么办、xxx脱壳。不是我说 我作为一个刚入行的小白自学了一些课程摩拳擦掌想来查收一下自己成果就这么对我。
#### ————摘抄自小白的心理活动
**或许你刚接触安卓逆向不久,或许你还没接触,或许你接触很久了但是只会用工具脱壳? 没错你就读本篇文章吧,一读一个准。**
## 笔者废话:
**很久没在咱论坛输出文章了 时隔一年,很抱歉消失了这么久,我回来了,这一次属于我的我要拿回来,本次笔者会用大白话来给各位表达什么是加壳,与脱壳的原理,分为几个主题:什么是加壳技术、加壳技术的迭代过程,脱壳原理,笔者废话二。想说很多废话,这些废话留到废话二说,感谢您阅读本篇本章,话说本章只说vmp技术出来之前的脱壳原理**
## 加壳有什么用?为什么大厂app不加壳
**简单来说就是给apk增加安全性,但是现在我们可以打开手机随机选中几个app,然后查看是否有加壳,注意这里选择的app是大厂的app,例如DY、MT、TB、王者荣耀、百度网盘,那这些app都是不加壳的,那为什么这些大厂他们的app不加壳呢,不是加壳给apk本身增加了安全性吗?**
**是的,原则上加壳确实能给apk本身带来诸多的安全性和增加被不法分子利用的可能性,但是在考虑到例如一万个人当中可能也就那么几个人会对这些app进行分析牟不牟利且不说,我作为一个大厂我就算不加壳我就不能有其他的手段来保护我的资产吗?是吧,如果真有这么容易解决一个大厂的app,那要我们这些真正想要做安全研究的人怎么办,那还有一个最主要的问题就是,这些app本身就是我们日常生活中都会使用的 而且是频繁的,而一个app加壳后最大影响的就是用户在使用过程中的体验,各位可以下载个中国移动看看,真的是一动不动,我每次用他们的app就想怒喷一堆垃圾话。**
## 加壳技术的迭代过程
**我整体分为以下4个过程**
一代壳:代码混淆
二代壳:动态加载dex,也叫做dex整体加固
三代壳:基于二代上做了函数抽取(也就是你把二代壳脱了之后,有些函数明明是有内容的也你却看不到,只有当函数在我们实际操作中被函数执行的时候才会修复填入函数中)这里的找不到例子就这样吧 这里是函数抽象化了,
四代壳:VMP保护(虚拟化保护,简单说就是:本来操作系统就是帮你加载app然后帮你解释执行,但是VMP保护他就偏不要系统帮你解释执行,他要自己解释自己,自己有个解释器自己解释自己)
四代壳:dex2c (这个叫做dex to c,保护方式如名字,转c代码执行,**将 Java 代码进行词法 , 句法分析 , 生成对应的 C / C++ 文件 , 然后交叉编译为 SO 动态库** )这里无图 但是dex2c的保护跟上面那张图你拖进去jadx里面看是差不多的
四代壳只会出现在企业版也就是加固厂商的收费版才会有,其他的基本都是一二三代壳
## 脱壳原理
**在进程加载app开始加载进内存之后会加载相对应的class是吧, 那加载这个class就是由一个加载器来加载的,这个加载器就叫做classloader(类加载器)注意了这里说的是Java的类加载器,还有Android的类加载器**
**然后这个classloader又有不同的classloader,还有个机制叫做双亲委派,我们先讲又哪些classloader**
**Bootstrap classLoader (根类加载器)负责加载系统核心库例如(java.lang.String)等**
**ExtClassLoader (拓展类加载器)负责加载jre/lib/ext目录下的一些扩展的jar**
**AppClassLoader (应用类加载器) 负责加载应用程序的主函数类**
**ok,这些加载器说完,接下来要说怎么加载这个dex了**
### 双亲委派
**OK上面是Java的类加载器说明 下面贴Android的类加载器 **
**为了区别上图加载器的区别,下面是通过代码调试给读者展示一下这些加载器都会加载一些什么**
```
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ClassLoader mainActivityClassloader = MainActivity.class.getClassLoader();
Log.v("wanbai", "MainActivity的类加载加载器:" + "hashcode:" + mainActivityClassloader.hashCode() + " classLoader:" + mainActivityClassloader);
ClassLoader contextClassloader = Context.class.getClassLoader();
Log.v("wanbai", "Context的类加载加载器:" + " hashcode:" + contextClassloader.hashCode() + " classLoader:" + contextClassloader);
ClassLoader listViewClassLoader = ListView.class.getClassLoader();
Log.v("wanbai", "ListView的类加载器:" + " hashcode:" + listViewClassLoader.hashCode() + " classLoader:" + listViewClassLoader);
ClassLoader activityContextImplClassloader = getClassLoader();
Log.v("wanbai", "应用程序默认加载器:" + " hashcode:" + activityContextImplClassloader.hashCode() + " classLoader:" + activityContextImplClassloader);
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
Log.v("wanbai", "系统类加载器:" + " hashcode:" + systemClassLoader.hashCode() + " classLoader:" + systemClassLoader);
ClassLoader threadClassLoader = Thread.currentThread().getContextClassLoader();
Log.v("wanbai", "当前线程的classloader:" + " hashcode:" + threadClassLoader.hashCode() + " classLoader:" + threadClassLoader);
Log.v("wanbai", "打印应用程序默认加载器的委派机制:");
ClassLoader classLoader = activityContextImplClassloader;
while (classLoader != null) {
Log.v("wanbai", "-------------- hashcode:" + classLoader.hashCode() + " classLoader:" + classLoader);
classLoader = classLoader.getParent();
}
Log.v("wanbai", "打印系统加载器的委派机制:");
classLoader = systemClassLoader;
while (classLoader != null) {
Log.v("wanbai", "-------------- hashcode:" + classLoader.hashCode() + " classLoader:" + classLoader);
classLoader = classLoader.getParent();
}
}
}
//output
2022-07-05 23:59:05.907 12751-12751/com.jacyzhou.testclassloader V/wanbai: MainActivity的类加载加载器:hashcode:103503071 classLoader:dalvik.system.PathClassLoader,nativeLibraryDirectories=]]
2022-07-05 23:59:05.907 12751-12751/com.jacyzhou.testclassloader V/wanbai: Context的类加载加载器: hashcode:227159363 classLoader:java.lang.BootClassLoader@d8a2d43
2022-07-05 23:59:05.907 12751-12751/com.jacyzhou.testclassloader V/wanbai: ListView的类加载器: hashcode:227159363 classLoader:java.lang.BootClassLoader@d8a2d43
2022-07-05 23:59:05.907 12751-12751/com.jacyzhou.testclassloader V/wanbai: 应用程序默认加载器: hashcode:103503071 classLoader:dalvik.system.PathClassLoader,nativeLibraryDirectories=]]
2022-07-05 23:59:05.907 12751-12751/com.jacyzhou.testclassloader V/wanbai: 系统类加载器: hashcode:130630956 classLoader:dalvik.system.PathClassLoader,nativeLibraryDirectories=]]
2022-07-05 23:59:05.907 12751-12751/com.jacyzhou.testclassloader V/wanbai: 当前线程的classloader: hashcode:103503071 classLoader:dalvik.system.PathClassLoader,nativeLibraryDirectories=]]
2022-07-05 23:59:05.907 12751-12751/com.jacyzhou.testclassloader V/wanbai: 打印应用程序默认加载器的委派机制:
2022-07-05 23:59:05.907 12751-12751/com.jacyzhou.testclassloader V/wanbai: -------------- hashcode:103503071 classLoader:dalvik.system.PathClassLoader,nativeLibraryDirectories=]]
2022-07-05 23:59:05.907 12751-12751/com.jacyzhou.testclassloader V/wanbai: -------------- hashcode:227159363 classLoader:java.lang.BootClassLoader@d8a2d43
2022-07-05 23:59:05.907 12751-12751/com.jacyzhou.testclassloader V/wanbai: 打印系统加载器的委派机制:
2022-07-05 23:59:05.907 12751-12751/com.jacyzhou.testclassloader V/wanbai: -------------- hashcode:130630956 classLoader:dalvik.system.PathClassLoader,nativeLibraryDirectories=]]
2022-07-05 23:59:05.907 12751-12751/com.jacyzhou.testclassloader V/wanbai: -------------- hashcode:227159363 classLoader:java.lang.BootClassLoader@d8a2d43
```
**当一个dex文件要加载 首先在DexClassloader开始,DexClassloader收到(提前这个dex不是自己已经加载过)这个dex 先丢给自己的父类加载也就是BaseDexClassloader问他是否加载过,如果加载过就返回无需再加载,如果没加载就继续找自己的父类,可以理解为儿子找爸爸,爸爸找自己的爸爸也就是儿子的爷爷,询问到爷爷那里时,爷爷说这个没加载过但是我不会加载就会丢给自己的儿子加载,如果BaseDexClassloader也说不会加载,那就是DexClassloader自己进行加载了,如果加载失败了就会抛出异常,**
**如上就是双亲委派的机制说明,这里说的很简单 如果深入Android系统源码中更能明白更多,但是我怕有些读者基础不好看不懂这里就不贴源码了 感兴趣可以自己去搜一下**
**回归正题,为什么我们要说这个类加载器呢,因为二三代壳都是从这里的基础上做的,那具体怎么开始加载这个dex的就要分析壳代码了,这里就不带大家分析壳代码,具体流程大概:内存运行apk——>进入壳代码——>执行OnCreate函数——>进入native层执行把dex文件解密并加载到内存中——>类加载器执行——>加载完毕运行原本app的代码**
**这里是DexFile的定义,也是我们脱壳的关键对象,具体寻找方式读者动手能力强可以找一下,寻找方式:寻找定义(Definition)DexClassLoader开始找,DexFile这个对象在源码中很多处有使用到,也就是说脱壳点是很多的,DexFile里面定义有两个常量,一个begin_即dex在内存中的位置,而size则是dex的文件大小,根据大小和起始位置也就是begin,就能把整个完整的dex文件从内存中dump下来,而且对象里面的成员函数提供了获取这两个成员变量以便调用,这也是二代壳的脱壳方式
下面贴一张DexFile的搜索结果是很多的,这是在libcore中进行的搜索,也可以从这里插入我们自己写的代码进行脱壳**
**下面这张是Android全部源码中搜索出来的结果,下面会介绍一个稳定的脱壳点**
```
static inline JValue Execute(Thread* self, const DexFile::CodeItem* code_item,
ShadowFrame& shadow_frame, JValue result_register) {
//addcodestart
char *dexfilepath=(char*)malloc(sizeof(char)*1000);
if(dexfilepath!=nullptr)
{
//先ArtMethod
ArtMethod* artmethod=shadow_frame.GetMethod();
//拿到DexFile
const DexFile* dex_file = artmethod->GetDexFile();
//拿到Begin和size随后进行dump和保存
const uint8_t* begin_=dex_file->Begin();// Start of data.
size_t size_=dex_file->Size();// Length of data.
int size_int_=(int)size_;
int fcmdline =-1;
char szCmdline= {0};
char szProcName = {0};
int procid = getpid();
sprintf(szCmdline,"/proc/%d/cmdline", procid);
fcmdline = open(szCmdline, O_RDONLY,0644);
if(fcmdline >0)
{
read(fcmdline, szProcName,256);
close(fcmdline);
}
if(szProcName)
{
memset(dexfilepath,0,1000);
sprintf(dexfilepath,"/sdcard/%s_%d_dexfile.dex",szProcName,size_int_);
int dexfilefp=open(dexfilepath,O_RDONLY,0666);
if(dexfilefp>0){
close(dexfilefp);
dexfilefp=0;
}else{
int fp=open(dexfilepath,O_CREAT|O_APPEND|O_RDWR,0666);
if(fp>0)
{
write(fp,(void*)begin_,size_);
fsync(fp);
close(fp);
}
}
}
if(dexfilepath!=nullptr)
{
free(dexfilepath);
dexfilepath=nullptr;
}
}
//addcodeend
DCHECK(!shadow_frame.GetMethod()->IsAbstract());
DCHECK(!shadow_frame.GetMethod()->IsNative());
shadow_frame.GetMethod()->GetDeclaringClass()->AssertInitializedOrInitializingInThread(self);
bool transaction_active = Runtime::Current()->IsActiveTransaction();
if (LIKELY(shadow_frame.GetMethod()->IsPreverified())) {
// Enter the "without access check" interpreter.
if (kInterpreterImplKind == kSwitchImpl) {
if (transaction_active) {
return ExecuteSwitchImpl<false, true>(self, code_item, shadow_frame, result_register);
} else {
return ExecuteSwitchImpl<false, false>(self, code_item, shadow_frame, result_register);
}
} else {
DCHECK_EQ(kInterpreterImplKind, kComputedGotoImplKind);
if (transaction_active) {
return ExecuteGotoImpl<false, true>(self, code_item, shadow_frame, result_register);
} else {
return ExecuteGotoImpl<false, false>(self, code_item, shadow_frame, result_register);
}
}
} else {
// Enter the "with access check" interpreter.
if (kInterpreterImplKind == kSwitchImpl) {
if (transaction_active) {
return ExecuteSwitchImpl<true, true>(self, code_item, shadow_frame, result_register);
} else {
return ExecuteSwitchImpl<true, false>(self, code_item, shadow_frame, result_register);
}
} else {
DCHECK_EQ(kInterpreterImplKind, kComputedGotoImplKind);
if (transaction_active) {
return ExecuteGotoImpl<true, true>(self, code_item, shadow_frame, result_register);
} else {
return ExecuteGotoImpl<true, false>(self, code_item, shadow_frame, result_register);
}
}
}
}
```
**接下来是第三代加壳也就是函数抽取了,在第二代壳的基础上,我们要处理得更多一些,因为函数被抽空了,这里我们只讲思路,不讲实现,如果有机会以后给读者讲解完整,**
**那第三代抽取壳,我们就得知道dex的文件结构了,具体这里就不贴了,大家可以网上搜一下了解一下dex的文件结构,简单来说就是第三代抽取壳,我们需要拿到code_item,code_item里面有函数的偏移,寄存器个数,使用的寄存器分别是谁以及函数的索引,索引就是函数的所在位置,然后我们拿到这个偏移就可以通过hook的方式来主动调来修复dex**
## 笔者废话2
**很感谢能看到这里,很久没输出过帖子了,这次写得挺差的。距离上次投稿已经一年了,消失的一年时间内我在疯狂的提升自己,安卓fridahook百例会继续更新,但是光用frida也不知道能不能更新百例感觉任重道远,有空就写点类似于原理的?感觉还是原理有意思哈。**
## 文中部分摘抄文章地址
[【新提醒】拨云见日:安卓APP脱壳的本质以及如何快速发现ART下的脱壳点 - 『移动安全区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn](https://www.52pojie.cn/thread-1026370-1-1.html)
[深入理解Android ClassLoader - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/136083521) 关于函数指令抽取补充一下,其实可以不获取code_item的 ias想 发表于 2023-12-5 19:10
怎么说 大佬是想直接逆加固算法静态还原?
对抗函数指令抽取,核心思路就是调用壳的回填函数,可以是LoadMethod或者其他用于回填的函数。当所有方法都被调用后,在内存中已经是解密的状态了,这时候dump的dex就是完整的。 前排支持,看看帖子,我还没整过手机app 本帖最后由 S5QpYJxg8y 于 2023-12-5 23:56 编辑
是的,原则上加壳确实能给apk本身带来诸多的安全性和增加被不法分子利用的可能性增加???
想说安全性增加 打错了吧
被不法分子利用的可能性应该是减少 Slimu 发表于 2023-12-5 18:56
关于函数指令抽取补充一下,其实可以不获取code_item的
怎么说 大佬是想直接逆加固算法静态还原? 前排支持,看看帖子 对我很有帮助,很赞 很久没从事安卓开发了,现在的加壳都这么高端了啊 看完了这篇和附录的一篇安卓APP脱壳的本质,对脱壳有了基础认识,感谢花时间写的教程{:301_1003:}