安卓逆向基础篇

安卓系统

其实安卓系统核心就是Linux系统,保持着“万物皆文件”的特性。

一、根目录文件夹:

  1. 根目录 (/):系统的最顶层目录,包含系统核心文件和目录链接。
  2. 系统目录 (/system):存放系统核心文件和库。
    • /system/app:系统应用的APK文件。
    • /system/bin:Linux系统命令和工具。
    • /system/etc:系统配置文件。
    • /system/framework:系统框架库。
    • /system/lib:系统共享库文件(.so)。
    • /system/media:系统媒体文件,如铃声、图片等。
  3. 数据目录 (/data):用户数据和应用程序数据。
    • /data/app:用户安装的应用APK文件。
    • /data/data/<package_name>:每个应用的私有数据目录。
  4. 缓存目录 (/cache):临时文件和缓存数据。
  5. 存储目录 (/sdcard):外部存储,用户视角下的“根目录”
  6. 设备目录 (/dev):设备文件。
  7. 进程目录 (/proc):系统运行时信息。映射内存中的进程信息
  8. 系统属性文件 (/system/build.prop):系统属性配置。

二、Android 启动过程

Pastedimage20250514214351.png

  • 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都发生了什么:
Editor _ Mermaid Chart-2025-05-16-132204.png

安卓文件结构

一、APK文件基本结构

我们随便找个apk解压,能看到这样的目录:
Pasted image 20250516142307.png

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
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
<?xml version="1.0" encoding="utf-8"?>
<!-- AndroidManifest.xml 是每个 Android 应用的核心配置文件,
用于声明应用的组件、权限、配置信息以及与系统的交互方式 -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">

<!-- 声明应用兼容的 SDK 版本范围 -->
<uses-sdk
android:minSdkVersion="21"
android:targetSdkVersion="33"
android:maxSdkVersion="33" />

<!-- 声明应用期望的设备配置 -->
<uses-configuration
android:reqFiveWayNav="false"
android:reqHardKeyboard="false"
android:reqKeyboardType="nokeys"
android:reqNavigation="nonav"
android:reqTouchScreen="finger" />

<!-- 声明应用需要的硬件或软件特性 -->
<uses-feature
android:name="android.hardware.camera"
android:required="true" />

<!-- 请求应用运行所需的权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />

<!-- 自定义权限定义,用于保护应用组件 -->
<permission
android:name="com.example.myapp.MY_PERMISSION"
android:label="string:permission_label"
android:description="string:permission_desc"
android:protectionLevel="normal" />

<!-- 权限树定义,用于组织相关权限组 -->
<permission-tree
android:name="com.example.myapp.permission_tree"
android:label="string:permission_tree_label" />

<!-- 权限组定义,用于对权限进行逻辑分组 -->
<permission-group
android:name="com.example.myapp.permission_group"
android:label="string:permission_group_label"
android:description="string:permission_group_desc" />

<!-- 声明测试工具类,用于自动化测试 -->
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.example.myapp" />

<!-- 声明应用支持的屏幕尺寸和密度 -->
<supports-screens
android:resizeable="true"
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:xlargeScreens="true"
android:anyDensity="true" />

<!-- 应用定义标签,包含应用的全局属性和组件 -->
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">

<!-- Activity 组件声明,用于提供用户界面 -->
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:exported="true">
<!-- Intent Filter 定义 Activity 可以响应的 Intent 类型 -->
<intent-filter>
<!-- 声明此 Activity 可以作为应用入口点 -->
<action android:name="android.intent.action.MAIN" />
<!-- 声明此 Activity 应显示在应用启动器中 -->
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<!-- Activity 别名声明,用于为现有 Activity 提供替代入口 -->
<activity-alias
android:name=".MainActivityAlias"
android:targetActivity=".MainActivity"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- 元数据定义,用于存储额外配置信息 -->
<meta-data
android:name="additional_info"
android:value="alias_info" />
</activity-alias>

<!-- Service 组件声明,用于在后台执行操作 -->
<service
android:name=".MyService"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="com.example.myapp.MY_SERVICE" />
</intent-filter>
<meta-data
android:name="service_config"
android:value="config_value" />
</service>

<!-- BroadcastReceiver 组件声明,用于监听系统或应用事件 -->
<receiver
android:name=".MyReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
<meta-data
android:name="receiver_data"
android:value="data_value" />
</receiver>

<!-- ContentProvider 组件声明,用于跨应用共享数据 -->
<provider
android:name=".MyContentProvider"
android:authorities="com.example.myapp.provider"
android:enabled="true"
android:exported="false">
<!-- 声明允许访问的 URI 权限 -->
<grant-uri-permission
android:path="/data"
android:pathPattern="/.*"
android:pathPrefix="/images" />
<meta-data
android:name="provider_info"
android:value="provider_value" />
</provider>

<!-- 声明应用需要链接的共享库 -->
<uses-library
android:name="android.test.runner"
android:required="true" />
</application>
</manifest>

5.classes.dex:

包含应用程序的所有Java类和方法,是应用程序的可执行文件。它是一个被编译过的DEX(Dalvik Executable)文件,用于在Android设备上运行Java代码,通常不止一个。

6.resources.arsc:

包含应用程序的所有资源信息,如布局文件、字符串、图片等。这个文件在应用程序编译过程中由aapt工具生成,并被打包进APK文件中

二、ELF文件格式

ELF是unix系统的二进制文件。前面说过安卓是基于Linux内核,如 init(系统初始化进程)、zygote(安卓应用孵化器)都是ELF文件,APP的动态链接库也是ELF文件。
Pasted image 20250516194339.png

ELF Header:描述了描述了体系结构和操作系统等基本信息并指出Section Header Table和Program Header Table在文件中的什么位置
Program Header Table: 保存了所有Segment的描述信息
Section Header Table:保存了所有Section的描述信息

常见字段含义:

Pasted image 20250516193625.png

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(全局偏移表)的结构,不立即解析函数地址。
静态重定位是在给延迟绑定做准备,动态重定位是在执行阶段。
Pasted image 20250516204047.png
与延迟绑定对应的是立即绑定。 动态链接器遍历所有外部符号(如printfmalloc)。 一次性解析所有符号的真实地址,并更新 GOT 表。 直接通过 GOT 表中的真实地址调用,无需动态链接器介入。

参考文档

Android目录结构全解析
Android启动流程
理解Android进程创建流程
深入理解Android-Runtime
AndroidManifest.xml 最全详解
Android Hook技术学习——常见的Hook技术方案总结