├── .gitignore ├── cover.pdf ├── cover.png ├── chapter-04 ├── images │ ├── play.png │ ├── 未标题-1.jpg │ ├── bootanimation.png │ ├── image-20230305113752289.png │ ├── image-20230305113916331.png │ └── image-20230305152441883.png └── README.md ├── chapter-07 └── images │ ├── dex模板.png │ ├── jiagu.png │ ├── codeitem.png │ ├── codeitem1.png │ ├── def_class.png │ ├── classloader.png │ ├── dex_header.png │ └── jiagu_liucheng.png ├── chapter-08 └── images │ └── tuoke.png ├── code ├── chapter-09 │ └── DobbyDemo.zip ├── chapter-12 │ ├── NativeDemo.zip │ ├── backtraceDemo.zip │ ├── backtrace │ │ └── Android.mk │ ├── reflection.h │ └── quick_jni_entrypoints.cc ├── chapter-06 │ ├── NativeCppDemo.zip │ └── rom_service │ │ ├── RomManagerService.java │ │ ├── IRomManager.aidl │ │ ├── RomManager.java │ │ └── Android.bp ├── chapter-05 │ ├── SystemAppDemo │ │ ├── SystemAppDemo.zip │ │ └── Android.mk │ └── mysodemo │ │ └── Android.mk ├── chapter-04 │ └── replaceIcon.py ├── chapter-02 │ ├── putGit.py │ └── initGit.py └── chapter-10 │ └── ndksnoop │ └── ndksnoop.bt ├── chapter-03 ├── images │ ├── android-boot.jpg │ ├── startkernel.png │ ├── android-framework.jpg │ ├── image-20230219161123065.png │ ├── image-20230304135859598.png │ ├── image-20230304140328010.png │ ├── image-20230304141143199.png │ ├── image-20230304142500744.png │ ├── image-20230304143003972.png │ ├── image-20230304143100841.png │ └── image-20230304145047084.png └── startkernel.drawio ├── chapter-01 ├── images │ ├── jadx_framework.png │ ├── mokee_product.png │ ├── pixelExperence.png │ ├── manager_service.png │ └── android_framework_details.png └── README.md ├── chapter-11 ├── images │ ├── ida_attach_err.png │ ├── ida_debug_attach.png │ └── ida_debug_process.png └── README.md ├── chapter-05 └── images │ ├── create_so_project.png │ └── create_no_activity.png ├── chapter-02 └── images │ ├── image-20230102183339463.png │ ├── image-20230102183708998.png │ ├── image-20230102184626538.png │ ├── image-20230102194041709.png │ ├── image-20230102194243774.png │ ├── image-20230102194331141.png │ ├── image-20230102194543812.png │ ├── image-20230102194722427.png │ ├── image-20230102194952517.png │ ├── image-20230102204953596.png │ ├── image-20230102205344659.png │ ├── image-20230102210010065.png │ ├── image-20230102211051431.png │ ├── image-20230103220519836.png │ ├── image-20230103220838404.png │ ├── image-20230103232052738.png │ ├── image-20230105221730348.png │ ├── image-20230108174706799.png │ ├── image-20230108190236615.png │ ├── image-20230108190631803.png │ ├── image-20230216211544482.png │ └── 69ba546fd55c4fea8ef9b5d55a9bd354.png ├── SUMMARY.md ├── book.toml ├── README.md ├── TOC.md ├── chapter-09 └── README.md ├── chapter-10 └── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /book/ 2 | .DS_Store 3 | .pdf 4 | .html 5 | /pdf/ 6 | -------------------------------------------------------------------------------- /cover.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/cover.pdf -------------------------------------------------------------------------------- /cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/cover.png -------------------------------------------------------------------------------- /chapter-04/images/play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-04/images/play.png -------------------------------------------------------------------------------- /chapter-04/images/未标题-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-04/images/未标题-1.jpg -------------------------------------------------------------------------------- /chapter-07/images/dex模板.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-07/images/dex模板.png -------------------------------------------------------------------------------- /chapter-07/images/jiagu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-07/images/jiagu.png -------------------------------------------------------------------------------- /chapter-08/images/tuoke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-08/images/tuoke.png -------------------------------------------------------------------------------- /code/chapter-09/DobbyDemo.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/code/chapter-09/DobbyDemo.zip -------------------------------------------------------------------------------- /chapter-07/images/codeitem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-07/images/codeitem.png -------------------------------------------------------------------------------- /chapter-07/images/codeitem1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-07/images/codeitem1.png -------------------------------------------------------------------------------- /chapter-07/images/def_class.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-07/images/def_class.png -------------------------------------------------------------------------------- /code/chapter-12/NativeDemo.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/code/chapter-12/NativeDemo.zip -------------------------------------------------------------------------------- /chapter-03/images/android-boot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-03/images/android-boot.jpg -------------------------------------------------------------------------------- /chapter-03/images/startkernel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-03/images/startkernel.png -------------------------------------------------------------------------------- /chapter-07/images/classloader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-07/images/classloader.png -------------------------------------------------------------------------------- /chapter-07/images/dex_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-07/images/dex_header.png -------------------------------------------------------------------------------- /code/chapter-06/NativeCppDemo.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/code/chapter-06/NativeCppDemo.zip -------------------------------------------------------------------------------- /code/chapter-12/backtraceDemo.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/code/chapter-12/backtraceDemo.zip -------------------------------------------------------------------------------- /chapter-01/images/jadx_framework.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-01/images/jadx_framework.png -------------------------------------------------------------------------------- /chapter-01/images/mokee_product.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-01/images/mokee_product.png -------------------------------------------------------------------------------- /chapter-01/images/pixelExperence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-01/images/pixelExperence.png -------------------------------------------------------------------------------- /chapter-04/images/bootanimation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-04/images/bootanimation.png -------------------------------------------------------------------------------- /chapter-07/images/jiagu_liucheng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-07/images/jiagu_liucheng.png -------------------------------------------------------------------------------- /chapter-11/images/ida_attach_err.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-11/images/ida_attach_err.png -------------------------------------------------------------------------------- /chapter-01/images/manager_service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-01/images/manager_service.png -------------------------------------------------------------------------------- /chapter-03/images/android-framework.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-03/images/android-framework.jpg -------------------------------------------------------------------------------- /chapter-05/images/create_so_project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-05/images/create_so_project.png -------------------------------------------------------------------------------- /chapter-11/images/ida_debug_attach.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-11/images/ida_debug_attach.png -------------------------------------------------------------------------------- /chapter-11/images/ida_debug_process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-11/images/ida_debug_process.png -------------------------------------------------------------------------------- /chapter-05/images/create_no_activity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-05/images/create_no_activity.png -------------------------------------------------------------------------------- /chapter-02/images/image-20230102183339463.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/image-20230102183339463.png -------------------------------------------------------------------------------- /chapter-02/images/image-20230102183708998.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/image-20230102183708998.png -------------------------------------------------------------------------------- /chapter-02/images/image-20230102184626538.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/image-20230102184626538.png -------------------------------------------------------------------------------- /chapter-02/images/image-20230102194041709.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/image-20230102194041709.png -------------------------------------------------------------------------------- /chapter-02/images/image-20230102194243774.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/image-20230102194243774.png -------------------------------------------------------------------------------- /chapter-02/images/image-20230102194331141.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/image-20230102194331141.png -------------------------------------------------------------------------------- /chapter-02/images/image-20230102194543812.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/image-20230102194543812.png -------------------------------------------------------------------------------- /chapter-02/images/image-20230102194722427.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/image-20230102194722427.png -------------------------------------------------------------------------------- /chapter-02/images/image-20230102194952517.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/image-20230102194952517.png -------------------------------------------------------------------------------- /chapter-02/images/image-20230102204953596.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/image-20230102204953596.png -------------------------------------------------------------------------------- /chapter-02/images/image-20230102205344659.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/image-20230102205344659.png -------------------------------------------------------------------------------- /chapter-02/images/image-20230102210010065.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/image-20230102210010065.png -------------------------------------------------------------------------------- /chapter-02/images/image-20230102211051431.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/image-20230102211051431.png -------------------------------------------------------------------------------- /chapter-02/images/image-20230103220519836.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/image-20230103220519836.png -------------------------------------------------------------------------------- /chapter-02/images/image-20230103220838404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/image-20230103220838404.png -------------------------------------------------------------------------------- /chapter-02/images/image-20230103232052738.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/image-20230103232052738.png -------------------------------------------------------------------------------- /chapter-02/images/image-20230105221730348.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/image-20230105221730348.png -------------------------------------------------------------------------------- /chapter-02/images/image-20230108174706799.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/image-20230108174706799.png -------------------------------------------------------------------------------- /chapter-02/images/image-20230108190236615.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/image-20230108190236615.png -------------------------------------------------------------------------------- /chapter-02/images/image-20230108190631803.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/image-20230108190631803.png -------------------------------------------------------------------------------- /chapter-02/images/image-20230216211544482.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/image-20230216211544482.png -------------------------------------------------------------------------------- /chapter-03/images/image-20230219161123065.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-03/images/image-20230219161123065.png -------------------------------------------------------------------------------- /chapter-03/images/image-20230304135859598.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-03/images/image-20230304135859598.png -------------------------------------------------------------------------------- /chapter-03/images/image-20230304140328010.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-03/images/image-20230304140328010.png -------------------------------------------------------------------------------- /chapter-03/images/image-20230304141143199.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-03/images/image-20230304141143199.png -------------------------------------------------------------------------------- /chapter-03/images/image-20230304142500744.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-03/images/image-20230304142500744.png -------------------------------------------------------------------------------- /chapter-03/images/image-20230304143003972.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-03/images/image-20230304143003972.png -------------------------------------------------------------------------------- /chapter-03/images/image-20230304143100841.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-03/images/image-20230304143100841.png -------------------------------------------------------------------------------- /chapter-03/images/image-20230304145047084.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-03/images/image-20230304145047084.png -------------------------------------------------------------------------------- /chapter-04/images/image-20230305113752289.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-04/images/image-20230305113752289.png -------------------------------------------------------------------------------- /chapter-04/images/image-20230305113916331.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-04/images/image-20230305113916331.png -------------------------------------------------------------------------------- /chapter-04/images/image-20230305152441883.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-04/images/image-20230305152441883.png -------------------------------------------------------------------------------- /chapter-01/images/android_framework_details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-01/images/android_framework_details.png -------------------------------------------------------------------------------- /code/chapter-05/SystemAppDemo/SystemAppDemo.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/code/chapter-05/SystemAppDemo/SystemAppDemo.zip -------------------------------------------------------------------------------- /chapter-02/images/69ba546fd55c4fea8ef9b5d55a9bd354.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feicong/android-rom-book/HEAD/chapter-02/images/69ba546fd55c4fea8ef9b5d55a9bd354.png -------------------------------------------------------------------------------- /code/chapter-05/SystemAppDemo/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | include $(CLEAR_VARS) 3 | 4 | LOCAL_SRC_FILES := SystemAppDemo.apk 5 | LOCAL_MODULE := SystemAppDemo 6 | LOCAL_MODULE_CLASS := APPS 7 | LOCAL_MODULE_TAGS := optional 8 | LOCAL_CERTIFICATE := platform 9 | LOCAL_DEX_PREOPT := false 10 | 11 | include $(BUILD_PREBUILT) -------------------------------------------------------------------------------- /code/chapter-06/rom_service/RomManagerService.java: -------------------------------------------------------------------------------- 1 | package com.android.server; 2 | 3 | import android.os.RemoteException; 4 | import android.os.IRomManager; 5 | 6 | public class RomManagerService extends IRomManager.Stub { 7 | 8 | private String TAG="RomManagerService"; 9 | @Override 10 | public String hello() throws RemoteException{ 11 | return "hello rom service"; 12 | } 13 | } -------------------------------------------------------------------------------- /code/chapter-05/mysodemo/Android.mk: -------------------------------------------------------------------------------- 1 | LOCAL_PATH := $(call my-dir) 2 | 3 | #-------------------------------- 4 | include $(CLEAR_VARS) 5 | 6 | LOCAL_MODULE := libmysodemo 7 | LOCAL_SRC_FILES_arm := libmysodemo.so 8 | LOCAL_SRC_FILES_arm64 := libmysodemo_arm64.so 9 | LOCAL_MODULE_TARGET_ARCHS:= arm arm64 10 | LOCAL_MULTILIB := both 11 | LOCAL_MODULE_SUFFIX := .so 12 | LOCAL_MODULE_CLASS := SHARED_LIBRARIES 13 | LOCAL_MODULE_TAGS := optional 14 | LOCAL_SHARED_LIBRARIES := liblog 15 | 16 | include $(BUILD_PREBUILT) 17 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | - [Chapter 0](./README.md) 4 | - [Chapter 1](./chapter-01/README.md) 5 | - [Chapter 2](./chapter-02/README.md) 6 | - [Chapter 3](./chapter-03/README.md) 7 | - [Chapter 4](./chapter-04/README.md) 8 | - [Chapter 5](./chapter-05/README.md) 9 | - [Chapter 6](./chapter-06/README.md) 10 | - [Chapter 7](./chapter-07/README.md) 11 | - [Chapter 8](./chapter-08/README.md) 12 | - [Chapter 9](./chapter-09/README.md) 13 | - [Chapter 10](./chapter-10/README.md) 14 | - [Chapter 11](./chapter-11/README.md) 15 | - [Chapter 12](./chapter-12/README.md) 16 | -------------------------------------------------------------------------------- /book.toml: -------------------------------------------------------------------------------- 1 | [book] 2 | authors = ["非虫", "missingkeys"] 3 | language = "zh-CN" 4 | multilingual = false 5 | src = "." 6 | title = "安卓系统定制:从入门到实践" 7 | description = "这是一本开源的讲解安卓系统定制入门的电子书" 8 | version = "1.0.0" 9 | 10 | [output.html] 11 | git-repository-url = "https://github.com/feicong/rom-course" 12 | 13 | [output.pdf] 14 | 15 | # 添加页眉页脚 16 | display-header-footer= true 17 | header-template = "

https://github.com/feicong/rom-course

" 18 | footer-template = "

/

" 19 | prefer-css-page-size = true 20 | 21 | [output.pdf-outline] 22 | like-wkhtmltopdf = true 23 | -------------------------------------------------------------------------------- /code/chapter-06/rom_service/IRomManager.aidl: -------------------------------------------------------------------------------- 1 | /* //device/java/android/android/os/IPowerManager.aidl 2 | ** 3 | ** Copyright 2007, The Android Open Source Project 4 | ** 5 | ** Licensed under the Apache License, Version 2.0 (the "License"); 6 | ** you may not use this file except in compliance with the License. 7 | ** You may obtain a copy of the License at 8 | ** 9 | ** http://www.apache.org/licenses/LICENSE-2.0 10 | ** 11 | ** Unless required by applicable law or agreed to in writing, software 12 | ** distributed under the License is distributed on an "AS IS" BASIS, 13 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | ** See the License for the specific language governing permissions and 15 | ** limitations under the License. 16 | */ 17 | 18 | package android.os; 19 | 20 | interface IRomManager 21 | { 22 | String hello(); 23 | } 24 | -------------------------------------------------------------------------------- /code/chapter-12/backtrace/Android.mk: -------------------------------------------------------------------------------- 1 | 2 | #-------------------------------- 3 | include $(CLEAR_VARS) 4 | 5 | LOCAL_MODULE := libxdl 6 | LOCAL_SRC_FILES_arm := libxdl.so 7 | LOCAL_SRC_FILES_arm64 := libxdl_arm64.so 8 | LOCAL_MODULE_TARGET_ARCHS:= arm arm64 9 | LOCAL_MULTILIB := both 10 | LOCAL_MODULE_SUFFIX := .so 11 | LOCAL_MODULE_CLASS := SHARED_LIBRARIES 12 | LOCAL_MODULE_TAGS := optional 13 | LOCAL_SHARED_LIBRARIES := liblog 14 | 15 | include $(BUILD_PREBUILT) 16 | 17 | #-------------------------------- 18 | include $(CLEAR_VARS) 19 | 20 | LOCAL_MODULE := libxunwind 21 | LOCAL_SRC_FILES_arm := libxunwind.so 22 | LOCAL_SRC_FILES_arm64 := libxunwind_arm64.so 23 | LOCAL_MODULE_TARGET_ARCHS:= arm arm64 24 | LOCAL_MULTILIB := both 25 | LOCAL_MODULE_SUFFIX := .so 26 | LOCAL_MODULE_CLASS := SHARED_LIBRARIES 27 | LOCAL_MODULE_TAGS := optional 28 | LOCAL_SHARED_LIBRARIES := liblog libxdl 29 | 30 | include $(BUILD_PREBUILT) 31 | 32 | #-------------------------------- 33 | include $(CLEAR_VARS) 34 | 35 | LOCAL_MODULE := libkbacktrace 36 | LOCAL_SRC_FILES_arm := libkbacktrace_32.so 37 | LOCAL_SRC_FILES_arm64 := libkbacktrace_64.so 38 | LOCAL_MODULE_TARGET_ARCHS:= arm arm64 39 | LOCAL_MULTILIB := both 40 | LOCAL_MODULE_SUFFIX := .so 41 | LOCAL_MODULE_CLASS := SHARED_LIBRARIES 42 | LOCAL_MODULE_TAGS := optional 43 | LOCAL_SHARED_LIBRARIES := liblog libxunwind 44 | 45 | include $(BUILD_PREBUILT) -------------------------------------------------------------------------------- /code/chapter-06/rom_service/RomManager.java: -------------------------------------------------------------------------------- 1 | package android.os; 2 | 3 | import android.annotation.NonNull; 4 | import android.annotation.Nullable; 5 | import android.compat.annotation.UnsupportedAppUsage; 6 | import android.content.Context; 7 | import android.annotation.SystemService; 8 | import android.os.IRomManager; 9 | 10 | @SystemService(Context.ROM_SERVICE) 11 | public final class RomManager { 12 | private static final String TAG = "RomManager"; 13 | IRomManager mService; 14 | public RomManager(IRomManager service) { 15 | mService = service; 16 | } 17 | private static RomManager sInstance; 18 | /** 19 | *@hide 20 | */ 21 | @NonNull 22 | @UnsupportedAppUsage 23 | public static RomManager getInstance() { 24 | synchronized (RomManager.class) { 25 | if (sInstance == null) { 26 | 27 | try { 28 | IBinder romBinder = ServiceManager.getServiceOrThrow(Context.ROM_SERVICE); 29 | IRomManager romService = IRomManager.Stub.asInterface(romBinder); 30 | sInstance= new RomManager(romService); 31 | } catch (ServiceManager.ServiceNotFoundException e) { 32 | throw new IllegalStateException(e); 33 | } 34 | } 35 | return sInstance; 36 | } 37 | } 38 | @Nullable 39 | public String hello(){ 40 | try{ 41 | return mService.hello(); 42 | }catch (RemoteException ex){ 43 | throw ex.rethrowFromSystemServer(); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #

安卓系统定制:从入门到实践

2 | 3 |

非虫 misskings 著

4 | 5 |
 
6 |

7 | 8 | > 大家在阅读过程中,发现什么内容错误、格式错误、错别字等问题,欢迎提交pr,这样可以帮助到更多想要学习的朋友。 9 | 10 | 本书开源,欢迎 Star & 贡献! 🚀 11 | 12 | GitHub 项目地址:[https://github.com/feicong/rom-course](https://github.com/feicong/rom-course) 13 | 14 | ## 书籍简介 15 | 16 | 这是一部面向安卓软件开发与安全研究人员的技术指南,涵盖 Android 系统定制的核心知识。从基础环境搭建到深入修改 AOSP(Android 开源项目),本书提供清晰的实战案例,帮助读者快速掌握 Android 底层开发与定制的关键技术。 17 | 18 | ## 适用人群 19 | 20 | - 想要深入理解 Android 系统架构的开发者 21 | 22 | - 需要定制 Android 设备的工程师 23 | 24 | - 关注 AOSP 开发、ROM 修改的技术爱好者 25 | 26 | - 安全研究员 27 | 28 | [点击下载最新版PDF电子书,不定时更新](https://t.zsxq.com/mBLwU) 29 | 30 | ## 技术交流 31 | 32 | 欢迎加入系统定制技术交流微信群,分享安卓系统定制资源,交流系统定制技术。 33 | 34 | > 微信添加**feicongcn**邀请入群。 35 | 36 |
37 | 微信公众号 38 | 知识星球 39 |
40 | 41 | ## 免责申明 42 | 43 | 本书籍内容仅供学习和研究使用,所有资料均来源于公开的安全技术文献、开源项目及个人经验分享。对于任何由于本书内容产生的直接或间接损失,本书作者及发布方概不负责。请读者在实际操作中遵守法律法规,避免从事任何非法或不道德的行为。 44 | 45 | 在使用本书中的技术或方法时,读者应当对自己的行为负责。作者不对因使用书中内容而造成的任何系统损害、数据丢失或其他安全问题承担责任。 46 | 47 | 本书中的信息和技术在不断变化,请读者关注最新的安全动向和最佳实践。 48 | 49 | ## 开源协议 50 | 51 | GPL3.0 52 | 53 | 本图书仅供技术交流,禁止使用书中的技术与工具进行任何商业活动与安全商业产品开发。 54 | -------------------------------------------------------------------------------- /chapter-03/startkernel.drawio: -------------------------------------------------------------------------------- 1 | 1Vlbj9o4GP01fuwoFxzsx4RLV9qtVO087HZfkId4SaYhRo4p0F+/nx2HJBimzJaZ0NEI2Z8vOOec7+KAwsl6/1GyTfZJpLxAgZfuUThFQUBwCJ/acKgNI0xrw0rmaW3yW8Nj/p1bo2et2zzlVW+iEqJQ+aZvXIqy5EvVszEpxa4/7V9R9L91w1bcMTwuWeFa/8pTlVmrH9F24DeerzL71SQY1wNr1ky2T1JlLBW7jimcoXAihVB1a72f8EJj1+BSr5tfGD0eTPJSXbPgOfrzeR9m4tPvz0XC/3kKP5b7D5adSh2aB+YpPL/tCqkysRIlK2atNZFiW6Zc7+pBr53zhxAbMPpgfOZKHSyZbKsEmDK1Luwo3+fqb738Advel87IdG93Np2D7dTn1Ie7+PjWVImtXPIXntk/gg+i5WLNlTzAOskLpvJv/f2Zlc/qOO+49LPI4ZsDzyrdx/gh7P5ZHTS6D73+jorJFVd2k5Y2aHRO1ZoMma8g1p7/Gyu29onQLEKEIpLoBp2gJEYz6HqIQmOM6AglGM0wSibaeJxzqo8++7ssV/xxwwzgO4gAfabtIbhUfP8yby4fdsHY6+EYRra/63hjMyfrOGIYXWawB/ZrkSUusgP40A39AduwagT5wrzoJ/3mp1DHZ/SMEYlRDFolKJnCfwKxtBAs5dKMgdUzDdC9UXY8RZRYN4jHelnsIzK5N4kfQ8VgEm9y2MAaB/jkQSeKD96D548ay5c6dYyixtBmDNM7dHufucwBFJDEzfNIdKXf0Fvkm1hKduhM2OjEUV1OR0HUFxX2ab86eN18aNQnuGmSiq5wapOSKEqiM47bOneklyTk3lwZe0O7cogH91yP9j0XKqNhHZde6bjBaMiMR38cg99b3QE9KcYoceQdnJN30Bz+5ig198QOTEwuM9iLyXUEBM6/clnqO+k84yx9eHRABDhUHylW5KsS2kuAResv0aDlcCeM7cA6T9PaUXiVf2dPZitNgY3KsC9OEJ7qvcA3qtpNbkRCiE8rYpcEH58jwXsrDkYOBwsNNpjOhGiwgIzieRPqibl9xIiMjAXKNFOskTmK3dvpeys+HJ3UZs2zDhbQfbcSroyEz4NN9K0uGZvyd2wufyOdVMlUow6NeHZ/GI+HTpq+W/8uFhvIR0weFtUuV8sM8AmiQgeOJwmtlW4ZbAHwOC9zZaCfI5pooEHkGnqi4U5mLfQzo/a6yAHCSGSZI7WbTOwNhk71lfzeeMKD31N8N0ca6GEvlpcPSyCGrTUQ5VO1MSCc9n/xXBAR/MNcQN4zFQRuKhhct/j/Vi2h91ZVS+AKt4JyVC2aYsUNLefyKIQJOu2EdqwvRRS8bWKzrM6pYzM6bV6AeCb6hCgxgQk+6bz2mYFZiu4u04ZuaQk+rhY1XNczZIYA6CTuVDpj3YX80CYHpyy6O0bI0PE+dN9q1w7zak7OeY3Lyb0xgPHgDLivbGDbhRILuS0NC1AniSWvqvNs9CV+oU66N9ij4O1gh277E2D9Xq39HTWc/Qc= -------------------------------------------------------------------------------- /code/chapter-04/replaceIcon.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import subprocess 4 | 5 | # 执行cmd命令 6 | def exec(cmd): 7 | proc = subprocess.Popen( 8 | cmd, 9 | shell=True, 10 | stdout=subprocess.PIPE, 11 | stderr=subprocess.STDOUT, 12 | stdin=subprocess.PIPE # 重定向输入值 13 | ) 14 | proc.stdin.close() # 既然没有命令行窗口,那就关闭输入 15 | result = proc.stdout.read() # 读取cmd执行的输出结果(是byte类型,需要decode) 16 | proc.stdout.close() 17 | return result.decode(encoding="utf-8") 18 | 19 | # 替换图标 20 | def replacePng(target,appName): 21 | # 搜索该路径下的图标 22 | cmdRes = exec(f"find ~/android_src/rom_gitlab/packages/ -name {target}") 23 | filePathList = cmdRes.split("\n") 24 | curpath=os.getcwd() 25 | # 遍历所有搜到的结果 26 | for filepath in filePathList: 27 | if filepath=="": 28 | continue 29 | # 为了避免其他应用的同名素材图标,所以使用appName过滤一下 30 | if appName not in filepath: 31 | continue 32 | print('Found file: ' + filepath) 33 | # 先将文件进行备份 34 | shutil.copy(filepath,filepath+".bak") 35 | # 然后将当前目录准备好的替换文件复制进去 36 | replacePath=curpath+"/images/"+target 37 | # 如果新文件不存在,则结束该文件的替换 38 | if os.path.exists(replacePath)==False: 39 | print("not found replace file:",replacePath) 40 | break 41 | shutil.copy(replacePath, filepath) 42 | 43 | # 使用备份的文件还原该图标 44 | def unReplacePng(target): 45 | # 查找目标文件 46 | cmdRes = exec(f"find ~/android_src/rom_gitlab/frameworks/base/packages/ -name {target}") 47 | filePathList = cmdRes.split("\n") 48 | # 遍历所有结果 49 | for filepath in filePathList: 50 | if filepath=="": 51 | continue 52 | print('Found file: ' + filepath) 53 | # 备份文件如果存在,则将其还原 54 | bakfile=filepath + ".bak" 55 | if os.path.exists(bakfile): 56 | shutil.copy(bakfile, filepath) 57 | print("unReplace file:",bakfile) 58 | 59 | def main(): 60 | # 替换为新素材 61 | replacePng('ic_launcher_settings.png',"Setting") 62 | replacePng('ic_contacts_launcher.png',"Contacts") 63 | replacePng('ic_launcher_calendar.png',"Calendar") 64 | 65 | # 还原素材 66 | # unReplacePng('ic_launcher_settings.png') 67 | # unReplacePng('ic_contacts_launcher.png') 68 | # unReplacePng('ic_launcher_calendar.png') 69 | 70 | if __name__ == '__main__': 71 | main() 72 | -------------------------------------------------------------------------------- /TOC.md: -------------------------------------------------------------------------------- 1 | # 安卓系统定制:从入门到实践 2 | 3 | ## 第1章 引言 4 | ### 1.1 AOSP是什么 5 | ### 1.2 系统开发,定制,刷机,改机分别是什么 6 | ### 1.3 安卓系统发展史 7 | ### 1.4 常见的第三方定制系统 8 | ### 1.5 初识系统定制 9 | ### 1.5.1 系统定制的优点 10 | ### 1.5.2 系统定制的缺点 11 | ### 1.5.3 系统定制的发展趋势 12 | ### 1.6 初识系统刷机 13 | ### 1.7 小结 14 | 15 | ## 第2章 系统开发环境与工具 16 | ### 2.1 重新看待系统定制 17 | ### 2.2 环境准备 18 | ### 2.2.1 Windows环境 19 | ### 2.2.2 Linux环境 20 | ### 2.3 源码拉取与同步 21 | #### 2.3.1 分支选择策略 22 | #### 2.3.2 repo配置 23 | #### 2.3.3 源码拉取与同步 24 | ### 2.4 系统编译 25 | #### 2.4.1 AOSP编译依赖库安装 26 | #### 2.4.2 系统编译 27 | ### 2.5 模块编译 28 | ### 2.6 内核编译 29 | ### 2.7 刷机 30 | #### 2.7.1 线刷 31 | #### 2.7.2 卡刷 32 | ### 2.8 源码的开发环境搭建 33 | ### 2.9 gitlab配合repo管理源码 34 | ### 2.10 小结 35 | 36 | ## 第3章 认识系统组件 37 | ### 3.1 源码结构介绍 38 | ### 3.2 Android的启动流程 39 | ### 3.3 内核启动 40 | ### 3.4 init进程启动 41 | ### 3.5 init.rc 42 | ### 3.6 Zygote启动 43 | ### 3.7 应用启动 44 | ### 3.8 认识services 45 | ### 3.9 认识framework 46 | ### 3.10 认识libcore 47 | ### 3.11 认识sepolicy 48 | ### 3.12 认识linker 49 | 50 | ## 第4章 系统美化 51 | ### 4.1 系统美化简介 52 | ### 4.2 常见的系统美化方式 53 | ### 4.3 主题修改 54 | ### 4.3.1 认识framework-res 55 | ### 4.3.2 编译framework-res 56 | ### 4.4 修改开机画面 57 | ### 4.5 修改字体 58 | ### 4.6 修改壁纸 59 | ### 4.7 修改图标 60 | ### 4.8 美化包使用 61 | 62 | ## 第5章 系统内置功能 63 | ### 5.1 什么是系统内置 64 | ### 5.2 系统内置app 65 | ### 5.3 构建系统 66 | ### 5.4 系统内置jar库 67 | ### 5.5 系统内置so动态库 68 | ### 5.6 系统内置证书 69 | ### 5.7 修改testkey 70 | ### 5.8 默认开启adb调试 71 | 72 | ## 第6章 功能定制 73 | ### 6.1 如何进行功能定制 74 | ### 6.2 插桩 75 | ### 6.2.1 静态插桩 76 | ### 6.2.2 动态插桩 77 | ### 6.2.3 ROM插桩 78 | ### 6.3 RegisterNative插桩 79 | ### 6.3.1 Native函数注册 80 | ### 6.3.2 RegisterNative执行流程 81 | ### 6.3.3 RegisterNative实现插桩 82 | ### 6.4 自定义系统服务 83 | ### 6.5 APP权限修改 84 | ### 6.5.1 APP权限介绍 85 | ### 6.5.2 权限解析源码跟踪 86 | ### 6.5.3 修改APP默认权限 87 | ### 6.6 进程注入 88 | ### 6.6.1 注入时机的选择 89 | ### 6.6.2 注入jar包 90 | ### 6.7 本章小结 91 | ## 第7章 类加载和函数调用 92 | ### 7.1 双亲委派机制 93 | ### 7.2 类的加载流程 94 | ### 7.3 函数调用流程 95 | ### 7.4 ExecuteMterpImpl 96 | ### 7.5 ExecuteSwitch 97 | 98 | ## 第8章 脱壳 99 | ### 8.1 壳,加壳,脱壳 100 | ### 8.1.1 什么是加壳 101 | ### 8.1.2 如何脱壳 102 | ### 8.2 壳的特征 103 | ### 8.3 动态加载壳的实现 104 | ### 8.4 如何脱壳 105 | ### 8.5 自动脱壳机 106 | 107 | ## 第9章 Android Hook框架 108 | ### 9.1 Xposed 109 | ### 9.2 Xposed实现原理 110 | ### 9.3 常见的Hook框架 111 | ### 9.4 集成pine 112 | ### 9.5 集成dobby 113 | ### 9.6 实战测试 114 | 115 | ## 第10章 系统集成开发eBPF 116 | ### 10.1 什么是eBPF 117 | ### 10.2 什么是bcc 118 | ### 10.3 安卓系统中使用bcc 119 | ### 10.3.1 为系统打上补丁 120 | ### 10.3.2 测试eBPF使能 121 | ### 10.4 使用eBPF实现安卓系统进程跟踪 122 | 123 | ## 第11章 反调试 124 | ### 11.1 反调试常见手段 125 | ### 11.2 常见反调试绕过方案 126 | ### 11.3 系统层面解决反调试 127 | ### 11.4 集成反反调试功能 128 | 129 | ## 第12章 基于定制系统的逆向实战 130 | ### 12.1 案例1 131 | ### 12.2 案例2 132 | ### 12.3 小结 133 | -------------------------------------------------------------------------------- /code/chapter-02/putGit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | import re,subprocess 5 | 6 | MANIFEST_XML = "./default.xml" 7 | ROOT = os.getcwd() 8 | LOG_FILE_PATH = os.path.join(ROOT, "push.log") 9 | 10 | MANIFEST_XML_PATH_NAME_RE = re.compile(r"[^\"]+)\"\s+name=\"(?P[^\"]+)\"\s+", 11 | re.DOTALL) 12 | SOURCE_CODE_ROOT = "~/android_src/aosp12_rom/" 13 | REMOTE = "git@192.168.2.189:aosp12/" 14 | manifest_xml_project_paths = [] 15 | 16 | def parse_repo_manifest(): 17 | with open(os.path.join(ROOT, MANIFEST_XML), "rb") as strReader: 18 | for line in strReader: 19 | if line is not None and len(line) != 0: 20 | this_temp_line = line.decode() 21 | if line.find("path".encode(encoding="utf-8")): 22 | 23 | s = MANIFEST_XML_PATH_NAME_RE.search(this_temp_line) 24 | 25 | if s is not None: 26 | manifest_xml_project_paths.append({"path":s.group("path"),"name":s.group("name")}) 27 | 28 | print("manifest_xml_project_paths=" + str(manifest_xml_project_paths)) 29 | print("manifest_xml_project_paths len=" + str(len(manifest_xml_project_paths))) 30 | 31 | def exec(cmd): 32 | proc = subprocess.Popen( 33 | cmd, 34 | shell=True, 35 | stdout=subprocess.PIPE, 36 | stderr=subprocess.STDOUT, 37 | stdin=subprocess.PIPE 38 | ) 39 | proc.stdin.close() 40 | result = proc.stdout.read() 41 | proc.stdout.close() 42 | return result.decode(encoding="utf-8") 43 | 44 | def push_source_code_by_folder(str_writer): 45 | for path in manifest_xml_project_paths: 46 | print("path=" + path["path"]) 47 | abs_path = SOURCE_CODE_ROOT + path["path"] 48 | 49 | if os.path.exists(abs_path): 50 | # change current work dir 51 | os.chdir(abs_path + "/") 52 | res= exec("git remote -v") 53 | print(res) 54 | if path["name"] in res: 55 | continue 56 | # 1\. delete .git & .gitignore folder 57 | rm_git_cmd = "rm -rf .git" 58 | rm_gitignore_cmd = "rm -rf .gitignore" 59 | os.system(rm_git_cmd) 60 | os.system(rm_gitignore_cmd) 61 | 62 | # 2\. list dir 63 | dir_data = os.listdir(os.getcwd()) 64 | 65 | cmd_list = [] 66 | 67 | print("changed cwd=" + os.getcwd()) 68 | 69 | if len(dir_data) == 0: 70 | echo_cmd = "echo \"This is a empty repository.\" > ReadMe.md" 71 | str_writer.write(f"empty repository:{abs_path}".encode() ) 72 | str_writer.write("\r\n".encode()) 73 | cmd_list.append(echo_cmd) 74 | 75 | git_init_cmd = "git init" 76 | cmd_list.append(git_init_cmd) 77 | 78 | git_remote_cmd = "git remote add origin " + REMOTE + path["name"] + ".git" 79 | cmd_list.append(git_remote_cmd) 80 | 81 | git_add_dot_cmd = "git add ." 82 | cmd_list.append(git_add_dot_cmd) 83 | 84 | git_commit_cmd = "git commit -m \"Initial commit\"" 85 | cmd_list.append(git_commit_cmd) 86 | 87 | git_push_cmd = "git push -u origin master" 88 | cmd_list.append(git_push_cmd) 89 | 90 | for cmd in cmd_list: 91 | print("begin exec cmd=" + cmd) 92 | os.system(cmd) 93 | print("end exec cmd=" + cmd) 94 | else: 95 | print("abs_path=" + abs_path + " is not exist.") 96 | str_writer.write(f"folder not exist:{abs_path}".encode() ) 97 | str_writer.write("\r\n".encode()) 98 | 99 | def wrapper_push_source_code_write_log(): 100 | with open(LOG_FILE_PATH, 'wb+') as strWriter: 101 | push_source_code_by_folder(strWriter) 102 | strWriter.close() 103 | 104 | # def test_only_dot_git_folder(): 105 | # subdir_and_file = os.listdir(os.getcwd()) 106 | # print("subdir_and_file=" + str(subdir_and_file)) 107 | # with open(LOG_FILE_PATH, 'wb+') as strWriter: 108 | # strWriter.write(str(subdir_and_file).encode()) 109 | # strWriter.write("\r\n".encode()) 110 | # strWriter.write(str(subdir_and_file).encode()) 111 | # strWriter.close() 112 | 113 | if __name__ == '__main__': 114 | parse_repo_manifest() 115 | wrapper_push_source_code_write_log() 116 | -------------------------------------------------------------------------------- /code/chapter-02/initGit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import gitlab 4 | import os 5 | import re 6 | import time 7 | 8 | MANIFEST_XML = "default.xml" 9 | ROOT = os.getcwd() 10 | ROOT_GROUP = "Android6NewC" 11 | MANIFEST_XML_PATH_NAME_RE = re.compile(r"[^\"]+)\"\s+name=\"(?P[^\"]+)\"\s+/>", 12 | re.DOTALL) 13 | 14 | gl = gitlab.Gitlab('http://192.168.50.10/', private_token='xxxxxxx') 15 | 16 | manifest_xml_project_paths = [] 17 | 18 | def parse_repo_manifest(): 19 | with open(os.path.join(ROOT, MANIFEST_XML), "rb") as strReader: 20 | for line in strReader: 21 | if line is not None and len(line) != 0: 22 | this_temp_line = line.decode() 23 | if line.find("path".encode(encoding="utf-8")): 24 | 25 | s = MANIFEST_XML_PATH_NAME_RE.search(this_temp_line) 26 | 27 | if s is not None: 28 | manifest_xml_project_paths.append(s.group("path")) 29 | 30 | print("manifest_xml_project_paths=" + str(manifest_xml_project_paths)) 31 | print("manifest_xml_project_paths len=" + str(len(manifest_xml_project_paths))) 32 | 33 | def create_group_and_project(): 34 | all_groups = gl.groups.list(all=True) 35 | print("all_groups=" + str(all_groups)) 36 | group_parent = None 37 | 38 | for g in all_groups: 39 | if g.name == ROOT_GROUP: 40 | group_parent = g 41 | break 42 | print("group parent=" + str(group_parent)) 43 | 44 | for path in manifest_xml_project_paths: 45 | print("path=" + path) 46 | paths = path.split("/") 47 | print("paths=" + str(paths)) 48 | 49 | last_path_index = len(paths) - 1 50 | 51 | group = group_parent 52 | for index in range(0, last_path_index): 53 | p = paths[index] 54 | print("p=" + p) 55 | # is the group exist 56 | print("parent group=" + group.name) 57 | try: 58 | all_groups = group.subgroups.list(all=True) 59 | except AttributeError: 60 | all_groups = [] 61 | print("AttributeError: clear all subgroups") 62 | 63 | is_group_exist = False 64 | for g in all_groups: 65 | if g.name == p: 66 | is_group_exist = True 67 | group = g 68 | print("group exist=" + g.name) 69 | break 70 | if is_group_exist: 71 | continue 72 | # create subgroup 73 | data = { 74 | "name": p, 75 | "path": p, 76 | "parent_id": group.id 77 | } 78 | 79 | try: 80 | group = gl.groups.create(data) 81 | print("group create success name=" + p) 82 | time.sleep(1) 83 | except gitlab.exceptions.GitlabCreateError as e: 84 | if e.response_code == 400: 85 | print("group:" + p + " has already been created") 86 | 87 | query_groups = gl.groups.list(all=True) 88 | print("query_groups:" + str(query_groups)) 89 | for query_group in query_groups: 90 | if query_group.name == p and query_group.parent_id == group.id: 91 | group = query_group 92 | print("update exit group:" + group.name) 93 | break 94 | 95 | project = paths[last_path_index] 96 | print("group project list group=" + group.name) 97 | real_group = gl.groups.get(group.id, lazy=True) 98 | all_projects = real_group.projects.list(all=True) 99 | print("group all projects=" + str(all_projects)) 100 | is_project_exist = False 101 | for p in all_projects: 102 | if p.name == project: 103 | is_project_exist = True 104 | print("project exist=" + p.name) 105 | break 106 | if not is_project_exist: 107 | print("create project=" + project) 108 | gl.projects.create({'name': project, 'path': project, 'namespace_id': group.id}) 109 | print("project create success name=" + project) 110 | time.sleep(1) 111 | 112 | def test_create_project_with_dot_name(): 113 | # need use path field, if don't use path, GitLab url will replace "." to "_" 114 | gl.projects.create({'name': "xxx.yy.xy", 'path': "xxx.yy.xy"}) 115 | 116 | if __name__ == '__main__': 117 | parse_repo_manifest() 118 | create_group_and_project() 119 | 120 | -------------------------------------------------------------------------------- /code/chapter-12/reflection.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef ART_RUNTIME_REFLECTION_H_ 18 | #define ART_RUNTIME_REFLECTION_H_ 19 | 20 | #include "base/enums.h" 21 | #include "base/locks.h" 22 | #include "dex/primitive.h" 23 | #include "jni.h" 24 | #include "obj_ptr.h" 25 | 26 | namespace art { 27 | namespace mirror { 28 | class Class; 29 | class Object; 30 | } // namespace mirror 31 | class ArtField; 32 | class ArtMethod; 33 | union JValue; 34 | class ScopedObjectAccessAlreadyRunnable; 35 | class ShadowFrame; 36 | 37 | ObjPtr BoxPrimitive(Primitive::Type src_class, const JValue& value) 38 | REQUIRES_SHARED(Locks::mutator_lock_); 39 | 40 | bool UnboxPrimitiveForField(ObjPtr o, 41 | ObjPtr dst_class, 42 | ArtField* f, 43 | JValue* unboxed_value) 44 | REQUIRES_SHARED(Locks::mutator_lock_); 45 | 46 | bool UnboxPrimitiveForResult(ObjPtr o, 47 | ObjPtr dst_class, 48 | JValue* unboxed_value) 49 | REQUIRES_SHARED(Locks::mutator_lock_); 50 | 51 | ALWAYS_INLINE bool ConvertPrimitiveValueNoThrow(Primitive::Type src_class, 52 | Primitive::Type dst_class, 53 | const JValue& src, 54 | JValue* dst) 55 | REQUIRES_SHARED(Locks::mutator_lock_); 56 | 57 | ALWAYS_INLINE bool ConvertPrimitiveValue(bool unbox_for_result, 58 | Primitive::Type src_class, 59 | Primitive::Type dst_class, 60 | const JValue& src, 61 | JValue* dst) 62 | REQUIRES_SHARED(Locks::mutator_lock_); 63 | 64 | // Invokes the given method (either an ArtMethod or a jmethodID) with direct/static semantics. 65 | template 66 | JValue InvokeWithVarArgs(const ScopedObjectAccessAlreadyRunnable& soa, 67 | jobject obj, 68 | MethodType mid, 69 | va_list args) 70 | REQUIRES_SHARED(Locks::mutator_lock_); 71 | 72 | // Invokes the given method (either an ArtMethod or a jmethodID) with reflection semantics. 73 | template 74 | JValue InvokeWithJValues(const ScopedObjectAccessAlreadyRunnable& soa, 75 | jobject obj, 76 | MethodType mid, 77 | const jvalue* args) 78 | REQUIRES_SHARED(Locks::mutator_lock_); 79 | 80 | // Invokes the given method (either an ArtMethod or a jmethodID) with virtual/interface semantics. 81 | // Note this will perform lookup based on the 'obj' to determine which implementation of the given 82 | // method should be invoked. 83 | template 84 | JValue InvokeVirtualOrInterfaceWithJValues(const ScopedObjectAccessAlreadyRunnable& soa, 85 | jobject obj, 86 | MethodType mid, 87 | const jvalue* args) 88 | REQUIRES_SHARED(Locks::mutator_lock_); 89 | 90 | // Invokes the given method (either an ArtMethod or a jmethodID) with virtual/interface semantics. 91 | // Note this will perform lookup based on the 'obj' to determine which implementation of the given 92 | // method should be invoked. 93 | template 94 | JValue InvokeVirtualOrInterfaceWithVarArgs(const ScopedObjectAccessAlreadyRunnable& soa, 95 | jobject obj, 96 | MethodType mid, 97 | va_list args) 98 | REQUIRES_SHARED(Locks::mutator_lock_); 99 | 100 | // num_frames is number of frames we look up for access check. 101 | template 102 | jobject InvokeMethod(const ScopedObjectAccessAlreadyRunnable& soa, 103 | jobject method, 104 | jobject receiver, 105 | jobject args, 106 | size_t num_frames = 1) 107 | REQUIRES_SHARED(Locks::mutator_lock_); 108 | 109 | void ShowVarArgs(const ScopedObjectAccessAlreadyRunnable& , 110 | const char* funcname, 111 | const char* data ) 112 | REQUIRES_SHARED(Locks::mutator_lock_); 113 | 114 | void ShowVarArgs(const ScopedObjectAccessAlreadyRunnable& , 115 | const char* funcname, 116 | jboolean* is_copy , 117 | const char* data ) 118 | REQUIRES_SHARED(Locks::mutator_lock_); 119 | 120 | void ShowVarArgs(const ScopedObjectAccessAlreadyRunnable& soa,const char* funcname, 121 | jclass java_class, const char* name, const char* sig,jmethodID methodID) 122 | REQUIRES_SHARED(Locks::mutator_lock_); 123 | 124 | void ShowVarArgs(const ScopedObjectAccessAlreadyRunnable& soa, 125 | const char* funcname, 126 | jmethodID mid, 127 | va_list valist) 128 | REQUIRES_SHARED(Locks::mutator_lock_); 129 | 130 | void ShowVarArgs(const ScopedObjectAccessAlreadyRunnable& soa, 131 | const char* funcname, 132 | jmethodID mid, 133 | va_list valist, 134 | jobject ret) 135 | REQUIRES_SHARED(Locks::mutator_lock_); 136 | 137 | 138 | 139 | // Special-casing of the above. Assumes that the method is the correct constructor, the class is 140 | // initialized, and that the receiver is an instance of the class. 141 | void InvokeConstructor(const ScopedObjectAccessAlreadyRunnable& soa, 142 | ArtMethod* constructor, 143 | ObjPtr receiver, 144 | jobject args) 145 | REQUIRES_SHARED(Locks::mutator_lock_); 146 | 147 | ALWAYS_INLINE bool VerifyObjectIsClass(ObjPtr o, ObjPtr c) 148 | REQUIRES_SHARED(Locks::mutator_lock_); 149 | 150 | bool VerifyAccess(Thread* self, 151 | ObjPtr obj, 152 | ObjPtr declaring_class, 153 | uint32_t access_flags, 154 | ObjPtr* calling_class, 155 | size_t num_frames) 156 | REQUIRES_SHARED(Locks::mutator_lock_); 157 | 158 | // This version takes a known calling class. 159 | bool VerifyAccess(ObjPtr obj, 160 | ObjPtr declaring_class, 161 | uint32_t access_flags, 162 | ObjPtr calling_class) 163 | REQUIRES_SHARED(Locks::mutator_lock_); 164 | 165 | // Get the calling class by using a stack visitor, may return null for unattached native threads. 166 | ObjPtr GetCallingClass(Thread* self, size_t num_frames) 167 | REQUIRES_SHARED(Locks::mutator_lock_); 168 | 169 | void InvalidReceiverError(ObjPtr o, ObjPtr c) 170 | REQUIRES_SHARED(Locks::mutator_lock_); 171 | 172 | void UpdateReference(Thread* self, jobject obj, ObjPtr result) 173 | REQUIRES_SHARED(Locks::mutator_lock_); 174 | 175 | } // namespace art 176 | 177 | #endif // ART_RUNTIME_REFLECTION_H_ 178 | -------------------------------------------------------------------------------- /chapter-04/README.md: -------------------------------------------------------------------------------- 1 | # 第四章 系统美化 2 | 3 | ​ 在我们日常生活中,看到的任何一款商业化的定制`Android`系统中,都是有着其独特的系统`UI`,以及各种炫酷的操作界面。而`Android`系统官方开源的`AOSP`系统`UI`界面是非常简洁的,本章简单讲解如何个性化的系统`UI`定制,完整的美化整个系统是一个非常繁琐的工作,如果对于系统`UI`美化有着非常高的要求,可以选择第三方美化开源的系统进行二次开发。 4 | 5 | 6 | ## 4.1 系统美化简介 7 | 8 | ​`AOSP`的UI界面非常简洁,缺少良好的本地化用户体验,而且系统也不是为了提供优美外观而设计的,这给厂商提供了很大的二次开发空间。常见的对`UI`界面进行美化大致分为如下几项: 9 | 10 | * 更换主题:下载主题软件,来定制主屏幕和菜单,使`Android`的用户界面变得更加个性化。 11 | 12 | * 自定义图标:使用图标包来替换默认的应用图标,为应用程序打造更加独特的外观。 13 | 14 | * 动态壁纸:将`Android`设备的背景设置为动态图片或视频,并可以使其与其他美化应用程序相互配合。 15 | 16 | * 安装定制字体:可以下载和安装自己喜欢的字体,使`Android`系统上的应用程序和菜单更加丰富多彩。 17 | 18 | * 修改开机动画:几乎所有的设备厂商,都有着各自特色的开机动画,以增强品牌产品的辨识度。 19 | 20 | 21 | ​`Android` UI美化是为了提高用户体验、突出品牌形象、展示产品价值和增加营销价值。设计美观、符合使用习惯、便于操作的用户界面,对于`Android`设备的吸引力和实用性尤为重要。下面介绍一下常见对美化系统的需求。 22 | 23 | * 个性化:许多用户希望能够个性化自己的手机界面,让它看起来更加独特和有趣。可以通过更改主题、图标、壁纸、字体等实现。 24 | 25 | * 提高用户体验:美化`UI`还有一个目的是为了提高用户体验。优秀的用户界面可以提高用户的舒适度、使用效率和满意度,从而吸引更多的用户。 26 | 27 | * 提高产品价值:一个好看的`UI`更容易吸引用户,从而提高产品的价值。这对于`Android`应用开发商或者手机厂商来说尤为重要。 28 | 29 | * 突出品牌形象:对于企业,使其品牌形象在`Android`界面中得到突出展示也是有一定需要的,通过`UI`美化营造品牌形象。 30 | 31 | * 凸显功能 : 突出重要功能,提高用户感知度和使用体验,可以通过`UI`设计实现。 32 | 33 | * 改善可用性: 使用条件恶劣的环境下设计`UI`,如夜间、低光、震动等,来提高系统的可用性。 34 | 35 | * 用户群体区分:美化`UI`还可以通过设计不同款式的主题来适应不同群体的需求,例如儿童、青少年、年轻人、老年人等。 36 | 37 | ​ 下图展示的是`Google`官方的`Android`系统桌面图,以及自己编译`AOSP`的桌面图。 38 | 39 | ![未标题-1](images/未标题-1.jpg) 40 | 41 | ​ 从上图中可以看到明显差异,`Google`官方的`ROM`相较于`AOSP` ROM多了一些功能和应用,例如谷歌应用套件:`Google Mobile Services(GMS)`包含了各种谷歌应用,如`Gmail、Google Maps、Play`商店等等。这些应用在`AOSP` ROM中是没有的。 42 | 43 | ​ 由于许多`Android`应用程序和服务都是由`Google`提供的,并且需要授权才能使用,因此在某些情况下,如果设备没有预装`Google`应用程序和服务,则无法获得它们。因此,一些`Android`爱好者和开发者创建了`GApps`包,通过安装这些包,可以在自己的设备上获得`Google`应用程序和服务。包括谷歌商店、谷歌地图、谷歌浏览器、谷歌日历、`Gmail`等等,如果想要的是一个界面类似官方的`ROM`,直接从官网:`https://opengapps.org/`下载`GApps`刷入即可。`GApps`的设备版本支持相对`AOSP`发布版本滞后,目前暂不支持`AOSP` 12,这里不再详细展开。 44 | 45 | 46 | ## 4.2 常见系统美化方式 47 | 48 | ​ 最简单的美化方式,就是直接使用一些管理主题壁纸图标的`App`来管理`UI`界面资源,这种方式无需对美化过程进行了解,只需要挑选喜欢的资源进行替换就能完成对部分`UI`界面进行变动。缺点就是较为被动,`App`提供的功能并不能完全满足一些人的定制需求。这种方式通常适用于普通`Android`用户。 49 | 50 | ​ 其次是使用`ROM`编辑类的工具,将编译好的镜像导入,然后由工具进行解析后,再修改主题,图标等。最后替换资源生成新的镜像,这种方式同样不需要深入了解具体美化的原理,但是和上一种相同的问题,提供的功能有限,并且类似的工具非常少见。并且还需要有一定的刷机经验。这种方式适用于一些`Android`发烧友。 51 | 52 | ​ 最后就是从原理层面了解资源所在位置,如何修改`Android`源码替换资源。实现对系统`UI`的定制化,从根本上了解美化系统过程,编译出来的镜像直接刷机后就能获得美化后的界面。这种定制方式的难度最高,同时也是最根源的办法。当掌握原理后,那么以上两种方式是如何做到美化的同样也会了如指掌。 53 | 54 | 55 | ## 4.3 美化UI的原理 56 | 57 | ​ 有多种方式可以修改`UI`,例如通过修改`Android`源码中对资源的配置,达到修改系统`UI`的目的,大多数的系统`UI`相关的资源和配置都存放在目录`frameworks/base/core/res/`。通过修改这些资源文件来实现改变系统`UI`的外观样式,而图标相关的素材一般在这个目录下的`drawable-*`的子目录中。可以直接替换图标素材来实现修改图标,或者是替换`framework-res.apk`实现切换主题。 58 | 59 | ​ 在源码编译刷入手机后,在手机中会有默认自带文件`/system/framework/framework-res.apk`,这是存放`Android`系统`UI`界面的资源文件,图片、布局、颜色、字符串等。`framework-res.apk`和`SystemUI.apk` 都是`Android`操作系统的应用程序包,`SystemUI.apk`同样也是系统`UI`相关的,但是他们的主要功能不同。 60 | 61 | * `framework-res.apk`包含了`Android`操作系统的核心`UI `组件(资源文件),例如系统主题、`UI `图标、颜色的定义、字体、过渡动画等等。。 62 | 63 | * `SystemUI.apk`负责设备状态栏和通知管理,锁定屏幕上的日期和时间, 系统`UI`中的图标、通知中心等等。当用户接收到来自应用程序或系统的通知时,负责将通知以可视化的方式展示给用户,并允许用户控制通知和设备状态栏的设置。 64 | 65 | ​ 由于`framework-res.apk`包含了`Android`操作系统的核心资源文件,因此它也被包括在`SystemUI.apk`中使用的资源文件中。这类系统应用程序包,通常不能被用户直接安装或卸载。不过,该应用程序包可以被理解为`Android`系统的一部分。`framework-res.apk` 文件的源码位于`frameworks/base/core/res/`目录下。 66 | 67 | 68 | ## 4.4 修改壁纸 69 | 70 | ​ 在前文中和`Google`官方`ROM`对比的界面图,就是`Android`的`UI`界面中的壁纸了,壁纸是在手机主页面的背景图,壁纸可以在手机中进行切换修改,同样也可以直接修改默认的壁纸,默认壁纸的路径是`frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.png`。下图是`AOSP`中的默认壁纸。 71 | 72 | ![image-20230305152441883](images/image-20230305152441883.png) 73 | 74 | ​ 知道壁纸素材的路径后,可以通过对这个素材进行替换来达到修改的目的,同样也可以通过查找设置的地方,修改默认设置选项,将壁纸切换为另一张图片来完成壁纸修改,前者的好处在于简单快捷,替换素材即可。而后者在于稳妥,随时可以调整切换回原素材。替换的方式较为简单就不再细说,这里看看通过修改设置的实现。 75 | 76 | ​ 首先,找到一个新的壁纸素材文件`new_wallpaper.png`,然后放到目录`frameworks/base/core/res/res/drawable-nodpi/`下,并且在`res`目录下的`values/symbols.xml`中添加相应的配置。 77 | 78 | ``` 79 | ... 80 | 81 | 82 | 83 | ... 84 | ``` 85 | 86 | ​ 接下来,修改默认壁纸设置的源码,将默认壁纸设置为新的图片。 87 | 88 | ```java 89 | public class WallpaperManager { 90 | ... 91 | private static final String PROP_WALLPAPER = "ro.config.wallpaper"; 92 | private static final String PROP_LOCK_WALLPAPER = "ro.config.lock_wallpaper"; 93 | private static final String WALLPAPER_CMF_PATH = "/wallpaper/image/"; 94 | ... 95 | public static InputStream openDefaultWallpaper(Context context, @SetWallpaperFlags int which) { 96 | final String whichProp; 97 | final int defaultResId; 98 | if (which == FLAG_LOCK) { 99 | return null; 100 | } else { 101 | whichProp = PROP_WALLPAPER; 102 | // 原本默认使用default_wallpaper,修改成最新的new_wallpaper 103 | // defaultResId = com.android.internal.R.drawable.default_wallpaper; 104 | defaultResId = com.android.internal.R.drawable.new_wallpaper; 105 | } 106 | // 优先从属性ro.config.wallpaper中获取一个默认的壁纸路径 107 | final String path = SystemProperties.get(whichProp); 108 | final InputStream wallpaperInputStream = getWallpaperInputStream(path); 109 | if (wallpaperInputStream != null) { 110 | return wallpaperInputStream; 111 | } 112 | // 属性路径获取失败后,尝试从cmf路径中获取默认壁纸 113 | final String cmfPath = getCmfWallpaperPath(); 114 | final InputStream cmfWallpaperInputStream = getWallpaperInputStream(cmfPath); 115 | if (cmfWallpaperInputStream != null) { 116 | return cmfWallpaperInputStream; 117 | } 118 | // 前两个失败的情况,从默认资源文件中获取默认壁纸 119 | try { 120 | return context.getResources().openRawResource(defaultResId); 121 | } catch (NotFoundException e) { 122 | // no default defined for this device; this is not a failure 123 | } 124 | return null; 125 | } 126 | 127 | private static InputStream getWallpaperInputStream(String path) { 128 | if (!TextUtils.isEmpty(path)) { 129 | final File file = new File(path); 130 | if (file.exists()) { 131 | try { 132 | return new FileInputStream(file); 133 | } catch (IOException e) { 134 | // Ignored, fall back to platform default 135 | } 136 | } 137 | } 138 | return null; 139 | } 140 | 141 | private static String getCmfWallpaperPath() { 142 | return Environment.getProductDirectory() + WALLPAPER_CMF_PATH + "default_wallpaper_" 143 | + VALUE_CMF_COLOR; 144 | } 145 | ... 146 | } 147 | ``` 148 | 149 | ​ 从源码中看到,可以从三个地方获取默认壁纸,同样想要修改的话,也能从这三个方式着手,比如添加一个属性设置默认壁纸路径,或者修改`cmfpath`的路径设置默认壁纸。修改后重新编译系统,刷入手机即可看到手机壁纸发生了变化。 150 | 151 | ``` 152 | source ./build/envsetup.sh 153 | lunch aosp_blueline-userdebug 154 | make update-api -j8 155 | make -j$(nproc --all) 156 | ``` 157 | 158 | 159 | ## 4.5 修改图标 160 | 161 | ​ 修改图标前,首先需要对`res`的目录结构有个简单的认知。`Android`中有两种类型的资源文件:`drawable`和`mipmap`。`mipmap`主要用于存储应用程序图标资源,而`drawable`则用于存储应用程序图标资源以外的其他资源,例如按钮、菜单、列表等等。 162 | 163 | ​ 在`Android`中资源文件是按屏幕的密度来进行分类的,屏幕密度是指在给定空间内包含多少个像素点,通常以`pixels per inch(ppi)`为单位来表示,`Android`将屏幕密度划分为以下几类: 164 | 165 | - `ldpi`:低密度屏幕,大约`120ppi`。 166 | - `mdpi`:中等密度屏幕,大约`160ppi`。 167 | - `hdpi`:高密度屏幕,大约`240ppi`。 168 | - `xhdpi`:超高密度屏幕,大约`320ppi`。 169 | - `xxhdpi`:极高密度屏幕,大约 `480ppi`。 170 | - `xxxhdpi`:超极高密度屏幕,大约`640ppi`。 171 | - `night`:特殊的资源目录,用于存储夜间模式下使用的图像资源。 172 | - `nodpi`:特殊的资源目录,用于存储不针对任何屏幕密度分类的图像资源。 173 | 174 | ​ 通常情况下,`Android`应用程序需要为不同的屏幕密度提供不同版本的图像资源,以确保在不同的设备上以最佳方式显示。例如,在`drawable-mdpi`目录中存储基于中等密度屏幕(`mdpi`)的图像资源,而在`drawable-hdpi`目录中存储基于高密度屏幕(`hdpi`)的图像资源。 175 | 176 | ​ 然而,有时候应用程序需要使用固定大小的图像资源,并且不希望这些图像被缩放或扩展以适应不同的屏幕密度。在这种情况下,就可以使用 `drawable-nodpi` 目录来存储这些图像资源。这些图像将忽略设备的屏幕密度,并显示为其原始大小。例如在前文看到的桌面壁纸,就是使用的`nodpi`目录存放的资源文件。 177 | 178 | ​ 需要注意的是,使用 `drawable-nodpi` 目录要慎重考虑,并尽可能避免使用。因为它们不适用于不同屏幕密度的需求,可能会导致在某些设备上显示不正确。需要确保图像资源已经按照目标大小进行了生成,并且能够在所有设备上显示正确。 179 | 180 | ​ 根据以上的信息,知道了图标是在`res`中`mipmap`开头的目录中。在这里以桌面的中的`Setting`应用为例子,找到目录`packages/apps/Settings/res/mipmap-hdpi/`看到了对应桌面中`Setting`的应用程序图标,文件名为`ic_launcher_settings.png`。而要替换该图标,直接使用新文件替换该图标即可。 181 | 182 | ​ 当然,如果要全部手动替换,来将系统中的图标更换是非常费力的一件事情,所以在了解完替换图标的原理后,可以通过开发一个简单的脚本来完成,例如`Setting`的图标是`ic_launcher_settings.png`,`Contacts`的图标是`ic_contacts_launcher.png`,`Calendar`的图标为`ic_launcher_calendar.png`,将系统中的常用图标找齐对应的文件名后,通过脚本来搜索目录,找到对应路径,然后根据新的素材文件对其一一替换,即可完成批量的工作。replaceIcon程序实现代码如下。 183 | 184 | ```python 185 | import os 186 | import shutil 187 | import subprocess 188 | 189 | # 执行cmd命令 190 | def exec(cmd): 191 | proc = subprocess.Popen( 192 | cmd, 193 | shell=True, 194 | stdout=subprocess.PIPE, 195 | stderr=subprocess.STDOUT, 196 | stdin=subprocess.PIPE # 重定向输入值 197 | ) 198 | proc.stdin.close() # 既然没有命令行窗口,那就关闭输入 199 | result = proc.stdout.read() # 读取cmd执行的输出结果(是byte类型,需要decode) 200 | proc.stdout.close() 201 | return result.decode(encoding="utf-8") 202 | 203 | # 替换图标 204 | def replacePng(target,appName): 205 | # 搜索该路径下的图标 206 | cmdRes = exec(f"find ~/android_src/rom_gitlab/packages/ -name {target}") 207 | filePathList = cmdRes.split("\n") 208 | curpath=os.getcwd() 209 | # 遍历所有搜到的结果 210 | for filepath in filePathList: 211 | if filepath=="": 212 | continue 213 | # 为了避免其他应用的同名素材图标,所以使用appName过滤一下 214 | if appName not in filepath: 215 | continue 216 | print('Found file: ' + filepath) 217 | # 先将文件进行备份 218 | shutil.copy(filepath,filepath+".bak") 219 | # 然后将当前目录准备好的替换文件复制进去 220 | replacePath=curpath+"/images/"+target 221 | # 如果新文件不存在,则结束该文件的替换 222 | if os.path.exists(replacePath)==False: 223 | print("not found replace file:",replacePath) 224 | break 225 | shutil.copy(replacePath, filepath) 226 | 227 | # 使用备份的文件还原该图标 228 | def unReplacePng(target): 229 | # 查找目标文件 230 | cmdRes = exec(f"find ~/android_src/rom_gitlab/frameworks/base/packages/ -name {target}") 231 | filePathList = cmdRes.split("\n") 232 | # 遍历所有结果 233 | for filepath in filePathList: 234 | if filepath=="": 235 | continue 236 | print('Found file: ' + filepath) 237 | # 备份文件如果存在,则将其还原 238 | bakfile=filepath + ".bak" 239 | if os.path.exists(bakfile): 240 | shutil.copy(bakfile, filepath) 241 | print("unReplace file:",bakfile) 242 | 243 | def main(): 244 | # 替换为新素材 245 | replacePng('ic_launcher_settings.png',"Setting") 246 | replacePng('ic_contacts_launcher.png',"Contacts") 247 | replacePng('ic_launcher_calendar.png',"Calendar") 248 | 249 | # 还原素材 250 | # unReplacePng('ic_launcher_settings.png') 251 | # unReplacePng('ic_contacts_launcher.png') 252 | # unReplacePng('ic_launcher_calendar.png') 253 | 254 | if __name__ == '__main__': 255 | main() 256 | ``` 257 | 258 | 259 | ### 4.6 修改开机动画 260 | 261 | ​ `Android`中的开机动画并不是一个视频文件,也不是一个`gif`文件,而是一个名为`bootanimation.zip`的压缩包文件。在这个压缩包文件中,有着若干`png`格式的图片,以及一个`desc`的描述文件,在开机时,系统会按照描述文件依次播放图片,关于该压缩文件的说明文档可以查看源码中的文件,它的路径是`frameworks/base/cmds/bootanimation/FORMAT.md`。下面是开机动画压缩包中的文件结构。 262 | 263 | ``` 264 | desc.txt - 播放的规则 265 | part0 \ 266 | part1 \ 存放着png图片的目录 267 | ... / 268 | partN / 269 | ``` 270 | 271 | 解压开机动画压缩包后的文件如下图。 272 | 273 | ![image-20230424205456876](images/bootanimation.png) 274 | 275 | `desc.txt`文件内容如下。 276 | 277 | ``` 278 | 832 520 30 279 | c 1 30 part0 280 | c 1 0 part1 281 | c 0 0 part2 282 | c 1 30 part3 283 | c 1 0 part4 284 | c 1 0 part5 285 | ``` 286 | 287 | ​ 第一行的`832 520`是图片的分辨率,`30`是播放帧率,每秒播放`30`张图片。 288 | 289 | ​ 第二行中,`c` 表示该部分将播放到完成,不管其他部分是否已经播放完毕。与此相反,如果第一个参数是`p` 表示该部分将一直播放,直到被新的部分替换或整个动画结束。第二个参数`1`代表播放`1`次。第三个参数表示播放的间隔,第四个参数表示对哪个目录的图片生效。 290 | 291 | ​ 查看其中一个目录下的文件如下图。 292 | 293 | ![image-20230424210838393](images/play.png) 294 | 295 | 对这些了解后,接着开始对其进行替换,为了便于简单演示,就不找新的素材进行替换了,直接将`androidtv`的开机动画替换为当前开机动画,找到文件`device/google/atv/products/bootanimations/bootanimation.zip`,将其复制到自定义的任意目录,例如新建目录`packages/bootstart/`,将启动动画拷贝到该目录中。然后在文件`build/make/target/product/generic_system.mk`添加配置,将其拷贝到`system/media/`目录下。相关修改如下。 296 | 297 | ``` 298 | PRODUCT_COPY_FILES += \ 299 | packages/bootstart/bootanimation.zip:system/media/bootanimation.zip \ 300 | ``` 301 | 302 | 如果是自己制作的开机动画压缩包,可以在`desc.txt`所在的目录中执行命令`zip -0 -r ../bootanimation.zip ./*`,则会在上一级目录生成新的开机动画压缩文件。 303 | 304 | 最后编译后,重新刷机。在开机时,即可看到开机动画发生了变动。在开机进入系统后,如果想要重新播放开机动画,可以执行下面的命令。 305 | 306 | ``` 307 | adb shell 308 | setprop ctl.start bootanim #执行开机动画 309 | getprop ctl.start bootanim #停止开机动画 310 | ``` 311 | 312 | ## 4.7 本章小结 313 | 314 | 本章主要讲解了`AOSP`系统美化的一些知识,介绍了了图标、壁纸、开机动画等系统资源的修改方法。这些内容只是系统美化很少的一部分,想要完整的实现整个系统的美化,需要深入了解`framework-res.apk`文件的结构与内容,这些内容就交给读者朋友们自己来探索了。 315 | 316 | -------------------------------------------------------------------------------- /chapter-01/README.md: -------------------------------------------------------------------------------- 1 | # 第一章 引言 2 | 3 | ​ **Android系统是一款开源的移动设备操作系统**,任何个人与组织都能轻易从网络上获取它的源码,编译修改并刷写到自己的设备上。这种独特魅力吸引了众多商业公司和开源社区以及来自全球的开发、测试与玩机人员不断修正、添加和测试系统功能。 4 | 5 | 经过数十载发展,如今的Android不仅应用于手机市场,还广泛应用于其他智能设备中。例如智能电视、机顶盒、车载娱乐系统、智能手表和智能家居等等。由于Android系统具有灵活性和开放性,使得其可定制性在各类设备中得到充分利用。开发者可以根据自身业务需求对Android进行自由改造。 6 | 7 | 本书将围绕着`Android`系统实际开发定制过程中的技巧,在编译修改、美化和安全分析增强等众多应用场景进行讲解。考虑到国内用户常称为"安卓"而非"Android",本书后面部分将使用两者互换描述同一个概念。 8 | 9 | 在这一章中,我们将简单介绍`Android`系统和定制,并通过学习帮助读者对于`Android`系统中常见名词有基本了解。 10 | 11 | 12 | ## 1.1 AOSP是什么 13 | 14 | ​ **Android操作系统是如今最流行、使用最广泛的智能手机操作系统**。它得到了广大手机厂商和用户的支持,而且由于其开源性质,任何人都可以进行定制化以满足不同厂商和用户的需求。 15 | 16 | AOSP全称为"Android Open Source Project",即安卓开源项目。这是由谷歌发起的移动设备操作系统开源项目,任何人都可以自由获取完整的项目源码,并提交更新来完善该项目。同时,谷歌也会不断进行迭代更新版本。 17 | 18 | 由于Android的开源性质,各大手机厂商能够自由地定制系统的源码来适配他们自己的硬件,并因此衍生出各种产品。常见国产安卓设备基本上都是厂商开发团队基于AOSP源码不断改造和优化而成。例如小米MIUI系统、华为HarmonyOS系统、一加H2OS等等,绝大多数都是基于AOSP进行修改而来。如今的Android之所以百花齐放并在手机市场中占据重要地位主要归功于AOSP的开放性。 19 | 20 | 考虑到AOSP项目过于庞大和复杂,在学习过程中需要善用查询官方提供的说明文档。你可以在[https://source.android.com](https://source.android.com)找到Android官方文档。 21 | 22 | 下图是官方文档中关于AOSP的框架结构图: 23 | 24 | ![android_framework_details](images/android_framework_details.png) 25 | 26 | 27 | ### 1.1.1 Android框架 28 | 29 | ​ 对于新手来说,刚开始接触安卓源码时,很多基础理论性的知识可能会感到困惑。但实际上,并不需要完全记住这些基础知识,只需要有个大概的概念,并了解它们之间的依赖关系就可以了。等你熟悉了Android源码后,许多理论知识部分会豁然开朗。 30 | 31 | 我们先简单介绍一下"框架"的概念。框架是一种软件开发的基础结构,它提供了一组标准化、可重用的代码和工具,帮助开发者更高效地开发应用程序。通常情况下,框架包括约定、规则和指南,并且提供可插入模块以供开发者扩展使用。 32 | 33 | 框架设计旨在简化开发过程、提高代码可维护性和可扩展性,并促进开发人员之间的合作。通过使用框架,开发人员可以专注于业务逻辑而无需处理底层实现细节,从而减少开发时间和成本,并降低错误率和风险。常见的框架包括Web框架、移动应用框架、游戏引擎框架等。 34 | 35 | 下面简单介绍一下Android框架的结构: 36 | 37 | - **应用层**:这是用户直接与之交互的部分,包括各种应用程序和系统界面。它们通过调用底层框架提供的API来实现功能。 38 | - **Java API框架层**:这一层是Android框架中最重要也最大的部分,它提供了许多核心组件和服务,例如活动(Activity)、服务(Service)、内容提供器(Content Provider)等。开发人员可以利用这些组件来构建复杂而强大的Android应用程序。 39 | - **C/C++本地库**:在某些情况下,需要使用C或C++编写性能敏感或底层操作相关的代码。Android允许将本地库与Java代码进行混合使用以获取更好的性能。 40 | - **运行时库**:提供安卓运行的基础环境。比如系统运行的一些核心库与ART虚拟机。 41 | - **硬件抽象层(HAL)**:该层允许上述软件与设备硬件进行通信,并在不同设备间保持平台兼容性。 42 | - **Linux内核及驱动程序**:作为整个系统基础,Linux内核负责管理系统资源、提供进程管理和设备驱动等功能。 43 | 44 | 这就是Android框架的大致结构,通过理解和熟悉这些组成部分,你将能够更好地理解和开发Android应用程序。 45 | 46 | 47 | ### 1.1.2 应用层 48 | 49 | `Apps`是日常开发的应用层,用户安装的应用与游戏等等都属于这一层。普通应用基本都在手机的目录`/data/app`中。包括`AOSP`提供的系统`APP`也属于应用层,例如相机、短信、图库,在`/system/app`目录下的应用。应用层直接对接用户的输入操作和输出展示。 50 | 51 | 这里所指的输入是指对手机进行的操作,例如点击屏幕或按键等所有对手机进行操作行为统称为一种输入。而输出则是指手机接收到输入行为后产生的反馈,例如亮起屏幕、震动或页面刷新等所有通过手机展示给你看到的内容都算作手机输出。 52 | 53 | 54 | ### 1.1.3 Java API框架层 55 | 56 | ​ `Framework`框架指的是包含了大量系统级服务和`API`的软件层。它位于操作系统内核(Linux)和应用程序之间,提供了一组通用的、可重用的软件组件,以方便开发者构建各种类型的应用程序。下面简单列举几个框架层中的关键组件。 57 | 58 | ​ `Framework`框架被设计成一个分层架构,包含多个组件,如`Activity Manager、Window Manager`等。这些组件与应用程序和硬件之间进行交互,并提供了许多通用功能,如界面管理、数据存储、网络通信等。 59 | 60 | ​ 通过使用`Framework`框架,开发人员可以快速地编写复杂的应用程序,并在不同设备上运行,同时也能够轻松地处理常见的`Android`应用程序逻辑。此外,由于`Framework`框架的广泛使用,它还为第三方开发者提供了良好的兼容性和稳定性。下面简单列举几个`Framework`中的组件。 61 | 62 | 1. **Activity Manager**负责了系统中四大组件的启动、调度、管理。所有的`Activity`的生命周期都是在它的范畴。 63 | 2. **Location Manager**位置信息和定位的管理组件 64 | 3. **Telephony Manager**电话管理组件,可以获取`sim`卡相关信息以及网络信息 65 | 4. **Window Manager**窗口程序管理组件 66 | 67 | ​ 当应用层调用管理组件后,管理组件会进而调用对应的`Service`来执行相应的函数。简单来说就是`XXX Manager`基本是提供给应用层调用,真正的处理逻辑是在`XXX Service`中处理。如下图所示: 68 | 69 | ![manager_service](images/manager_service.png) 70 | 71 | ​ 也可以更直观的去看`framework`的实现,连接手机。将`jar`文件传到电脑 72 | 73 | ``` 74 | adb shell 75 | cp /system/framework/framework.jar /sdcard/ 76 | adb pull /sdcard/framework.jar ./ 77 | ``` 78 | 79 | ​ 然后使用反编译工具`jadx`打开系统框架文件`framework.jar`。反编译后的程序代码会在程序的主窗口显示,左侧显示的包名与类列表,则是`AOSP`框架不同的组件的实现部分。如下图所示: 80 | 81 | ![jadx_framework](images/jadx_framework.png) 82 | 83 | 84 | ### 1.1.4 C/C++本地库 85 | 86 | ​ `C/C++`的常用库的支持,比如比较典型的`libc、ssl、OpenGL、WebKit`等等。为开发大大的提高了效率和性能。避免每次用到都需要自己去找工具库。 87 | 88 | 89 | ### 1.1.5 运行时库 90 | 91 | 安卓运行时缩写为`ART`,是`Android`系统5.0使用的新技术,取代了以前的`Dalvik`虚拟机运行环境。尽管`Android`开发常常使用的是`Java`编程语言,但是应用程序并不是在传统的 `JVM` 虚拟机中运行,而是类似于针对`dex`结构设计的虚拟机——即`Dalvik`虚拟机或者现在更普遍使用的`ART`虚拟机。 92 | 93 | 从Android 4.4开始测试引入了 ART 功能,并逐渐将其作为Dalvik虚拟机的全面替代品。与Dalvik不同,ART使用一种更高效的代码编译技术称为"Ahead-of-Time"(AOT)编译。这意味着在安装应用程序时,它会将程序代码预先编译成可执行的机器码,在运行时就无需再进行实时编译。这样提高了应用程序的运行效率,并大幅度改善用户体验。 94 | 95 | 96 | ### 1.1.6 HAL硬件抽象层 97 | 98 | ​ 主要作用于系统内核与硬件之间的接口,是`Android`系统底层的部分,具有提供给硬件供应商实现的标准接口,`Android`系统通过接口访问硬件设备,也就是硬件的驱动程序开发,因此`Android`操作系统能够与各类硬件设备进行交互,不用关心底层硬件的处理。也有个说法是因为硬件供应商不愿意自身的驱动放在内核中被一起开源。所以单独抽出了`HAL`层来对接硬件驱动。 99 | 100 | 101 | ### 1.1.7 Linux内核及驱动程序 102 | 103 | 系统的核心部分是内核,它负责与设备底层硬件进行交互。这包括显示驱动、音频驱动、WIFI驱动、电源驱动、内存管理、进程管理和网络通信等功能。可以说,内核是操作系统的灵魂。 104 | 105 | 目前,国内大多数手机厂商都会开源其配套的内核源代码。从安卓11开始,引入了GKI(Generic Kernel Image)内核的概念,旨在实现手机内核接口的统一化,减少碎片化,并使通用内核能够启动不同型号的设备。例如,在某些小米手机上使用GKI内核进行刷机替换后成功启动设备时,并无需编译厂商开源的特定版本内核代码。 106 | 107 | 对于Linux 内核来说,修改需要非常谨慎,因为错误的修改可能导致设备无法正常启动。相较于系统组件代码而言,在调试过程中处理内核代码更加困难。本书将在后面讨论有关修改和编译 Linux 内核代码方面的内容。 108 | 109 | 110 | ## 1.2 系统开发,定制,刷机,改机分别是什么 111 | 112 | 系统开发是指开发新的操作系统或对现有操作系统进行修改和优化的过程。这通常需要具备专业的编程技能和深入的计算机系统知识。 113 | 114 | 定制是指根据自身应用场景和功能需求,对软件或硬件进行修改,以满足特定需求。这可以包括添加新功能、修改现有功能或更改用户界面等。 115 | 116 | 刷机是指在手机或其他电子设备上安装或替换操作系统的过程。这通常需要进行一些技术性操作,例如解锁设备的引导加载程序,或使用特殊的刷机工具进行刷写。 117 | 118 | 改机是指对手机或其他电子设备进行硬件或软件方面的修改,以增强其性能或添加新功能。对软件方面的修改称为软改,而对硬件方面的修改称为硬改。在软件安全领域中,软改通常指一种更改设备指纹信息以隐藏设备信息检测或绕过风控系统等行为;而硬改则涉及更换处理器、增加内存、添加新传感器或修改设备固件等。需要注意的是,错误地进行此类修改可能会导致设备失去保修资格甚至无法正常使用。此外,请谨慎运用改机技术,并理解它代表了给设备增加“伪装”的行为。在网络安全领域中,改机过的设备常被视为计算机信息犯罪工具或黑灰产业的代名词。不合法使用改机技术存在极高的法律风险。 119 | 120 | 121 | ### 1.2.1 系统开发 122 | 123 | 人们口中常说的`Android`开发,通常指的是App应用层开发。在开发应用程序时,会接触到大量的安卓SDK提供的`API`,为获取权限、跨进程通信、网络请求等功能提供了便利。而运行的 App 整个生存周期都是寄托于 Android 框架中。 124 | 125 | 作为开发人员,往往最多面对的问题就是,系统给你提供了哪些功能,系统允许你获取哪些权限。但是并不代表着能满足所有群体的所有需求。SDK中提供了通用统一的编程接口,而一些定制化的需求则需要依赖系统没有导出或者隐藏起来的API。这个时候就需要了解Android系统架构与代码修改方法,并根据自己的需求去添加功能提供接口(API)给应用层开发者使用。 126 | 127 | AOSP是谷歌开源并由谷歌官方定制和出品的操作系统,并不一定完全符合当前国家用户习惯。因此,在本地化定制和需求旺盛之下,国内手机厂商纷纷进行系统修改和扩展。考虑到不同国家用户使用体验时,界面布局、样式呈现、操作方式以及服务本地化等都成为了系统定制关注的重点。在现代化物联网时代,网络硬件适配和功能融合也成为了系统定制的一个重要方向。 128 | 129 | 这些内容的实现都离不开对Android系统底层原有代码进行修改和定制化,调整部分框架和核心代码以实现新功能。例如,硬件驱动支持、优化系统性能以及实现本地化功能的系统ROM。 130 | 131 | 132 | ### 1.2.2 定制 133 | 134 | ​系统定制是系统开发的一种形式,属于轻量级的二次开发。在这个过程中,通常不会对系统核心进行大规模改动。例如,在安全领域的App逆向分析场景中,将系统比作一个分析沙箱,应用程序在该沙箱中运行。当需要对应用程序进行分析和观测时,可以通过修改小盒子的运行流程,在相关系统接口和与系统相关的调用上注入分析代码来监测应用程序执行时的各种状态。只要对Android系统有足够了解,就能像拥有上帝视角一样对普通应用程序进行跨维度分析。 135 | 136 | 掌握了系统框架结构并了解其运作原理后,可以对预装应用进行修改、优化桌面结构、预装特权应用、替换默认应用、修改配置等操作都变得轻而易举。 137 | 138 | 在实际定制过程中,厂商和大团队多人协同开发ROM功能,并按照层次和模块划分工作内容,在整个源码范围内完成针对系统UI、性能优化以及驱动支持等方面的全面改造。这可以被视为系统开发。而个人或小团队则可能只关注自身需求而进行ROM修改,并不考虑代码兼容性、多设备适配、用户安全和隐私保护等因素,这种行为可以称为专属定制。 139 | 140 | 141 | ### 1.2.3 刷机 142 | 143 | 早期的Android版本界面非常简陋,而且功能也没有十分完善。随着各大第三方团队的加入,非官方ROM越来越多。长时间使用官方原版ROM后会感觉到卡顿。因此,喜欢折腾手机的用户开始尝试使用功能相对完善、界面漂亮且性能更好的第三方ROM。切换ROM的过程被称为刷机,通常有两种方式:线刷和卡刷。 144 | 145 | 线刷是指在刷机时需要通过数据线将电脑与设备连接起来,并将线刷包通过数据线传输至设备中进行安装。这里所说的线刷包是指用于系统镜像安装的文件,一般以.img作为后缀名。首先执行`adb reboot bootloader`命令进入设备的刷机模式,然后再执行`fastboot flash`命令将对应分区的镜像写入设备中。而AOSP编译完成后,默认输出结果就是分区镜像。 146 | 147 | 卡刷则是指在不需要使用数据线连接电脑的情况下进行恢复模式启动,并直接将存放在设备上的"卡刷包"安装到设备中去。常见第三方ROM如魔趣、LineageOS和PixelExperience等,在编译完成后,默认输出结果就是可用于刷机的卡刷包。而"卡刷包"通常是一个zip格式的压缩文件,内部集成了更新脚本以及相应需要更新的内容模块,通过Recovery模式提供的刷机功能来完成整个刷机过程。目前比较流行的第三方设备Recovery模式工具是TWRP。刷机流程首先在设备上使用fastboot命令将TWRP镜像写入设备中,然后启动设备进入TWRP工作模式,在其中选择存放在手机sdcard中的刷机包,并导入该包以完成整个刷机过程。 148 | 149 | 150 | ### 1.2.4 改机 151 | 152 | ​每个设备都有独特的标识和对应的指纹信息。这里所说的指纹信息并不是指手指上的指纹,而是一个概念。举例来说,每个人的手指纹路都是不同的,因为它们由许多微小细节组成。当所有这些细节结合在一起时,就形成了每个人独特的手指纹。设备也类似,在其各种设备信息组合之后,可以作为该设备唯一标识的指纹特征。 153 | 154 | 在风险控制对抗中,某些应用会收集设备各种信息以创建设备指纹并进行记录。其中一些设备信息可能被视为高风险设备,并接受特殊处理。例如自定义编译的AOSP系统或谷歌原生支持(Pixel)手机很容易被判定为高风险设备。因此,在自己编译ROM时常常会遇到检测情况。如果被检测出属于风险设备,则可能导致应用崩溃、无法正常运行或服务器将用户标记为风险用户等情况发生。 155 | 在这种情况下,需要修改获取设备信息相关接口或直接修改与之相关联文件来伪装成普通手机。 156 | 157 | 有时候应用程序并不是通过系统接口来获取设备指纹,而是直接访问系统目录或将系统文件结构作为设备信息。另外,它们可能会绕过您的修改,通过底层内联汇编执行系统调用以读取文件来获取您的设备信息。因此,在不同情况下需要进行分析和持续对抗。 158 | 159 | 160 | ## 1.3 安卓系统发展史 161 | 162 | ​ 2003年由安迪.鲁宾、克里斯.怀特等人开发制作。最初方向只是创建一个数字相机的操作系统,后来由于数码相机市场的萎缩,智能手机的快速发展,最后重新将安卓定位为开发一款智能手机操作系统。于2005年7月11日被`Google`收购。 163 | 164 | ​ 2007年11月,`Google`牵头联合了硬件制造商,软件开发商,通讯运营商成立了“开放手机联盟”,共同研发`Android`,随后开放了`Android`源码。11月5日,`Android1.0`公测版面世 165 | 166 | ​ 2008年9月,发布了第一款安卓智能机:`HTC Dream`,这个时候还是诺基亚塞班系统的天下,这是时候并没有多少人看好`Android`系统。 167 | 168 | ​ 2010年末,刚出世两年的`Android`操作系统在市场占有率上打败了塞班系统。这时的`Android2.3`版本 169 | 170 | ​ 2011年10月,`Android4.0`发布,首次支持通过使用照相机拍摄用户的脸部来解锁手机 171 | 172 | ​ 2014年,`Android5.0`发布,这是第一个使用`Google`全新`Material Design`语言的版本,用户操作界面和`UI`的外挂得到了升级。同时加上了对双`SIM`卡的支持。 173 | 174 | ​ 2016年,`Android7.0`发布,改用新的`JIT`编译器加快应用的运行速度。 175 | 176 | ​ 2017年,`Android`全球网络流量和设备超越微软的`Windows`,正式成为全球第一大操作系统。 177 | 178 | 179 | ## 1.4 其他常见的第三方定制系统 180 | 181 | ​ `AOSP`并不是唯一可以定制的`ROM`,同样有很多优秀的第三方团队研发的定制系统。由于`AOSP`基本只对`Google`系的机型适配,所以国内普通用户一般会选择使用第三方的定制`ROM`,这些第三方开源`ROM`都有着各自的优点,有很多人会直接选择在这些已经修改过`ROM`上二次开发,对于学习来说,选择哪个`ROM`的区别并不会太大,因为底层使用的`AOSP`这一套,只要摸透一款,开发的功能也可以迁移到其他的系统上。 182 | 183 | ### 1.4.1 Mokee 184 | 185 | ​ 官网:https://www.mokeedev.com/ 186 | 187 | ​ 中文名魔趣,发起于2012年12月12日,是国内最大的第三方非盈利性开源`ROM`,适配了大量国内主流机型,高达近300种。魔趣是由一群热爱`Android`开源社区的热心志愿者维护,他们致力于为用户提供最新、最安全、最稳定的定制`Android`系统,主要针对的用户群体也是国内用户,所以在功能和操作方面非常贴切国人的使用习惯,同时简单纯净,性能出众,因此有着不少用户使用魔趣`ROM`作为日常用的手机。下面是魔趣支持的手机厂商, 188 | 189 | | ARK | 华硕 | Essential | 谷歌 | 190 | | :--: | :--: | :-------: | :--: | 191 | | HTC | 华为 | 乐视 | 联想 | 192 | | LG | 魅族 | 摩托罗拉 | Nextbit | 193 | | 努比亚 | 一加 | OPPO | Realme | 194 | | 红米 | 三星 | 锤子 | 索尼 | 195 | | Wileyfox | 小米 | YU | 中兴 | 196 | | ZUK | | | | 197 | 198 | ​ 魔趣系统自发起以来,一直深受国内玩机用户的青睐。但遗憾的是经过十年的发展,该系统于2022年下半年关闭了,不得不说是一个遗憾。 199 | 200 | 201 | ### 1.4.2 PixelExperience 202 | 203 | ​ 官网:https://download.pixelexperience.org/ 204 | 205 | ​ 这是最接近原生`Android`系统的第三方`ROM`,它是在`Google Pixel`系列设备发布的,拥有着`Pixel`系列设备的原生外观和功能,并且添加了更多可定制的功能,它还预装了谷歌插件,并且还提供了大量可定制的主题,以及强大的安全性功能。而正常编译出来的`AOSP`刷入后是没有谷歌插件,并且界面`UI`都是非常简陋的。如果你想要打造一个和原生`Android`非常相似的`ROM`,那么完全可以选择它,原生`Android`的界面风格以及自带谷歌套装,以及适配了更多的手机厂商。下面是它所支持的各大厂商。 206 | 207 | | ASUS | Google | Lenovo | Motorola | 208 | | :----: | :-----: | :------: | :------: | 209 | | Nokia | Nubia | OnePlus | Razer | 210 | | Realme | Samsung | Teracube | Xiaomi | 211 | 212 | 213 | ### 1.4.3 LineageOS 214 | 215 | ​ 官网:https://www.lineageos.org/ 216 | 217 | ​ `LineageOS`的前身是大名鼎鼎的`CyanogenMod`,简称`CM`,这个开发团队是全球最大的`Android`第三方编译团队。早在`Android1.6`版本时,就为很多手机厂商定制出稳定的`ROM`。2016年12月`Cyanogen`公司宣布停止开发并关闭项目,随后复刻后更名为`LineageOS`由原团队继续研发。 `LineageOS`的风格同样是类似原生`Android`的,但是相比起`PixelExperience`要更加的精简,运行起来也是非常流畅,同样也是非常纯净不会携带任何第三方应用,并且具有高度的安全性,可以在经过认证的设备上使用,能防止恶意软件攻击或病毒感染,也有着定制功能,允许用户自己定制操作系统,适配各大厂商机型。下面是它所支持的各大厂商。 218 | 219 | | ASUS | BQ | Dynalink | Essential | 220 | | :-------: | :-----: | :------: | :-------: | 221 | | Fairphone | FxTec | Google | LeEco | 222 | | Lenovo | LG | Motorola | Nextbit | 223 | | Nokia | Nubia | Nvidia | OnePlus | 224 | | Razer | Samsung | SHIFT | Sony | 225 | | Xiaomi | Zuk | Extras | | 226 | 227 | ## 1.5 初识系统定制 228 | 229 | ​ 除了各大知名团队的开源`ROM`,也有一些深入了解安卓原理的开发人员会自己修改`ROM`定制自己的专属工具,亦或者为团队定制专用的功能。不过定制并不是能解决所有问题,要了解定制的优点和缺点,在各种需求场景采用更合适的方案。 230 | 231 | ### 1.5.1 系统定制的优点 232 | 233 | ​学习定制系统的好处非常多,以下是简要列举几点: 234 | 235 | 1. 深入了解Android设计原理:通过阅读源代码,可以深入了解Android系统的工作原理。这对于异常错误排查、性能优化以及应用提权等开发应用层程序非常有帮助。 236 | 237 | 2. 逆向分析与修改:在系统级别上运行的应用程序可以通过修改ROM来轻松获取执行过程中使用的数据。类似于游戏外挂一样,这种方式能够降低复杂度。 238 | 239 | 3. 自定义个人设备:定制化系统允许你打造自己喜欢的日常使用设备。你可以为自己DIY一些方便实用的小功能,或者创建专门用于特定目的的工具手机。 240 | 241 | 4. 控制操作系统体积和提升性能效率:通过定制化系统,可以有效地控制操作系统体积,并更好地改进产品性能和效率水平。 242 | 243 | 244 | ### 1.5.2 系统定制的缺点 245 | 246 | ​定制系统的优势有时也代表着缺点,可以形象地说成"杀鸡用牛刀"。因为很多情况下,并不需要修改ROM来实现一些需求,其他方案可能更加简单易行。以下是对几个缺点的简要总结: 247 | 248 | 1. 学习难度大:初学者在开始阶段搭建编译ROM环境会比较繁琐,再加上源代码庞大且结构复杂,容易让一些初学者望而却步。 249 | 250 | 2. 开发成本高:每次修改代码后都需要重新刷机,即使只是做一个小功能,在测试过程中也要反复编译和刷机。此外,还需要熟悉源码结构,并深入理解Android系统以便充分利用系统提供的API来制定合理的定制方案。 251 | 252 | 3. 维护成本高:即使产品开发完成后仍然需要进行优化和更新,并且难免会出现BUG。对于个人开发者而言,在更新时只能选择全部重新刷机,并且每次新版本发布都需重新定制和迁移,这将消耗大量时间和精力。 253 | 254 | 4. 安全性低:基于AOSP(Android Open Source Project)定制的系统安全性相对较低,因此定制系统可能存在安全漏洞,使得其比原生系统更容易受到攻击。 255 | 256 | 257 | ### 1.5.3 系统定制的发展趋势 258 | 259 | 技术本身会随着市场需求不断变化。安卓系统定制的发展趋势主要取决于不同领域中第三方ROM在相应市场中的发展情况。我认为可以从以下几个方面进行讨论: 260 | 261 | 1. 安全性:随着社会的发展,个人隐私和数据安全也变得越来越重要。目前,国内手机厂商在开发新功能时除了跟随同步厂家安全补丁外,还需要考虑一些增强安全性的功能。例如加强数据加密、权限管理以及应用程序的安全性等。国内已经有一些设备已经加入了App行为记录功能,可以记录运行过的App以及敏感行为等信息。这种功能非常贴心。而在国外,有一个名为`GrapheneOS`的第三方ROM专注于用户隐私与安全,并且该项目是作为非营利性开源项目开发的移动操作系统,它具备Android 应用程序兼容性并提供了多项安全特性如权限管理、WIFI 隐私、密码长度、指纹解锁增强、浏览器增强、安全缓解增强以及加密备份等细节上打磨工作。安全问题非常重要,对于第三方开发者来说,这个市场在短时间内不会消亡。 262 | 263 | 2. 用户体验:用户体验是安卓系统定制的另一个重要方面。随着用户对移动设备的需求不断提高,安卓系统定制需要更加注重用户体验的设计和实现。例如优化系统响应速度、提高应用程序稳定性以及增强系统易用性等。谷歌官方的安卓UI一直在更新迭代,但由于众口难调,在满足所有用户需求上存在一定困难。然而,通过自定义ROM可以很大程度上满足用户个性化需求,比如提供更多主题、字体和壁纸等选项以打造独特个人风格。此外,在我之前介绍过的第三方ROM - `PixelExperience` 中也为使用者提供了最接近谷歌官方`Pixel`手机Android体验,并附带了所有Google 应用程序、Pixel桌面、壁纸、图标和启动动画等元素使设备看起来像一部真正的`Pixel`手机 。总之,在满足个性化需求领域中,系统定制仍属小众市场且目前没有明显发展趋势。 264 | 265 | 3. 高性能:通过对系统的优化和修改,可以让一些老旧设备体验到最新的谷歌官方系统。这方面需求一直很大,也是`LineageOS` 这个第三方ROM长期存在的主要原因之一。`LineageOS`专注于高性能和广泛适用性,并且大多数其他第三方ROM都是基于它进行二次开发。在市场上,该领域具有较大玩机市场,在Mokee 退出市场后,无论是做安全增强、用户体验还是安全系统定制,`LineageOS` 都将成为首选。 266 | 267 | 4. 环境定制:某些与安卓系统相关联的业务环境离不开对安卓系统的定制。例如传统医疗设备行业中, 有些设备的控制程序运行在经过定制的安卓设备上;而在安全分析领域中, 动态沙箱需要根据实际情况来设置App运行时所需的分析环境;脱壳机则需要针对特定需求来实现系统层面上解密应用等功能。这个市场相对稳定且变化不大。 268 | 269 | 5. 移植性:另一个与安卓系统定制相关的领域就是系统移植,即将安卓系统移植到主要的开发板、电视盒子等硬件设备上。这个领域中,一部分移植工作由开发板厂商完成。目前,大多数主要的ARM芯片SOC出厂都提供了对安卓系统的支持, 这使得在开发板上使用安卓系统变得非常普遍。另一方面, 一些名气较大的开发板(如树莓派)虽然没有官方提供相应定制ROM ,但仍有第三方开发人员为其适配安卓系统。同样地,电视盒子也是如此,它们的更新与否主要取决于芯片SOC相关驱动程序是否完善以及硬件在市场上是否热销。无论如何,在相比其他领域而言,该市场规模较小众且不会消亡或有太大进展。 270 | 271 | 总体而言,在未来很长时间内,安卓系统定制技术不会消亡,并将随着系统本身的更新而不断进化。从业者需要不断更新知识储备以应对新应用场景中所面临的挑战。 272 | 273 | 274 | ## 1.6 小结 275 | 276 | 本节主要介绍了安卓系统的基础架构、系统定制的基本概念,以及常见的第三方定制ROM的功能和设备支持情况。同时还讲解了系统定制在不同领域中的发展趋势。 277 | 278 | 系统定制涉及到广泛而丰富的前置知识,但并不需要完全掌握这些知识才能开始学习本书后面的内容。读者在阅读完本章后应该清楚地明确了系统定制的目标,并找准了自己感兴趣或专注于研究的领域,然后再深入学习相关内容。这种以目标为导向、有针对性地学习方法是非常有效和可行的,希望读者能够好好体会其中蕴含着的价值。 279 | 280 | 从下一章开始,将正式进入安卓系统定制之旅,在实践中亲手完成安卓系统定制所带来的成就感。期待你能享受这个过程! 281 | -------------------------------------------------------------------------------- /chapter-11/README.md: -------------------------------------------------------------------------------- 1 | # 第十一章 反调试技术 2 | 3 | 反调试是指在软件运行时,防止恶意用户或黑客使用调试器来分析和修改程序的行为。在安卓系统中,反调试技术是应用程序开发中非常重要的一部分,因为它可以提高应用程序的安全性,防止黑客攻击和数据泄露。同时,恶意软件也利用反调试技术来避免被安全系统和其他软件进行分析。 4 | 5 | 本章将介绍反调试的概念、原理以及常见的对抗方案,并探讨如何针对这些方案进行相应的安全对抗。 6 | 7 | 8 | ## 11.1 反调试常见手段 9 | 10 | 在Android逆向分析中,攻防对抗是最常见的情况。攻击者通过对样本进行静态分析和动态调试等手段来获取他们所需的信息。而保护方则通过混淆和其他多种加固方式来保护自己的重要信息。 11 | 12 | 例如,保护方可以使用加固技术干扰攻击者的静态分析。这些技术包括代码混淆、反编译难度增加和变量名修改等,以使源代码更难以理解和还原。 13 | 14 | 此外,保护方还可以通过检测运行环境来对抗攻击者注入hook函数,并添加各种调试检测机制来阻止攻击者进行动态分析。 15 | 16 | 这些方法都旨在提高应用程序的安全性,降低被恶意用户逆向工程或篡改的风险。 17 | 18 | 19 | ### 11.1.1 检测调试标志 20 | 21 | 调试软件离不开调试器,而调试器又高度依赖于`ptrace`。`ptrace`是Linux操作系统提供的一个系统调用,它允许一个进程监控另一个进程的执行,并能够在运行时修改其寄存器和内存等资源。`ptrace`通常被用于调试应用程序、分析破解软件以及实现进程间沙盒隔离等场景。 22 | 23 | 使用`ptrace`来监控目标进程时,需要以"tracer"(追踪者)的身份启动一个新的进程,并通过`ptrace()`函数请求操作系统将目标进程挂起并转交给"tracer"进程。一旦目标进程被挂起,"tracer"就可以读写其虚拟地址空间中的数据、修改寄存器值、单步执行指令等操作。当“tracer”完成对目标进程的调试操作后,可以通过`ptrace()`函数将控制权还原到目标进程上,使其继续执行。 24 | 25 | 由于`ptrace()`功能强大且灵活,在一些安全敏感场合中也常被恶意攻击者广泛应用于破解软件或进行恶意攻击。因此,在这些情况下为了防止恶意攻击者使用`ptrace()`来监控和修改程序行为,需要采取一些反调试的手段来加强保护。 26 | 27 | 通过在被保护程序中定期检测其父进程是否为指定的“tracer”进程,可以避免恶意攻击者使用 `ptrace()`跟踪程序的执行流程。这种方式能够帮助防止非授权调试器对目标进程进行操控,并增强应用程序的安全性。 28 | 29 | ​ 接下来写一个简单的实例来进行测试。`Android Studio`创建`native c++`的项目。修改函数如下。 30 | 31 | ```c++ 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #define LOG_TAG "native-lib" 38 | #define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG , LOG_TAG, __VA_ARGS__) 39 | 40 | extern "C" JNIEXPORT jstring JNICALL 41 | Java_cn_rom_nativedemo_MainActivity_stringFromJNI( 42 | JNIEnv* env, 43 | jobject /* this */) { 44 | std::string hello = "Hello from C++"; 45 | int ppid= getppid(); 46 | ALOGD("my ppid=%d",ppid); 47 | return env->NewStringUTF(hello.c_str()); 48 | } 49 | ``` 50 | 51 | ​ 然后添加一个按钮,每次点击时则调用该函数,便于随时观测到`ppid`的变化。 52 | 53 | ```java 54 | Button btn1; 55 | @Override 56 | protected void onCreate(Bundle savedInstanceState) { 57 | super.onCreate(savedInstanceState); 58 | 59 | binding = ActivityMainBinding.inflate(getLayoutInflater()); 60 | setContentView(binding.getRoot()); 61 | 62 | TextView tv = binding.sampleText; 63 | tv.setText(stringFromJNI()); 64 | btn1=findViewById(R.id.button); 65 | btn1.setOnClickListener(v->{ 66 | tv.setText(stringFromJNI()); 67 | }); 68 | } 69 | ``` 70 | 71 | ​ 在调用该函数时,就会打印其`ppid`(父进程`id`)。运行该函数后输出如下。 72 | 73 | ``` 74 | cn.rom.nativedemo D/native-lib: my ppid=1053 75 | ``` 76 | 77 | ​ 然后查看该进程`id`对应哪个进程。 78 | 79 | ``` 80 | adb shell 81 | ps -e|grep 1053 82 | 83 | // 输出如下 84 | root 1053 1 14644500 115568 0 0 S zygote64 85 | ``` 86 | 87 | ​ 发现该进程是`zygote`进程,说明没有被调试。接下来使用`ida`调试该进程。找到`ida`下的`dbgsrv`目录,将其中的`android_server64`拷贝到`Android`系统中,将调试的端口`23946`转发到本地。并且将该服务启动起来,操作如下。 88 | 89 | ``` 90 | adb push android_server64 /data/local/tmp/ 91 | adb forward tcp:23946 tcp:23946 92 | adb shell 93 | cd /data/local/tmp/ 94 | chmod +x ./android_server64 95 | su 96 | ./android_server64 97 | ``` 98 | 99 | ​接下来打开`ida`,选择`Debugger->Attach->Remote Arm linux/android debugger`,在`hostname`选项中填本地回环地址`127.0.0.1`,如下图。 100 | 101 | ![image-20230403223624911](images/ida_debug_attach.png) 102 | 103 | ​点击`ok`后,则会展示所有`Android`中的进程,在其中进行过滤,找到目标进程。如下图 104 | 105 | ![image-20230403223830842](images/ida_debug_process.png) 106 | 107 | ​成功挂起调试后,检查日志中的 `ppid`,发现并没有任何变化,依然是`zygote`作为父进程。 108 | 109 | ​当使用`IDA`进行调试时,`IDA`会创建一个调试器进程,并将其作为目标进程的父进程。但是,由于目标进程最初是由 `zygote`进程`fork`出来的,因此在查询其父进程`id`时,仍然会返回`zygote`进程的`id`。这并不意味着调试器进程没有被正确设置为目标进程的父进程。实际上,在`IDA`调试过程中,目标进程的执行状态确实是由调试器进程所控制的。因此,即使查询到的父进程`id`不正确,也不会影响`IDA`对目标进程的控制和调试操作。 110 | 111 | ​尽管查询`ppid`无法判断出进程被调试了,但是依然有其他地方会出现被调试的信息,例如`/proc//status`文件中的字段`TracerPid`,就能看到调试进程的`id`。下面查看该文件。 112 | 113 | ``` 114 | // 没有调试时的文件内容 115 | Name: .rom.nativedemo 116 | Umask: 0077 117 | State: S (sleeping) 118 | Tgid: 7759 119 | Ngid: 0 120 | Pid: 7759 121 | PPid: 1053 122 | TracerPid: 0 123 | 124 | // ida附加调试后的文件内容 125 | 126 | Name: .rom.nativedemo 127 | Umask: 0077 128 | State: t (tracing stop) 129 | Tgid: 7759 130 | Ngid: 0 131 | Pid: 7759 132 | PPid: 1053 133 | TracerPid: 7525 134 | ``` 135 | 136 | ​ 查看该`id`对应哪一个进程。 137 | 138 | ``` 139 | ps -e|grep 7525 140 | 141 | // 显示结果 142 | root 7525 7523 10803524 33392 0 0 S android_server64 143 | ``` 144 | 145 | ​除了`status`文件外,`/proc//wchan`文件同样可以用来检测。下面是调试附加前,和附加后的对比。 146 | 147 | ``` 148 | // 附加前 149 | SyS_epoll_wait 150 | 151 | // 附加后,中断时 152 | ptrace_stop 153 | ``` 154 | 155 | ​文件`/proc//stat`也可以用来检测,当进程被中断等待时,内容将会由`S`变成`t`。对比如下。 156 | 157 | ``` 158 | // 附加前 159 | S 1027 1027 0 0 -1 1077952832 29093 4835 0 0 81 9 0 0 20 0 19 0 424763 15088168960 24716 18446744073709551615 1 1 0 0 0 0 4612 1 1073775864 0 0 0 17 0 0 0 0 0 0 0 0 0 0 0 0 0 0 160 | 161 | // 附加后 162 | t 1027 1027 0 0 -1 1077952832 29405 4835 0 0 81 9 0 0 20 0 19 0 424763 15088168960 24987 18446744073709551615 1 1 0 0 0 0 4612 1 1073775864 0 0 0 17 1 0 0 0 0 0 0 0 0 0 0 0 0 0 163 | ``` 164 | 165 | 166 | ### 11.1.2 禁止调试器附加 167 | 168 | 由于动态调试基本上都依赖于`ptrace`对进程的追踪,我们可以通过了解`ptrace`的使用特性来有针对性地检查自身是否被调试。当一个进程已经被附加时,它同时只能被一个进程进行附加,第二次附加将会失败。因此,我们可以在自身上执行`ptrace()`操作,并观察是否成功来判断是否正在被调试。 169 | 170 | 当我们尝试对自身进行`ptrace()`处理时,如果发现无法成功附加到自己,则说明已经处于调试状态。同时,在成功对自身进行附加后,也可以阻止其他进程再次对其进行附加调试。 171 | 172 | 这种方式能够帮助我们及时发现并防止恶意攻击者使用动态调试工具来监控和修改程序行为。通过利用`ptrace()`的特性来检测和阻止不受信任的进程对目标程序的调试操作,提高应用程序的安全性和可靠性。 173 | 174 | 下面看实现代码。 175 | 176 | ```c++ 177 | extern "C" JNIEXPORT jstring JNICALL 178 | Java_cn_rom_nativedemo_MainActivity_stringFromJNI( 179 | JNIEnv* env, 180 | jobject /* this */) { 181 | std::string hello = "Hello from C++"; 182 | prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0); 183 | pid_t pid = getpid(); 184 | int ret=ptrace(PTRACE_TRACEME,pid, 0, 0); 185 | // 检测是否正在被调试 186 | if (ret < 0) { 187 | ALOGD("I'm being debugged! %d\n",ret); 188 | } else { 189 | ALOGD("Not being debugged %d\n",ret); 190 | } 191 | return env->NewStringUTF(hello.c_str()); 192 | } 193 | ``` 194 | 195 | ​在`AOSP12`中,为了增强`Android`系统的安全性,`Google`限制了应用程序使用`ptrace`对自身进行调试。在当前进程中调用`ptrace(PTRACE_TRACEME)`函数将始终返回-1。但是我们可以创建一个子进程,来进行测试。下面是调整后的代码。 196 | 197 | ```c++ 198 | extern "C" JNIEXPORT jstring JNICALL 199 | Java_cn_rom_nativedemo_MainActivity_stringFromJNI( 200 | JNIEnv* env, 201 | jobject /* this */) { 202 | std::string hello = "Hello from C++"; 203 | pid_t mypid = getpid(); 204 | pid_t pid = fork(); 205 | if (pid == -1) { 206 | perror("fork"); 207 | exit(1); 208 | } else if (pid == 0 ) { 209 | // 这里是子进程的代码 210 | ALOGD("I'm child process, my PID is %d\n", getpid()); 211 | int ret=ptrace(PTRACE_TRACEME,0, 0, 0); 212 | // 检测是否正在被调试 213 | if (ret < 0) { 214 | ALOGD("I'm being debugged! %d\n",ret); 215 | } else { 216 | ALOGD("Not being debugged %d\n",ret); 217 | sleep(30); 218 | } 219 | } else { 220 | // 这里是父进程的代码 221 | ALOGD("I'm parent process, my PID is %d and my child's PID is %d\n", mypid, pid); 222 | } 223 | return env->NewStringUTF(hello.c_str()); 224 | } 225 | ``` 226 | 227 | ​然后使用`ida`尝试对子进程进行调试,发现无法正常附加该进程了,错误如下。 228 | 229 | ![image-20230405162058014](images/ida_attach_err.png) 230 | 231 | 232 | ### 11.1.4 检测跟踪工具 233 | 234 | 除了常规调试器,一些安全分析工具都具备调试跟踪功能。比如Frida,可以向目标程序中注入Javascript脚本,来Hook跟踪与修改程序的执行状态。在执行反调试检测时,这类工具也是需要重点关注的对象。 235 | 236 | 使用Frida时,需要在设备上执行`frida-server`命令。检测Frida的思路来源于该工具运行时的文件与进行特征信息。例如,执行Frida注入代码到一个程序后,它的`/proc/pid/maps`中有留有注入的动态库的痕迹: 237 | 238 | ``` 239 | $ adb shell pidof com.android.settings 240 | $ frida -U -p 27507 241 | ____ 242 | / _ | Frida 16.1.1 - A world-class dynamic instrumentation toolkit 243 | | (_| | 244 | > _ | Commands: 245 | /_/ |_| help -> Displays the help system 246 | . . . . object? -> Display information about 'object' 247 | . . . . exit/quit -> Exit 248 | . . . . 249 | . . . . More info at https://frida.re/docs/home/ 250 | . . . . 251 | . . . . Connected to Android Emulator 5554 (id=emulator-5554) 252 | 253 | [Android Emulator 5554::PID::27507 ]-> 254 | [Android Emulator 5554::PID::27507 ]-> Process.enumerateModules().filter(m => { 255 | // console.log(m.path) 256 | var ret = false 257 | if (m.path) { 258 | if (m.path.includes("frida")) { 259 | console.log(m.path) 260 | } 261 | } 262 | }) 263 | /data/local/tmp/re.frida.server/frida-agent-64.so 264 | [] 265 | ``` 266 | 267 | `Process.enumerateModules()`执行后返回的是`/proc/self/maps`的内容,这里过滤显示留住`frida`字符串的路径。可以看到,输出中有`/data/local/tmp/re.frida.server/frida-agent-64.so`。这是`frida-server`注入代码时释放的动态库。检测它就能检测到程序注入了Frida。 268 | 269 | 270 | ### 11.1.4 系统调试检测接口 271 | 272 | ​除了上述提到的两种常见的检测方式之外,还有许多其他方法可以进行调试检测。这些检测方法主要围绕着调试过程产生的特征展开,在真实保护场景中,开发者通常会结合多种方案来防止被攻击者进行动态调试。以下是其他一些检测方案的介绍: 273 | 274 | - 使用Android本身提供的API来判断是否处于调试状态,例如`android.os.Debug.isDebuggerConnected()`。然而,这种检测方法很容易被Hook修改或替换。 275 | - 检查默认端口和进程名称:比如IDA使用的默认端口23946以及前文中提到的android_server进程名称等。但是这种方式同样容易受到处理,攻击者可能会修改默认端口和进程名称。 276 | - 运行效率检测:在函数执行过程中计算执行消耗的时间。正常情况下,执行效率应该非常快速。如果时间较长,则说明很有可能正在进行单步调试。然而,这种方式属于后知后觉,并不能根本性地阻止对方进行调试。 277 | - 断点指令检测:在调试器进行调试时,在目标代码部分插入断点指令(breakpoint)。可以通过获取目标可执行文件(so)并搜索其中是否存在断点指令来判断是否被调试。 278 | - `ro.debuggable`是一个系统级属性,当处于调试模式时,该值为1,否则为0。因此有时也会用它来检测是否正在进行调试。 279 | 280 | 需要注意的是,在实际应用中,开发者通常会结合多种方法和技术来增加检测的准确性和可靠性,并确保应用程序对动态调试的防护更加健壮。 281 | 282 | 除了一些常规的检测反调试,还有一些措施是针对反反调试的,例如通常情况下,检测`/proc//status`中的`TracerPid`来判断是否被调试了,而开发者同时也知道,攻击者会选择将`status`文件重定向,或者采取其他方式,让`TracerPid`固定返回0,而这种情况,可以先检测,是否有攻击者将`status`文件进行的特殊出合理,例如先对自己的进程使用`ptrace`,然后检测`status`中的`TracerPid`是否有变更,如果结果为0,说明是被攻击者使用某种手段篡改了该值。 283 | 284 | ​由于大多数情况下,反调试手段会被攻击者使用各种`Hook`的方式进行替换处理,所以有些开发者会采用非常规的手段来获取,用来判断是否为调试状态的信息。例如内联汇编通过`svc`来执行对应的系统调用。 285 | 286 | 287 | ## 11.2 常见反调试绕过方案 288 | 289 | 常见的反调试技术,都有相应的反反调试,也就是反调试绕过技术方案。 290 | 291 | 1. `Hook`技术。 292 | `Hook`是一种常见的反调试绕过方案。它可以修改目标进程的内存数据与代码,从而绕过应用程序的反调试技术。使用`Hook`技术时,在程序运行时替换函数的实现,以此来绕过应用程序中针对调试检测所采取的措施。例如,通过使用注入工具如Frida,并修改进程中与调试标志相关联的接口内容来完成自身是否被进行了调试标志检测。 293 | 294 | 2. 内存修改。 295 | 内存修改技术是另一种常见的反调试绕过方案,黑客可以利用它来修改应用程序在内存中保存的数据从而达到躲避应用程序设置好的防止被调式捉住功能。举例来说,黑客可以借助内存编辑工具直接更改应用程序在内存中储存在关键位置上需要被阻止观察或者捉住所需信息, 进而实现规避该等安全机制. 296 | 297 | 3. 反编译修改。 298 | 另一个普遍采取之方法为使用反编译手段去找出App源代码里面负责执行检测应用是否被调试的部分逻辑,并对其进行修改以绕过反调试技术。此种手段需要对目标程序进行大量的分析研究,通常结合多个工具来执行反编译与重新打包等操作。在安卓安全领域尚未成熟时期,这类方案得到了广泛采纳。但如今,使用`Hook`方案和系统级别的反反调试技术更为普遍。 299 | 300 | 4. 系统级反反调试。 301 | 系统级别的反反调试技术是一种底层的方法,通过修改系统中与防止被调式相关联的代码逻辑,使得整个系统认定该程序处于非调式状态。这样做可以稳定地绕过应用程序设置好的防止被调式机制,并且不容易被检测到。因此,在实际应用中较为常见且广泛采用。 302 | 303 | 304 | ## 11.3 系统级反反调试 305 | 306 | ​了解常见的反调试检测后,就可以对其进行修改,这些修改并不会完美解决反调试的所有问题,主要是处理掉一些常规的检测办法。来尽量减少分析成本。下面开始简单的对几种检测方式进行修改处理。 307 | 308 | ​修改内核文件`fs/proc/array.c`,修改如下。 309 | 310 | ```c++ 311 | static inline void task_state(struct seq_file *m, struct pid_namespace *ns, 312 | struct pid *pid, struct task_struct *p) 313 | { 314 | struct user_namespace *user_ns = seq_user_ns(m); 315 | struct group_info *group_info; 316 | int g, umask; 317 | struct task_struct *tracer; 318 | const struct cred *cred; 319 | pid_t ppid, tpid = 0, tgid, ngid; 320 | unsigned int max_fds = 0; 321 | 322 | rcu_read_lock(); 323 | ppid = pid_alive(p) ? 324 | task_tgid_nr_ns(rcu_dereference(p->real_parent), ns) : 0; 325 | 326 | tracer = ptrace_parent(p); 327 | if (tracer) 328 | tpid = task_pid_nr_ns(tracer, ns); 329 | // 固定tpid为0 330 | tpid=0; 331 | ... 332 | } 333 | ``` 334 | 335 | ​在这里的`tpid`就是前文中`status`中的`TracerPid`。被调试时,该值将是调试进程`id`,但是考虑到刚刚说的反反调试检测的情况,不能直接固定将文件中的调试特征去掉,而是添加控制,当我们需要调试时,才让其调试的特征不要被检测。这里可以通过应用层和内核层交互,传递参数过来,当该参数的值为1时,就修改其过滤掉调试特征。这里就不详细展开了,继续看下一个特征的修改。 336 | 337 | ​同样是在这个文件中,修改函数`get_task_state`,这里同样可以优化成,由值来控制是否使用新的数组,修改内容如下。 338 | 339 | ```c++ 340 | static const char * const task_state_array[] = { 341 | "R (running)", /* 0 */ 342 | "S (sleeping)", /* 1 */ 343 | "D (disk sleep)", /* 2 */ 344 | "T (stopped)", /* 4 */ 345 | "t (tracing stop)", /* 8 */ 346 | "X (dead)", /* 16 */ 347 | "Z (zombie)", /* 32 */ 348 | }; 349 | // 将上面的数组拷贝一个,将T (stopped) 和t (tracing stop)都修改为S (sleeping) 350 | static const char * const task_state_array_no_debug[] = { 351 | "R (running)", /* 0 */ 352 | "S (sleeping)", /* 1 */ 353 | "D (disk sleep)", /* 2 */ 354 | "S (sleeping)", /* 4 */ 355 | "S (sleeping)" , /* 8 */ 356 | "X (dead)", /* 16 */ 357 | "Z (zombie)", /* 32 */ 358 | }; 359 | 360 | static inline const char *get_task_state(struct task_struct *tsk) 361 | { 362 | unsigned int state = (tsk->state | tsk->exit_state) & TASK_REPORT; 363 | 364 | /* 365 | * Parked tasks do not run; they sit in __kthread_parkme(). 366 | * Without this check, we would report them as running, which is 367 | * clearly wrong, so we report them as sleeping instead. 368 | */ 369 | if (tsk->state == TASK_PARKED) 370 | state = TASK_INTERRUPTIBLE; 371 | // 修改使用新定义的数组 372 | BUILD_BUG_ON(1 + ilog2(TASK_REPORT) != ARRAY_SIZE(task_state_array_no_debug)-1); 373 | // 使用新定义的数组 374 | return task_state_array_no_debug[fls(state)]; 375 | } 376 | ``` 377 | 378 | ​最后处理`wchan`的对应代码,修改内核文件`fs/proc/base.c`,相关修改如下。 379 | 380 | ```c++ 381 | static int proc_pid_wchan(struct seq_file *m, struct pid_namespace *ns, 382 | struct pid *pid, struct task_struct *task) 383 | { 384 | unsigned long wchan; 385 | char symname[KSYM_NAME_LEN]; 386 | 387 | wchan = get_wchan(task); 388 | 389 | if (wchan && ptrace_may_access(task, PTRACE_MODE_READ_FSCREDS) 390 | && !lookup_symbol_name(wchan, symname)) 391 | seq_printf(m, "%s", symname); 392 | else{ 393 | // add 394 | if (strstr(symname,"trace")){ 395 | seq_printf(m, "%s", "SyS_epoll_wait"); 396 | } 397 | // addend 398 | seq_putc(m, '0'); 399 | } 400 | return 0; 401 | } 402 | ``` 403 | 404 | ## 11.4 集成反反调试功能 405 | 406 | ​所有这些对系统的修改,都是针对不同场景反调试而产生的对应解决方案,需要重新编译系统代码。涉及内核代码的部分,需要重新编译内核,涉及`framework`的部分编译生成`ROM`。整个过程可以编写自动化操作脚本,将重复性的工作做简化处理。 407 | 408 | ​在实践过程中,调试与反调试技术都是随时攻防的不断升级实时变化的,例如,有一些软件壳会对系统状态与接口作检测,这个时候,这里介绍的一些公开的方法可能就失效了。这种情况下,需要结合实际,使用安全分析技术,对目标程序做进一步的分析,确定其使用的反调试技术,重新调整系统文件修改点,然后编译打包测试效果。 409 | 410 | 411 | ## 11.5 本章小结 412 | 413 | 本章主要介绍了在软件安全对抗领域的反调试与反反调试技术。以及简要介绍了不同反调试方法的原理,最后,讲解了一些系统级绕过反调试的方法。 414 | 415 | 除了公开的反调试技术外,还有一些鲜为人知,不被公开的反调试方法在商业与恶意软件中得到了应用。安全攻防技术的公开一般由社区安全研究人员实践并公开,隐藏的反调试方法会让防守方在一定的时间线内较得相对较高的安全防护能力。一个对抗方法的对外公开,意味着该方法失去了强有力的防护能力。因此,在安全对抗白热化的今天,安全对抗技术公开讨论的也越来越少了,一些新的思路与方法,需要研究人员自行探索与挖掘。 416 | -------------------------------------------------------------------------------- /code/chapter-06/rom_service/Android.bp: -------------------------------------------------------------------------------- 1 | package { 2 | // See: http://go/android-license-faq 3 | // A large-scale-change added 'default_applicable_licenses' to import 4 | // all of the 'license_kinds' from "frameworks_base_license" 5 | // to get the below license kinds: 6 | // SPDX-license-identifier-Apache-2.0 7 | // SPDX-license-identifier-BSD 8 | // legacy_unencumbered 9 | default_applicable_licenses: ["frameworks_base_license"], 10 | } 11 | 12 | filegroup { 13 | name: "framework-core-sources", 14 | srcs: [ 15 | "**/*.java", 16 | "**/*.aidl", 17 | ], 18 | visibility: ["//frameworks/base"], 19 | } 20 | 21 | filegroup { 22 | name: "IKeyAttestationApplicationIdProvider.aidl", 23 | srcs: ["android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl"], 24 | } 25 | 26 | filegroup { 27 | name: "IDropBoxManagerService.aidl", 28 | srcs: ["com/android/internal/os/IDropBoxManagerService.aidl"], 29 | } 30 | 31 | filegroup { 32 | name: "ITracingServiceProxy.aidl", 33 | srcs: ["android/tracing/ITracingServiceProxy.aidl"], 34 | } 35 | 36 | // These are subset of framework-core-sources that are needed by the 37 | // android.test.mock library. The implementation of android.test.mock references 38 | // private members of various components to allow mocking of classes that cannot 39 | // be mocked without access to those internal implementation details. 40 | filegroup { 41 | name: "framework-core-sources-for-test-mock", 42 | srcs: [ 43 | "android/accounts/AccountManagerCallback.java", 44 | "android/accounts/AccountManagerFuture.java", 45 | "android/accounts/AccountManager.java", 46 | "android/accounts/AccountsException.java", 47 | "android/accounts/AuthenticatorException.java", 48 | "android/accounts/OperationCanceledException.java", 49 | "android/app/Application.java", 50 | "android/app/IApplicationThread.aidl", 51 | "android/app/IServiceConnection.aidl", 52 | "android/app/PackageDeleteObserver.java", 53 | "android/content/ComponentCallbacks2.java", 54 | "android/content/ComponentCallbacks.java", 55 | "android/content/ContentInterface.java", 56 | "android/content/ContentProvider.java", 57 | "android/content/ContentProviderNative.java", 58 | "android/content/ContentResolver.java", 59 | "android/content/Context.java", 60 | "android/content/ContextWrapper.java", 61 | "android/content/DialogInterface.java", 62 | "android/content/IContentProvider.java", 63 | "android/content/Intent.java", 64 | "android/content/IntentSender.java", 65 | "android/content/OperationApplicationException.java", 66 | "android/content/pm/ActivityInfo.java", 67 | "android/content/pm/ApplicationInfo.java", 68 | "android/content/pm/InstantAppInfo.java", 69 | "android/content/pm/IPackageDataObserver.aidl", 70 | "android/content/pm/KeySet.java", 71 | "android/content/pm/PackageManager.java", 72 | "android/content/pm/VerifierDeviceIdentity.java", 73 | "android/content/res/Resources.java", 74 | "android/database/CrossProcessCursor.java", 75 | "android/database/CrossProcessCursorWrapper.java", 76 | "android/database/Cursor.java", 77 | "android/database/CursorWrapper.java", 78 | "android/os/Binder.java", 79 | "android/os/Bundle.java", 80 | "android/os/IBinder.java", 81 | "android/os/IInterface.java", 82 | "android/os/Parcelable.java", 83 | "android/os/ParcelFileDescriptor.java", 84 | "android/os/RemoteException.java", 85 | "android/os/storage/VolumeInfo.java", 86 | "android/util/AndroidException.java", 87 | "android/view/DisplayAdjustments.java", 88 | "android/view/ViewDebug.java", 89 | ], 90 | visibility: ["//frameworks/base/test-mock"], 91 | } 92 | 93 | filegroup { 94 | name: "libincident_aidl", 95 | srcs: [ 96 | "android/os/IIncidentDumpCallback.aidl", 97 | "android/os/IIncidentManager.aidl", 98 | "android/os/IIncidentReportStatusListener.aidl", 99 | ], 100 | } 101 | 102 | filegroup { 103 | name: "libvibrator_aidl", 104 | srcs: [ 105 | "android/os/IExternalVibrationController.aidl", 106 | "android/os/IExternalVibratorService.aidl", 107 | ], 108 | } 109 | 110 | filegroup { 111 | name: "libpowermanager_aidl", 112 | srcs: [ 113 | "android/os/Temperature.aidl", 114 | "android/os/CoolingDevice.aidl", 115 | "android/os/IHintManager.aidl", 116 | "android/os/IHintSession.aidl", 117 | "android/os/IThermalEventListener.aidl", 118 | "android/os/IThermalStatusListener.aidl", 119 | "android/os/IThermalService.aidl", 120 | "android/os/IPowerManager.aidl", 121 | "android/os/IRomManager.aidl", 122 | ], 123 | } 124 | 125 | genrule { 126 | name: "statslog-framework-java-gen", 127 | tools: ["stats-log-api-gen"], 128 | cmd: "$(location stats-log-api-gen) --java $(out) --module framework" + 129 | " --javaPackage com.android.internal.util --javaClass FrameworkStatsLog --worksource", 130 | out: ["com/android/internal/util/FrameworkStatsLog.java"], 131 | } 132 | 133 | java_library { 134 | name: "uieventloggerlib", 135 | srcs: [ 136 | "com/android/internal/logging/UiEvent.java", 137 | "com/android/internal/logging/UiEventLogger.java", 138 | "com/android/internal/logging/UiEventLoggerImpl.java", 139 | "com/android/internal/logging/InstanceId.java", 140 | "com/android/internal/logging/InstanceIdSequence.java", 141 | ":statslog-framework-java-gen", 142 | ], 143 | } 144 | 145 | filegroup { 146 | name: "framework-services-net-module-wifi-shared-srcs", 147 | srcs: [ 148 | "android/net/DhcpResults.java", 149 | "android/util/LocalLog.java", 150 | ], 151 | } 152 | 153 | // keep these files in sync with the package/Tethering/jarjar-rules.txt and 154 | // package/Connectivity/jarjar-rules.txt for the tethering module and connectivity module. 155 | filegroup { 156 | name: "framework-connectivity-shared-srcs", 157 | srcs: [ 158 | "android/util/IndentingPrintWriter.java", 159 | "android/util/LocalLog.java", 160 | // This should be android.util.IndentingPrintWriter, but it's not available in all branches. 161 | "com/android/internal/util/IndentingPrintWriter.java", 162 | "com/android/internal/util/IState.java", 163 | "com/android/internal/util/MessageUtils.java", 164 | "com/android/internal/util/State.java", 165 | "com/android/internal/util/StateMachine.java", 166 | "com/android/internal/util/WakeupMessage.java", 167 | ], 168 | } 169 | 170 | // keep these files in sync with the apex/jobscheduler/service jarjar-rules.txt for 171 | // the jobscheduler module. 172 | filegroup { 173 | name: "framework-jobscheduler-shared-srcs", 174 | srcs: [ 175 | ":modules-utils-preconditions-srcs", 176 | "com/android/internal/util/ArrayUtils.java", 177 | "com/android/internal/util/BitUtils.java", 178 | "com/android/internal/util/CollectionUtils.java", 179 | "com/android/internal/util/ConcurrentUtils.java", 180 | "com/android/internal/util/DumpUtils.java", 181 | "com/android/internal/util/FastPrintWriter.java", 182 | "com/android/internal/util/FastXmlSerializer.java", 183 | "com/android/internal/util/FunctionalUtils.java", 184 | "com/android/internal/util/ParseUtils.java", 185 | "com/android/internal/util/RingBufferIndices.java", 186 | "com/android/internal/util/StatLogger.java", 187 | "com/android/internal/util/XmlUtils.java", 188 | ], 189 | } 190 | 191 | // Keep these files in sync with the apex/permission/jarjar-rules.txt for the permission module. 192 | filegroup { 193 | name: "framework-permission-s-shared-srcs", 194 | srcs: [ 195 | ":modules-utils-preconditions-srcs", 196 | "com/android/internal/infra/AndroidFuture.java", 197 | "com/android/internal/infra/ServiceConnector.java", 198 | "com/android/internal/infra/AndroidFuture.aidl", 199 | "com/android/internal/infra/IAndroidFuture.aidl", 200 | "android/os/HandlerExecutor.java", 201 | ], 202 | } 203 | 204 | // Keep these files in sync with the apex/permission/jarjar-rules.txt for the permission module. 205 | filegroup { 206 | name: "service-permission-shared-srcs", 207 | srcs: [ 208 | "android/util/IndentingPrintWriter.java", 209 | "com/android/internal/util/dump/DualDumpOutputStream.java", 210 | ], 211 | } 212 | 213 | filegroup { 214 | name: "incremental_aidl", 215 | srcs: [ 216 | "android/os/incremental/IIncrementalServiceConnector.aidl", 217 | "android/os/incremental/IncrementalFileSystemControlParcel.aidl", 218 | ], 219 | } 220 | 221 | filegroup { 222 | name: "dataloader_aidl", 223 | srcs: [ 224 | "android/content/pm/DataLoaderParamsParcel.aidl", 225 | "android/content/pm/DataLoaderType.aidl", 226 | "android/content/pm/FileSystemControlParcel.aidl", 227 | "android/content/pm/IDataLoader.aidl", 228 | "android/content/pm/IDataLoaderManager.aidl", 229 | "android/content/pm/InstallationFileParcel.aidl", 230 | "android/content/pm/InstallationFileLocation.aidl", 231 | "android/content/pm/IDataLoaderStatusListener.aidl", 232 | "android/content/pm/IPackageInstallerSessionFileSystemConnector.aidl", 233 | ], 234 | } 235 | 236 | filegroup { 237 | name: "incremental_manager_aidl", 238 | srcs: [ 239 | "android/os/incremental/IIncrementalService.aidl", 240 | "android/os/incremental/IStorageLoadingProgressListener.aidl", 241 | "android/os/incremental/IncrementalNewFileParams.aidl", 242 | "android/os/incremental/IStorageHealthListener.aidl", 243 | "android/os/incremental/PerUidReadTimeouts.aidl", 244 | "android/os/incremental/StorageHealthCheckParams.aidl", 245 | ], 246 | } 247 | 248 | filegroup { 249 | name: "activity_manager_procstate_aidl", 250 | srcs: [ 251 | "android/app/ProcessStateEnum.aidl", 252 | ], 253 | } 254 | 255 | cc_defaults { 256 | name: "incremental_default", 257 | cflags: [ 258 | "-Wall", 259 | "-Wextra", 260 | "-Wextra-semi", 261 | "-Werror", 262 | "-Wzero-as-null-pointer-constant", 263 | "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION", 264 | ], 265 | shared_libs: [ 266 | "libbinder", 267 | "libutils", 268 | ], 269 | aidl: { 270 | include_dirs: [ 271 | "frameworks/native/aidl/binder", 272 | ], 273 | export_aidl_headers: true, 274 | }, 275 | } 276 | 277 | cc_library { 278 | name: "libincremental_aidl-cpp", 279 | srcs: [ 280 | ":incremental_aidl", 281 | ], 282 | defaults: ["incremental_default"], 283 | } 284 | 285 | cc_library { 286 | name: "libdataloader_aidl-cpp", 287 | srcs: [ 288 | ":dataloader_aidl", 289 | ], 290 | defaults: ["incremental_default"], 291 | shared_libs: [ 292 | "libincremental_aidl-cpp", 293 | ], 294 | } 295 | 296 | cc_library { 297 | name: "libincremental_manager_aidl-cpp", 298 | srcs: [ 299 | ":incremental_manager_aidl", 300 | ], 301 | defaults: ["incremental_default"], 302 | shared_libs: [ 303 | "libincremental_aidl-cpp", 304 | "libdataloader_aidl-cpp", 305 | ], 306 | } 307 | 308 | // Build Rust bindings for PermissionController. Needed by keystore2. 309 | aidl_interface { 310 | name: "android.os.permissions_aidl", 311 | unstable: true, 312 | local_include_dir: ".", 313 | srcs: [ 314 | "android/os/IPermissionController.aidl", 315 | ], 316 | backend: { 317 | rust: { 318 | enabled: true, 319 | }, 320 | }, 321 | } 322 | 323 | // Avoid including Parcelable classes as we don't want to have two copies of 324 | // Parcelable cross the libraries. This is used by telephony-common (frameworks/opt/telephony) 325 | // and TeleService app (packages/services/Telephony). 326 | filegroup { 327 | name: "framework-telephony-common-shared-srcs", 328 | srcs: [ 329 | ":modules-utils-preconditions-srcs", 330 | "android/os/RegistrantList.java", 331 | "android/os/Registrant.java", 332 | "android/util/IndentingPrintWriter.java", 333 | "android/util/LocalLog.java", 334 | "android/util/TimeUtils.java", 335 | "com/android/internal/os/SomeArgs.java", 336 | "com/android/internal/util/AsyncChannel.java", 337 | "com/android/internal/util/AsyncService.java", 338 | "com/android/internal/util/BitwiseInputStream.java", 339 | "com/android/internal/util/FastXmlSerializer.java", 340 | "com/android/internal/util/HexDump.java", 341 | "com/android/internal/util/IState.java", 342 | "com/android/internal/util/IndentingPrintWriter.java", 343 | "com/android/internal/util/State.java", 344 | "com/android/internal/util/StateMachine.java", 345 | "com/android/internal/util/UserIcons.java", 346 | ], 347 | } 348 | 349 | // Avoid including Parcelable classes as we don't want to have two copies of 350 | // Parcelable cross the process. 351 | filegroup { 352 | name: "framework-cellbroadcast-shared-srcs", 353 | srcs: [ 354 | ":modules-utils-preconditions-srcs", 355 | "android/os/HandlerExecutor.java", 356 | "android/util/LocalLog.java", 357 | "com/android/internal/util/IState.java", 358 | "com/android/internal/util/State.java", 359 | "com/android/internal/util/StateMachine.java", 360 | ], 361 | } 362 | 363 | filegroup { 364 | name: "framework-ims-common-shared-srcs", 365 | srcs: [ 366 | ":modules-utils-preconditions-srcs", 367 | "android/os/RegistrantList.java", 368 | "android/os/Registrant.java", 369 | "com/android/internal/os/SomeArgs.java", 370 | ], 371 | } 372 | 373 | // utility classes statically linked into wifi-service 374 | filegroup { 375 | name: "framework-wifi-service-shared-srcs", 376 | srcs: [ 377 | "android/net/InterfaceConfiguration.java", 378 | "android/util/BackupUtils.java", 379 | "android/util/Rational.java", 380 | "com/android/internal/util/FastXmlSerializer.java", 381 | "com/android/internal/util/HexDump.java", 382 | "com/android/internal/util/IState.java", 383 | "com/android/internal/util/MessageUtils.java", 384 | "com/android/internal/util/State.java", 385 | "com/android/internal/util/StateMachine.java", 386 | "com/android/internal/util/WakeupMessage.java", 387 | ], 388 | visibility: [ 389 | "//frameworks/opt/net/wifi/service", 390 | "//packages/modules/Wifi/service", 391 | ], 392 | } 393 | 394 | // protolog start 395 | filegroup { 396 | name: "protolog-common-src", 397 | srcs: [ 398 | "com/android/internal/protolog/common/**/*.java", 399 | ], 400 | } 401 | 402 | java_library { 403 | name: "protolog-lib", 404 | platform_apis: true, 405 | srcs: [ 406 | "com/android/internal/protolog/ProtoLogImpl.java", 407 | "com/android/internal/protolog/ProtoLogViewerConfigReader.java", 408 | ":protolog-common-src", 409 | ], 410 | } 411 | 412 | java_library { 413 | name: "protolog-groups", 414 | srcs: [ 415 | "com/android/internal/protolog/ProtoLogGroup.java", 416 | ":protolog-common-src", 417 | ], 418 | } 419 | 420 | // protolog end 421 | -------------------------------------------------------------------------------- /code/chapter-12/quick_jni_entrypoints.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | #include "art_method-inl.h" 20 | #include "base/casts.h" 21 | #include "entrypoints/entrypoint_utils-inl.h" 22 | #include "indirect_reference_table.h" 23 | #include "mirror/object-inl.h" 24 | #include "palette/palette.h" 25 | #include "thread-inl.h" 26 | #include "verify_object.h" 27 | #include "utils/Log.h" 28 | 29 | // For methods that monitor JNI invocations and report their begin/end to 30 | // palette hooks. 31 | #define MONITOR_JNI(kind) \ 32 | { \ 33 | bool should_report = false; \ 34 | PaletteShouldReportJniInvocations(&should_report); \ 35 | if (should_report) { \ 36 | kind(self->GetJniEnv()); \ 37 | } \ 38 | } 39 | 40 | namespace art { 41 | 42 | static_assert(sizeof(IRTSegmentState) == sizeof(uint32_t), "IRTSegmentState size unexpected"); 43 | static_assert(std::is_trivial::value, "IRTSegmentState not trivial"); 44 | 45 | static inline void GoToRunnableFast(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_); 46 | 47 | extern void ReadBarrierJni(mirror::CompressedReference* declaring_class, 48 | Thread* self ATTRIBUTE_UNUSED) { 49 | DCHECK(kUseReadBarrier); 50 | if (kUseBakerReadBarrier) { 51 | DCHECK(declaring_class->AsMirrorPtr() != nullptr) 52 | << "The class of a static jni call must not be null"; 53 | // Check the mark bit and return early if it's already marked. 54 | if (LIKELY(declaring_class->AsMirrorPtr()->GetMarkBit() != 0)) { 55 | return; 56 | } 57 | } 58 | // Call the read barrier and update the handle. 59 | mirror::Class* to_ref = ReadBarrier::BarrierForRoot(declaring_class); 60 | declaring_class->Assign(to_ref); 61 | } 62 | 63 | // Called on entry to fast JNI, push a new local reference table only. 64 | extern uint32_t JniMethodFastStart(Thread* self) { 65 | JNIEnvExt* env = self->GetJniEnv(); 66 | DCHECK(env != nullptr); 67 | uint32_t saved_local_ref_cookie = bit_cast(env->GetLocalRefCookie()); 68 | env->SetLocalRefCookie(env->GetLocalsSegmentState()); 69 | 70 | if (kIsDebugBuild) { 71 | ArtMethod* native_method = *self->GetManagedStack()->GetTopQuickFrame(); 72 | CHECK(native_method->IsFastNative()) << native_method->PrettyMethod(); 73 | } 74 | 75 | return saved_local_ref_cookie; 76 | } 77 | 78 | // Called on entry to JNI, transition out of Runnable and release share of mutator_lock_. 79 | extern uint32_t JniMethodStart(Thread* self) { 80 | JNIEnvExt* env = self->GetJniEnv(); 81 | DCHECK(env != nullptr); 82 | uint32_t saved_local_ref_cookie = bit_cast(env->GetLocalRefCookie()); 83 | env->SetLocalRefCookie(env->GetLocalsSegmentState()); 84 | // add 85 | Runtime* runtime=Runtime::Current(); 86 | if(runtime->GetConfigItem().isJNIMethodPrint){ 87 | ArtMethod* native_method = *self->GetManagedStack()->GetTopQuickFrame(); 88 | std::string methodname=native_method->PrettyMethod(); 89 | if(strstr(methodname.c_str(),runtime->GetConfigItem().jniFuncName)){ 90 | ALOGD("[ROM] enter jni %s %p",methodname.c_str(),self); 91 | runtime->GetConfigItem().jniEnable=true; 92 | } 93 | } 94 | //endadd 95 | if (kIsDebugBuild) { 96 | ArtMethod* native_method = *self->GetManagedStack()->GetTopQuickFrame(); 97 | CHECK(!native_method->IsFastNative()) << native_method->PrettyMethod(); 98 | } 99 | 100 | // Transition out of runnable. 101 | self->TransitionFromRunnableToSuspended(kNative); 102 | return saved_local_ref_cookie; 103 | } 104 | 105 | extern uint32_t JniMethodStartSynchronized(jobject to_lock, Thread* self) { 106 | self->DecodeJObject(to_lock)->MonitorEnter(self); 107 | return JniMethodStart(self); 108 | } 109 | 110 | static void GoToRunnable(Thread* self) NO_THREAD_SAFETY_ANALYSIS { 111 | if (kIsDebugBuild) { 112 | ArtMethod* native_method = *self->GetManagedStack()->GetTopQuickFrame(); 113 | CHECK(!native_method->IsFastNative()) << native_method->PrettyMethod(); 114 | } 115 | 116 | self->TransitionFromSuspendedToRunnable(); 117 | } 118 | 119 | ALWAYS_INLINE static inline void GoToRunnableFast(Thread* self) { 120 | if (kIsDebugBuild) { 121 | // Should only enter here if the method is @FastNative. 122 | ArtMethod* native_method = *self->GetManagedStack()->GetTopQuickFrame(); 123 | CHECK(native_method->IsFastNative()) << native_method->PrettyMethod(); 124 | } 125 | 126 | // When we are in @FastNative, we are already Runnable. 127 | // Only do a suspend check on the way out of JNI. 128 | if (UNLIKELY(self->TestAllFlags())) { 129 | // In fast JNI mode we never transitioned out of runnable. Perform a suspend check if there 130 | // is a flag raised. 131 | DCHECK(Locks::mutator_lock_->IsSharedHeld(self)); 132 | self->CheckSuspend(); 133 | } 134 | } 135 | 136 | static void PopLocalReferences(uint32_t saved_local_ref_cookie, Thread* self) 137 | REQUIRES_SHARED(Locks::mutator_lock_) { 138 | JNIEnvExt* env = self->GetJniEnv(); 139 | if (UNLIKELY(env->IsCheckJniEnabled())) { 140 | env->CheckNoHeldMonitors(); 141 | } 142 | env->SetLocalSegmentState(env->GetLocalRefCookie()); 143 | env->SetLocalRefCookie(bit_cast(saved_local_ref_cookie)); 144 | } 145 | 146 | static inline void UnlockJniSynchronizedMethod(jobject locked, Thread* self) 147 | NO_THREAD_SAFETY_ANALYSIS REQUIRES(!Roles::uninterruptible_) { 148 | // Save any pending exception over monitor exit call. 149 | ObjPtr saved_exception = nullptr; 150 | if (UNLIKELY(self->IsExceptionPending())) { 151 | saved_exception = self->GetException(); 152 | self->ClearException(); 153 | } 154 | // Decode locked object and unlock, before popping local references. 155 | self->DecodeJObject(locked)->MonitorExit(self); 156 | if (UNLIKELY(self->IsExceptionPending())) { 157 | LOG(FATAL) << "Synchronized JNI code returning with an exception:\n" 158 | << saved_exception->Dump() 159 | << "\nEncountered second exception during implicit MonitorExit:\n" 160 | << self->GetException()->Dump(); 161 | } 162 | // Restore pending exception. 163 | if (saved_exception != nullptr) { 164 | self->SetException(saved_exception); 165 | } 166 | } 167 | 168 | // Otherwise there's just too much repetitive boilerplate. 169 | 170 | extern void JniMethodEnd(uint32_t saved_local_ref_cookie, Thread* self) { 171 | 172 | // add 173 | Runtime* runtime=Runtime::Current(); 174 | if(runtime->GetConfigItem().isJNIMethodPrint){ 175 | ArtMethod* native_method = *self->GetManagedStack()->GetTopQuickFrame(); 176 | std::string methodname=native_method->PrettyMethod(); 177 | ALOGD("[ROM] JniMethodEnd jni %s",methodname.c_str()); 178 | if(strstr(methodname.c_str(),runtime->GetConfigItem().jniFuncName)){ 179 | runtime->GetConfigItem().jniEnable=false; 180 | ALOGD("[ROM] leave jni %s",methodname.c_str()); 181 | } 182 | } 183 | //endadd 184 | // ArtMethod* native_method = *self->GetManagedStack()->GetTopQuickFrame(); 185 | // if(native_method!=nullptr){ 186 | // std::string methodname=native_method->PrettyMethod(); 187 | // ALOGD("[ROM] JniMethodEnd %s",methodname.c_str()); 188 | // } 189 | 190 | GoToRunnable(self); 191 | PopLocalReferences(saved_local_ref_cookie, self); 192 | } 193 | 194 | extern void JniMethodFastEnd(uint32_t saved_local_ref_cookie, Thread* self) { 195 | // ArtMethod* native_method = *self->GetManagedStack()->GetTopQuickFrame(); 196 | // if(native_method!=nullptr){ 197 | // std::string methodname=native_method->PrettyMethod(); 198 | // ALOGD("[ROM] JniMethodFastEnd %s",methodname.c_str()); 199 | // } 200 | GoToRunnableFast(self); 201 | PopLocalReferences(saved_local_ref_cookie, self); 202 | } 203 | 204 | extern void JniMethodEndSynchronized(uint32_t saved_local_ref_cookie, 205 | jobject locked, 206 | Thread* self) { 207 | // ArtMethod* native_method = *self->GetManagedStack()->GetTopQuickFrame(); 208 | // if(native_method!=nullptr){ 209 | // std::string methodname=native_method->PrettyMethod(); 210 | // ALOGD("[ROM] JniMethodEndSynchronized %s",methodname.c_str()); 211 | // } 212 | GoToRunnable(self); 213 | UnlockJniSynchronizedMethod(locked, self); // Must decode before pop. 214 | PopLocalReferences(saved_local_ref_cookie, self); 215 | } 216 | 217 | // Common result handling for EndWithReference. 218 | static mirror::Object* JniMethodEndWithReferenceHandleResult(jobject result, 219 | uint32_t saved_local_ref_cookie, 220 | Thread* self) 221 | NO_THREAD_SAFETY_ANALYSIS { 222 | 223 | // add 224 | Runtime* runtime=Runtime::Current(); 225 | if(runtime->GetConfigItem().isJNIMethodPrint){ 226 | ArtMethod* native_method = *self->GetManagedStack()->GetTopQuickFrame(); 227 | std::string methodname=native_method->PrettyMethod(); 228 | if(strstr(methodname.c_str(),runtime->GetConfigItem().jniFuncName)){ 229 | runtime->GetConfigItem().jniEnable=false; 230 | ALOGD("[ROM] leave jni %s",methodname.c_str()); 231 | } 232 | } 233 | //endadd 234 | // ArtMethod* native_method = *self->GetManagedStack()->GetTopQuickFrame(); 235 | // if(native_method!=nullptr){ 236 | // std::string methodname=native_method->PrettyMethod(); 237 | // ALOGD("[ROM] JniMethodEndWithReferenceHandleResult %s",methodname.c_str()); 238 | // } 239 | // Must decode before pop. The 'result' may not be valid in case of an exception, though. 240 | ObjPtr o; 241 | if (!self->IsExceptionPending()) { 242 | o = self->DecodeJObject(result); 243 | } 244 | PopLocalReferences(saved_local_ref_cookie, self); 245 | // Process result. 246 | if (UNLIKELY(self->GetJniEnv()->IsCheckJniEnabled())) { 247 | // CheckReferenceResult can resolve types. 248 | StackHandleScope<1> hs(self); 249 | HandleWrapperObjPtr h_obj(hs.NewHandleWrapper(&o)); 250 | CheckReferenceResult(h_obj, self); 251 | } 252 | VerifyObject(o); 253 | return o.Ptr(); 254 | } 255 | 256 | extern mirror::Object* JniMethodFastEndWithReference(jobject result, 257 | uint32_t saved_local_ref_cookie, 258 | Thread* self) { 259 | GoToRunnableFast(self); 260 | return JniMethodEndWithReferenceHandleResult(result, saved_local_ref_cookie, self); 261 | } 262 | 263 | extern mirror::Object* JniMethodEndWithReference(jobject result, 264 | uint32_t saved_local_ref_cookie, 265 | Thread* self) { 266 | GoToRunnable(self); 267 | return JniMethodEndWithReferenceHandleResult(result, saved_local_ref_cookie, self); 268 | } 269 | 270 | extern mirror::Object* JniMethodEndWithReferenceSynchronized(jobject result, 271 | uint32_t saved_local_ref_cookie, 272 | jobject locked, 273 | Thread* self) { 274 | GoToRunnable(self); 275 | UnlockJniSynchronizedMethod(locked, self); 276 | return JniMethodEndWithReferenceHandleResult(result, saved_local_ref_cookie, self); 277 | } 278 | 279 | extern uint64_t GenericJniMethodEnd(Thread* self, 280 | uint32_t saved_local_ref_cookie, 281 | jvalue result, 282 | uint64_t result_f, 283 | ArtMethod* called) 284 | NO_THREAD_SAFETY_ANALYSIS { 285 | // ALOGD("[ROM] GenericJniMethodEnd "); 286 | bool critical_native = called->IsCriticalNative(); 287 | bool fast_native = called->IsFastNative(); 288 | bool normal_native = !critical_native && !fast_native; 289 | 290 | // @CriticalNative does not do a state transition. @FastNative usually does not do a state 291 | // transition either but it performs a suspend check that may do state transitions. 292 | if (LIKELY(normal_native)) { 293 | MONITOR_JNI(PaletteNotifyEndJniInvocation); 294 | GoToRunnable(self); 295 | } else if (fast_native) { 296 | GoToRunnableFast(self); 297 | } 298 | // We need the mutator lock (i.e., calling GoToRunnable()) before accessing the shorty or the 299 | // locked object. 300 | if (called->IsSynchronized()) { 301 | DCHECK(normal_native) << "@FastNative/@CriticalNative and synchronize is not supported"; 302 | jobject lock = GetGenericJniSynchronizationObject(self, called); 303 | DCHECK(lock != nullptr); 304 | UnlockJniSynchronizedMethod(lock, self); 305 | } 306 | char return_shorty_char = called->GetShorty()[0]; 307 | if (return_shorty_char == 'L') { 308 | return reinterpret_cast(JniMethodEndWithReferenceHandleResult( 309 | result.l, saved_local_ref_cookie, self)); 310 | } else { 311 | if (LIKELY(!critical_native)) { 312 | PopLocalReferences(saved_local_ref_cookie, self); 313 | } 314 | switch (return_shorty_char) { 315 | case 'F': { 316 | if (kRuntimeISA == InstructionSet::kX86) { 317 | // Convert back the result to float. 318 | double d = bit_cast(result_f); 319 | return bit_cast(static_cast(d)); 320 | } else { 321 | return result_f; 322 | } 323 | } 324 | case 'D': 325 | return result_f; 326 | case 'Z': 327 | return result.z; 328 | case 'B': 329 | return result.b; 330 | case 'C': 331 | return result.c; 332 | case 'S': 333 | return result.s; 334 | case 'I': 335 | return result.i; 336 | case 'J': 337 | return result.j; 338 | case 'V': 339 | return 0; 340 | default: 341 | LOG(FATAL) << "Unexpected return shorty character " << return_shorty_char; 342 | UNREACHABLE(); 343 | } 344 | } 345 | } 346 | 347 | extern uint32_t JniMonitoredMethodStart(Thread* self) { 348 | uint32_t result = JniMethodStart(self); 349 | MONITOR_JNI(PaletteNotifyBeginJniInvocation); 350 | return result; 351 | } 352 | 353 | extern uint32_t JniMonitoredMethodStartSynchronized(jobject to_lock, Thread* self) { 354 | uint32_t result = JniMethodStartSynchronized(to_lock, self); 355 | MONITOR_JNI(PaletteNotifyBeginJniInvocation); 356 | return result; 357 | } 358 | 359 | extern void JniMonitoredMethodEnd(uint32_t saved_local_ref_cookie, Thread* self) { 360 | MONITOR_JNI(PaletteNotifyEndJniInvocation); 361 | return JniMethodEnd(saved_local_ref_cookie, self); 362 | } 363 | 364 | extern void JniMonitoredMethodEndSynchronized(uint32_t saved_local_ref_cookie, 365 | jobject locked, 366 | Thread* self) { 367 | MONITOR_JNI(PaletteNotifyEndJniInvocation); 368 | return JniMethodEndSynchronized(saved_local_ref_cookie, locked, self); 369 | } 370 | 371 | extern mirror::Object* JniMonitoredMethodEndWithReference(jobject result, 372 | uint32_t saved_local_ref_cookie, 373 | Thread* self) { 374 | MONITOR_JNI(PaletteNotifyEndJniInvocation); 375 | return JniMethodEndWithReference(result, saved_local_ref_cookie, self); 376 | } 377 | 378 | extern mirror::Object* JniMonitoredMethodEndWithReferenceSynchronized( 379 | jobject result, 380 | uint32_t saved_local_ref_cookie, 381 | jobject locked, 382 | Thread* self) { 383 | MONITOR_JNI(PaletteNotifyEndJniInvocation); 384 | return JniMethodEndWithReferenceSynchronized(result, saved_local_ref_cookie, locked, self); 385 | } 386 | 387 | } // namespace art 388 | -------------------------------------------------------------------------------- /chapter-09/README.md: -------------------------------------------------------------------------------- 1 | # 第九章 Android Hook框架 2 | 3 | 在前面的章节中,我们简要介绍了如何将开发的模块内置到系统中,并将其注入到应用程序中执行。而内置并注入第三方开发的工具,与之前介绍的简单内置注入过程没有太大区别。关键步骤是加载工具所依赖的动态库,然后再加载核心业务组件。本章将以几个典型的Hook框架作为例子,展示如何将它们内置在系统中。 4 | 5 | 通过这些例子,我们可以更深入地理解和学习Hook技术,并掌握如何将其集成到系统中。 6 | 7 | 8 | ## 9.1 Xposed 9 | 10 | `Xposed`是一个Android Hook框架,它可以在不修改APK文件的情况下改变系统和应用程序的行为。通过开发模块,我们能够对目标进程的Java函数调用进行Hook拦截。然而,要使用该框架中的模块功能,需要将其安装在Root权限的Android设备上。 11 | 12 | 根据Xposed框架原理衍生出了很多类似的框架,比如Edxposed、Lsposed等等。 13 | 14 | 在Xposed的架构中,主要包含三个部分:Xposed Installer、Xposed Bridge和Xposed Module。其中,Xposed Installer是用户安装和管理Xposed模块的应用程序;Xposed Bridge是实现系统与模块之间相互通信的核心组件;而Xpoed Module则是开发者使用Xpose API编写并且加载到目标进程中来实现对目标进程函数调用拦截和修改。 15 | 16 | 运行时, Xpose Installer会通过Android的PackageManager查询已经安装好了那些 app 并将相关信息传递给Xpose Bridge. 当目标app启动时, Xposd bridge就会load相关module到target process中,并建立起相应管道以便后续操作. 17 | 18 | 编写一个xposed模块主要涉及两个方面: 19 | 1. 继承 IXpodeHookLoadPackage 接口来完成启动事件监听; 20 | 2. 使用 xposd API 来进行函数调用拦截和修改. 21 | 22 | 这些API包括XposedHelpers.findAndHookMethod和XposedHelpers.callMethed等,它们可以帮助我们定位到目标进程中的函数,并对其进行拦截和修改。 23 | 24 | 本章将详细解析Xposed的原理,学习Xposed如何利用Android的运行机制来实现注入。 25 | 26 | 27 | ## 9.2 Xposed实现原理 28 | 29 | 在开始分析`Xposed`源码前,首先回顾一下第三章中,讲解`Android`启动流程时,最后根据`AOSP`的源码得到的以下结论。 30 | 31 | 1. `zygote`进程启动是通过`app_process`执行程序启动的 32 | 2. 由`init`进程解析`init.rc`时启动的第一个`zygote` 33 | 3. 在第一个`zygote`进程中创建的`ZygoteServer`,并开始监听消息。 34 | 4. `zygote`是在`ZygoteServer`这个服务中收到消息后,再去`fork`出新进程的 35 | 5. 所有进程均来自于`zygote`进程的`fork`而来,所以`zygote`是进程的始祖 36 | 37 | 从上面的结论中可以看到,`app_process`执行程序在其中占据着非常重要的位置,而`Xposed`的核心原理,就是将`app_process`替换为`Xposed`修改过的`app_process`,这样就会让所有进程都会通过它的业务逻辑处理。首先找到项目`https://github.com/rovo89/Xposed`。查看文件`Android.mk`。 38 | 39 | ``` 40 | ifeq (1,$(strip $(shell expr $(PLATFORM_SDK_VERSION) \>= 21))) 41 | LOCAL_SRC_FILES := app_main2.cpp 42 | LOCAL_MULTILIB := both 43 | LOCAL_MODULE_STEM_32 := app_process32_xposed 44 | LOCAL_MODULE_STEM_64 := app_process64_xposed 45 | else 46 | LOCAL_SRC_FILES := app_main.cpp 47 | LOCAL_MODULE_STEM := app_process_xposed 48 | endif 49 | ``` 50 | 51 | 可以看到这里是用来编译一个`Xposed`专用的`app_process`。当`Android`版本大于21(`Android 5`)时,使用`app_main2.cpp`来编译。接下来查看入口函数的实现。 52 | 53 | ```cpp 54 | #define XPOSED_CLASS_DOTS_TOOLS "de.robv.android.xposed.XposedBridge$ToolEntryPoint" 55 | 56 | int main(int argc, char* const argv[]) 57 | { 58 | ... 59 | // 检测Xposed的参数 60 | if (xposed::handleOptions(argc, argv)) { 61 | return 0; 62 | } 63 | ... 64 | if (zygote) { 65 | // Xposed 框架的初始化,为后续的 Hook 操作和代码注入操作提供支持。 66 | isXposedLoaded = xposed::initialize(true, startSystemServer, NULL, argc, argv); 67 | runtimeStart(runtime, isXposedLoaded ? XPOSED_CLASS_DOTS_ZYGOTE : "com.android.internal.os.ZygoteInit", args, zygote); 68 | } else if (className) { 69 | isXposedLoaded = xposed::initialize(false, false, className, argc, argv); 70 | runtimeStart(runtime, isXposedLoaded ? XPOSED_CLASS_DOTS_TOOLS : "com.android.internal.os.RuntimeInit", args, zygote); 71 | } else { 72 | fprintf(stderr, "Error: no class name or --zygote supplied.\n"); 73 | app_usage(); 74 | LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied."); 75 | return 10; 76 | } 77 | } 78 | ``` 79 | 80 | 在这个特殊的`app_process`中,首先是对启动进程的参数进行检查,然后初始化`Xposed`框架,如果初始化成功了,则使用`Xposed`的入口`de.robv.android.xposed.XposedBridge$ToolEntryPoint`来替换系统原本的`com.android.internal.os.ZygoteInit`入口。 81 | 82 | `xposed::initialize`是一个非常关键的函数,它完成了 `Xposed `框架的初始化工作。查看实现代码如下。 83 | 84 | ```c++ 85 | bool initialize(bool zygote, bool startSystemServer, const char* className, int argc, char* const argv[]) { 86 | #if !defined(XPOSED_ENABLE_FOR_TOOLS) 87 | ... 88 | // 将参数保存 89 | xposed->zygote = zygote; 90 | xposed->startSystemServer = startSystemServer; 91 | xposed->startClassName = className; 92 | xposed->xposedVersionInt = xposedVersionInt; 93 | 94 | #if XPOSED_WITH_SELINUX 95 | xposed->isSELinuxEnabled = is_selinux_enabled() == 1; 96 | xposed->isSELinuxEnforcing = xposed->isSELinuxEnabled && security_getenforce() == 1; 97 | #else 98 | xposed->isSELinuxEnabled = false; 99 | xposed->isSELinuxEnforcing = false; 100 | #endif // XPOSED_WITH_SELINUX 101 | 102 | ... 103 | 104 | if (startSystemServer) { 105 | if (!determineXposedInstallerUidGid() || !xposed::service::startAll()) { 106 | return false; 107 | } 108 | // 启动Xposed框架的日志记录,将xposed框架日志写入logcat中。 109 | xposed::logcat::start(); 110 | // SELinux启用的情况 111 | #if XPOSED_WITH_SELINUX 112 | } else if (xposed->isSELinuxEnabled) { 113 | // 用于启动Xposed框架的 membased 服务,该服务实现hooking功能 114 | if (!xposed::service::startMembased()) { 115 | return false; 116 | } 117 | #endif // XPOSED_WITH_SELINUX 118 | } 119 | // SELinux启用的情况 120 | #if XPOSED_WITH_SELINUX 121 | // 限制内存继承,以确保Xposed服务只能被当前进程和其子进程使用,而不能被其他进程使用 122 | if (xposed->isSELinuxEnabled) { 123 | xposed::service::membased::restrictMemoryInheritance(); 124 | } 125 | #endif // XPOSED_WITH_SELINUX 126 | 127 | // 是否禁用xposed 128 | if (zygote && !isSafemodeDisabled() && detectSafemodeTrigger(shouldSkipSafemodeDelay())) 129 | disableXposed(); 130 | 131 | if (isDisabled() || (!zygote && shouldIgnoreCommand(argc, argv))) 132 | return false; 133 | // 将Xposed JAR文件添加到应用程序或服务的类路径中 134 | return addJarToClasspath(); 135 | } 136 | 137 | ``` 138 | 139 | 在启用`SELinux`的情况下,`Xposed`需要使用`membased`服务来实现`hooking`功能。但是,为了确保安全性,`Xposed`需要限制将`Xposed`服务复制到其他进程中的能力。通过调用`restrictMemoryInheritance`函数,`Xposed`会防止任何进程继承`Zygote`进程的内存,这将确保`Xposed`服务只能被当前进程和其子进程使用。 140 | 141 | 初始化完成时,将`XposedBridge.jar`文件添加到了`CLASSPATH`环境变量中,查看`addJarToClasspath`的实现。 142 | 143 | ```java 144 | #define XPOSED_JAR "/system/framework/XposedBridge.jar" 145 | 146 | bool addJarToClasspath() { 147 | ALOGI("-----------------"); 148 | if (access(XPOSED_JAR, R_OK) == 0) { 149 | if (!addPathToEnv("CLASSPATH", XPOSED_JAR)) 150 | return false; 151 | 152 | ALOGI("Added Xposed (%s) to CLASSPATH", XPOSED_JAR); 153 | return true; 154 | } else { 155 | ALOGE("ERROR: Could not access Xposed jar '%s'", XPOSED_JAR); 156 | return false; 157 | } 158 | } 159 | ``` 160 | 161 | 初始化成功后,接着继续追踪替换后的入口点`de.robv.android.xposed.XposedBridge$ToolEntryPoint`,该入口点的实现是在`XposedBridge.jar`中。查看项目`https://github.com/rovo89/XposedBridge`,文件`XposedBridge.java`的实现代码如下。 162 | 163 | ```java 164 | package de.robv.android.xposed; 165 | 166 | public final class XposedBridge { 167 | protected static final class ToolEntryPoint { 168 | protected static void main(String[] args) { 169 | isZygote = false; 170 | XposedBridge.main(args); 171 | } 172 | } 173 | 174 | protected static void main(String[] args) { 175 | // 初始化Xposed框架和模块 176 | try { 177 | if (!hadInitErrors()) { 178 | initXResources(); 179 | SELinuxHelper.initOnce(); 180 | SELinuxHelper.initForProcess(null); 181 | 182 | runtime = getRuntime(); 183 | XPOSED_BRIDGE_VERSION = getXposedVersion(); 184 | 185 | if (isZygote) { 186 | // hook Android 资源系统 187 | XposedInit.hookResources(); 188 | // 初始化 Xposed 框架的 zygote 进程,创建用于跨进程通信的 Binder 对象,并注册相关的 Service。这样就能够实现跨进程的 Hook 功能 189 | XposedInit.initForZygote(); 190 | } 191 | // 加载Xposed模块 192 | XposedInit.loadModules(); 193 | } else { 194 | Log.e(TAG, "Not initializing Xposed because of previous errors"); 195 | } 196 | } catch (Throwable t) { 197 | Log.e(TAG, "Errors during Xposed initialization", t); 198 | disableHooks = true; 199 | } 200 | 201 | // 调用原始应用的入口 202 | if (isZygote) { 203 | ZygoteInit.main(args); 204 | } else { 205 | RuntimeInit.main(args); 206 | } 207 | } 208 | } 209 | ``` 210 | 211 | 到这里,`Xposed`的启动流程基本完成了,`Xposed`首先替换原始的`app_process`,让每个进程启动时使用自己的`app_process_xposed`,在执行`zygote`入口函数前,先初始化了自身的环境,然后每个进程后是先进入的`XposedBridge`,在完成自身的逻辑后,才调用`zygote`的入口函数,进入应用正常启动流程。这也意味着,对于系统定制者来说,所谓的`Root`权限才能使用`Xposed`并不是必须的。最后看看`loadModules`的实现,是如何加载`Xposed`模块的。 212 | 213 | ```java 214 | private static final String INSTANT_RUN_CLASS = "com.android.tools.fd.runtime.BootstrapApplication"; 215 | 216 | // 加载模块列表 217 | static void loadModules() throws IOException { 218 | 219 | final String filename = BASE_DIR + "conf/modules.list"; 220 | BaseService service = SELinuxHelper.getAppDataFileService(); 221 | if (!service.checkFileExists(filename)) { 222 | Log.e(TAG, "Cannot load any modules because " + filename + " was not found"); 223 | return; 224 | } 225 | // 拿到顶端的ClassLoader 226 | ClassLoader topClassLoader = XposedBridge.BOOTCLASSLOADER; 227 | ClassLoader parent; 228 | while ((parent = topClassLoader.getParent()) != null) { 229 | topClassLoader = parent; 230 | } 231 | // 读取模块列表 232 | InputStream stream = service.getFileInputStream(filename); 233 | BufferedReader apks = new BufferedReader(new InputStreamReader(stream)); 234 | String apk; 235 | // 使用顶端ClassLoader加载每个模块 236 | while ((apk = apks.readLine()) != null) { 237 | loadModule(apk, topClassLoader); 238 | } 239 | apks.close(); 240 | } 241 | 242 | private static void loadModule(String apk, ClassLoader topClassLoader) { 243 | Log.i(TAG, "Loading modules from " + apk); 244 | 245 | if (!new File(apk).exists()) { 246 | Log.e(TAG, " File does not exist"); 247 | return; 248 | } 249 | 250 | DexFile dexFile; 251 | try { 252 | dexFile = new DexFile(apk); 253 | } catch (IOException e) { 254 | Log.e(TAG, " Cannot load module", e); 255 | return; 256 | } 257 | // 如果加载成功,说明该应用启用了 Instant Run 258 | if (dexFile.loadClass(INSTANT_RUN_CLASS, topClassLoader) != null) { 259 | Log.e(TAG, " Cannot load module, please disable \"Instant Run\" in Android Studio."); 260 | closeSilently(dexFile); 261 | return; 262 | } 263 | // 尝试在目标模块中加载XposedBridge类,可以获取到说明已经成功注入XposedBridge 264 | if (dexFile.loadClass(XposedBridge.class.getName(), topClassLoader) != null) { 265 | Log.e(TAG, " Cannot load module:"); 266 | Log.e(TAG, " The Xposed API classes are compiled into the module's APK."); 267 | Log.e(TAG, " This may cause strange issues and must be fixed by the module developer."); 268 | Log.e(TAG, " For details, see: http://api.xposed.info/using.html"); 269 | closeSilently(dexFile); 270 | return; 271 | } 272 | 273 | closeSilently(dexFile); 274 | // 由于模块实际都是apk,而apk本质是压缩包,所以使用Zip来处理文件 275 | ZipFile zipFile = null; 276 | InputStream is; 277 | try { 278 | zipFile = new ZipFile(apk); 279 | // 解压出xposed_init文件,这里存放着模块启动的入口 280 | ZipEntry zipEntry = zipFile.getEntry("assets/xposed_init"); 281 | if (zipEntry == null) { 282 | Log.e(TAG, " assets/xposed_init not found in the APK"); 283 | closeSilently(zipFile); 284 | return; 285 | } 286 | is = zipFile.getInputStream(zipEntry); 287 | } catch (IOException e) { 288 | Log.e(TAG, " Cannot read assets/xposed_init in the APK", e); 289 | closeSilently(zipFile); 290 | return; 291 | } 292 | // 动态加载模块 293 | ClassLoader mcl = new PathClassLoader(apk, XposedBridge.BOOTCLASSLOADER); 294 | BufferedReader moduleClassesReader = new BufferedReader(new InputStreamReader(is)); 295 | try { 296 | String moduleClassName; 297 | while ((moduleClassName = moduleClassesReader.readLine()) != null) { 298 | moduleClassName = moduleClassName.trim(); 299 | if (moduleClassName.isEmpty() || moduleClassName.startsWith("#")) 300 | continue; 301 | 302 | try { 303 | // 加载模块的入口类 304 | Log.i(TAG, " Loading class " + moduleClassName); 305 | Class moduleClass = mcl.loadClass(moduleClassName); 306 | // 检查该类是否有实现接口 307 | if (!IXposedMod.class.isAssignableFrom(moduleClass)) { 308 | Log.e(TAG, " This class doesn't implement any sub-interface of IXposedMod, skipping it"); 309 | continue; 310 | } else if (disableResources && IXposedHookInitPackageResources.class.isAssignableFrom(moduleClass)) { 311 | Log.e(TAG, " This class requires resource-related hooks (which are disabled), skipping it."); 312 | continue; 313 | } 314 | // 使用该类初始化一个对象 315 | final Object moduleInstance = moduleClass.newInstance(); 316 | if (XposedBridge.isZygote) { 317 | // 不同的实现接口有各自对应的处理,这里是Zygote模块初始化时使用的模块 318 | if (moduleInstance instanceof IXposedHookZygoteInit) { 319 | IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam(); 320 | param.modulePath = apk; 321 | param.startsSystemServer = startsSystemServer; 322 | ((IXposedHookZygoteInit) moduleInstance).initZygote(param); 323 | } 324 | // 普通应用的模块接口 325 | if (moduleInstance instanceof IXposedHookLoadPackage) 326 | // 调用了模块中的实现。 327 | XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance)); 328 | 329 | if (moduleInstance instanceof IXposedHookInitPackageResources) 330 | XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper((IXposedHookInitPackageResources) moduleInstance)); 331 | } else { 332 | if (moduleInstance instanceof IXposedHookCmdInit) { 333 | IXposedHookCmdInit.StartupParam param = new IXposedHookCmdInit.StartupParam(); 334 | param.modulePath = apk; 335 | param.startClassName = startClassName; 336 | ((IXposedHookCmdInit) moduleInstance).initCmdApp(param); 337 | } 338 | } 339 | } catch (Throwable t) { 340 | Log.e(TAG, " Failed to load class " + moduleClassName, t); 341 | } 342 | } 343 | } catch (IOException e) { 344 | Log.e(TAG, " Failed to load module from " + apk, e); 345 | } finally { 346 | closeSilently(is); 347 | closeSilently(zipFile); 348 | } 349 | } 350 | ``` 351 | 352 | 分析完加载模块的实现后,这时就明白模块开发时定义的入口是如何被调用的,以及被调用的时机在哪里。理解其中的原理后,同样可以自己进行修改,在其他的时机来选择注入。用自己的方式来定义模块。 353 | 354 | 355 | ## 9.3 常见的hook框架 356 | 357 | ​ 根据`Xposed`的源码分析不难看出其关键在于`XposedBridge.jar`的注入,然后由`XposedBridge.jar`实现对函数`Hook`的关键逻辑,因为`Xposed`框架提供了非常方便和灵活的`API`,使得开发者可以快速地编写自己的`Hook`模块并且可以兼容大多数`Android`系统版本和设备。所以很多`Hook`框架都会兼容支持`Xposed`框架。 358 | 359 | ​ `SandHook`是作用在`Android ART`虚拟机上的`Java`层`Hook`框架,作用于进程内是不需要` Root `的,支持`Android 4.4 - Android 10`,该框架兼容`Xposed Api`调用。 360 | 361 | ​ 除了支持常规的`Java`层`Hook`外,`Sandhook`还支持对`Native`层的函数进行`Hook`。它通过使用系统提供的符号表来获取函数地址,并将函数地址转换为可执行代码,从而实现`Native Hook`。 362 | 363 | ​ `Sandhook`本身是没有注入功能的,开发完模块功能后,需要自行重打包,或者使用其他工具将模块注入。从开发`AOSP`的角度,可以参考前文内置`JAR`包的做法,直接将`Sandhook`内置到`AOSP`系统中,并实现对任意进程自动注入。 364 | 365 | ​ `pine`是一个在虚拟机层面、以`Java`方法为粒度的运行时动态`Hook`框架,它可以拦截本进程内几乎所有的`java`方法调用。支持`Android 4.4 - Android 12`。同样该框架也兼容`Xposed Api`调用。`Pine`支持两种方案,一种是替换入口,即修改`ArtMethod`的`entrypoint`;另一种类似于`native`的`inline hook`,即覆盖掉目标方法的代码开始处的一段代码,用于弥补`Android 8.0`以下版本入口替换很有可能不生效的问题。 366 | 367 | ​ `Dobby`是一个基于`Android NDK`开发的`Native Hook`框架。它可以在`Android`应用程序中注入自定义代码段,从而实现函数替换、跳转、插桩等操作。`Dobby`主要使用了动态链接库和指令重写技术,通过`Hook`目标进程中的函数来达到修改目的。 368 | 369 | ​ 相比`Java`层的`Hook`框架,`Native Hook`有一些优势。首先,`Native Hook`可以直接操作目标进程的内存空间,更加灵活;其次,`Native Hook`可以通过指令重写技术来控制执行流程,效果更加精准;最后,`Native Hook`避免了`Java`层`Hook`可能引起的兼容性问题,适用范围更广。 370 | 371 | 372 | ## 9.4 集成dobby 373 | 374 | 集成方式与`pine`相同,首先开发一个使用`dobby`的样例,然后将其中的依赖动态库集成到系统中,最后在进程启动的过程中,将其加载即可。由于`dobby`是对`native`函数进行`hook`的,所以`Android Studio`创建一个`native c++`的项目,然后使用`git`将`dobby`项目拉取下来。项目地址:`https://github.com/jmpews/Dobby`。然后修改项目中`cpp`目录下的`CMakeLists.txt`文件,将`dobby`加入其中。修改如下。 375 | 376 | ```cmake 377 | cmake_minimum_required(VERSION 3.18.1) 378 | // 设置dobby源码的目录 379 | set(DobbyHome ~/git_src/Dobby) 380 | enable_language(C ASM) 381 | 382 | include_directories( 383 | dlfc 384 | utils 385 | ) 386 | 387 | project("mydobby") 388 | 389 | add_library( 390 | mydobby 391 | SHARED 392 | native-lib.cpp) 393 | 394 | find_library( 395 | log-lib 396 | log) 397 | 398 | target_link_libraries( 399 | mydobby 400 | dobby 401 | ${log-lib}) 402 | 403 | # 使用设置的路径,引入Dobby 404 | include_directories( 405 | ${DobbyHome}/include 406 | ${DobbyHome}/source 407 | ${DobbyHome}/builtin-plugin 408 | ${DobbyHome}/builtin-plugin/AndroidRestriction 409 | ${DobbyHome}/builtin-plugin/SymbolResolver 410 | ${DobbyHome}/external/logging 411 | ) 412 | 413 | macro(SET_OPTION option value) 414 | set(${option} ${value} CACHE INTERNAL "" FORCE) 415 | endmacro() 416 | 417 | SET_OPTION(DOBBY_DEBUG ON) 418 | SET_OPTION(DOBBY_GENERATE_SHARED ON) 419 | SET_OPTION(Plugin.LinkerLoadCallback OFF) 420 | 421 | add_subdirectory(~/git_src/Dobby dobby.build) 422 | 423 | if(${CMAKE_ANDROID_ARCH_ABI} STREQUAL "arm64-v8a") 424 | add_definitions(-DCORE_SO_NAME="${LIBRARY_NAME}") 425 | elseif(${CMAKE_ANDROID_ARCH_ABI} STREQUAL "armeabi-v7a") 426 | add_definitions(-DCORE_SO_NAME="${LIBRARY_NAME}") 427 | endif() 428 | ``` 429 | 430 | ​ 将`dobby`的源码引入后,就可以在项目中使用`dobby`进行`hook`处理了。修改`native-lib.cpp`文件,添加测试的`hook`代码,内容如下。 431 | 432 | ```c++ 433 | #include 434 | #include 435 | #include 436 | #include "dobby.h" 437 | 438 | #define LOG_TAG "native-lib" 439 | 440 | #define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG , LOG_TAG, __VA_ARGS__) 441 | 442 | int (*source_openat)(int fd, const char *path, int oflag, int mode) = nullptr; 443 | 444 | // 替换后的新函数 445 | int MyOpenAt(int fd, const char *pathname, int flags, int mode) { 446 | ALOGD("[ROM] MyOpenAt pathname :%s",pathname); 447 | if (strcmp(pathname, "/sbin/su") == 0 || strcmp(pathname, "/system/bin/su") == 0) { 448 | pathname = "/system/xbin/Mysu"; 449 | } 450 | // 执行原来的openat函数 451 | return source_openat(fd, pathname, flags, mode); 452 | } 453 | 454 | void HookOpenAt() { 455 | // 找到函数对应的地址 456 | void *__openat = 457 | DobbySymbolResolver("libc.so", "__openat"); 458 | 459 | if (__openat == nullptr) { 460 | ALOGD("__openat null "); 461 | return; 462 | } 463 | ALOGD("拿到 __openat 地址 "); 464 | //dobby hook 函数 465 | if (DobbyHook((void *) __openat, 466 | (dobby_dummy_func_t) MyOpenAt, 467 | (dobby_dummy_func_t*) &source_openat) == RT_SUCCESS) { 468 | ALOGD("DobbyHook __openat sucess"); 469 | } 470 | } 471 | 472 | jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { 473 | 474 | ALOGD("Hello JNI_OnLoad 开始加载"); 475 | JNIEnv *env = nullptr; 476 | //改变openat 指定函数 函数地址 替换成自己的 477 | HookOpenAt(); 478 | 479 | if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) == JNI_OK) { 480 | return JNI_VERSION_1_6; 481 | } 482 | return 0; 483 | } 484 | ``` 485 | 486 | ​ 样例应用准备完毕,将该样例编译并运行后,就能成功看到对`openat`进行`hook`的输出如下。 487 | 488 | ``` 489 | ... 490 | D [ROM] MyOpenAt pathname :/data/vendor/gpu/esx_config_cn.rom.devchangemodule.txt 491 | D [ROM] MyOpenAt pathname :/data/vendor/gpu/esx_config.txt 492 | D [ROM] MyOpenAt pathname :/data/misc/gpu/esx_config_cn.rom.devchangemodule.txt 493 | D [ROM] MyOpenAt pathname :/data/misc/gpu/esx_config.txt 494 | D [ROM] MyOpenAt pathname :/data/vendor/gpu/esx_config_cn.rom.devchangemodule.txt 495 | D [ROM] MyOpenAt pathname :/data/vendor/gpu/esx_config.txt 496 | D [ROM] MyOpenAt pathname :/data/misc/gpu/esx_config_cn.rom.devchangemodule.txt 497 | D [ROM] MyOpenAt pathname :/data/misc/gpu/esx_config.txt 498 | ... 499 | ``` 500 | 501 | ​ 接下来将该样例应用编译的`apk`文件进行解压,在`lib`目录中找到依赖的动态库,分别是`libdobby.so`和`libmydobby.so`,其中前者是`hook`框架的核心库,后者是刚刚对`openat`进行`hook`的业务代码。只需要在任何进程启动前,按顺序将依赖的核心动态库,和业务代码加载,即可完成集成的工作,`libdobby.so`可以选择集成到系统中,也可以选择跟业务代码动态库一起放同一个目录进行加载。下面看实现加载的代码。 502 | 503 | ```java 504 | 505 | private static void loadSoModule(String soName){ 506 | String soPath=""; 507 | if(System.getProperty("os.arch").indexOf("64") >= 0) { 508 | soPath = String.format("/data/data/cn.rom.dobbydemo/%s", soName); 509 | }else{ 510 | soPath = String.format("/data/data/cn.rom.dobbydemo/%s", soName); 511 | } 512 | File file = new File(soPath); 513 | if (file.exists()){ 514 | Log.e("[ROM]", "load so "+soPath); 515 | System.load(tmpPath); 516 | Log.e("[ROM]", "load over so "+soPath); 517 | }else{ 518 | Log.e("[ROM]", "load so "+soPath+" not exist"); 519 | } 520 | } 521 | 522 | private void handleBindApplication(AppBindData data) { 523 | ... 524 | app = data.info.makeApplication(data.restrictedBackupMode, null); 525 | // Propagate autofill compat state 526 | app.setAutofillOptions(data.autofillOptions); 527 | // Propagate Content Capture options 528 | app.setContentCaptureOptions(data.contentCaptureOptions); 529 | sendMessage(H.SET_CONTENT_CAPTURE_OPTIONS_CALLBACK, data.appInfo.packageName); 530 | mInitialApplication = app; 531 | // 非系统进程则注入jar包 532 | int flags = mBoundApplication == null ? 0 : mBoundApplication.appInfo.flags; 533 | if(flags>0&&((flags&ApplicationInfo.FLAG_SYSTEM)!=1)){ 534 | loadSoModule("libdobby.so"); 535 | loadSoModule("libmydobby.so"); 536 | } 537 | } 538 | ``` 539 | 540 | 这只是一个简单的加载样例演示。在安装目标应用后,还需要将两个动态库拷贝到相应的目录中。在实际运用场景中,我们尽量不要将动态库路径和要加载的库名称固定写入源代码中。最好通过配置的方式来管理这些需要加载的参数。 541 | 542 | 为了确保加载动态库成功,需要确保目录具有执行权限,并且最好将文件放在当前应用程序的私有目录中。 543 | 544 | 完成修改后,无论安装任何应用程序并打开它时,都会被hook `openat`函数。 545 | 546 | 547 | ## 9.5 本章小结 548 | 549 | 本章主要介绍了安卓系统上一些常见的Hook框架。以及讲解了如何修改系统代码,集成Hook框架到系统中。Hook框架由于其特性,检测与反检测技术也是安全领域时常讨论的话题,本书不深入讨论,有兴趣的朋友可以网络上搜索相应的文章与工具。这种方式最大的优点是,集成的自定义的框架具有较高的权限与隐蔽性,缺点也很明显,就是框架代码的升级比较麻烦,可能需要重新编译修改ROM与替换系统文件。 550 | 551 | -------------------------------------------------------------------------------- /code/chapter-10/ndksnoop/ndksnoop.bt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bpftrace 2 | /* 3 | * ndksnoop trace APK .so calls. 4 | * For Android, uses bpftrace and eBPF. 5 | * 6 | * Also a basic example of bpftrace. 7 | * 8 | * USAGE: ndksnoop.bt 9 | * 10 | * 11 | * Copyright 2023 fei_cong@hotmail.com (https://github.com/feicong/ebpf-course) 12 | * Licensed under the Apache License, Version 2.0 (the "License") 13 | * 14 | * 09-Apr-2023 fei_cong created first version for libc.so tracing. 15 | */ 16 | 17 | #ifndef BPFTRACE_HAVE_BTF 18 | #include 19 | #include 20 | #else 21 | #include 22 | #include 23 | #endif 24 | 25 | 26 | BEGIN 27 | { 28 | // # ls -an /data/data/io.github.vvb2060.mahoshojo 29 | if ($1 != 0) { 30 | @target_uid = (uint64)$1; 31 | } else { 32 | @target_uid = (uint64)2000; // 10241; 33 | } 34 | 35 | printf("Tracing android ndk so functions for uid %d. Hit Ctrl-C to end.\n", @target_uid); 36 | } 37 | 38 | /* 39 | int execl(const char *pathname, const char *arg, ... 40 | int execlp(const char *file, const char *arg, ... 41 | int execle(const char *pathname, const char *arg, ... 42 | int execv(const char *pathname, char *const argv[]); 43 | int execvp(const char *file, char *const argv[]); 44 | int execvpe(const char *file, char *const argv[], 45 | char *const envp[]); 46 | int execve(const char *pathname, char *const argv[], 47 | char *const envp[]); 48 | */ 49 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:execl /uid == @target_uid/ { 50 | printf("execl [%s %s]\n", str(arg0), str(arg1)); 51 | } 52 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:execlp /uid == @target_uid/ { 53 | printf("execlp [%s %s]\n", str(arg0), str(arg1)); 54 | } 55 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:execle /uid == @target_uid/ { 56 | printf("execle [%s %s]\n", str(arg0), str(arg1)); 57 | } 58 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:execv /uid == @target_uid/ { 59 | printf("execv [%s]\n", str(arg0)); 60 | join(arg1) 61 | } 62 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:execvp /uid == @target_uid/ { 63 | printf("execvp [%s]\n", str(arg0)); 64 | join(arg1) 65 | } 66 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:execvpe /uid == @target_uid/ { 67 | printf("execvpe [%s]\n", str(arg0)); 68 | join(arg1) 69 | } 70 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:execve /uid == @target_uid/ { 71 | printf("execve [%s]\n", str(arg0)); 72 | join(arg1) 73 | } 74 | 75 | // int mkdir(const char *pathname, mode_t mode); 76 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:mkdir /uid == @target_uid/ { 77 | printf("mkdir [%s, mode:%d]\n", str(arg0), arg1); 78 | } 79 | 80 | // DIR *opendir(const char *name); 81 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:opendir /uid == @target_uid/ { 82 | printf("opendir [%s]\n", str(arg0)); 83 | } 84 | 85 | // int rmdir(const char *pathname); 86 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:rmdir /uid == @target_uid/ { 87 | printf("rmdir [%s]\n", str(arg0)); 88 | } 89 | 90 | // FILE *popen(const char *command, const char *type); 91 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:popen /uid == @target_uid/ { 92 | printf("popen [%s %s]\n", str(arg0), str(arg1)); 93 | } 94 | 95 | // int access(const char *pathname, int mode); 96 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:access /uid == @target_uid/ { 97 | printf("access [%s]\n", str(arg0)); 98 | } 99 | 100 | // int faccessat(int dirfd, const char *pathname, int mode, int flags); 101 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:faccessat /uid == @target_uid/ { 102 | printf("faccessat [%s]\n", str(arg1)); 103 | } 104 | 105 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:open /uid == @target_uid/ { 106 | printf("open [%s]\n", str(arg0)); 107 | } 108 | 109 | // char *getenv(const char *name); 110 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:getenv /uid == @target_uid/ { 111 | printf("getenv [%s]\n", str(arg0)); 112 | } 113 | 114 | // int putenv(char *string); 115 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:putenv /uid == @target_uid/ { 116 | printf("putenv [%s]\n", str(arg0)); 117 | } 118 | 119 | // FILE *fopen64(const char *pathname, const char *mode); 120 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:fopen64 /uid == @target_uid/ { 121 | printf("fopen64 [%s %s]\n", str(arg0), str(arg1)); 122 | } 123 | 124 | // FILE *freopen64(const char *pathname, const char *mode, FILE *stream); 125 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:freopen64 /uid == @target_uid/ { 126 | printf("freopen64 [%s %s]\n", str(arg0), str(arg1)); 127 | } 128 | 129 | // int system(const char *command); 130 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:system /uid == @target_uid/ { 131 | printf("system [%s]\n", str(arg0)); 132 | } 133 | 134 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:__system_property_get /uid == @target_uid/ { 135 | @name[tid] = str(arg0); 136 | @val[tid] = arg1; 137 | } 138 | 139 | uretprobe:/apex/com.android.runtime/lib64/bionic/libc.so:__system_property_get /uid == @target_uid/ { 140 | if (sizeof(@name[tid]) > 0) { 141 | printf("getprop [%s:0x%x:%s], ret:%d\n", @name[tid], (int32)(@val[tid]), str(@val[tid]), retval); 142 | } 143 | 144 | delete(@name[tid]); 145 | delete(@val[tid]); 146 | } 147 | 148 | // __fastcall __system_property_foreach 149 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:__system_property_foreach /uid == @target_uid/ { 150 | printf("__system_property_foreach [%s]\n", ustack); 151 | } 152 | 153 | // const prop_info* __system_property_find(const char* __name); 154 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:__system_property_find /uid == @target_uid/ { 155 | printf("__system_property_find [%s]\n", str(arg0)); 156 | } 157 | 158 | // http://www.aospxref.com/android-13.0.0_r3/xref/bionic/libc/include/sys/system_properties.h 159 | // int __system_property_set(const char* __name, const char* __value); 160 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:__system_property_set /uid == @target_uid/ { 161 | printf("__system_property_set [%s %s]\n", str(arg0), str(arg1)); 162 | } 163 | 164 | // void abort(void); 165 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:abort /uid == @target_uid/ { 166 | printf("abort [%s]\n", ustack); 167 | } 168 | 169 | // int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 170 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:accept /uid == @target_uid/ { 171 | $address = (struct sockaddr *)arg1; 172 | if ($address->sa_family == AF_INET) { 173 | $sa = (struct sockaddr_in *)$address; 174 | $port = $sa->sin_port; 175 | $addr = ntop($address->sa_family, $sa->sin_addr.s_addr); 176 | printf("accept [%s %d %d]\n", $addr, bswap($port), $address->sa_family); 177 | } else { 178 | $sa6 = (struct sockaddr_in6 *)$address; 179 | $port = $sa6->sin6_port; 180 | $addr6 = ntop($address->sa_family, $sa6->sin6_addr.s6_addr); 181 | printf("accept [%s %d %d]\n", $addr6, bswap($port), $address->sa_family); 182 | } 183 | } 184 | 185 | // unsigned int alarm(unsigned int seconds); 186 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:alarm /uid == @target_uid/ { 187 | printf("alarm [%d]\n", arg0); 188 | } 189 | 190 | // struct hostent *gethostbyname(const char *name); 191 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:gethostbyname /uid == @target_uid/ { 192 | printf("gethostbyname [%s]\n", str(arg0)); 193 | } 194 | 195 | // struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type); 196 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:gethostbyaddr /uid == @target_uid/ { 197 | printf("gethostbyaddr [%s %d %d]\n", str(arg0), arg1, arg2); 198 | } 199 | 200 | // int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 201 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:bind /uid == @target_uid/ { 202 | $address = (struct sockaddr *)arg1; 203 | if ($address->sa_family == AF_INET) { 204 | $sa = (struct sockaddr_in *)$address; 205 | $port = $sa->sin_port; 206 | $addr = ntop($address->sa_family, $sa->sin_addr.s_addr); 207 | printf("bind [%s %d %d]\n", $addr, bswap($port), $address->sa_family); 208 | } else { 209 | $sa6 = (struct sockaddr_in6 *)$address; 210 | $port = $sa6->sin6_port; 211 | $addr6 = ntop($address->sa_family, $sa6->sin6_addr.s6_addr); 212 | printf("bind [%s %d %d]\n", $addr6, bswap($port), $address->sa_family); 213 | } 214 | } 215 | 216 | // int socket(int domain, int type, int protocol); 217 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:socket /uid == @target_uid/ { 218 | printf("socket [%d %d %d]\n", arg0, arg1, arg2); 219 | } 220 | 221 | // int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 222 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:connect /uid == @target_uid/ { 223 | $address = (struct sockaddr *)arg1; 224 | if ($address->sa_family == AF_INET) { 225 | $sa = (struct sockaddr_in *)$address; 226 | $port = $sa->sin_port; 227 | $addr = ntop($address->sa_family, $sa->sin_addr.s_addr); 228 | printf("connect [%s %d %d]\n", $addr, bswap($port), $address->sa_family); 229 | } else { 230 | $sa6 = (struct sockaddr_in6 *)$address; 231 | $port = $sa6->sin6_port; 232 | $addr6 = ntop($address->sa_family, $sa6->sin6_addr.s6_addr); 233 | printf("connect [%s %d %d]\n", $addr6, bswap($port), $address->sa_family); 234 | } 235 | } 236 | 237 | // int unlink(const char *pathname); 238 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:unlink /uid == @target_uid/ { 239 | printf("unlink [%s]\n", str(arg0)); 240 | } 241 | 242 | // int unlinkat(int dirfd, const char *pathname, int flags); 243 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:unlinkat /uid == @target_uid/ { 244 | printf("unlinkat [%d %s %d]\n", arg0, str(arg1), arg2); 245 | } 246 | 247 | // int sscanf(const char *str, const char *format, ...); 248 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:sscanf /uid == @target_uid/ { 249 | // printf("sscanf [%s %s]\n", str(arg0), str(arg1)); 250 | } 251 | 252 | // int scanf(const char *format, ...); 253 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:scanf /uid == @target_uid/ { 254 | // printf("scanf [%s]\n", str(arg0)); 255 | } 256 | 257 | // ssize_t getxattr(const char *path, const char *name, void *value, size_t size); 258 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:getxattr /uid == @target_uid/ { 259 | @path[tid] = str(arg0); 260 | @name[tid] = str(arg1); 261 | @val[tid] = arg2; 262 | @sz[tid] = arg3; 263 | } 264 | uretprobe:/apex/com.android.runtime/lib64/bionic/libc.so:getxattr /uid == @target_uid/ { 265 | printf("getxattr [%s %s %s %d]\n", @path[tid], @name[tid], str(@val[tid]), @sz[tid]); 266 | 267 | delete(@path[tid]); 268 | delete(@name[tid]); 269 | delete(@val[tid]); 270 | delete(@sz[tid]); 271 | } 272 | 273 | // ssize_t lgetxattr(const char *path, const char *name, void *value, size_t size); 274 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:lgetxattr /uid == @target_uid/ { 275 | @path[tid] = str(arg0); 276 | @name[tid] = str(arg1); 277 | @val[tid] = arg2; 278 | @sz[tid] = arg3; 279 | } 280 | uretprobe:/apex/com.android.runtime/lib64/bionic/libc.so:lgetxattr /uid == @target_uid/ { 281 | printf("lgetxattr [%s %s %s %d]\n", @path[tid], @name[tid], str(@val[tid]), @sz[tid]); 282 | 283 | delete(@path[tid]); 284 | delete(@name[tid]); 285 | delete(@val[tid]); 286 | delete(@sz[tid]); 287 | } 288 | 289 | // ssize_t fgetxattr(int fd, const char *name, void *value, size_t size); 290 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:fgetxattr /uid == @target_uid/ { 291 | @path_[tid] = arg0; 292 | @name[tid] = str(arg1); 293 | @val[tid] = arg2; 294 | @sz[tid] = arg3; 295 | } 296 | uretprobe:/apex/com.android.runtime/lib64/bionic/libc.so:fgetxattr /uid == @target_uid/ { 297 | printf("fgetxattr [%d %s %s %d]\n", @path_[tid], @name[tid], str(@val[tid]), @sz[tid]); 298 | 299 | delete(@path[tid]); 300 | delete(@name[tid]); 301 | delete(@val[tid]); 302 | delete(@sz[tid]); 303 | } 304 | 305 | // int chown(const char *pathname, uid_t owner, gid_t group); 306 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:chown /uid == @target_uid/ { 307 | printf("chown [%s %d %d]\n", str(arg0), arg1, arg2); 308 | } 309 | 310 | // int fchown(int fd, uid_t owner, gid_t group); 311 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:fchown /uid == @target_uid/ { 312 | printf("fchown [%d %d %d]\n", arg0, arg1, arg2); 313 | } 314 | 315 | // int lchown(const char *pathname, uid_t owner, gid_t group); 316 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:lchown /uid == @target_uid/ { 317 | printf("lchown [%s %d %d]\n", str(arg0), arg1, arg2); 318 | } 319 | 320 | // int fchownat(int dirfd, const char *pathname, uid_t owner, gid_t group, int flags); 321 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:lchown /uid == @target_uid/ { 322 | printf("lchown [%d %s %d %d]\n", arg0, str(arg1), arg2, arg3); 323 | } 324 | 325 | // int chmod(const char *pathname, mode_t mode); 326 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:chmod /uid == @target_uid/ { 327 | printf("chmod [%s %d]\n", str(arg0), arg1); 328 | } 329 | 330 | // int fchmod(int fd, mode_t mode); 331 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:fchmod /uid == @target_uid/ { 332 | printf("fchmod [%d %d]\n", arg0, arg1); 333 | } 334 | 335 | // int fchmodat(int dirfd, const char *pathname, mode_t mode, int flags); 336 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:fchmodat /uid == @target_uid/ { 337 | printf("fchmodat [%d %s %d %d]\n", arg0, str(arg1), arg2, arg3); 338 | } 339 | 340 | // int rename(const char *oldpath, const char *newpath); 341 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:rename /uid == @target_uid/ { 342 | printf("rename [%s %s]\n", str(arg0), str(arg1)); 343 | } 344 | 345 | // int renameat(int olddirfd, const char *oldpath, int newdirfd, const char *newpath); 346 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:renameat /uid == @target_uid/ { 347 | printf("renameat [%d %s %d %s]\n", arg0, str(arg1), arg2, str(arg3)); 348 | } 349 | 350 | // int renameat2(int olddirfd, const char *oldpath, int newdirfd, const char *newpath, unsigned int flags); 351 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:renameat2 /uid == @target_uid/ { 352 | printf("renameat2 [%d %s %d %s %d]\n", arg0, str(arg1), arg2, str(arg3), arg4); 353 | } 354 | 355 | // ssize_t readlink(const char *pathname, char *buf, size_t bufsiz); 356 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:readlink /uid == @target_uid/ { 357 | @pathname[tid] = str(arg0); 358 | @buf[tid] = arg1; 359 | @bufsiz[tid] = arg2; 360 | } 361 | uretprobe:/apex/com.android.runtime/lib64/bionic/libc.so:readlink /uid == @target_uid/ { 362 | printf("readlink [%s %s %d]\n", @pathname[tid], str(@buf[tid]), @bufsiz[tid]); 363 | 364 | delete(@pathname[tid]); 365 | delete(@buf[tid]); 366 | delete(@bufsiz[tid]); 367 | } 368 | 369 | // ssize_t readlinkat(int dirfd, const char *pathname, char *buf, size_t bufsiz); 370 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:readlinkat /uid == @target_uid/ { 371 | @dirfd[tid] = arg0; 372 | @pathname[tid] = str(arg1); 373 | @buf[tid] = arg2; 374 | @bufsiz[tid] = arg3; 375 | } 376 | uretprobe:/apex/com.android.runtime/lib64/bionic/libc.so:readlinkat /uid == @target_uid/ { 377 | printf("readlinkat [%d %s %s %d]\n", @dirfd[tid], @pathname[tid], str(@buf[tid]), @bufsiz[tid]); 378 | 379 | delete(@dirfd[tid]); 380 | delete(@pathname[tid]); 381 | delete(@buf[tid]); 382 | delete(@bufsiz[tid]); 383 | } 384 | 385 | // int scandir(const char *dirp, struct dirent ***namelist, int (*filter)(const struct dirent *), int (*compar)(const struct dirent **, const struct dirent **)); 386 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:scandir /uid == @target_uid/ { 387 | printf("scandir [%s]\n", str(arg0)); 388 | } 389 | 390 | // int scandirat(int dirfd, const char *dirp, struct dirent ***namelist, int (*filter)(const struct dirent *), int (*compar)(const struct dirent **, const struct dirent **)); 391 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:scandirat /uid == @target_uid/ { 392 | printf("scandirat [%d %s]\n", arg0, str(arg1)); 393 | } 394 | 395 | // int semget(key_t key, int nsems, int semflg); 396 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:semget /uid == @target_uid/ { 397 | printf("semget [%d %d %d]\n", arg0, arg1, arg2); 398 | } 399 | 400 | // const char *getprogname(void); 401 | uretprobe:/apex/com.android.runtime/lib64/bionic/libc.so:getprogname /uid == @target_uid/ { 402 | printf("getprogname [%s]\n", str(retval)); 403 | } 404 | 405 | // void setprogname(const char *progname); 406 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:setprogname /uid == @target_uid/ { 407 | printf("setprogname [%s]\n", str(arg0)); 408 | } 409 | 410 | // ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); 411 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:sendmsg /uid == @target_uid/ { 412 | printf("sendmsg [%d %d %d]\n", arg0, arg1, arg2); 413 | } 414 | 415 | // ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); 416 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:recvmsg /uid == @target_uid/ { 417 | printf("recvmsg [%d %d %d]\n", arg0, arg1, arg2); 418 | } 419 | 420 | // char *strstr(const char *haystack, const char *needle); 421 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:strstr /uid == @target_uid/ { 422 | printf("strstr [%s %s]\n", str(arg0), str(arg1)); 423 | } 424 | 425 | // char *strnstr(const char *haystack, const char *needle, size_t len); 426 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:strnstr /uid == @target_uid/ { 427 | printf("strnstr [%s %s %d]\n", str(arg0), str(arg1), arg2); 428 | } 429 | 430 | // int strcmp(const char *s1, const char *s2); 431 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:strcmp /uid == @target_uid/ { 432 | printf("strcmp [%s %s]\n", str(arg0), str(arg1)); 433 | } 434 | 435 | // int strncmp(const char *s1, const char *s2, size_t n); 436 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:strncmp /uid == @target_uid/ { 437 | printf("strncmp [%s %s %d]\n", str(arg0), str(arg1), arg2); 438 | } 439 | 440 | // size_t strlen(const char *s); 441 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:strlen /uid == @target_uid/ { 442 | printf("strlen [%s]\n", str(arg0)); 443 | } 444 | 445 | // size_t strnlen(const char *s, size_t maxlen); 446 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:strnlen /uid == @target_uid/ { 447 | printf("strnlen [%s %d]\n", str(arg0), arg1); 448 | } 449 | 450 | // char *strcat(char *restrict s1, const char *restrict s2); 451 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:strcat /uid == @target_uid/ { 452 | printf("strcat [%s %s]\n", str(arg0), str(arg1)); 453 | } 454 | 455 | // char *strncat(char *restrict s1, const char *restrict s2, size_t n); 456 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:strncat /uid == @target_uid/ { 457 | printf("strncat [%s %s %d]\n", str(arg0), str(arg1), arg2); 458 | } 459 | 460 | // char *stpncpy(char * dst, const char * src, size_t len); 461 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:stpncpy /uid == @target_uid/ { 462 | printf("stpncpy [%s %s %d]\n", str(arg0), str(arg1), arg2); 463 | } 464 | 465 | // int fstat(int fildes, struct stat *buf); 466 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:fstat /uid == @target_uid/ { 467 | printf("fstat [%d]\n", arg0); 468 | } 469 | 470 | // int lstat(const char *restrict path, struct stat *restrict buf); 471 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:lstat /uid == @target_uid/ { 472 | printf("lstat [%s]\n", str(arg0)); 473 | } 474 | 475 | // int stat(const char *restrict path, struct stat *restrict buf); 476 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:stat /uid == @target_uid/ { 477 | printf("stat [%s]\n", str(arg0)); 478 | } 479 | 480 | // int fstatat(int fd, const char *path, struct stat *buf, int flag); 481 | // uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:fstatat /uid == @target_uid/ { 482 | // printf("fstatat [%d %s]\n", arg0, str(arg1)); 483 | // } 484 | 485 | // int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg); 486 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:pthread_create /uid == @target_uid/ { 487 | printf("pthread_create [%x %s]\n", arg2, ustack); 488 | } 489 | /* 490 | // int memcmp(const void *s1, const void *s2, size_t n); 491 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:memcmp /uid == @target_uid/ { 492 | printf("memcmp [%r %r %d]\n", buf(arg0, arg2), buf(arg1, arg2), arg2); 493 | } 494 | */ 495 | // int __android_log_print(int prio, const char *tag, const char *fmt, ...); 496 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:__android_log_print /uid == @target_uid/ { 497 | printf("__android_log_print [%d %s %s]\n", arg0, str(arg1), str(arg2)); 498 | } 499 | 500 | // ssize_t getline(char ** restrict linep, size_t * restrict linecapp, FILE * restrict stream); 501 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:getline /uid == @target_uid/ { 502 | @line[tid] = *(int8 **)arg0; 503 | } 504 | uretprobe:/apex/com.android.runtime/lib64/bionic/libc.so:getline /uid == @target_uid/ { 505 | if ((int64)retval != -1) { 506 | printf("getline [%s]\n", str(@line[tid])); 507 | } 508 | 509 | delete(@line[tid]); 510 | } 511 | 512 | // void* dlopen(const char* path, int mode); 513 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:dlopen /uid == @target_uid/ { 514 | printf("dlopen [%s %d]\n", str(arg0), arg1); 515 | } 516 | 517 | // void* dlsym(void* handle, const char* symbol); 518 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:dlsym /uid == @target_uid/ { 519 | printf("dlsym [%p %s]\n", arg0, str(arg1)); 520 | } 521 | 522 | // 523 | 524 | -------------------------------------------------------------------------------- /chapter-10/README.md: -------------------------------------------------------------------------------- 1 | # 第十章 系统集成开发eBPF 2 | 3 | 安卓系统的安全攻防技术日新月异,如今正进入一个全新的高度。随着eBPF技术的崛起,国内外的安全专家们也积极探索eBPF技术在安全领域中的应用场景。 4 | 5 | 本章将为安卓系统开发与定制人员提供一种结合代码修改和eBPF可观测性的系统定制细路。这旨在为安全行业的发展提供一点启示和引导作用,希望能够激发更多创意和思考。 6 | 7 | 8 | ## 10.1 eBPF概述 9 | 10 | eBPF(扩展伯克利数据包过滤器)是一种现代化的Linux内核技术,它使开发人员能够对网络数据包进行更细粒度的过滤和修改。相较于传统的Berkeley Packet Filter (BPF),eBPF具有更高的灵活性、可扩展性和安全性,因此在广泛应用中受到了认可。 11 | 12 | 实际上,通过使用eBPF技术可以实现多项功能,例如网络流量监控、日志记录、流量优化以及安全审计等。这使得它在各个领域都具备广泛的应用前景。 13 | 14 | 15 | ### 10.1.1 eBPF发展背景 16 | 17 | 在2008年,Linux内核开发者提出了BPF(Berkeley Packet Filter)的概念,它是一种用于过滤和修改网络数据包的内核模块。BPF是一个非常强大的工具,它允许开发人员对网络数据包进行细粒度的过滤和修改,从而实现网络流量监控、日志记录、性能优化等功能。 18 | 19 | 然而,BPF也存在一些限制。首先,编写和编译BPF模块需要由内核开发人员手动完成,这对非专业开发人员来说可能是具有挑战性的任务。其次,在编写BPF模块时需要一定的技术知识和经验,否则可能会导致内核崩溃或其他问题。最后,并且很重要的是,由于BPF模块拥有高级别的访问权限,意味着它们可以访问系统中所有的内存和网络资源,在安全上需要进行严格控制。 20 | 21 | 为了解决这些问题,在2014年Linux内核引入了eBPF(扩展伯克利数据包过滤器)技术。eBPF是BPF的扩展版本,并提供更多功能以及更加灵活可扩展与安全性方面优势。相比传统 BFP技术,eBFP得到广泛应用与认可。 22 | 23 | eBPF最初的发展目标是实现高效网络数据包过滤。随着发展,除了扩展传统的数据包过滤字节码格式外,eBPF还支持在整个操作系统不同模块中运行多种类型的eBPF程序。最初提倡的可观测性领域也已扩展为支持对数据进行观测与修改(包括用户态数据和内核函数返回值)。这样的进展使得eBPF技术看起来更像一个现代化的 Hook 技术框架,因此受到安全从业人员青睐。 24 | 25 | 可以预见,在内核版本更新中,eBPF内置功能将会越来越丰富。作为eBPF能力核心部分,eBFP 内核方法接口也会变得更加多样化。基于这些接口所实现的安全功能必定会影响整个行业的发展。 26 | 27 | 28 | ### 10.1.2 eBPF的工作原理 29 | 30 | eBPF的工作原理可以概括为以下三个步骤: 31 | 32 | 1. **解析**:在系统启动时,内核会加载eBPF符号表到内存中。这个二进制文件包含了eBPF模块的所有符号和参数。同时,内核还会将eBPF模块的二进制代码转换为机器码,并加载到内存中。 33 | 34 | 2. **执行**:当网络数据包到达时,内核首先检查是否匹配到了与之关联的eBPF模块。如果匹配成功,内核会执行该模块中的代码来对网络数据包进行过滤或修改。在执行过程中,内核使用eBPF运行时数据结构体来传递参数和上下文信息。 35 | 36 | 3. **卸载**:当eBPF模块执行完毕后,内核会将其从内存中卸载并清除。此时,所有符号和参数都被还原成二进制码,并从内存中清除。 37 | 38 | 39 | ### 10.1.3 eBPF的应用场景 40 | 41 | eBPF是一种非常强大的技术,可以实现许多网络流量监控、日志记录和性能优化等功能。下面列举了一些常见的eBPF应用场景: 42 | 43 | 1. **日志记录**:eBPF可用于记录网络流量、系统调用和错误事件等信息,实现全面的系统监控和日志记录。在云原生安全领域,安全监控工具如`sysdig`和`falco`使用eBPF来监控系统调用。 44 | 45 | 2. **流量控制**:通过eBPF可以实现网络流量的控制,例如限制同一主机或端口的网络流量。防火墙工具如著名的基于eBPF扩展版本的`iptables`就是典型应用。 46 | 47 | 3. **流量优化**:使用eBPF可以对网络流量进行优化,例如过滤重复数据包、压缩数据包以及优化TCP/IP协议栈等。这方面应用包括开发透明代理工具、网络数据镜像转发工具和流量优化工具等。 48 | 49 | 4. **安全审计**:利用eBPF可以实现安全审计功能,如记录系统用户操作并检查资源使用情况。主机安全类防护产品(如HIDS)在这个领域有着广泛应用空间。一个例子是安全工具`Tracee`。 50 | 51 | 总的来说,eBPF技术在各个领域都有广泛应用,并且具备强大的灵活性和可扩展性。 52 | 53 | 54 | ## 10.2 eBPF相关的开发工具 55 | 56 | eBPF是一种现代化的Linux内核技术,允许开发者在内核中安全地运行外部程序,用于处理网络数据包、系统调用等场景。相较于传统的内核模块,eBPF具有更高的安全性和可移植性,因此得到了越来越广泛的应用。虽然eBPF运行在内核中,但控制它的程序却是运行在用户态。下面将介绍一些开发eBPF工具时常用的方法和库。 57 | 58 | **1. bcc**:bcc(BPF Compiler Collection)是一个基于LLVM编译器框架构建而成的工具集合。它提供了一组功能强大且易于使用的命令行工具和库来开发、测试和分析eBPF程序。通过bcc可以编写高级语言(如C/C++)来生成eBPF代码,并能够以安全方式注入到目标系统中。 59 | 60 | **2. bpftrace**:bpftrace是一个动态追踪工具,可以使用类似awk语法的脚本语言对系统进行实时监测与跟踪。它利用libbpf库解析并执行由用户定义的事件处理逻辑,并支持实时查看、过滤和聚合各种类型的跟踪数据。 61 | 62 | **3. libbpf**:libbpf是一个用户空间库,提供了与eBPF交互的API。它允许开发者在用户态编写和加载eBPF程序,并提供了一些辅助函数用于操作eBPF映射(maps)和事件处理。 63 | 64 | 这些工具/库为开发人员提供了丰富的资源来编写、测试和分析eBPF程序,从而更加便捷地利用eBPF技术解决各种问题。 65 | 66 | 67 | ### 10.2.1 bcc 68 | 69 | `bcc`是一款开源的eBPF快速开发工具,最初使用Python作为eBPF程序的开发语言。随着社区的发展,该工具支持了C语言开发eBPF程序。你可以在其仓库地址[https://github.com/iovisor/bcc](https://github.com/iovisor/bcc)找到该项目。 70 | 71 | 该仓库提供了一组用Python编写的eBPF工具集,位于tools目录下。这些工具涉及文件、进程、网络、延时、性能观测等多个应用场景,并且提供了C语言版本的eBPF工具集,位于libbpf-tools目录下。这些C语言实现版本是非常好的学习资料,适合初学者入门。 72 | 73 | 在项目README中列出了不同系统位置上运行eBPF所需环境的分布图,并介绍了tools目录下各个工具的用途。例如,要监控文件打开操作,可以执行以下命令: 74 | 75 | ``` 76 | $ sudo python3 tools/opensnoop.py 77 | ``` 78 | 79 | 这将启动opensnoop脚本,监控文件的打开操作并输出相关信息。 80 | 81 | 82 | 83 | ### 10.2.2 bpftrace 84 | 85 | `bpftrace`是一个用于记录和追踪系统方法调用的工具。它使用eBPF程序来处理网络数据包、系统调用、文件访问等场景。通过使用`bpftrace`,开发者可以快速验证要观测的函数是否支持使用eBPF实现。 86 | 87 | `bpftrace`是一个开源工具,你可以在其仓库地址[https://github.com/iovisor/bpftrace](https://github.com/iovisor/bpftrace)找到它。按照官方说明安装好该工具后,会提供一个名为 `bpftrace` 的主程序。这个主程序接受单选命令以及一个bt格式的脚本作为输入。在脚本中,你可以设置观测程序的入口和出口、参数传递等信息,非常方便。 88 | 89 | 需要注意的是,目前`bpftrace`只提供了观测功能,并没有提供数据修改功能。相比之下,在这一点上不如`bcc`和`libbpf`。 90 | 91 | 执行以下命令可观测所有文件打开操作: 92 | 93 | ``` 94 | $ sudo bpftrace -e 'tracepoint:syscalls:sys_enter_openat { printf("%s %s\n", comm, str(args->filename)); }' 95 | ``` 96 | 97 | 当运行此命令时,将会监控所有文件打开操作并输出进程名称与文件名。 98 | 99 | 100 | ### 10.2.3 libbpf 101 | 102 | `libbpf`是一个用于编写和运行eBPF程序的开源库。你可以在其仓库地址[https://github.com/libbpf/libbpf](https://github.com/libbpf/libbpf)找到它。该库提供了一组C接口函数,允许开发者使用C或Rust语言编写和运行eBPF程序。 103 | 104 | 除此之外,`libbpf`官方还单独提供了一些使用该库进行eBPF程序开发的示例代码,这些示例代码位于仓库地址[https://github.com/libbpf/libbpf-bootstrap](https://github.com/libbpf/libbp-bootstrap)下的examples/c目录中。这些示例代码与 `bcc`的`libbpf-tools`目录下的样例类似,前者更注重简洁性而后者则功能更加丰富。 105 | 106 | 总体来说,`bcc`、`bpftrace`和`libbpf`都是非常重要的工具,用于开发与eBPF相关的工具。它们提供了丰富的功能和工具集合,并为开发人员提供便利,在进行eBPF程序的开发、调试和追踪时都能够起到很大帮助。 107 | 108 | 109 | ## 10.3 安卓系统集成eBPF功能 110 | 111 | eBPF的功能实现和完善在优先支持x86_64架构上进行。对于其他系统架构如arm64等,会在后续逐步补充支持。大多数安卓手机设备采用的是arm64架构的处理器,因此与arm64版本的Linux发行版相比,eBPF在这些设备上的功能支持可能会稍有延迟,并且其程度也与arm64版本的Linux其他发行版保持一致。例如,在使用相同内核版本的Ubuntu系统和安卓系统中,它们对eBPF功能的支持基本是一致的。 112 | 113 | 需要注意的是,随着时间推移和开源社区不断努力改进,ARM体系结构上对eBPF功能的支持将不断提高,并逐渐接近x86_64架构所具备的水平。 114 | 115 | 116 | ### 10.3.1 不同版本内核对eBPF的影响 117 | 118 | 安卓系统的版本更新通常伴随着系统内核版本的升级。目前最新的安卓14采用了6.1版本的内核。在该版本中,默认的内核配置已经支持了一些常用功能,如`kprobes`、`uprobes`、`tracepoint`和`raw_tracepoint`,并且与同样是arm64架构下使用6.1版本内核的Ubuntu系统上eBPF功能保持一致。 119 | 120 | 然而,还有一些特性,如TRACING类型的eBPF程序:`fentry`, `fmod_ret`, `kfuncs`, `LSM`, `SYSCALL`, 和`tp_btf`等,在6.1的arm64内核中仍然不被支持。意味着在基于Ubuntu arm64架构上使用6.1内核时仍会遇到测试失败问题。这种问题在内核6.4 RC1版本的一个补丁合并中得到了修正,可以通过以下链接查看这个合并的详情:[https://github.com/torvalds/linux/commit/df45da57cbd35715d590a36a12968a94508ccd1f](https://github.com/torvalds/linux/commit/df45da57cbd35715d590a36a12968a94508ccd1f)。其中,有一个Tracing的名为**Support for "direct calls" in ftrace, which enables BPF tracing for arm64**的更新,并且这个补丁目前合并进入了安卓的主线内核中,这意味着,在不久最新版本的安卓系统中,不需要对内核做任何的补丁,就完美的支持eBPF开发与测试。安卓主线内核的更新日志可以通过链接[https://android.googlesource.com/kernel/common/+log/refs/heads/android-mainline](https://android.googlesource.com/kernel/common/+log/refs/heads/android-mainline)查看。 121 | 122 | 安卓12使用的内核是5.10,安卓13所采用的Linux内核是5.10与5.15。这两个版本对于上述提到的`uprobes`、`tracepoint`和`raw_tracepoint`功能提供了支持。然而,在对于 `kprobes` 的支持上有一些不足之处,只能说是部分支持。这是由于安卓的GKI 2.0引入了一些变化,导致无法成功启动像`CONFIG_DYNAMIC_FTRACE`这样的选项。具体细节将在下面关于内核配置注意事项的章节进行说明。 123 | 124 | 125 | ### 10.3.2 一些需要注意的内核配置 126 | 127 | 与安卓的eBPF相关的内核配置有以下几个: 128 | 129 | 1. `CONFIG_DYNAMIC_FTRACE`:如果内核开启了该选项,Ftrace框架内部的`mcount`函数会被实现为空函数(只包含一条`ret`指令)。在系统启动时,所有调用到的`mcount`都会被替换成无操作(nop)指令。当开启跟踪器后,所有函数入口处位置将动态替换为跳转至 `ftrace_caller()`的指令。这个选项是`fentry`内核配置中`CONFIG_FPROBE`的依赖项,因此可能导致`fentry`无法生效。 130 | 131 | 2. `CONFIG_FUNCTION_TRACER`: 开启该选项后,在每个函数入口处插入一个跳转指令(`bl mcount`)到`mcount()`函数中进行运行时追踪。在`mcount()`中会检查是否注册了函数指针 `ftrace_trace_function`, 默认情况下注册为空函数`ftrace_stub`。这是用于静态方法跟踪的Ftrace内核配置选项。同样地,这个选项也是"fentry"内核配置中CONFIG_FPROBE的依赖项,并且还提供一个名为available_filter_functions的文件来供用户配置Ftrace跟踪功能。如果未开启此选项,则由于缺少此功能而导致`bpftrace`在Kprobe功能函数列表时失败。 132 | 133 | 3. `CONFIG_FTRACE_SYSCALLS`: 这是几乎所有Ubuntu发行版本中默认开启的内核配置选项,但在安卓中默认关闭。此外,在Pixel6及以上设备上启用该选项,并同时启用Kprobe相关选项,可能会导致设备性能下降。该内核配置会在tracefs的events目录下增加一个syscalls子目录,以支持对所有系统调用进行单独的跟踪和观测。这是一个非常有用的内核配置选项。 134 | 135 | 关于其他与eBPF相关的内核配置对影响,可以参考`bcc`提供的一个内核配置说明文档:[https://github.com/iovisor/bcc/blob/master/docs/kernel_config.md](https://github.com/iovisor/bcc/blob/master/docs/kernel_config.md)。 136 | 137 | 138 | ### 10.3.3 为低版本系统打上eBPF补丁 139 | 140 | eBPF的强大功能很大一部分来源于其内核辅助方法。在这里不得不提两个功能强大的方法:`bpf_probe_read_user`与`bpf_probe_write_user`,这两个接口允许eBPF读取与写入内存地址指定的数据,它们拥有内核一样的能力,却有着比内核高得多的稳定性,功能不可谓不强大。 141 | 142 | 大多数eBPF程序都有观测函数方法的参数的需求,对于整形的参数,数据来源于其上下文的寄存器。直接读取其值便可以。涉及到字符串或结构体类型的数据,则需要使用 `bpf_probe_read_user`方法来读取。如果该方法在内核中功能欠缺,则会让eBPF程序无法实现其整体功能。然而,在arm64架构5.5版本之前的内核中就存在这种问题。由于arm64平台更新滞后,只在Linux主线内核5.5中引入了`bpf_probe_read_user`接口对arm64平台进行支持。具体补丁链接为:[https://github.com/torvalds/linux/commit/358fdb456288d48874d44a064a82bfb0d9963fa0](https://github.com/torvalds/linux/commit/358fdb456288d48874d44a064a82bfb0d9963fa0)。该补丁内容非常繁多,修改了17个文件,包括导出bpf.h头文件接口声明、添加bpf/core.c接口实现逻辑以及更新与内存相关的接口等,共计597处修改和197处删除。 143 | 144 | 要在安卓11内核5.4上使用`bpf_probe_read_user`接口,需要对内核代码进行向前移植(backport)操作。这一操作难度可控,在相应位置按照补丁中的代码进行添加和修改即可。更低版本如4.19和4.14则更加麻烦,主要因为主线内核大版本不同造成接口变化较大。版本5的内核在多线程同步下做了很多精细工作,而这些在内核4中是没有的,所以整个向前移植会变得更加困难。我自己尝试过将补丁应用于安卓10模拟器上的4.14内核和安卓11模拟器上的5.4内核,并使其正常运行。 145 | 146 | 针对5.4内核补丁已经有网络上讨论并提供了具体解决方案。有人发布了适用于安卓5.4内核的补丁代码,并提供了完成补丁后的分支代码。当然,大部分人关心如何利用修复后的结果而不是补丁的具体内容。因此,后者更受欢迎。这里提供一个已经修改好的方案链接:[https://github.com/HorseLuke/aosp_android_common_kernels/tree/android-11-5.4-bpf_probe_read_user](https://github.com/HorseLuke/aosp_android_common_kernels/tree/android-11-5.4-bpf_probe_read_user)。 147 | 148 | 编译内核采用官方的build.sh脚本。执行下面的命令,下载内核代码。 149 | 150 | ``` 151 | mkdir -p android-kernel && pushd android-kernel 152 | repo init -u https://android.googlesource.com/kernel/manifest -b common-android11-5.4 153 | echo Syncing code. 154 | repo sync -cj8 155 | ``` 156 | 157 | 下载完成后,做一个内核代码替换,执行下面的命令: 158 | 159 | ``` 160 | rm -rf common 161 | git clone https://github.com/feicong/aosp_android_common_kernels common 162 | cd common 163 | git checkout android-11-5.4-bpf_probe_read_user 164 | ``` 165 | 166 | 最后,执行下面的命令编译生成内核。 167 | 168 | ``` 169 | BUILD_CONFIG=common-modules/virtual-device/build.config.goldfish.aarch64 SKIP_MRPROPER=1 CC=clang build/build.sh -j12 170 | ``` 171 | 172 | 如果读者不关心内核与编译,可以到这里下载编译好的内核文件。https://github.com/feicong/ebpf-course/releases/tag/latest。比如,安卓模拟器5.4内核,其名字为android-arm64-common-5.4-kernelgz开头的zip文件,解压密码:qq121212。下载后,将其放到模拟器镜像目录下,替换kernel文件即可。 173 | 174 | 175 | ## 10.4 测试eBPF功能 176 | 177 | 安卓设备环境准备好后,需要`bcc`与`bpftrace`等工具来测试eBPF功能。这里使用的工具名叫ExtendedAndroidTools。将下载的bpftools推送到设备上。执行如下命令。 178 | 179 | ``` 180 | $ adb push bpftools /data/local/tmp/ 181 | ``` 182 | 183 | 执行`bcc`工具集需要管理员权限,执行如下命令获取root shell权限。 184 | 185 | ``` 186 | $ adb root 187 | ``` 188 | 189 | `bcc`工具集支持主流x86_64处理器的Linux系统。而对安卓系统的支持是有限的。主要的原因是常用的工具集使用的系统调用hook点,有可能在安卓系统上不存在。在执行命令过程中,如果出现错误,需要具体的问题具体分析,找出相应的解决方法。 190 | 191 | 打开一个adb shell,然后执行如下命令,开启文件打开监控。注意,所有的工具位于share/bcc/tools/目录下。 192 | 193 | ``` 194 | $ adb shell 195 | # cd /data/local/tmp/bpftools 196 | # ./python3 share/bcc/tools/opensnoop 197 | ``` 198 | 199 | 如果不出意外,会有打开的文件列表输出。有一些工具会用到debugfs路径,在执行命令前需要执行如下命令,加载debugfs。 200 | 201 | ``` 202 | mount -t debugfs debugfs /sys/kernel/debug 203 | ``` 204 | 205 | 有一些工具内容输出采用的Ftrace提供的tracing接口-bpf_trace_printk。这个时候,需要先打开Ftrace的日志输出开关。执行如下命令即可。 206 | 207 | ``` 208 | # echo 1 > /sys/kernel/tracing/tracing_on 209 | ``` 210 | 211 | 后面,想要监控输出的内容,可以执行下面的命令。 212 | 213 | ``` 214 | # cat /sys/kernel/tracing/trace_pipe 215 | ``` 216 | 217 | 接下来,测试一下`bpftrace`工具的使用效果。`bpftrace`工具位于share/bpftrace/tools/目录下。执行方法与`bcc`一样。如尝试执行如下命令,监控命令执行操作。 218 | 219 | ``` 220 | $ adb shell 221 | # cd /data/local/tmp/bpftools 222 | # ./bpftrace share/bpftrace/tools/execsnoop.bt 223 | share/bpftrace/tools/execsnoop.bt:21-23: ERROR: tracepoints not found: syscalls:sys_enter_exec* 224 | ``` 225 | 226 | 从上面的输出可以看到,在内核没有开启`CONFIG_FTRACE_SYSCALLS`的情况下,是没有“tracepoint/syscalls”这个类别的,而execsnoop.bt使用这个跟踪点就会报错。解决这个问题有两种方法: 227 | 228 | 1. 将tracepoint更改为kprobe,然后调整参数名字与输出。 229 | 230 | 2. 为内核开启`CONFIG_FTRACE_SYSCALLS`,如果设备不支持开启,可以考虑更新开发板或模拟器环境。 231 | 232 | 执行如下命令可以监控设备的TCP网络连接。 233 | 234 | ``` 235 | # ./bpftrace share/bpftrace/tools/tcpconnect.bt 236 | Attaching 2 probes... 237 | Tracing tcp connections. Hit Ctrl-C to end. 238 | TIME PID COMM SADDR SPORT DADDR DPORT 239 | ``` 240 | 241 | 更多工具的使用与用法见`bpftrace`官方的说明文档。仓库地址是:https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md。 242 | 243 | 244 | ## 10.5 eBPF实现安卓App动态库调用跟踪 245 | 246 | 本小节讲解如何使用eBPF开发一个完整的功能的跟踪工具。该工具名为`ndksnoop`。是笔者使用`bpftrace`实现的安卓NDK中常见的so动态库接口的跟踪工具。 247 | 248 | 整个工具分为三部分组成。头文件申明、BEGIN初始化块、Hook函数体。下面,分别进行讲解。 249 | 250 | 251 | ### 10.5.1 头文件的引用 252 | 253 | 新版本的`bpftrace`使用BTF来确定要处理的方法的参数类型、返回值与结构体类型。在没有开启支持BTF的环境中运行的话,或者Hook的第三方库没有BTF文件,只有头文件。这时,需要将使用到的类型信息通过头文件的方式引入到.bt脚本的开头。如下所示。 254 | 255 | ``` 256 | #!/usr/bin/env bpftrace 257 | /* 258 | * ndksnoop trace APK .so calls. 259 | * For Android, uses bpftrace and eBPF. 260 | * 261 | * Also a basic example of bpftrace. 262 | * 263 | * USAGE: ndksnoop.bt 264 | * 265 | * 266 | * Copyright 2023 fei_cong@hotmail.com 267 | * Licensed under the Apache License, Version 2.0 (the "License") 268 | * 269 | * 09-Apr-2023 fei_cong created first version for libc.so tracing. 270 | */ 271 | 272 | #ifndef BPFTRACE_HAVE_BTF 273 | #include 274 | #include 275 | #else 276 | #include 277 | #endif 278 | ``` 279 | 280 | 最开始的部分,是.bt文件的用途与版本说明信息。说明脚本开发的目的、时间、作者、功能等。 281 | 然后,根据`BPFTRACE_HAVE_BTF`宏判断是否支持BTF来引入不同的头文件。这里引入的是与网络相头的socket结构体相头的申明,里面涉及到的Hook点,将在下在小节进行讲解。 282 | 283 | 在这里,除了使用`#include`引入头文件,还可以像C语言那样直接申明类型。如`typedef`、`#define`、`struct xxx{}`等。 284 | 285 | ### 10.5.2 传入参数的处理 286 | 287 | 有时候脚本需要使用传入参数来指定变化的参数信息。例如、`ndksnoop`需要支持对不同的安卓App进行过滤,这里使用到的过滤参数是App相关的`uid`。 288 | 289 | 安卓App在安装时,会被赋予一个不变的`uid`数值。可以对这个值进行过滤,来Hook指定的App。比如`com.android.settings`也就是设置应用,它的`uid`为1000,`shell`用户的`uid`为2000。想要查看一个App的`uid`。可以在adb shell下执行如下命令。 290 | 291 | ``` 292 | # ls -an /data/data/com.android.systemui 293 | total 36 294 | drwx------ 4 10095 10095 4096 2023-02-03 17:47 . 295 | drwxrwx--x 139 1000 1000 8192 2023-03-16 09:32 .. 296 | drwxrws--x 2 10095 20095 4096 2023-02-03 17:47 cache 297 | drwxrws--x 2 10095 20095 4096 2023-02-03 17:47 code_cache 298 | ``` 299 | 300 | `ls`命令的`-n`参数,会列出目录的`uid`信息。上面的命令列出的是systemui包的`uid`信息。对于的`cache`与`code_cache`目前行可以看出,第2列的`uid`值为10095。 301 | 302 | `bpftrace`支持解析传入参数,以`$1`、`$2`、`$N`来命名。只传入一个`uid`,则执行如下命令传入的参数在脚本中`$1`的值为10095。 303 | 304 | ``` 305 | # ./bpftrace ndksnoop.bt 10095 306 | ``` 307 | 308 | `BEGIN`块是.bt脚本的初始化部分,可以用于对传入参数进行处理。如下所示。 309 | 310 | ``` 311 | BEGIN 312 | { 313 | // # ls -an /data/data/io.github.vvb2060.mahoshojo 314 | if ($1 != 0) { 315 | @target_uid = (uint64)$1; 316 | } else { 317 | @target_uid = (uint64)10095; 318 | } 319 | 320 | printf("Tracing android ndk so functions for uid %d. Hit Ctrl-C to end.\n", @target_uid); 321 | } 322 | ``` 323 | 324 | 脚本的`$1`传给了@target_uid变量,前面的`@`表示这是一个全局变量,临时变量使用`$`。 325 | 当脚本没有传入参数时,`$1`的值为0,这个时候,可以给它一个默认的值10095,或者其它感兴趣的App的`uid`。 326 | 327 | 最后,使用`printf`方法打印输出一行调试信息。 328 | 329 | 330 | ### 10.5.3 Hook方法的实现 331 | 332 | Hook用户态的程序与动态库,使用`uprobe`与`uretprobe`来实现。 333 | `uprobe`负责处理方法执行前的上下文信息,`uretprobe`用于处理方法执行完返回时的返回值信息,通常一些输出的字符串与缓冲区信息也在这里进行处理。 334 | 335 | 以`libc.so`动态库的`mkdir`方法为例。它的Hook逻辑实现如下: 336 | 337 | ``` 338 | // int mkdir(const char *pathname, mode_t mode); 339 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:mkdir /uid == @target_uid/ { 340 | printf("mkdir [%s, mode:%d]\n", str(arg0), arg1); 341 | } 342 | ``` 343 | 344 | `//`是注释,语法与C语言一样。主要是方便理解与阅读。 345 | 346 | `uprobe`关键字指定进行`uprobe`类型的Hook。后面跟上库名或完整的库路径。在安卓系统上,`bpftrace`无法找到安卓apex目录下的动态库,因此,需要手动输入完整的路径。 347 | 348 | `//`是过滤器,中间的内容`uid == @target_uid`为过滤表达式,表明,只有当表达满足时,才执行方法体内容。这里的表达式含义是:只Hook当前执行时`uid`为`@target_uid`的方法调用。`uid`关键字是`bpftrace` 的保留字,由`bpftrace`程序替换表示当前执行时的程序的`uid`。而`@target_uid`则上上面初始化部分设置好的目标`uid`,这样就完成了过滤操作。 349 | 350 | `uprobe`的参数为`arg0`-`arg5`。取参数很简单,整形直接赋值就可以了!字符串类型使用`str()`来读取。字节数组使用`buf()`来读取。更多的方法参考`bpftrace`文档。 351 | 352 | 代码部分只有两行!就完成了一个方法的跟踪与参数值输出,实在是太方便了。 353 | 354 | ### 10.5.4 特殊参数与字段的处理 355 | 356 | 有一些参数,它们传入时没有传,只有在方法执行返回时才设置内容。对于这些方法,可以使用`uprobe`传入时保存指针,`uretprobe`执行时解析。如下所示,是`__system_property_get()`方法的Hook代码。 357 | 358 | ``` 359 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:__system_property_get /uid == @target_uid/ { 360 | @name[tid] = str(arg0); 361 | @val[tid] = arg1; 362 | } 363 | 364 | uretprobe:/apex/com.android.runtime/lib64/bionic/libc.so:__system_property_get /uid == @target_uid/ { 365 | if (sizeof(@name[tid]) > 0) { 366 | printf("getprop [%s:0x%x:%s], ret:%d\n", @name[tid], (int32)(@val[tid]), str(@val[tid]), retval); 367 | } 368 | 369 | delete(@name[tid]); 370 | delete(@val[tid]); 371 | } 372 | ``` 373 | 374 | `__system_property_get()`用于读取属性系统的值。传入的第一个参数为字符串类型的key,第二个参数为返回的内容。在`uprobe`中,使用`str()`读取了key的内容。而`arg1`存放的值,只保存了它的指针。在`uretprobe`中会对其进行`str()`内容读取。注意,最后需要调用`delete()`来删除这两个变量,因为它们是与`tid`相关的线程变量,执行后不删除,会让内存消耗越来越多,直到程序崩溃。 375 | 376 | 还有一类是比较复杂的结构体。比如`connect()`方法的第二个参数`struct sockaddr`,想要从这个参数中取得IP地址。可以使用如下方法。 377 | 378 | ``` 379 | 380 | // int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 381 | uprobe:/apex/com.android.runtime/lib64/bionic/libc.so:connect /uid == @target_uid/ { 382 | $address = (struct sockaddr *)arg1; 383 | if ($address->sa_family == AF_INET) { 384 | $sa = (struct sockaddr_in *)$address; 385 | $port = $sa->sin_port; 386 | $addr = ntop($address->sa_family, $sa->sin_addr.s_addr); 387 | printf("connect [%s %d %d]\n", $addr, bswap($port), $address->sa_family); 388 | } else { 389 | $sa6 = (struct sockaddr_in6 *)$address; 390 | $port = $sa6->sin6_port; 391 | $addr6 = ntop($address->sa_family, $sa6->sin6_addr.s6_addr); 392 | printf("connect [%s %d %d]\n", $addr6, bswap($port), $address->sa_family); 393 | } 394 | } 395 | ``` 396 | 397 | 这是一种类C语言的语法,通过结构体指针强转的方式,来处理结构体中的字段信息。将字节数组的内容转换成IP地址,使用`ntop()`方法,而网络字节序的转换,使用`bswap()`方法。 398 | 399 | 400 | ### 10.5.5 效果展示 401 | 402 | 执行对`uid`为1000的`libc.so`方法调用跟踪。效果如下所示。 403 | 404 | ``` 405 | emulator64_arm64:/data/local/tmp/bpftools # ./bpftrace ./ndksnoop.bt 1000 406 | WARNING: Cannot parse DWARF: libdw not available 407 | Attaching 64 probes... 408 | Tracing android ndk so functions for uid 1000. Hit Ctrl-C to end. 409 | __system_property_find [net.qtaguid_enabled] 410 | getenv [ANDROID_NO_USE_FWMARK_CLIENT] 411 | getenv [ANDROID_NO_USE_FWMARK_CLIENT] 412 | __system_property_find [persist.log.tag.android.hardware.vibrator-service.example] 413 | __system_property_find [log.tag.android.hardware.vibrator-service.example] 414 | __system_property_find [persist.log.tag] 415 | __system_property_find [log.tag] 416 | __system_property_find [debug.renderengine.capture_skia_ms] 417 | __system_property_find [debug.renderengine.capture_skia_ms] 418 | __system_property_find [debug.renderengine.capture_skia_ms] 419 | __system_property_find [persist.log.tag.AutofillManagerService] 420 | __system_property_find [log.tag.AutofillManagerService] 421 | __system_property_find [persist.log.tag.ActivityTaskManager] 422 | __system_property_find [log.tag.ActivityTaskManager] 423 | __system_property_find [debug.force_rtl] 424 | __system_property_find [debug.force_rtl] 425 | open [/proc/uid_procstat/set] 426 | opendir [/proc/1041/task] 427 | open [/proc/1041/timerslack_ns] 428 | open [/proc/1048/timerslack_ns] 429 | open [/proc/1050/timerslack_ns] 430 | open [/proc/1054/timerslack_ns] 431 | open [/proc/1056/timerslack_ns] 432 | open [/proc/1057/timerslack_ns] 433 | open [/proc/1058/timerslack_ns] 434 | open [/proc/1059/timerslack_ns] 435 | open [/proc/1060/timerslack_ns] 436 | open [/proc/1061/timerslack_ns] 437 | open [/proc/1063/timerslack_ns] 438 | open [/proc/1064/timerslack_ns] 439 | open [/proc/1066/timerslack_ns] 440 | open [/proc/1078/timerslack_ns] 441 | open [/proc/1079/timerslack_ns] 442 | open [/proc/1107/timerslack_ns] 443 | open [/proc/1225/timerslack_ns] 444 | open [/proc/1241/timerslack_ns] 445 | open [/proc/1282/timerslack_ns] 446 | open [/proc/1341/timerslack_ns] 447 | open [/proc/1361/timerslack_ns] 448 | open [/proc/1372/timerslack_ns] 449 | open [/proc/1374/timerslack_ns] 450 | __system_property_find [debug.renderengine.capture_skia_ms] 451 | open [/proc/1375/timerslack_ns] 452 | open [/proc/1378/timerslack_ns] 453 | open [/proc/1490/timerslack_ns] 454 | open [/proc/1817/timerslack_ns] 455 | open [/proc/2226/timerslack_ns] 456 | open [/proc/2227/timerslack_ns] 457 | open [/proc/2250/timerslack_ns] 458 | open [/proc/2521/timerslack_ns] 459 | open [/proc/6029/timerslack_ns] 460 | opendir [/proc/889/task] 461 | open [/proc/889/timerslack_ns] 462 | open [/proc/902/timerslack_ns] 463 | open [/proc/911/timerslack_ns] 464 | open [/proc/913/timerslack_ns] 465 | ...... 466 | open [/proc/6256/timerslack_ns] 467 | open [/proc/6258/timerslack_ns] 468 | open [/proc/6260/timerslack_ns] 469 | __system_property_find [persist.log.tag.AutofillManagerService] 470 | __system_property_find [log.tag.AutofillManagerService] 471 | __system_property_find [persist.log.tag.BpBinder] 472 | __system_property_find [log.tag.BpBinder] 473 | __system_property_find [persist.log.tag] 474 | __system_property_find [log.tag] 475 | __system_property_find [debug.renderengine.capture_skia_ms] 476 | __system_property_find [persist.log.tag.goldfish-address-space] 477 | __system_property_find [log.tag.goldfish-address-space] 478 | __system_property_find [persist.log.tag] 479 | __system_property_find [log.tag] 480 | __system_property_find [debug.renderengine.capture_skia_ms] 481 | __system_property_find [debug.renderengine.capture_skia_ms] 482 | getenv [ART_APEX_DATA] 483 | getenv [ANDROID_DATA] 484 | getenv [ANDROID_DATA] 485 | faccessat [/system_ext/priv-app/Launcher3QuickStep] 486 | getenv [ART_APEX_DATA] 487 | getenv [ART_APEX_DATA] 488 | open [/system_ext/priv-app/Launcher3QuickStep/oat/arm64/Launcher3Quic] 489 | open [/system_ext/priv-app/Launcher3QuickStep/oat/arm64/Launcher3Quic] 490 | open [/system_ext/priv-app/Launcher3QuickStep/Launcher3QuickStep.apk] 491 | readlink [/proc/self/fd/426 /system_ext/priv-app/Launcher3QuickStep/Launcher3QuickStep.apk 0] 492 | readlink [/proc/self/fd/380 /system_ext/priv-app/Launcher3QuickStep/Launcher3QuickStep.apk 0] 493 | open [/system_ext/priv-app/Launcher3QuickStep/Launcher3QuickStep.apk] 494 | faccessat [/data/misc/iorapd/com.android.launcher3/31/com.android.launcher] 495 | faccessat [/proc/1041/stat] 496 | open [/proc/1041/stat] 497 | __system_property_find [debug.renderengine.capture_skia_ms] 498 | __system_property_find [debug.renderengine.capture_skia_ms] 499 | __system_property_find [debug.renderengine.capture_skia_ms] 500 | ...... 501 | __system_property_find [debug.renderengine.capture_skia_ms] 502 | __system_property_find [debug.renderengine.capture_skia_ms] 503 | faccessat [/data/system_ce/0/snapshots] 504 | faccessat [/data/system_ce/0/snapshots/135.proto.bak] 505 | open [/data/system_ce/0/snapshots/135.proto.new] 506 | __system_property_find [debug.renderengine.capture_skia_ms] 507 | __system_property_find [debug.renderengine.capture_skia_ms] 508 | open [/data/system_ce/0/snapshots/135.jpg] 509 | __system_property_find [debug.renderengine.capture_skia_ms] 510 | open [/data/system_ce/0/snapshots/135_reduced.jpg] 511 | __system_property_find [debug.renderengine.capture_skia_ms] 512 | __system_property_find [debug.renderengine.capture_skia_ms] 513 | ^C 514 | 515 | @target_uid: 1000 516 | 517 | emulator64_arm64:/data/local/tmp/bpftools # 518 | ``` 519 | 520 | 目前,Hook监控了`libc.so`共计64个接口方法。后面,可以扩展`ndksnoop`,实现对其它方法与其它库的方法跟踪。这种方式Hook最大的好处是输出内容中,没有多余的信息,所有的输出都是目标进程的行为捕获。缺点也是有的,那就是无法捕获直接使用系统调用方式执行的方法。 521 | 522 | 523 | ## 10.6 小结 524 | 525 | 本节主要介绍了eBPF相关的信息,以及在安卓系统上配置eBPF开发与运行环境的方法。最后,通过`ndksnoop`工具的代码示例,详细讲解了如何跟踪分析安卓系统中的动态库调用。 526 | 527 | 在学习系统定制与软件安全过程中,每个工具和技术方案都有其优势和限制。因此,在实际应用中,我们应根据具体情况结合不同方案,并充分利用各自长处、避免短板,以达到最终目标。 528 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------