Android杂项

本人Android逆向小菜鸡一名,且文学水平有限,明白意思但说不明白,各位看官能看明白多少算多少吧

apk及系统的启动流程

        开机后,引导芯片会从固化的 ROM(只读Read Only Memroy) 处执行预设代码,将 Bootloader 加载到 RAM(可读写Random Access Memory) 中,Bootloader 设置系统硬件参数,检查 RAM,把操作系统映像文件拷贝到RAM中去,然后跳转到它的入口处去执行。内核启动,创建第一个内核进程 idle 进程,最终创建第一个用户空间进程 init;

init进程 :
    1.创建和挂载启动相关的文件目录。
    2.初始化和启动属性服务(类似于pc的注册表)。
    3.解析 .rc 配置文件,将zygote 和 startSystemServer 设为true。
    4.判断zygote 为true后,调用 runtime.start函数传入包名"com.android.internal.os.ZygoteInit" 与start-sytem-server 。-> 

runtime.start函数:
    1.创建虚拟机
    JniInvocation jni_invocation;
  	2.加载 ART 虚拟机的核心动态库,如:libart.so
    jni_invocation.Init(NULL);
    JNIEnv* env;
  	3.启动 ART 虚拟机
    if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) {return;}
    4.找到 com.android.internal.os.ZygoteInit 中 static void main(String argv[]) 方法
    jmethodID startMeth = env->GetStaticMethodID(startClass, "main",([Ljava/lang/String;));
    5.执行上述查询到的方法,启动Zygote进程中的main函数
    env->CallStaticVoidMethod(startClass, startMeth, strArray);

Zygote进程:
    1.创建 Server 端 Socket,用于和其他进程通信
    ZygoteServer zygoteServer = new ZygoteServer();
    2.启动 SystemServer 进程
    Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);
    3.等待 ActivityManagerService 的请求来创建新的应用程序进程
    caller = zygoteServer.runSelectLoop(abiList);
     -> SystemServer ,ActivityManagerService,Launcher;Launcher进程加载图标,包名,androidmanifest。

        Launcher进程收到点击图标消息时,使用Binder方式通知system_server进程启动app,之后Launcher进程将自己挂起,system_server进程将收到的Binder消息发配给AMS服务,AMS服务以socket方式通知Zygote进程,Zygote fork自身创建子进程,向进程中导入ActivityThread类,执行该类的main函数,

在启动ApplicationThread(在每个ActivityThread被创建的时候, 都需要向ActivityManagerService绑定,或者说是向远程服务AMS注册自己),调用bindApplication方法传入绑定的pid,在调用makeApplication,最终调用callApplicationOnCreate方法执行,所以说Application中的OnCreate方法早于所有的aictivity OnCrete调用。

        题外话:第一个activiy的启动,正常情况下会优先执行Oncreate函数;意外情况:入口点类内有static块,里面的代码会被先执行,例如System.loadLibrary函数加载so文件。

        下图中的,以Proxy结尾的玩意,是Binder驱动在每个进程中的对象,通过其 使用Binder通信服务。

在这里插入图片描述

  1.  点击桌面APP图标时,Launcher的startActivity()方法,通过Binder通信,调用system_server进程中AMS(安卓10以上加入ActivityTaskManagerService,简称ATMS,分担一部分的AMS的工作)服务的startActivity方法,发起启动请求。
  2. AMS服务判断app是否启动:以Socket通讯(线程同步,防止多线程创建进程,进程创建只能支持单线程,所以后续AMS与Zygote的通信不能用Binder通信,选择使用Socket )方式向Zygote进程发送启动app消息(冷启动);已经启动了,运行application的入口函数(热启动)。
  3. Zygote进程fork出新进程,进程中导入ActivityThread类,通过 反射机制 调用ActivityThread类的main方法。
  4. 在main函数中首先会开启looper消息循环,创建ActivityThread类的实例,通过实例调用ActivityThread类的attach方法(attach为类中的私有函数)。
  5. attach通过AMS提供的接口,拿到AMS对象,通过对象调用 attachApplication 方法将 ApplicationThread 对象绑定到 AMS, 这样AMS就可以通过这个对象和app程序进行通信(ApplicationThread是ActivityThread的私有内部类,实现了IBinder接口)并控制四大组件的生命周期了。
  6. attachApplication 中调用 attachApplicationLocked 中调用 attachApplicationLocked 方法,attachApplicationLocked中有两个主要函数 bindApplication (在经过多层调用,最终创建 application 并调用 onCreate 方法) 与 attachApplicationLocked(与前面传入的参数不同,是一个重写的函数;主要作用就是创建并启动activity)
  7. 至此app的onCreate方法已经执行完毕,并且也启动了 AndroidManifest 中指定的第一个activity,与上图略有不同,放图片的目的是表达通讯方式。

SystemServer 

1.作用:初始化时间,时区,语言等;设置虚拟机运行库路径;清除虚拟机内存限制(各大厂商的dalvik限制了每个进程的堆空间最大值),设置堆利用率(0.8);

       ****创建looper(消息管理)线程,初始化native服务,加载Android_servers;初始化系统上下文;启动ActivityManagerService(广播,activity,content,server),PackageManagerService,WindowManagerService 等服务以及 binder 线程池。****

  • ActivityManagerService:主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作,对于一些进程的启动,都会通过 Binder 通信机制传递给 AMS,再处理给 Zygote。
  • PackageManagerService,主要负责应用包的一些操作,比如安装,卸载,解析 AndroidManifest.xml,扫描文件信息等等。
  • WindowManagerService,主要负责窗口相关的一些服务,比如窗口的启动,添加,删除等。

binder

        Binder是Android中用于进程间通信(IPC)的一种机制。在Binder通信过程中,有三个主要角色:服务端、客户端和Binder驱动。

                1. 服务端:服务端创建Binder对象,实现Binder接口,并将其注册到Binder驱动中。

                2.客户端:客户端通过Binder驱动获取服务端提供的Binder对象,并将请求发送给服务端。

                3.Binder驱动:它负责管理所有的Binder对象,并确保它们之间的通信,存在于内核空间中。

binder通信过程:

  • server(服务)进程:向Binder驱动发起注册请求,Binder驱动将请求转发给Server Manager(系统启动时,Server Manager会将自己注册为Binder驱动的管理者,同时也管理着其他所有服务),server在Binder驱动中创建一个实体/对象(保存在 /proc/binder/proc 目录中,以进程的ID命名的文件夹),该实体为server在Binder驱动中的代表,其中保存着server和Server Manager的信息。
  • 获取服务:客户端发起请求,将要获取的服务的方法发送给Binder驱动,驱动再将请求转发给Server Manager,Server Manager找到对应的Binder实体,将其返回给Binder驱动,驱动将server的代理对象返回给客户端,客户端操作代理对象,代理对象再通过Binder驱动让真正的Binder对象完成操作。
  • 使用服务:

        1.Binder准备工作:Binder驱动创建一块内核缓冲区,通过Server Manager 进程的server信息,找到对应的server进程,实现 内核缓冲区&用户空间 的内存映射(内存映射:通过调用mmap系统函数,可以简单理解为内存块 1 ,2 存在内存映射关系,向1中写入数据,相当于同时向 1 与 2 写入相同数据)。

        2.客户进程发送请求:客户进程通过 binder_thread_write (内核中调用copy_from_user)将请求(目标方法的标识符、参数 及 方法对象标识符,接收返回值的参数)发送给内核缓冲区,客户进程被挂起,Binder通过代理对象找到server的实体对象,再把客户进程发送过来的数据拷贝到,与步骤 1 中存在映射关系的内存中,最后通知server进程解包。

        3.服务进程调用方法:收到Binder的消息后,进行解包,通过包中的信息调用函数执行,将返回结果写入接收参数。

        4.服务进程返回结果:将返回包写入与内核共享的,用户空间内存中,Binder驱动通过实体对象找到代理对象(将返回包数据写入代理对象?),唤醒客户进程并通知其接受返回结果,客户进程调用 binder_thread_read (内核调用copy_to_user)获取代理对象,其中的reply保存返回值(步骤 2 中的接收返回值参数)。

        Binder服务在初始化时会创建一个虚拟目录(/proc/binder/proc)与一个虚拟文件(/dev/binder设备文件)

/proc/binder/proc

每一个使用了Binder进程间通信机制的进程在该目录下都对应有一个文件,这些文件以进程ID来命名,通过他们就可以读取到各个进程的Binder线程池、Binder实体对象、Binder引用对象以及内核缓冲区的信息。

/dev/binder设备文件
可以理解为Binder驱动程序的代码,客户端在使用Binder时,第一步就是创建 IBinder 类型对象,该对象就是Binder驱动的引用;在binder初始化时指定全局变量 binder_fops,提供方法接口给开发者使用。

  • 打开方法binder_open

  • 内存映射binder_mmap

  • IO控制binder_ioctl

  • 进程消息读写 binder_thread_read 与 binder_thread_write 等

ELF文件

ELF 文件标准大概包含了以下四种文件类型:

  • 可重定位文件(Relocatable File) : 主要包含代码和数据,可以被用来链接成可执行文件或者共享目标文件,静态链接库也归类于这一类,包括 Linux 的 .o 文件,Windows 的 .obj 文件
  • 可执行文件(Executable File) : 包含可以直接执行的程序,比如 Linux 下的 /bin/bash,Windows 下的 .exe
  • 共享目标文件(Shared Object File) : 主要包含代码和数据,第一种用途可以与其它文件链接生成可重定位或者共享目标文件,再者直接链接到可执行文件,作为进程映象的一部分动态执行。常见的 Linux 下的 .so,Windows下的 .dll
  • 核心转储文件(Core Dump File): 这个格式调试 bug 时很有用,进程意外终止时产生的,保留程序终止时进程的信息,Linux 下的 Core dump。

dex、odex、vdex文件

  1. dex文件(Dalvik EXecutable):是Android应用程序的核心可执行文件,包含了应用程序的字节码和其他资源信息。dex文件经过优化后可以被Dalvik虚拟机或ART虚拟机执行。

  2. odex文件(Optimized DEX):是针对dex文件的优化版本,通过预先将dex文件的字节码和其他信息进行优化处理,使得在程序运行时加载和执行更加高效。odex文件通常是由dex文件通过系统的dex优化工具(dexopt)生成的。

  3. vdex文件(DEX Optimized Virtual Machine EXecutable):是针对ART虚拟机的优化版本,是一个全新的文件格式,相比于odex文件更加高效和安全。vdex文件包含了预编译的应用程序字节码和其他信息,可直接被ART虚拟机加载和执行。vdex文件通常是由系统的dex2oat工具在应用程序安装时生成的。

        应用程序在第一次启动app的时候,会在/dalvik/dalvik-cache目录下生成odex文件结构,odex 是 dex 进行优化 生成的 可执行二进制码 文件, 

dex文件格式

1.header: Dex 文件头,包含 magic 字段、adler32 校验值、SHA-1 哈希值、string_ids 的个数 以及偏移地址等。Dex 文件头结构固定,占用 0x70 个字节。

2.String_Id: 定义了字符串数据的偏移(4字节指针,指向string结构体:string大小,string数据);

3.Type_Id: 表示应用程序代码中使用到的具体类型,如整型、字符串、类和方法等,类和方法以字符串形式保存在DexStringId中,*DexTypeId为对应的DexStringId的下标。

4.Proto_id:方法声明的信息,结构体包含:4字节常数,为string_id数组的下标,通过该下标可以找到方法名称;type_id的下标,方法返回值类型;指向参数结构体的指针,结构体保存参数数量与参数类型(type_id下标方式)

5.field_id:字段的信息(类成员变量);所在类信息,所属类型(type_id下标);字段名(string_id下标)

6.method_id:方法的信息;所属类(type_id[]),方法的声明(proto_id[])(所属类与声明都是以short类型索引保存,所以一个dex中最多只能有65535个方法),方法名(string_id[]);

7.class_def:类的信息;类的相对路径(如Ljava/lang/String),访问权限(public等),父类信息(type_id[]),接口信息(绝对地址),源文件名称(string[]),注释信息(绝对地址),类中字段,方法的数量(绝对地址),类的静态数据的值(绝对地址)

8.CLass_data:类的字段信息、方法信息和相关的访问标志等。

class与dex

class文件中保存的是Java字节码,需要再次进行翻译为Dalvik字节码,在生成dex文件。

区别:

1.dex文件相比于class文件格式相对更加紧凑,减少冗余,加载一个dex文件相当于加载了多个class。

2.class保存的是Java字节码,是基与栈运行的;而dex存的是Dalvik字节码,是基于寄存器的。

3.dex基于寄存器寻址更加方便,适合移动端,而基于栈寻址则需要多次出栈入栈操作。

        (5条消息) Android虚拟机的几个面试技术点_安卓虚拟机面试_Mr.Louis的博客-CSDN博客

        Dalvik虚拟机与java虚拟机的区别 - 简书 (jianshu.com)

dex文件解析、执行流程

        Dalvik虚拟机虚拟机通过调用PathClassLoader(只能加载已安装的apk的.dex文件)或DexClassLoader(可加载所有dex文件),解析dex文件为Dalvik字节码后运行,dex文件中保存了一个程序所有执行的逻辑。

Android系统的几个阶段

        JIT即时编译:程序执行时,系统将dex加载到内存,一边执行一边将dex中的字节码转为机器码,当哪个代码段被判定为热执行函数时,会将其编为机器码并优化保存在内存中;JIT编译所有的流程不会涉及生成新的文件,即使判定为热函数,优化后的函数也是在内存中,不会落地,程序结束就都没了。

        AOT预编译:程序安装时,一次性将dex编译为机器码(.odex文件),保存至 /data/dalvilk-cache 目录下(都这么说,但是我用模拟器在该文件夹下没找到odex后缀的文件)。

4.4之前

        只使用jit即时编译技术,优点是储存空间占用小,每次启动都会生成新的热执行函数;缺点是内存占用较大,启动和执行速度满(相对与aot来讲)。

4.4~7.0

        AOT在4.4版本被引入,但编译主要还是用的是JIT技术,5.0--7.0 AOT完全取代JIT,在App安装时就将所有dex编译为机器码(odex文件)保存到本地,程序运行时,直接运行odex文件,

优点是app的启动与运行速度大幅度提高,缺点是安装时间明显变长,且静态文件(不是内存,是手机存储odex文件)变大,导致收集储存空间不足。

7.0+

        引入AOT + JIT结合编译,此时的delvik虚拟机从32位演变为64位(在此之前的delvik虚拟机只支持32位程序),支持的最大堆内存由1.2GB变为10+GB;数据类型大小改变(32位int 4字节,64位的为8字节);指令集由32位的 ARMv5TE 变为 ARMv8;性能提高。

        引入vdex文件取代odex,文件格式不同;vdex存储的是 机器码 + 一部分原dex文件数据(类结构,注释等),vdex是在程序安装时,art通过对dex静态分析和优化的产物,并不会将所有的dex字节码转为机器码;程序运行时,会直接执行vdex内的数据,根据判断函数执行频率将其定义为热执行函数,保存到.art文件中,手机空闲时,读取.art文件,将其中记录的函数编译为机器码保存到vdex中。

  • 分析信息会存储在代码缓存中,并会在内存紧张时作为垃圾被回收。
    • 无法保证在应用处于后台运行状态时所捕获的快照能够包含完整的数据(即 JIT 编译的所有内容)。
    • 该过程不会尝试确保记录所有内容(因为这会影响运行时性能)。
  • 方法可能有三种不同的状态:
    • 已经过解释(dex 代码)
    • 已经过 JIT 编译(记录应用启动热点函数相关地址,方便寻址,以.art文件格式存储)
    • 已经过 AOT 编译(热点函数、方法已经过编译成了机器码,ART可以直接执行)
    如果同时存在 JIT 和 AOT 代码(例如,由于反复进行逆优化),经过 JIT 编译的代码将是首选代码。
  • 在不影响前台应用性能的情况下运行 JIT 所需的内存取决于相关应用。大型应用比小型应用需要更多内存。一般来说,大型应用所需的内存稳定维持在 4 MB 左右。

实现 ART 即时 (JIT) 编译器  |  Android 开源项目  |  Android Open Source Project (google.cn)

arm指令与字节码

字节码叫法由来:每一条指令就是一个字节,可以表示256条不同的指令。

区别:cpu可以执行arm指令,但无法执行字节码,字节码需要转换;arm是基于寄存器,字节码基于堆栈;字节码没有地址的概念,而arm可以解引用。

arm中断指令

软中断指令格式:SWI{cond} immed_24 

         通过SWI指令触发软中断,切换到特权模式,后面是一个24位立即数,和调用号差不多,通过该立即数来执行不同的内核函数。

过程:执行至SWI指令时,PC被置为指向 异常向量表地址0x08 的指针,再在该处设置指针

b mySWI,这样触发中断后就会执行我们的代码了。

APK安装流程相关

安装的方式:

  1. 系统安装:开机的时候,没有安装界面
  2. adb 命令安装:通过abd命令行安装,没有安装界面
  3. 应用市场安装,这个要视应用的权限,有系统的权限无安装界面(例如MUI的小米应用商店)
  4. 第三方安装,有安装界面,通过packageinstaller.apk来处理安装及卸载的过程的界面

涉及目录:

  • /system/app:系统自带的应用程序,获得adb root 权限才能删除
  • /data/app:用户程序安装的目录。安装时把apk文件复制到此目录
  • /data/data:存放应用程序的数据
  • /data/dalvik-cache:将apk中的dex文件安装到dalvik-cache目录下,所有系统与用户的app缓存数据,以.dex后缀保存对应虚拟机的字节码(dalvik/art)。
  • /data/system:该目录下的packages.xml文件。类似于Window的注册表,这个文件是解析apk时由writeLP()创建的,里面记录了系统的permissons,以及每个apk的name,codePath,flag,ts,version,userid等信息,这些信息主要通过apk的AndroidManifest解析获取,解析完apk后将更新信息写入这个文件并保存到flash,下次开机的时候直接从里面读取相关信息并添加到内存相关列表中。当有apk升级,安装或删除时会更新这个文件。
  • /data/system/package.xml与/data/system/package.list:packages.list指定了应用的默认存储位置/data/data/com.xxx.xxx;package.xml中包含了该应用申请的权限、签名和代码所在的位置等信息系,并且两者都有同一个userld。之所以每个应用都要一个userId,是因为Android在系统设计上把每个应用当做Linux系统上的一个用户对待,这样就可以利用已有的Linux用户管理机制来设计Android应用,比如应用目录,应用权限,应用进程管理等。

安装流程简述:

  • 点击 APK 安装,会启动 PackageInstallerActivity,再进入 InstallInstalling 这两个 Activity 显示应用信息
  • 点击页面上的安装,将 APK 信息存入 PackageInstaller.Session 传到 PMS
  • PMS会做两件事,拷贝安装包和装载代码
  • 在拷贝安装包过程中会开启 Service 来 copyAPK 、检查apk安装路径,包的状态
  • 拷贝完成以 base.apk 形式存在/data/app包名下
  • 装载代码过程中,会继续解析 APK,把清单文件内容存放于 PMS
  • 对 apk 进行签名校验
  • 安装成功后,更新应用设置权限,发送广播通知桌面显示APP图标,安装失败则删除安装包和各种缓存文件
  • 执行 dex2oat 优化

Android常用算法:

MD5、SHA、HMAC:不可逆,但是MD5与SHA可以通过暴力碰撞破解

        MD5:穷举法(知道原数据长度一个个试)&字典法(将一大堆试出来的MD5存起来,以后直接找MD5库)

        过程:设原文件长度为x,填充文件长度至n*512(bit) + 448 ,第一位使用1,后面填0,在将长度x用64位记录,拼接至448之后,第一次使用标准幻数A=0X67452301L,B=0XEFCDAB89L,C=0X98BADCFEL,D=0X10325476L与第一组16 * 4字节数据进行四轮计算,生成16 字节结果,在与下一组512(bit)计算,最终生成16字节。

        SHA1:咔咔一顿算,最后生成32字节,,SHA256 算出32字节,位数多了,比MD5安全。计算稍慢,SHA256最慢。

        过程:和MD5差不多,SHA1是16 * 5字节为一组的,生成20字节;SHA2是 16 * 6为一组生成32字节。

        HMAC:MD5与SHA算法结合体,加入密钥。客户端使用密钥与要发送的消息进行一次或多次摘要计算出结果,将结果打包发给服务端,服务端用相同的密钥与消息计算,最后校验结果。

        CRC:完整性校验,和MD5差不多,计算简单多用于网络收发包,步骤:要发送的数据 :send,发送端生成的帧:key,特定的数:num,send + key 能整除 num,这里采用的是模2除法(具体过程没看懂)

        

        对称性加密算法有:

        AES:分为AES-128(192,256),密钥长度可以是128 || 192 || 256,密钥长度不同,最优加密轮数不同,对应的分别为10 || 12 ||14轮,步骤:字节代换(查表映射)、位移、列混合(矩阵相乘)、密钥计算(密钥与矩阵异或)

        DES、3DES:64位密钥加密64位明文的算法,三次使用的密钥相同就是DES,不同就是3DES,下图重复执行16轮,由于是异或运算,所以加密多少轮都可以解密回来,算法简单,计算慢,比较low。

        非对称性算法有:RSA、DSA、ECC(全看不懂,QTMD);

        Base64:不是加密算法,只是编码传输数据。

        过程:从计算之前的二进制数中取出6位(二进制6位的值域位0~63,所以叫base64),扩充为8位,前两位补0,每8位在查表替换(A-Z + a-z + 0-9 + '+' + '/'共64个字符)

so文件加载流程与自定义 linker / so 加固

so文件的加载

         在Android系统中,SO文件的加载和链接是通过一个名为"linker"的系统组件实现的

         静态注册:现在用的太少了,也太low,对于逆向而言一眼看穿,就不说了。

        动态注册:

         通过 Java层的System.loadLibrary("相对路径")或System.load("绝对路径")调用so文件。

System.loadLibrary -> Runtime.loadLibrary -> PathClassLoader.findLibrary -> 
Runtime.nativeload -> Dalvilk_java_lang_Runtime_nativeLoad -> dymLoadNativeCode -> 
dlopen -> //选读
dlsym(JNI_OnLoad) -> //重点函数:一般的解密,验证等都会写在这里面
JNI_VERSION_1_6 -> Success    
finish :JNI_OnUnLoad(与JNI_OnLoad都是非必须要实现的)

dlopen:

        经过几层的调用,最终调用到 load_library 函数(在这之前会判断so是否已经被加载),在load_library中执行 装载,创建soinfo,链接 三个过程

装载

        创建ElfReader对象,通过 ElfReader 对象的 Load 方法将 SO 文件装载到内存。

//标准linker的装载
bool ElfReader::Load() {
  return ReadElfHeader() &&      //读取elf header
         VerifyElfHeader() &&    //验证elf header
         ReadProgramHeader() &&  //读取program header
         ReserveAddressSpace() &&  //分配空间
         LoadSegments() &&         //按照program header的指示装载segments
         FindPhdr();               //找到装载后的phdr
}

创建soinfo

struct soinfo {
  char name[SOINFO_NAME_LEN]; // 共享库名字
  const ElfW(Phdr)* phdr;     // 指向段头表(program header)的指针
  size_t phnum;               // 段头表中入口的数量
  ElfW(Addr) entry;           // 共享库入口地址
  ElfW(Addr) base;            // 共享库基地址
  size_t size;                // 共享库大小(字节数)
  uint32_t unused1;           // 保留字段
  ElfW(Dyn)* dynamic;         // 指向动态段的指针
  uint32_t unused2;           // 保留字段
  uint32_t unused3;           // 保留字段
  soinfo* next;               // 指向下一个 soinfo 结构体的指针
  uint32_t flags;             // 共享库标志
  const char* strtab;         // 字符串表地址
  ElfW(Sym)* symtab;          // 符号表地址
  size_t nbucket;             // Number of buckets in the ELF hash table.
  size_t nchain;              // Number of chains in the ELF hash table.
  uint32_t* bucket;           // Address of the ELF hash table.
  uint32_t* chain;            // Address of the ELF hash table.
  unsigned int* gnu_nbucket;  // Address of the GNU hash table size.
  unsigned int* gnu_maskwords;// GNU hash table Bloom filter size (words).
  unsigned int* gnu_shift2;   // GNU hash table Bloom filter shift.
  unsigned int* gnu_bloom_filter; // GNU hash table Bloom filter address.
  ...
};

链接

最重要的一步就是链接,链接主要步骤是:

  1. 定位 dynamic segment
  2. 解析 dynamic section
  3. 加载该so所依赖的so  :linker中的 __dl__Z14find_librariesP19android_namespace_tP6soinfoPKPKcmPS2_PNSt3__16vectorIS2_NS8_9allocatorIS2_EEEEmiPK17android_dlextinfobPNS9_IS0_NSA_IS0_EEEE 函数
  4. 重定位
  5. 执行ELF文件中的init_array函数:暂时没找到,有时间再说

         dl_open -> dlopen_ext -> do_dlopen,do_dlopen首先会判断该so文件是否已经加载,如未加载,会首先调用so文件中的 init 函数,中主要调用find_library (判断so是否已经加载,未加载则调用load_library加载so文件),find_library会返回一个soinfo指针,在执行soinfo结构体中的成员函数CallConstructors(如果要修改一个已经加载的so文件soinfo,可以在find_library返回时修改),CallConstructors 函数会首先调用所有依赖的 SO 的 soinfo 的 CallConstructors 函数,接着调用自己的 soinfo 的 init_func 和 init_array 指定的函数,这两个变量在在解析 dynamic section 时赋值。

JNI_OnLoad:

          so文件中可以重写init_arrayinit_func(老版本的liniux系统)或者创建.init_arry区段,此区段的函数指针会在JNI_OnLoad函数执行之前挨个执行,.fini_arry区段保存着C++的析构函数,会在程序正常结束时被调用,释放全局资源,这两个函数会在dlopen中被调用,本质上这是两个数组,里面存放了函数指针,里面的函数会依次执行。

        之后执行回调函数JNI_OnLoad,其类似于类中的OnCreate函数,会被默认调用,所以一般的so文件都会通过重写JNI_OnLoad来实现解密,反调等等功能。

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved);
JNI_OnLoad 函数的主要作用是完成以下任务:

1.获取 JNIEnv 指针:在 JNI_OnLoad 中,可以通过 vm 参数获取到当前的 JNIEnv 指针。该指针
可以用于后续调用 Java 方法或者访问 Java 对象。
2.注册本地方法:通过调用 JNIEnv 的 RegisterNatives 函数,可以将本地方法注册到 JVM 中,
从而使得 Java 代码能够调用本地库中的函数。
3.执行其他初始化操作:JNI_OnLoad 函数也可以用于执行其他与本地库相关的初始化操作,例如初
始化全局变量、创建线程、打开文件等。

RegisterNatives 函数

作用:将本地方法(so中的函数)注册到jvm虚拟机中,之后Java层就可以通过JNI接口直接调用。

RegisterNatives(JNIEnv *env, jclass clazz, const JNINativeMethod *methods, jint nMethods);

RegisterNatives函数第三个参数JNINativeMethod结构中保存着注册的方法名称,签名,函数指针三个指针,通过调试该函数,可以拿到Java层调用的函数对应的so中的函数地址。

自定义linker / so 加固

        以自定义的文件格式保存so文件,一定程度上的阻止IDA的静态反编译,apk运行时,通过自定义的 linker(加载so文件的流程),将so文件解析为正确的格式,返回 soinfo 结构体供其他函数调用(比如原so文件开头保存的是 header 信息,自定义后将heard信息保存到固定的偏移0x6666处或将原 header 二进制文件加密保存等)。

        加密过程:加壳程序通过解析原正确格式的 so 文件,拿到各各区段信息,将区段加密,将加密后的区段重新拼接成自定义的文件格式(现在的自定义 linker 壳基本都会把加壳器单独存为一个so文件)。

        解密过程:

        1.将加密后的so解密为正确的so文件格式并加载到内存。//初始化 soinfo结构体信息,然后链接。

        2.linker_soinfo_map修复(关键):根据 r0ysue 姐的文章,安卓7以下可以通过dlopen自身,获取系统维护的 soinfo 表(系统加载的是加密的so文件,所以得到的soinfo结构体是错误的,需要修复),遍历 soinfo表 中的so名称找到我们的so对应的 soinfo结构体,在将步骤 1 中解析出的区段信息,填充到对应的 soinfo结构体中。

        3.加载该so文件的依赖;各种导入表,导出表,重定位表,依赖的函数地址等的修复。

        4.主动调用so中的 init_array 和 JNI_Onload 方法(解密过程看的我是三眼懵逼,开发人员请参考佬的文章《基于linker实现so加壳技术基础》)。

        总结:无论中间过程怎么样,解密后提供给 系统soinfo表 的 soinfo结构体肯定是对的,soinfo结构体中保存的是so文件的详细信息,可以通过该结构体得到正确的so文件。

        

Java/C++ 中局部变量与全局变量

        Android中局部变量和全局变量的存储方式与C/C++语言类似,局部变量使用栈来保存,全局变量和动态分配的内存用堆来保存。

C中的堆与栈需要开发者自己维护,栈中保存 局部变量、参数、返回值、返回地址 等,堆中保存 全局变量 与 动态分配(编译时无法确定其大小) 的内存等。

堆栈区别:

        栈存储数据由 高地址 -> 低地址 存储,内存是连续的,   由系统自动分配的,速度快,不会有碎片,操作困难;

        堆存储数据由 低地址 -> 高地址 存储,内存不是连续的,开发者自己申请的,速度慢,会产生碎片,操作容易;

Java中,栈是由 jvm 管理的,方便,但是限制了灵活性。

Android抓包

        Https:安卓7以上,且app的xml中的 android:targetSdkVersion 属性大于24时,系统不在信任用户的CA证书,无root权限将无法抓到Https的包;

                    有root权限,可以将抓包工具提供的证书通过 adb push 到 /etc/security/cacerts/ 文件夹下,证书名称是 CA 证书 subjectDN 的 Md5 值前四位移位取或,后缀名是 .0,比如 00673b5b.0。

  无root,targetSdkVersion大于24时,绕过方法:重打包apk,将xml文件中的 targetSdkVersion属性调低;2.平行空间或者 VirtualApp 抓包,平行空间更稳定,但版本要在 4.0.8625 以下;

      3.root手机,使用  HttpCanary v2.8.0 之后的版本,可直接导出以 .0结尾的CA证书,在安装到手机的系统证书目录。

        

Unidbg补环境

        可以在无需了解so内部算法原理的情况下,主动调用so中的函数,传入所需的参数、补全运行所需的环境,即可运行出所需要的结果。达到辅助分析、算法还原、SO调试与逆向等等功能。

        在该so文件中可能会通过JNI调用Java函数,但是在Unidbg的环境中无法找到该函数,这时候就需要我们把其中调用的Java函数给补全。

Kernelsu

        KernelSU是一种基于内核的root解决方案,主要工作在内核空间,可以提供针对内核的HOOK接口,可以对内核中的几乎任意函数进行拦截,例如,我们可以在内核模式下为任何进程添加硬件断点;我们可以在任何进程的物理内存中访问,而无人知晓;我们可以在内核空间拦截任何系统调用; 等等。

APP汉化

大部分需要汉化的文件 RotSeek.smali strings.xml camera_preferences.xml lol.smali defcomk.smali lol1.smali arrays.xml faa.smali ;

游戏二开

基于Unity(java类)与il2cpp(so文件)框架开发的游戏,使用fakerandroid可将apk还原成一个Android Studio 工程,编写 native-lib.app 的 fakeApp 等函数实现 hook 功能。

1.修改 Androidmanifest.xml 的入口点为 FakerApp,MAIN & LAUNCHER 类为 FakerActivity。

2.FakerApp 中加载 native-lib.so,并执行其中的静态导出函数 fakeDex 与 fakeApp 函数。

3.fakeApp 函数中用到的 fakeCpp:参数1  要hook的函数地址,参数2  将参数1函数重定向到你自己函数的函数名,参数3  &参数1(原因没研究)

4.Unity游戏 com.unity3d.play.UnityPlayer 中 loadNative 函数,通过字符串加载so文件,最终通过main.so中的动态注册的Native函数 load 加载so文件。

Activity 生命周期

4个的状态

[分享]某vmp壳原理分析笔记-Android安全-看雪论坛-安全社区|安全招聘|bbs.pediy.com (kanxue.com)


参考:https://blog.csdn.net/hzwailll/article/details/85339714

          Application的作用 - 简书 (jianshu.com)

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值