mirror of
https://github.com/feicong/rom-course.git
synced 2025-11-05 10:23:32 +00:00
commit
352b9df8bd
@ -1,6 +1,6 @@
|
||||
# 第二章 系统开发环境与工具
|
||||
|
||||
经过第一章的学习,对`AOSP`定制进行简略的介绍后,大家应该对系统定制开发的基础有了大致的理解。所谓的系统定制,实际相当于在一款成熟的产品上进行二次开发。二次开发最耳熟能详的要属开发板二次开发领域了,比如知名经典的ARM架构开发板树霉派,厂家在出厂时提供了一些基础的系统与工具套件,开发人员可以扩展这个板子的玩法,实现玩具车、智能家居、电视盒子、路由器、小型内容服务器等多种功能的开发,这里面涉及到的二次开发知识也是相当的丰富。
|
||||
经过第一章的学习,对`AOSP`定制进行简略的介绍后,大家应该对系统定制开发的基础有了大致的理解。所谓的系统定制,实际相当于在一款成熟的产品上进行二次开发。二次开发最耳熟能详的要属开发板二次开发领域了,比如知名经典的ARM架构开发板树莓派,厂家在出厂时提供了一些基础的系统与工具套件,开发人员可以扩展这个板子的玩法,实现玩具车、智能家居、电视盒子、路由器、小型内容服务器等多种功能的开发,这里面涉及到的二次开发知识也是相当的丰富。
|
||||
|
||||
当然,本书不讨论功能庞大复杂的系统定制,而是以安全领域为面,技术功能为切入点的形式来讨论系统二次定制的一般方法。和常见的软件项目的二次开发的学习步骤类似,不会有太大的出入,细节的区别就在于,`Android`源码相比其他软件项目要更加庞大复杂,需要做一些基础知识的储备,另外,修改编译以及测试系统是一个反复执行的动作,它占用了定制开发中所耗费的大多数时间。
|
||||
|
||||
@ -25,15 +25,15 @@
|
||||
|
||||
如果你的系统是`Windows10`,那么你需要先查询当前系统版本,`Win10`下必须是18917或更高的版本才支持`WSL2`。在`cmd`命令行中输入`winver`命令查看当前系统版本号。
|
||||
|
||||

|
||||

|
||||
|
||||
由于是系统自带的,所以安装起来非常方便,可以直接在控制面版->程序->启动或关闭`Window`功能中开启支持即可,如下图
|
||||
|
||||

|
||||

|
||||
|
||||
或者是采用命令的方式开启虚拟机平台和`Linux`子系统,使用管理员权限启动。
|
||||
|
||||

|
||||

|
||||
|
||||
打开Powershell,执行下面的命令开启功能。
|
||||
|
||||
@ -46,7 +46,7 @@ Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-L
|
||||
|
||||
启动完成这些特性后,重新启动计算机,就可以开始安装一个`Ubuntu`系统了。打开`Microsoft Store`应用商店输入关键字`Ubuntu`进行搜索,然后选择自己需要的版本即可,例如这里安装的是22.04版本,如下图。
|
||||
|
||||

|
||||

|
||||
|
||||
成功获取`Ubuntu`系统后,从应用中启动系统即开始正式安装。安装过程只需要设置好用户名与密码即可。完成后会进行一个`shell`环境供用户输入。第一次启动时,需要设置用户名与密码来完成初始化配置。以后,只需要在终端工具的界面上,选择Ubuntu就可以启动了,整个启动过程中非常的快,只要数秒就会在窗口中给出一个Shell提示符,供用户输入操作。
|
||||
|
||||
@ -79,27 +79,27 @@ wsl --import ubuntu22 E:\wsl2\ubuntu22_wsl E:\wsl2\ubuntu22.tar
|
||||
1. 下载并安装`VMware`虚拟机,然后下载`Ubuntu22.04`系统`ISO`镜像文件。
|
||||
2. `VWware`创建虚拟机,选择指定镜像
|
||||
|
||||

|
||||

|
||||
|
||||
3. 设置初始账号密码
|
||||
|
||||

|
||||

|
||||
|
||||
4. 选择虚拟机保存位置,这里不要保存在`C`盘,记得磁盘要有至少`300G`的空间
|
||||
|
||||

|
||||

|
||||
|
||||
5. 虚拟硬件`CPU`核心根据你的电脑配置进行调整,尽量多分点给虚拟机。
|
||||
|
||||

|
||||

|
||||
|
||||
6. 虚拟内存分配,至少保证`16G`以上的内存,否则可能会碰到内存不足编译失败的情况。
|
||||
|
||||

|
||||

|
||||
|
||||
7. 虚拟硬盘分配,这里至少分配`500G`的空间,考虑到性能,我选择的是单文件吗,这里如果选择立即分配所有磁盘空间,能提高一定的性能。如果你的电脑配置不是很高,建议你选择立即分配。
|
||||
|
||||

|
||||

|
||||
|
||||
虚拟机开机后,将默认进入`Ubuntu`安装界面,按照提示进行选择语言,区域等待安装完成即可。
|
||||
|
||||
@ -174,9 +174,9 @@ export REPO_URL='https://mirrors.tuna.tsinghua.edu.cn/git/git-repo/'
|
||||
|
||||
#### 2.2.2 分支选择策略
|
||||
|
||||
根据自己的需求来选择合适的版本,比如想要在`Android`12的基础上进行二次开发,参考官方文档:https://source.android.com/docs/setup/about/build-numbers?hl=zh-cn,找到对应的版本描述,例如下图,可以看到各个版本号关联的代码分支,各分支版本支持哪些设备。
|
||||
根据自己的需求来选择合适的版本,比如想要在`Android`12的基础上进行二次开发,参考官方文档:https://source.android.com/docs/setup/about/build-numbers?hl=zh-cn ,找到对应的版本描述,例如下图,可以看到各个版本号关联的代码分支,各分支版本支持哪些设备。
|
||||
|
||||

|
||||

|
||||
|
||||
这么多版本,要根据自身的需求选一个适合的版本,例如我的选择策略如下:
|
||||
|
||||
@ -186,7 +186,7 @@ export REPO_URL='https://mirrors.tuna.tsinghua.edu.cn/git/git-repo/'
|
||||
|
||||
如果该`ROM`主要在虚拟机中刷机测试的,那么选择支持版本最多的分支即可。这里我的测试设备是`pixel`3,根据上文中的选择策略找到了版本`SP1A.210812.016.A1`,对应的分支代码是`android-12.0.0_r3`,如下图。
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
#### 2.2.3 源码拉取与同步
|
||||
@ -242,7 +242,7 @@ export OUT_DIR=~/android_src/aosp12_out
|
||||
|
||||
在开始编译前,还需要准备对应设备的驱动,根据前面选择的版本号`SP1A.210812.016.A1`,在官网地址:`https://developers.google.com/android/drivers`中找到对应的版本号。
|
||||
|
||||

|
||||

|
||||
|
||||
第一个文件`Vendor`是用来存储厂商特定的文件,比如设备驱动程序。`Android`驱动会根据提供的这些设备驱动来正确的加载硬件。这个文件通常由设备厂商提供。如果你成功编译`Android`后,输出目录缺少`vendor.img`文件,那么你就需要检查下是否忘记导入对应型号的设备驱动了。
|
||||
|
||||
@ -418,9 +418,9 @@ Common goals are:
|
||||
|
||||
系统编译完成后,可以在编译的镜像结果中看到文件`boot.img`,这个文件是内核镜像文件。但是这个内核默认采用`Android`源码树中预编译好的内核文件,没有使用源码编译出来的内核文件,如果想要为编译的系统纳入自编译的内核,需要拉取对应分支的内核代码参与编译,并将编译结果放入`Android`源码树中的指定路径,最后再重新编译打包`Android`镜像。这样,生成的系统刷入手机后,使用的内核就是自编译的版本了。
|
||||
|
||||
首先,找到对应当前手机的内核分支,官网提供了详细的说明文档:https://source.android.com/docs/setup/build/building-kernels。根据下图可以看到,对应`Pixel`3测试机分支是`android-msm-crosshatch-4.9-android12`。
|
||||
首先,找到对应当前手机的内核分支,官网提供了详细的说明文档:https://source.android.com/docs/setup/build/building-kernels 。根据下图可以看到,对应`Pixel`3测试机分支是`android-msm-crosshatch-4.9-android12`。
|
||||
|
||||

|
||||

|
||||
|
||||
接下来,按照官网的说明拉取代码。
|
||||
|
||||
@ -440,7 +440,7 @@ repo init -u https://android.googlesource.com/kernel/manifest -b android-msm-cro
|
||||
repo sync -j$(nproc --all)
|
||||
```
|
||||
|
||||
成功拉取代码后,还需要将内核检出对应的`commit ID`,确保和官方镜像的内核分支一致。首先下载官方镜像,下载地址:https://developers.google.com/android/images,找到`Pixel 3`下的版本`SP1A.210812.016.A1`的官方镜像下载。
|
||||
成功拉取代码后,还需要将内核检出对应的`commit ID`,确保和官方镜像的内核分支一致。首先下载官方镜像,下载地址:https://developers.google.com/android/images ,找到`Pixel 3`下的版本`SP1A.210812.016.A1`的官方镜像下载。
|
||||
|
||||
解压官方镜像,然后手机进入`bootloader`引导模式,运行官方镜像包中的`flash-all.bat`刷入手机,操作如下。
|
||||
|
||||
@ -629,11 +629,11 @@ fastboot reboot fastboot
|
||||
|
||||
这时的界面如下图,使用音量键减,切换到`Enter recovery`,然后按电源键进入`recovery`模式。
|
||||
|
||||

|
||||

|
||||
|
||||
`recovery`模式的界面,选择`Apply update from ADB`
|
||||
|
||||

|
||||

|
||||
|
||||
使用命令`adb devices`查看当前状态显示为`sideload`,即可直接通过命令`adb sideload ota.zip`进行刷机。
|
||||
|
||||
@ -1003,7 +1003,7 @@ exit
|
||||
|
||||
后面需要创建大量的子模块仓库,不可能在`web`页面上手动一个个的创建,下面使用命令来创建一个`manifests.git`仓库。这种方式需要`gitlab`账号的`Access Token`。可以在`web`中登录账号,点击右上角的用户图标,选择`Preferences`来到用户设置页面,然后进入`Access Tokens`栏目,填写`token`名称以及勾选权限,最后点击生成,例如生成的`token`为`27zctxyWZP9Txksenkxb`。流程见下图。
|
||||
|
||||

|
||||

|
||||
|
||||
首先,在`gitlab`中手动创建一个根目录的`group`,这里创建了一个`android12_r3`的组,所有的子模块仓库都将在这个分组下。在`gitlab`页面中点击左上角`Groups->your Groups`。点击`New group`创建分组。成功创建后,记录下这个分组的`id`,比如我的根目录组`id`是6.
|
||||
|
||||
|
||||
@ -282,7 +282,7 @@ static int run_init_process(const char *init_filename)
|
||||
|
||||
这里能看到最后是通过`execve`拉起来了系统的第一个进程,`init`进程。总结内核启动的简单流程图如下。
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
## 3.4 Init进程启动
|
||||
@ -1620,7 +1620,7 @@ pid-3638 W start CallStaticVoidMethod current proce
|
||||
|
||||
结合观测到的代码流程,再看下面的一个汇总图。不需要完全理解启动过程中的所有的处理,重点是在这里留下一个大致的印象以及简单的整理。
|
||||
|
||||

|
||||

|
||||
|
||||
## 3.7 Android app应用启动
|
||||
|
||||
@ -2493,7 +2493,7 @@ public void callApplicationOnCreate(Application app) {
|
||||
|
||||
看一张经典的`Android`架构图。
|
||||
|
||||

|
||||

|
||||
|
||||
从上图中可以看到`Framewok`的组成部分,它们的功能分别是:
|
||||
|
||||
@ -3048,11 +3048,11 @@ allow zygote apk_data_file:dir search;
|
||||
|
||||
接着给`010 Editor`编辑器安装一个`ELF`格式解析的模板,在工具栏找到模板->模板存储库。搜索`ELF`,点击安装,操作见下图。
|
||||
|
||||

|
||||

|
||||
|
||||
模板安装后,关闭文件,重新使用`010 Editor`打开后,将编辑方式切换为模板后,就能成功看到使用ELF格式解析so文件的结果了,如下图。
|
||||
|
||||

|
||||

|
||||
|
||||
`ELF`头部定义了`ELF`文件的基本属性和结构,也为后续的段表和节表等信息提供了重要的指导作用。加载`ELF`文件的第一步就是解析`ELF`头部后,再根据头部信息去解析其他部分的数据,`ELF`头部(`elf_header`)结构包含以下成员:
|
||||
|
||||
@ -3086,7 +3086,7 @@ allow zygote apk_data_file:dir search;
|
||||
|
||||
下图是`010 Edtior`解析展示的结果图。
|
||||
|
||||

|
||||

|
||||
|
||||
`program header table`是一种用于描述可执行文件和共享库的各个段(`section`)在进程内存中的映射关系的结构,也称为段表。每个程序头表入口表示一个段。在`Linux`系统中,它是被操作系统用于将`ELF`文件加载到进程地址空间的重要数据结构之一。每个`program header table`具有相同的固定结构,相关字段如下:
|
||||
|
||||
@ -3108,7 +3108,7 @@ allow zygote apk_data_file:dir search;
|
||||
|
||||
下图是编辑器中解析so看到的值
|
||||
|
||||

|
||||

|
||||
|
||||
`section header table`(节头表)是用于描述`ELF`文件中所有节(`section`)的元信息列表,也称为节表。它包含了每个节在文件中的位置、大小、类型、属性等信息。节头表的中相关字段如下:
|
||||
|
||||
@ -3134,7 +3134,7 @@ allow zygote apk_data_file:dir search;
|
||||
|
||||
通过这些信息,`section header table`可以为执行链接和动态加载提供必要的元数据信息。样例数据看下图
|
||||
|
||||

|
||||

|
||||
|
||||
`ELF`文件中有各种节用于存放对应的信息,几个常见的节点存放数据的描述如下。
|
||||
|
||||
@ -3152,7 +3152,7 @@ allow zygote apk_data_file:dir search;
|
||||
|
||||
* `.shstrtab` 节点(`Section Header String Table`)存储节名称字符串,即每个节的名称和节头表中的节名称偏移量。它包含了`ELF`文件中每个节的字符串名称,方便读取程序在加载时快速访问。在`Android`中,`.shstrtab`节点是一个特殊的节,它位于节头表的末尾,可以通过`ELF`文件头的`e_shstrndx`字段找到。
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
### 3.12.2 动态库加载流程
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
|
||||
下图展示的是`Google`官方的`Android`系统桌面图,以及自己编译`AOSP`的桌面图。
|
||||
|
||||

|
||||

|
||||
|
||||
从上图中可以看到明显差异,`Google`官方的`ROM`相较于`AOSP` ROM多了一些功能和应用,例如谷歌应用套件:`Google Mobile Services(GMS)`包含了各种谷歌应用,如`Gmail、Google Maps、Play`商店等等。这些应用在`AOSP` ROM中是没有的。
|
||||
|
||||
@ -69,7 +69,7 @@
|
||||
|
||||
在前文中和`Google`官方`ROM`对比的界面图,就是`Android`的`UI`界面中的壁纸了,壁纸是在手机主页面的背景图,壁纸可以在手机中进行切换修改,同样也可以直接修改默认的壁纸,默认壁纸的路径是`frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.png`。下图是`AOSP`中的默认壁纸。
|
||||
|
||||

|
||||

|
||||
|
||||
知道壁纸素材的路径后,可以通过对这个素材进行替换来达到修改的目的,同样也可以通过查找设置的地方,修改默认设置选项,将壁纸切换为另一张图片来完成壁纸修改,前者的好处在于简单快捷,替换素材即可。而后者在于稳妥,随时可以调整切换回原素材。替换的方式较为简单就不再细说,这里看看通过修改设置的实现。
|
||||
|
||||
@ -270,7 +270,7 @@ if __name__ == '__main__':
|
||||
|
||||
解压开机动画压缩包后的文件如下图。
|
||||
|
||||

|
||||

|
||||
|
||||
`desc.txt`文件内容如下。
|
||||
|
||||
@ -290,7 +290,7 @@ c 1 0 part5
|
||||
|
||||
查看其中一个目录下的文件如下图。
|
||||
|
||||

|
||||

|
||||
|
||||
对这些了解后,接着开始对其进行替换,为了便于简单演示,就不找新的素材进行替换了,直接将`androidtv`的开机动画替换为当前开机动画,找到文件`device/google/atv/products/bootanimations/bootanimation.zip`,将其复制到自定义的任意目录,例如新建目录`packages/bootstart/`,将启动动画拷贝到该目录中。然后在文件`build/make/target/product/generic_system.mk`添加配置,将其拷贝到`system/media/`目录下。相关修改如下。
|
||||
|
||||
|
||||
@ -366,7 +366,7 @@ android_app_import {
|
||||
|
||||
有多种方式可以实现内置`JAR`包功能。接下来将介绍两种方法来集成一个自己编写的JAR包到系统中。首先要创建一个没有Activity的Android项目,并命名为`MyJar` ,如下图所示:
|
||||
|
||||

|
||||

|
||||
|
||||
接着简单的写两个测试函数。在最后内置成功后,将对这个函数进行调用测试是否内置成功。
|
||||
|
||||
@ -520,7 +520,7 @@ protected void onCreate(Bundle savedInstanceState) {
|
||||
|
||||
首先准备一个测试项目,创建Native C++的项目。见下图。
|
||||
|
||||

|
||||

|
||||
|
||||
这个项目并不需要启动,所以直接删除`MainActivity`文件,添加一个类来加载动态库。并且修改`cpp`中对应的函数名称,相关修改如下。
|
||||
|
||||
|
||||
@ -934,7 +934,7 @@ DexFile::DexFile(const uint8_t* base,
|
||||
|
||||
使用`010 Editor`工具,通过模板库在线安装`DEX.bt`模板,然后打开之前的样例文件,查看在例子中`header_`的真实数据。
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
## 7.3 函数调用流程
|
||||
@ -994,7 +994,7 @@ public interface Opcodes {
|
||||
|
||||
使用`010 Editor`工具,将样例程序解压后获得的`classes.dex`拖入`010 Editor`打开。看到结果如下。
|
||||
|
||||

|
||||

|
||||
|
||||
接下来在`dex_class_defs`中寻找刚刚分析的目标类`MyCommon`。
|
||||
|
||||
@ -1004,11 +1004,11 @@ struct class_def_item class_def[2205] public cn.rom.myjar.MyCommon 1243C4h 20h F
|
||||
|
||||
将其展开后,能看到该`class`的详细信息,在上一节的类加载中,当`DEX`被解析后,加载的类在内存中就是以这样的结构存储着数据。
|
||||
|
||||

|
||||

|
||||
|
||||
在其中的函数结构体下面的`code_item`类型的数据,就存储着该函数要执行的`java`字节码,继续展开该结构。
|
||||
|
||||

|
||||

|
||||
|
||||
这里就能看到对该函数结构的描述了,`insns`中则存储着函数要执行的指令。每个指令的单位是`ushort`,即两个字节存储,将这里的三个指令转换为16进制表示则是。
|
||||
|
||||
|
||||
@ -980,7 +980,7 @@ D/k.myservicedem: [ROM] DefineClass write 2 /data/data/cn.rom.myservicedemo/defi
|
||||
|
||||
最后将这个文件传到电脑中,使用反编译工具`jadx`打开看到脱壳后的结果。
|
||||
|
||||

|
||||

|
||||
|
||||
在这个自动脱壳的例子中,并不限于在哪个调用时机来对其保存到文件,只要是在加载过程中,能获取到`DexFile`结果的地方,大多数都是能拿到动态加载壳所保护的目标。当你不确定的情况,可以先加上打桩信息,然后逐步去排查来判断你使用的调用时机是否可用。
|
||||
|
||||
|
||||
@ -98,11 +98,11 @@ su
|
||||
|
||||
接下来打开`ida`,选择`Debugger->Attach->Remote Arm linux/android debugger`,在`hostname`选项中填本地回环地址`127.0.0.1`,如下图。
|
||||
|
||||

|
||||

|
||||
|
||||
点击`ok`后,则会展示所有`Android`中的进程,在其中进行过滤,找到目标进程。如下图
|
||||
|
||||

|
||||

|
||||
|
||||
成功挂起调试后,检查日志中的 `ppid`,发现并没有任何变化,依然是`zygote`作为父进程。
|
||||
|
||||
@ -226,7 +226,7 @@ Java_cn_rom_nativedemo_MainActivity_stringFromJNI(
|
||||
|
||||
然后使用`ida`尝试对子进程进行调试,发现无法正常附加该进程了,错误如下。
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
### 11.1.4 检测跟踪工具
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user