安卓逆向基础篇
安卓系统
其实安卓系统核心就是Linux系统,保持着“万物皆文件”的特性。
一、根目录文件夹:
- 根目录 (/):系统的最顶层目录,包含系统核心文件和目录链接。
- 系统目录 (/system):存放系统核心文件和库。
/system/app:系统应用的APK文件。/system/bin:Linux系统命令和工具。/system/etc:系统配置文件。/system/framework:系统框架库。/system/lib:系统共享库文件(.so)。/system/media:系统媒体文件,如铃声、图片等。
- 数据目录 (/data):用户数据和应用程序数据。
/data/app:用户安装的应用APK文件。/data/data/<package_name>:每个应用的私有数据目录。
- 缓存目录 (/cache):临时文件和缓存数据。
- 存储目录 (/sdcard):外部存储,用户视角下的“根目录”
- 设备目录 (/dev):设备文件。
- 进程目录 (/proc):系统运行时信息。映射内存中的进程信息
- 系统属性文件 (/system/build.prop):系统属性配置。
二、Android 启动过程

- 1. 引导加载程序(Bootloader)启动:
引导加载程序通常存储在设备固件中,类似电脑的 UEFI 固件(或手机的底层引导程序),主要任务是初始化硬件(如 CPU、内存、存储),并加载操作系统内核。其关键功能是验证系统分区的完整性和签名:通过厂商公钥校验核心分区(如boot内核分区、system系统分区、vbmeta签名校验分区)头部的签名数据,确保只有官方认证的系统才能启动。
我们常说的 Bootloader 解锁,是指通过厂商允许的操作(如 Fastboot 指令)触发 Bootloader 的解锁机制,使其不再强制执行分区签名校验(部分厂商会标记 “解锁状态” 并清除校验逻辑),从而允许用户刷入第三方系统或修改系统分区。 - 2.内核加载:
引导加载程序会根据预定义的配置从设备存储中加载操作系统内核。 - 3.内核初始化:
一旦内核加载到内存中,引导加载程序会将控制权转交给内核。内核开始执行初始化过程,包括对硬件进行初始化、建立虚拟文件系统、创建进程和线程等。 - 4.启动 init 进程:
内核初始化完成后,会启动名为init的用户空间进程。init进程是Android系统的第一个用户空间进程,它负责系统的进一步初始化和启动。init进程会读取系统配置文件(例如 init.rc),并根据其中的指令启动系统服务和应用程序。 - 5.启动 Zygote 进程:
在init进程启动后,会启动名为Zygote的进程。Zygote进程是Android应用程序的孵化器,它会预加载常用的Java类和资源,以加速应用程序的启动。 - 6.启动系统服务(SystemServer):
在Zygote进程启动后,还会启动一系列系统服务,例如SurfaceFlinger、ActivityManager、PackageManager等。这些系统服务负责管理系统的各个方面,包括显示、应用程序生命周期、包管理等。 - 7.启动桌面程序(Launcher):
一旦系统服务启动完成,Android系统就处于可用状态。就会启动桌面程序,用户可以开始启动应用程序并使用设备进行各种操作了。
三、Android Runtime
JVM
安卓的app大多都是由Java或者Kotlin编写的,JVM就是Java程序的运行时环境,但是严格来说不算是安卓运行时。
使用Java编写的代码,需要经过javac或者kotlinc编译成.class文件,最后在JVM上个运行。
而JVM又可简单分为Class Loader、Runtime Data Area以及Execution Engine三个部分,Class Loader负责从.class加载和分配内存。Runtime Data负责存储数据,分为方法区、堆区、栈区、程序计数器以及本地方法栈。Execution Engine负责二进制代码的执行以及垃圾回收,其中又有Interpreter(解释器执行)和JIT(即时编译)两种执行模式,JIT会把长期执行的热点代码编译成本地机器码缓存起来,后续无需解释可直接执行。
Dalvik
针对JVM还要说的是,这是一种基于栈的虚拟机,如果移动端使用这种运行时,必然会对设备的性能要求很高。因此引入基于寄存器的虚拟机——Dalvik。
Dalvik虚拟机的可执行文件是.dex,可以通过.class转换得来。Dalvik同样有interpreter和JIT两种执行模式。
ART
到了Android4.4,Google推出了新的Android Runtime——ART。
为了缩短应用启动时长,采用了AOT(Ahead-of-time)编译方式,即在程序安装时就将dex提前编译成机器码,生成一个oat文件。但这会导致安装时间延长,占用更多的存储空间。
四、进程创建
1.整个过程涉及到多个IPC(进程间通信),主要是binder、sockert机制。
- binder是安卓系统特有的跨进程通信机制,基于 C/S 架构,主要用于系统进程和应用进程间通信
- socket是一种是网络通信方式,多用于跨设备间进程通信,但是同样可以用本地回环地址实现本地跨进程通信。
安卓系统中还有一种通信用方式得比较多吧,intent机制,又可以分为显式和隐式两种。但是它是实现的组件间通信,结合binder机制可以实现跨进程的组件通信。
2.进程创建大致分为四个阶段:
- a. 点击桌面快捷方式之后,Laucher所在进程向system_server发送请求
- b. system_server中的AMS启动Proccess.start,向Zygote发送新建进程的请求。
- c. Zygote进程在系统启动过程开始创建,经过执行
ZygoteInit.main()后便进入runSelectLoop()循环体内,当收到创建新的进程请求后,Zygote进程执行ZygoteConnection.runOnce()方法,最后通过fork()创建新进程。 - d. 新建的app进程相当于Zygote的子进程,接着执行handleChildProc方法,最后调用ActivityThread.main()方法。
每个阶段都有着较为复杂的函数调用,具体的源码分析移步理解Android进程创建流程
3.下面这个图可以大致解释启动一个app都发生了什么:
安卓文件结构
一、APK文件基本结构
我们随便找个apk解压,能看到这样的目录:
1.assets:
存放静态资源文件,如图片、视频等。这些文件不会被编译,而是直接打包到最终的程序中
2.lib:
存放.so库文件,可能会针对不同架构,在其下一级目录设计多个文件夹,如armeabi-v7a、arm64-v8a、x86
3.META-INF:
存放数字签名相关文件,包括MANIFEST.MF、CERT.SF和CERT.RSA
- MANIFEST.MF:这是摘要文件。程序遍历Apk包中的所有文件(entry),对非文件夹非签名文件的文件,逐个用SHA1(安全哈希算法)生成摘要信息,再用Base64进行编码。如果你改变了apk包中的文件,那么在apk安装校验时,改变后的文件摘要信息与MANIFEST.MF的检验信息不同,于是程序就不能成功安装。
- CERT.SF:这是对摘要的签名文件。对前一步生成的MANIFEST.MF,使用SHA1-RSA算法,用开发者的私钥进行签名。在安装时只能使用公钥才能解密它。解密之后,将它与未加密的摘要信息(即,MANIFEST.MF文件)进行对比,如果相符,则表明内容没有被异常修改。
- CERT.RSA文件中保存了公钥、所采用的加密算法等信息。
4.AndroidMainfest.xml:
包含应用程序的基本信息,如包名、版本号、权限、组件等。系统根据这个文件来管理应用程序的生命周期
1 |
|
5.classes.dex:
包含应用程序的所有Java类和方法,是应用程序的可执行文件。它是一个被编译过的DEX(Dalvik Executable)文件,用于在Android设备上运行Java代码,通常不止一个。
6.resources.arsc:
包含应用程序的所有资源信息,如布局文件、字符串、图片等。这个文件在应用程序编译过程中由aapt工具生成,并被打包进APK文件中
二、ELF文件格式
ELF是unix系统的二进制文件。前面说过安卓是基于Linux内核,如 init(系统初始化进程)、zygote(安卓应用孵化器)都是ELF文件,APP的动态链接库也是ELF文件。
ELF Header:描述了描述了体系结构和操作系统等基本信息并指出Section Header Table和Program Header Table在文件中的什么位置
Program Header Table: 保存了所有Segment的描述信息
Section Header Table:保存了所有Section的描述信息
常见字段含义:

GOT
- 全称:Global Offset Table(全局偏移表)。
- 位置:数据段(
.got/.got.plt)。 - 作用:存储全局变量和外部函数的地址,运行时可被动态链接器修改。
PLT
- 全称:Procedure Linkage Table(程序链接表)。
- 位置:代码段(
.plt)。 - 作用:包含汇编指令,实现外部函数的延迟绑定和调用跳转。
- 特性:程序加载后不可修改(只读),但可通过 GOT 间接控制跳转目标。
重定位
重定位(Relocation)是计算机程序链接和加载过程中的关键机制,用于解决程序中符号(如函数、变量)地址的动态分配问题。简单来说,它是将程序中符号的逻辑地址转换为实际物理地址**的过程。那么在Linux系统中,重定位的过程实际就是通过GOT表和PLT表来实现的。可以分为两个阶段:
- 链接重定位:也叫静态重定位。在链接阶段,当发现函数是外部函数时,链接器在PLT段生成一段跳转代码PLT Stub,然后将代码中调用该函数的地址换成stub的地址
- 运行时重定位:也叫动态重定位。在运行时,把外部函数的地址加载到got表中,调用该函数时,通过plt调转到got表中存储的地址实现调用。
延迟绑定
延迟绑定指的是将外部函数地址的解析推迟到首次调用时,从而减少程序启动时间。
程序启动时,仅初始化 PLT(程序链接表)和 GOT(全局偏移表)的结构,不立即解析函数地址。
静态重定位是在给延迟绑定做准备,动态重定位是在执行阶段。
与延迟绑定对应的是立即绑定。 动态链接器遍历所有外部符号(如printf、malloc)。 一次性解析所有符号的真实地址,并更新 GOT 表。 直接通过 GOT 表中的真实地址调用,无需动态链接器介入。
参考文档
Android目录结构全解析
Android启动流程
理解Android进程创建流程
深入理解Android-Runtime
AndroidManifest.xml 最全详解
Android Hook技术学习——常见的Hook技术方案总结