内容纲要

搭建相关

SDK(Software Development Kit)

https://developer.android.com/studio

studio使用

image-20210626010451132

image-20210626011458657

image-20210626011709239

image-20210626012203024

常用linux shell命令

#列出文件目录
ls /dir
#输出文件内容
cat ./file
#搜索匹配文本内容
grep -r "xxx" ./dir       #在dir目录下搜索所有包含xxx字符串的文件并列出
grep -r "xxx" ./dir -A 10     #输出前10条
grep -r "xxx" ./ | grep "aaa"   #当前目录下同时包含xxx和aaa的文件
#创建多级目录 -p
mkdir -p x/xx
#export导出环境变量
export AS="……"
AS x.s -o x.o

Windows+Docker编译Android源码

下载Kitematic:https://github.com/docker/kitematic/releases

搜索aosp,第一个点击CREATE

image-20210626021215296

image-20210626022538099

adb

adb start-server    /*启动*/
adb kill-server     /*关闭*/
adb devices         /*查看设备*/
/*假设我们要安装一个ebook.apk文件,可以使用如下的命令。*/
adb install ebook.apk
/*假设ebook.apk中的package是net.blogjava.mobile.ebook,可以使用如下的命令卸载这个应用程序。*/
adb uninstall net.blogjava.mobile.ebook
/*重新安装。*/
adb install -r ebook.apk
/*加上-k命令行参数保留数据和缓冲目录,只卸载应用程序。*/
adb uninstall -k net.blogjava.mobile.ebook

image-20210707105434957

https://blog.csdn.net/shengerjianku/article/details/54927530

简述Android知识相关

Android系统架构

系统静态架构图

image-20210703162746552

系统启动架构图

image-20210703163155126

图解:Android系统启动过程由上图从下往上的一个过程是由Boot Loader引导开机,然后依次进入 -> Kernel -> Native -> Framework -> App

Loader层

Boot ROM: 当手机处于关机状态时,长按Power键开机,引导芯片开始从固化在 ROM里的预设代码开始执行,然后加载引导程序到 RAM;

Boot Loader:这是启动Android系统之前的引导程序,主要是检查RAM,初始化硬件参数等功能。

Linux内核层

Android平台的基础是Linux内核,比如ART虚拟机最终调用底层Linux内核来执行功能。Linux内核的安全机制为Android提供相应的保障,也允许设备制造商为内核开发硬件驱动程序。

启动Kernel的swapper进程(pid=0):该进程又称为idle进程, 系统初始化过程Kernel由无到有开创的第一个进程, 用于初始化进程管理、内存管理,加载Display,Camera Driver,Binder Driver等相关工作

启动kthreadd进程(pid=2):是Linux系统的内核进程,会创建内核工作线程kworkder,软中断线程ksoftirqd,thermal等内核守护进程。 kthreadd进程是所有内核进程的鼻祖。

硬件抽象层(HAL)

硬件抽象层 (HAL) 提供标准接口,HAL包含多个库模块,其中每个模块都为特定类型的硬件组件实现一组接口,比如WIFI/蓝牙模块,当框架API请求访问设备硬件时,Android系统将为该硬件加载相应的库模块。

Android Runtime&系统库

每个应用都在其自己的进程中运行,都有自己的虚拟机实例。ART通过执行DEX文件可在设备运行多个虚拟机,DEX文件是一种专为Android设计的字节码格式文件,经过优化,使用内存很少。ART主要功能包括:预先(AOT)和即时(JIT)编译,优化的垃圾回收(GC),以及调试相关的支持。

这里的Native系统库主要包括init孵化来的用户空间的守护进程、HAL层以及开机动画等。启动init进程(pid=1),是Linux系统的用户进程, init进程是所有用户进程的鼻祖

init进程会孵化出ueventd、logd、healthd、installd、adbd、lmkd等用户守护进程

init进程还启动 servicemanager(binder服务管家)、 bootanim(开机动画)等重要服务

init进程孵化出Zygote进程,Zygote进程是Android系统的第一个Java进程(即虚拟机进程), Zygote是所有Java进程的父进程,Zygote进程本身是由init进程孵化而来的。

Framework层

Zygote进程,是由init进程通过解析init.rc文件后fork生成的,Zygote进程主要包含:

  • 加载ZygoteInit类,注册Zygote Socket服务端套接字
  • 加载虚拟机
  • 提前加载类preloadClasses
  • 提前加载资源preloadResouces
  • System Server进程,是由Zygote进程fork而来, SystemServer是Zygote孵化的第一个进程,System Server负责启动和管理整个Java framework,包含ActivityManager,WindowManager,PackageManager,PowerManager等服务。
  • Media Server进程,是由init进程fork而来,负责启动和管理整个C++framework,包含AudioFlinger,Camera Service等服务。

App层

Zygote进程孵化出的第一个App进程是Launcher,这是用户看到的桌面App

Zygote进程还会创建Browser,Phone,Email等App进程,每个App至少运行在一个进程上。

所有的App进程都是由Zygote进程fork生成的

Syscall && JNI

  • Native与Kernel之间有一层系统调用(SysCall)层(Linux系统调用(Syscall)原理);
  • Java层与Native(C/C++)层之间的纽带JNI。

通信方式

无论是Android系统,还是各种Linux衍生系统,各个组件、模块往往运行在各种不同的进程和线程内,这里就必然涉及进程/线程之间的通信。对于IPC(Inter-Process Communication, 进程间通信),Linux现有管道、消息队列、共享内存、套接字、信号量、信号这些IPC机制,Android额外还有Binder IPC机制Android OS中的Zygote进程的IPC采用的是Socket机制在上层system server、media server以及上层App之间更多的是采用Binder IPC方式来完成跨进程间的通信。对于Android上层架构中,很多时候是在同一个进程的线程之间需要相互通信,例如同一个进程的主线程与工作线程之间的通信,往往采用的Handler消息机制

想深入理解Android内核层架构,必须先深入理解Linux现有的IPC机制;对于Android上层架构,则最常用的通信方式是Binder、Socket、Handler,当然也有少量其他的IPC方式,比如杀进程Process.killProcess()采用的是signal方式。

image-20210703165302628

关于linuxIPC和Binder采用原因详情:https://www.zhihu.com/question/39440766/answer/89210950

Binder

image-20210703165419620

Socket

Socket通信方式也是C/S架构,比Binder简单很多。在Android系统中采用Socket通信方式的主要有:

  • zygote:用于孵化进程,system_server创建进程是通过socket向zygote进程发起请求;
  • installd:用于安装App的守护进程,上层PackageManagerService很多实现最终都是交给它来完成;
  • lmkd:lowmemorykiller的守护进程,Java层的LowMemoryKiller最终都是由lmkd来完成;
  • adbd:这个也不用说,用于服务adb;
  • logcatd:这个不用说,用于服务logcat;
  • vold:即volume Daemon,是存储类的守护进程,用于负责如USB、Sdcard等存储设备的事件处理。

等等。

Socket方式更多的用于Android framework层与native层之间的通信。

Handler

Binder/Socket用于进程间通信,而Handler消息机制用于同进程的线程间通信,Handler消息机制是由一组MessageQueue、Message、Looper、Handler共同组成的,为了方便且称之为Handler消息机制。

image-20210703165712465

image-20210703165806877

总结

image-20210703165958837

image-20210703170016880

详情:https://cloud.tencent.com/developer/article/1415759

Dalvik虚拟机

https://zh.wikipedia.org/wiki/Dalvik%E8%99%9A%E6%8B%9F%E6%9C%BA

image-20210703172846469

Android可执行文件

可执行文件格式DEX(Dalvik VM executes)

一般java编写后的脚本文件是.java,.class是字节码文件,.dex是android平台可执行文件类型,一般java文件打包成jar包后里面的jar资源是.class,如果需要运行于android,那必须jar里面资源为.dex。

程序的生成步骤

APK(Android Package),实际上为一个zip压缩包

每个APK文件中包含一个classes.dex,即为Dalvik虚拟机的可执行文件

image-20210629002936886

image-20210629002955298

APK打包过程:

img

①打包资源文件,生成R.java文件

检查AndroidManifest.xml的合法性

对res目录下的资源子目录进行处理(处理内容:资源文件名的合法性检查,向资源表table添加条目等)

编译res与asserts目录下的资源并生成resources.arsc,调用函数生成R.java文件

对res目录的子目录下的xml文件分别进行编译(xml文件被加密)

将所有的资源与编译生成的 resources.arsc文件以及”加密“过的AndroidManifest.xml文件打包压缩为resources.ap_文件

②处理aidl文件,生成相应的java文件。

aidl(android interface definition language,Android接口描述语言)

③编译工程源代码,生成相应的class文件。

调用javac编译工程src目录下所有的java源文件(如果有native代码,也会使用NDK编译C/C++代码。

④转换所有的class文件,生成classes.dex文件。

⑤打包生成APK文件。

以包含resources.arsc的文件为基础生成apk文件,文件以.ap_结尾。

添加资源,写入依赖库。

⑥对APK文件进行签名。

⑦对签名后的APK文件进行对齐处理。

使apk包中的所有资源文件距离文件起始偏移为4字节整数倍(通过内存映射访问apk文件时的速度会更快)。

(Java源代码首先被编译成.class文件,然后Android SDK自带的dx工具会将这些.class文件转换成classes.dex。所以我们只需要想办法反编译classes.dex即可得到java源代码。)

dex文件格式

class文件显然有很多可以优化的地方,比如每一个class文件都有一个常量池,如果有重复字符串就造成了资源浪费,所以Dalvik的dex文件对其进行了优化。

数据结构

img

img

dex文件结构

一个dex文件由7个部分组成。

image-20210629052530361

dex header为dex文件头,指定一些属性和其他6部分数据结构在dex文件中的物理偏移。

string_ids和class_def结构为”索引结构区“。

真实的数据存放在data数据区。

link_data为静态连接数据区。(目前生成的dex文件,始终为空)

详情:https://juejin.cn/post/6844903847647772686#heading-2

源码:http://androidxref.com/9.0.0_r3/xref/dalvik/libdex/DexFile.h

img

![img](https://skytoby.github.io/2019/Android dex,odex,oat,vdex,art文件结构/dex.png)

odex文件格式

odex(OptimizedDEX)

采用odex方式优化的dex文件,包含了加载dex必须的依赖库文件列表,Dalvik虚拟机只需检测并加载所需的依赖库即可执行相应的dex文件,大大的缩短了读取dex文件所需时间。

Apk在安装(installer)时,就会进行验证和优化,目的是为了校验代码合法性及优化代码执行速度,验证和优化后,会产生ODEX文件,运行Apk的时候,直接加载ODEX,避免重复验证和优化,加快了Apk的响应时间。

odex文件结构

![img](https://skytoby.github.io/2019/Android dex,odex,oat,vdex,art文件结构/odex.png)

img

img

dexopt验证与优化

img

ART虚拟机

image-20210703180800408

image-20210703180903486

image-20210703180920356

image-20210703180723875

详细看这个:https://paul.pub/android-art-vm/

Android程序文件

Activity类

android程序每个应用界面对应一个Activity类

AndroidManifest.xml配置文件

android程序中是使用配置文件来配置入口的Activity界面

image-20210703202932522

uses-permission :表示用户需要授权该程序的权限,上面xml中配置的需要用户授予访问网络的权限。

application : 这个配置节点很重要,它的子节点 activity 就是配置android程序的入口,

android:name=".MainActivity" 配置了程序的初始视图界面为MainActivity (这个MainActivity类在src文件夹中)。

action节点中的android.intent.action.MAIN表明这个Activity是整个应用程序的入口点;

而category中的android.intent.category.LAUNCHER意思是把这个Activity归属到加载器类,即把这个Activity标注为自动会加载和启动的Activity,这样程序启动时候就先加载这个Activity了。

项目文件夹

  1. src:存放项目的源代码。

    image-20210703204633127

  2. gen:该文件是创建项目时候自动生成的,里面包了一个R.java的静态类,它里面包括很多静态类(内部类),每个静态类中的静态成员名称都对应res文件夹中的一个资源名称,保存着该资源的索引,方便在代码中进行获取资源。

    image-20210703204844296

  3. assets:assets文件夹里面都保存原始的文件格式,在代码中通过AssetManager来进行访问。

    assets文件夹主要保存原始的文件格式,比如我需要在代码中访问加载一个html文件,或者一个txt文档,那么就需要把html文件和txt文档保存到assets文件下。

  4. bin:存放编译后的apk和资源文件。

  5. res:存放项目需要的资源文件,比如字符串,布局,皮肤等,这里面每个资源的索引都保存在R.java类中。

image-20210703205105807

Gradle

image-20210706194201697

image-20210706194228791

wrapper就用来解决解决兼容性问题。wrapper里定义了gradle的版本。若今后构建这个工程时,它都会用我所绑定的gradle版本(避免版本兼容性问题)所以wrapper的作用就是会来检查在你构建这个工程的机器上有没有5.4.1这个版本。如果有就开始构建,发现没有的话就会去到所提供的url下载这个版本。

image-20210706194510020

image-20210706195552109

https://zhuanlan.zhihu.com/p/139685763

smali——未完成

.local指定局部变量的个数

.parameter指定方法的参数(一对一)

img

语句

ndk

JNI

JNI (Java Native Interface英文缩写),译为Java本地接口。是Java众多开发技术中的一门技术,意在利用本地代码,为Java程序提供更高效、更灵活的拓展。尽管Java一贯以其良好的跨平台性而著称,但真正的跨平台非C/C++莫属,因为当前世上90%的系统都是基于C/C++编写的。同时,Java的跨平台是以牺牲效率换来对多种平台的兼容性,因而JNI就是这种跨平台的主流实现方式之一。

总之,JNI是一门技术,是Java 与C/C++ 沟通的一门技术。

其他

注解

image-20210629200721485

https://www.liaoxuefeng.com/wiki/1252599548343744/1265102413966176

Android开发相关

SDK+NDK

image-20210703215430884

image-20210703220044570

image-20210703220918137

image-20210703220954377

image-20210703225602858

默认创建Android NDK工程时,Android提供了一个简单的JNI交互示例,返回一个字符串给Java层,方法名的格式为:Java_包名_类名_方法名

//.java文件中load lib
static {
        System.loadLibrary("native-lib");
    }
//声明本地的方法
public native String stringFromJNI();

//.cpp文件
//jstring对应返回类型string
//注意方法命名
//JNIEnv*是定义任意native函数的第一个参数,表示指向JNI环境的指针,可以通过它来访问JNI提供的接口方法。
//jobject表示Java对象中的this,如果是静态方法则表示jclass。
//JNIEXPORT和JNICALL: 它们是JNI中所定义的宏,可以在jni.h这个头文件中查找到。
extern "C" JNIEXPORT jstring JNICALL
Java_com_drioider_testndk_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

image-20210704020005838

Java 类型 Native类型
java.lang.Class jclass
java.lang.Throwable jthrowable
java.lang.String jstring
jjava.lang.Object[] jobjectArray
Byte[] jbyteArray
Char[] jcharArray
Short[] jshortArray
int[] jintArray
long[] jlongArray
float[] jfloatArray
double[] jdoubleArray

image-20210704020221211

例子:

//调用
String result = operateString("待操作的字符串");
Log.d("xfhy", result);

//定义
public native String operateString(String str);
//native访问java.lang.String 对应的JNI类型jstring时,不能像访问基本数据类型那样使用,因为它是一个Java的引用类型,所以在本地代码中只能通过类似GetStringUTFChars这样的JNI函数来访问字符串的内容。
//参数string放在了第三个,前两个固定
//调用了env->GetStringUTFChars(jstring)
extern "C"
JNIEXPORT jstring JNICALL
Java_com_xfhy_jnifirst_MainActivity_operateString(JNIEnv *env, jobject thiz, jstring str) {
    //从java的内存中把字符串拷贝出来  在native使用
    const char *strFromJava = (char *) env->GetStringUTFChars(str, NULL);
    if (strFromJava == NULL) {
        //必须空检查
        return NULL;
    }

    //将strFromJava拷贝到buff中,待会儿好拿去生成字符串
    char buff[128] = {0};
    strcpy(buff, strFromJava);
    strcat(buff, " 在字符串后面加点东西");

    //释放资源
    env->ReleaseStringUTFChars(str, strFromJava);

    //调用env->NewStringUTF(string)
    //自动转为Unicode
    return env->NewStringUTF(buff);
}
//java使用Unicode编码,C/C++默认使用UTF编码,所以在native层与java层进行字符串交流的时候需要进行编码转换。GetStringUTFChars就刚好可以把jstring指针(指向JVM内部的Unicode字符序列)的字符串转换成一个UTF-8格式的C字符串。

image-20210704021827233

详情:https://segmentfault.com/a/1190000037594523

Native调用Java静态方法

public class MyJNIClass {

    public int age = 30;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public static String getDes(String text) {
        if (text == null) {
            text = "";
        }
        return "传入的字符串长度是 :" + text.length() + "  内容是 : " + text;
    }

}
extern "C"
JNIEXPORT void JNICALL
Java_com_xzh_allinone_jni_CallMethodActivity_callJavaStaticMethod(JNIEnv *env, jobject thiz) {
    //调用某个类的static方法
    //1. 从classpath路径下搜索MyJNIClass这个类,并返回该类的Class对象
    jclass clazz = env->FindClass("com/xzh/jni/jni/MyJNIClass");
    //2. 从clazz类中查找getDes方法 得到这个静态方法的方法id
    jmethodID mid_get_des = env->GetStaticMethodID(clazz, "getDes", "(Ljava/lang/String;)Ljava/lang/String;");
    //3. 构建入参,调用static方法,获取返回值
    jstring str_arg = env->NewStringUTF("我是xzh");
    jstring result = (jstring) env->CallStaticObjectMethod(clazz, mid_get_des, str_arg);
    const char *result_str = env->GetStringUTFChars(result, NULL);
    LOGI("获取到Java层返回的数据 : %s", result_str);

    //4. 移除局部引用
    env->DeleteLocalRef(clazz);
    env->DeleteLocalRef(str_arg);
    env->DeleteLocalRef(result);
}
  1. Native调用Java实例方法。
  2. 获取构造方法的id,获取需要调用方法的id。其中获取构造方法时,方法名称固定写法就是<init>,然后后面是方法签名。
  3. 使用NewObject()函数构建一个Java对象。
  4. 调用Java对象的setAge和getAge方法,获取返回值,打印结果。
  5. 删除引用。

NDK原生程序——未完成

破解相关

基础

寻找字符串

http://yocosqamaq.top/android%e9%80%86%e5%90%91/

反编译apk文件成功后,会在当前的outdir目录下生成一系列目录与文件。

smali目录下存放了程序所有的反汇编代码,res目录则是程序中所有的资源文件,这些目录的子目录和文件与开发时的源码目录组织结构是一致的。

字符串可能硬编码到源码中,也可能引用自“res\values”目录下的strings.xml文件,apk文件在打包时,strings.xml中的字符串被加密存储为resources.arsc文件保存到apk程序包中。

开发Android程序时,String.xml文件中的所有字符串资源都在“gen//R.java"文件的String类中被标识,每个字符串都有唯一的int类型索引值,使用apktool反编译apk文件后,所有的索引值保存在string.xml文件同目录下的public.xml文件中。

img

img

修改smali,用signapk.jar工具对apk文件进行签名。

ida

apk解压缩后找到classes.dex文件拖入ida

image-20210629140938127

image-20210629140959307

通过ida的hex View修改二进制文件。

修改完需要更改二进制文件的checksum。

将修改后的classes.dex文件覆盖原文件,删除META-INF文件夹,再进行签名之后即可安装。

静态分析

每个Activity都是Android程序的一个显示”页面“,主要负责数据的处理及展示工作。

每个Android程序有且只有一个主Activity,它是程序启动的第一个Activity。

程序的代码入口处:OnCreate()方法。(检查是否有Application类,如果有,先看其OnCreate())

动态分析——未完成

NDK分析

用as初始包含NDK源码进行生成APK

对得到的apk进行解压

与无NDK编写的APK对比:

image-20210703231248479

将lib下的.so文件拖入ida

image-20210703231334932

image-20210703231437933

待解决

Android系统架构需更底层了解

linuxIPC机制

BInder等一些专有机制原理不清