安卓逆向_1 --- 逆向环境、apk结构、apk打包、apk启动 (点击图标,APP加载流程)、smali

1、Java 环境、安卓sdk和ndk

安卓是基于 java 开发的,所以需要 java 环境,以及 安卓开发环境 ( 安卓 sdk、ndk )

所有 "环境配置、路径" 不要有 "中文、空格、特殊符号"

  • 1. Java 环境:最好是 jdk8 或者 jdk11。
  • 2. 安卓开发环境 ( 安卓 sdk、ndk ):。安卓 sdk 和 ndk 下载完成后直接解压,然后配置环境变量即可。也可以通过 android studio 进行 sdk 和 ndk 的安装。ndk 的安装:官网下载 ( https://developer.android.google.cn/ndk/ )  ndk 进行安装

2、apk 文件结构

APK 是 Android PacKage 的缩写,是 Android 源文件打包后的安装包。apk 其实就是一个 zip 格式的压缩包。想要知道 apk 包含了什么,可以修改后缀名,然后用解压缩工具打开 apk 文件。 如果有 代码混淆加密,通过普通解压缩工具打开里面的文件或目录会看到各种乱码

APK 文件 目录:

    assets 不经过 aapt 编译的资源文件
    lib .so 文件
    META-INF  文件摘要,摘要加密和签名证书文件目录
    CERT.RSA 公钥和加密算法描述
    CERT.SF 加密文件,它是使用私钥对摘要明文加密后得到的 密文信息,只有使用私钥配对的公钥才能解密该文件
    MANIFEST.MF 程序清单文件,它包含包中所有文件的摘要明文
    res 资源文件目录,二进制格式
    drawable 图片
    layout 布局
    menu 菜单
    resources.arsc 经过 aapt 编译过的资源文件
    classes.dex 可执行文件
    AndroidManifest.xml 配置文件

文件、目录 说明:

  • assets 文件夹:程序资源目录。assets 文件夹用于保存需要保持原始文件的资源文件夹,开发过程中拖了什么到里面,打包完之后里面还是什么。一般用于存放音频,网页(帮助页面之类的),字体等文件。主要需要知道的点是,它与 res 文件夹的区分以及如何在应用中访问该文件夹的资源,如它可以有多级目录而 res 则只有两级。
  • res 文件夹:顾名思义,该文件夹是资源文件夹。它里面存放的所有文件都会被映射到 R 文件中,生成对应的资源 ID,便于代码中通过 ID 直接访问。其中的资源文件包括了动画(anim),图像(drwable),布局(layout),常量值(values),颜色值(colors),尺寸值(dimens),字符串(strings),自定义样式(styles)等。在编译时会自动生成索引文件(R.java),在 Java 代码中用 R.xxx.yyy 来引用。而 assets 目录下资源文件不会生成索引,在 Java 代码中需要使用 AssetManager 来访问。一般使用 Java 开发的 Android 工程使用的资源文件都会放在 res下。
  • lib 文件夹:so 文件存放位置。该目录存放着应用需要的 native 库文件。文件夹下有时会多一个层级,这是根据不同CPU 型号而划分的,如 ARM,ARM-v7a,x86等。
  • META-INF:签名证书目录
  • AndroidManifest.xml:这是 Android 应用的全局配置文件,它包含了这个应用的很多配置信息,例如包名、版本号、所需权限、注册的服务等。可以根据这个文件在相当程度上了解这个应用的一些信息。该文件是被编译为二进制的 XML 文件,可以通过一些工具(如 apktool、jadx、jeb、AndroidKiller 等)反编译后进行查看。也可以通过 android studio ---> build ---> Analyze Apk 来分析 apk
  • dex 文件:classes.dex 文件是 Android 系统运行于 Dalvik Virtual Machine 上的可执行文件,也是Android 应用程序的核心所在。项目工程中的 Java 源码通过 javac 生成 class 文件,再通过 dx 工具转换为 classes.dex,注意到我们这里有 classes2.dex 和 classes3.dex。这是方法数超过一个 dex 的上限,分 dex 的结果。
  • resource.arsc 文件:字符串、资源索引文件。它记录了资源文件,资源文件位置(各个维度的路径)和资源 id 的映射关系。并且将所有的 string 都存放在了 string pool 中,节省了在查找资源时,字符串处理的开销。
  • META-INF 文件夹:该目录的主要作用是用于保证 APK 的完整性以及安全性。该文件夹下,主要有三个文件。
            MANIFEST.MF:这个文件保存 整个apk文件所有文件的文件名 + SHA-1后的编码值。这也就意味着,MANIFEST.MF 象征着 apk 包的完整性。
            XXX.RSA:这个文件保存了公钥和加密方式的信息。
            XXX.SF:这个文件与 MANIFEST.MF 的结构一样,只是其编码会被被私钥加密。
                               这样一来每次安装时,通过该文件夹中的文件,就可以完成验证的过程。
                               如果 apk 包被改变了,而篡改者没有私钥生成 CERT.SF,则无法完成校验。

3、apk 打包、反编译后重打包

apk 打包流程

官网文档 ---> 配置构建:https://developer.android.google.cn/studio/intro?hl=zh-cn

下图的是官网对于Android编译打包流程的介绍。

官方的介绍非常笼统,简而言之,其大致流程就是: 编译–>DEX–>打包–>签名和对齐

来一张外国大神的图片(注:这张图少了签名的步骤)

流程图:

重点关心的是

  • (1)这个过程的输入是什么?
  • (2)这个过程的输出是什么?
  • (3)这个过程使用了什么工具?至于使用什么参数,可以看对应命令的帮助。

aapt -> aidl -> javac -> dx(dex) -> apkbuilder -> jarsigner -> zipalign

步骤中提到的工具如下表:

名称 功能介绍 在操作系统中的路径
aapt Android 资源打包工具 ${ANDROID_SDK_HOME}/platform-tools/appt
aidl

Android接口描述语言转化为.java文件的工具

( aidl 全名 Android Interface Definition Language,即Android接口定义语言 )

${ANDROID_SDK_HOME}/platform-tools/aidl
javac Java Compiler ${JDK_HOME}/javac或/usr/bin/javac
dex 转化.class文件为Davik VM能识别的.dex文件 ${ANDROID_SDK_HOME}/platform-tools/dx
apkbuilder 生成 apk 包 (SDK3.0 之后弃用而使用 sdklib.jar 打包 apk ${ANDROID_SDK_HOME}/tools/opkbuilder
jarsigner .jar文件的签名工具 ${JDK_HOME}/jarsigner或/usr/bin/jarsigner
zipalign 字节码对齐工具 ${ANDROID_SDK_HOME}/tools/zipalign

补充:apkbuilder 在 SDK3.0 之前使用 apkbuilder 去打包,在 SDK3.0 之后就弃用了,而使用 sdklib.jar 打包 apk。

下面各个工具在打包中的用法:

编译打包步骤:

  • 1. 打包资源文件,生成R.java文件。打包资源的工具是aapt(The Android Asset Packaing Tool)(E:\Documents\Android\sdk\build-tools\25.0.0\aapt.exe)。在这个过程中,项目中的AndroidManifest.xml文件和布局文件XML都会编译,然后生成相应的R.java,另外AndroidManifest.xml会被aapt编译成二进制。存放在APP的res目录下的资源,该类资源在APP打包前大多会被编译,变成二进制文件,并会为每个该类文件赋予一个resource id。对于该类资源的访问,应用层代码则是通过resource id进行访问的。Android应用在编译过程中aapt工具会对资源文件进行编译,并生成一个resource.arsc文件,resource.arsc文件相当于一个文件索引表,记录了很多跟资源相关的信息。
  • 2. 处理aidl文件,生成相应的Java文件。这一过程中使用到的工具是aidl(Android Interface Definition Language),即Android接口描述语言(E:\Documents\Android\sdk\build-tools\25.0.0\aidl.exe)。aidl工具解析接口定义文件然后生成相应的Java代码接口供程序调用。如果在项目没有使用到aidl文件,则可以跳过这一步。
  • 3. 编译项目源代码,生成class文件。项目中所有的Java代码,包括R.java.aidl文件,都会变Java编译器(javac)编译成.class文件,生成的class文件位于工程中的bin/classes目录下。
  • 4. 转换所有的class文件,生成classes.dex文件。dx工具生成可供Android系统Dalvik虚拟机执行的classes.dex文件,该工具位于(E:\Documents\Android\sdk\build-tools\25.0.0\dx.bat)。任何第三方的libraries.class文件都会被转换成.dex文件。dx工具的主要工作是将Java字节码转成成Dalvik字节码、压缩常量池、消除冗余信息等。
  • 5. 打包生成APK文件。所有没有编译的资源,如images、assets目录下资源(该类文件是一些原始文件,APP打包时并不会对其进行编译,而是直接打包到APP中,对于这一类资源文件的访问,应用层代码需要通过文件名对其进行访问);编译过的资源和.dex文件都会被apkbuilder工具打包到最终的.apk文件中。打包的工具apkbuilder位于 android-sdk/tools目录下。apkbuilder为一个脚本文件,实际调用的是(E:\Documents\Android\sdk\tools\lib)文件中的com.android.sdklib.build.ApkbuilderMain类。
  • 6. 对APK文件进行签名。一旦APK文件生成,它必须被签名才能被安装在设备上。在开发过程中,主要用到的就是两种签名的keystore。一种是用于调试的debug.keystore,它主要用于调试,在Eclipse或者Android Studio中直接run以后跑在手机上的就是使用的debug.keystore。另一种就是用于发布正式版本的keystore。
  • 7. 对签名后的APK文件进行对齐处理。如果你发布的 apk 是正式版的话,就必须对APK进行对齐处理,用到的工具是zipalign(E:\Documents\Android\sdk\build-tools\25.0.0\zipalign.exe)。对齐的主要过程是将APK包中所有的资源文件距离文件起始偏移为4字节整数倍,这样通过内存映射访问apk文件时的速度会更快。对齐的作用就是减少运行时内存的使用。

上述流程都是 Android Studio 在编译时调用各种编译命令自动完成的

第一步:打包资源文件,生成R.java文件

编译 R.java 类 需要用到 AndroidSDK 提供的 aapt 工具,aapt 参数众多,以下是主要参数:

 -d  one or more device assets to include, separated by commas  
 -f  force overwrite of existing files  
 -g  specify a pixel tolerance to force images to grayscale, default 0  
 -j  specify a jar or zip file containing classes to include  
 -k  junk path of file(s) added  
 -m  make package directories under location specified by -J  
 -u  update existing packages (add new, replace older, remove deleted files)  
 -v  verbose output  
 -x  create extending (non-application) resource IDs  
 -z  require localization of resource attributes marked with  
     localization="suggested"  
 -A  additional directory in which to find raw asset files  
 -G  A file to output proguard options into.  
 -F  specify the apk file to output  
 -I  add an existing package to base include set  
 -J  specify where to output R.java resource constant definitions  
 -M  specify full path to AndroidManifest.xml to include in zip  
 -P  specify where to output public resource definitions  
 -S  directory in which to find resources.  Multiple directories will be scann  

aapt 编译 R.java 文件具体如下:
需要进入应用程序目录,新建一个gen目录,没有gen目录,命令将会出现找不到文件的错误!
命令成功执行后将会在 gen 目录下生成成包结构的目录树,及 R.java 文件。

列子:

aapt 资源编译

  1. 编译assets目录和res/raw目录下的资源
  2. 编译res目录下的资源文件
  3. 给res目录下的每个资源赋予一个资源ID,生成resource.arsc资源索引文件
  4. 解析并编译AndroidMainifest.xml
  5. 资源打包成*.ap_,资源ID常量定义自R.java

资源索引

aapt 给每一个非 assets 目录的资源定义一个资源ID,它是一个4字节(byte = 32bit)的数字,格式是PPTTNNNN,PP代表资源所属的包(package),TT代表资源的类型(Type),NNNN代表这个类型下面的资源名称(Entry ID)。

  • Package ID相当于是一个命名空间,标定资源的来源。系统资源的命名空间,它的package ID等于0x01;自己的APP资源名称空间,Package ID一般定义为0x7f。
  • Type ID是指资源的类型ID。资源的类型都有animator、anim、color、drawable、layout、menu、raw、string和xml等等若干种,每一种都会被赋予一个ID。
  • Entry ID是指每一个资源在其所属的资源类型中所出现的次序。

代码编译和打包

  • AIDL -> 生成对应的java接口
  • Javac -> 生成.class文件
  • dex-> 生成dex文件
  • APkBuilder:aapt打包好的资源、dex打包好的代码文件、第三方库资源和jar文件、native -> apk

第二步:处理AIDL文件,生成对应的.java文件

当然,有很多工程没有用到AIDL,那这个过程就可以省了

将 .aidl 文件生成 .java 文件需要用到 AndroidSDK 自带的 aidl 工具,此工具具体参数如下:

-I<DIR>    search path for import statements.  
-d<FILE>   generate dependency file.  
-p<FILE>   file created by --preprocess to import.  
-o<FOLDER> base output folder for generated files.  
-b         fail when trying to compile a parcelable.  
值得注意的是:这个工具的参数与参数值之间不能有空格。

例子:

第三步:编译Java文件,生成对应的.class文件

javac 命令用法如下:

其中,可能的选项包括:  
  -g                         生成所有调试信息  
  -g:none                    不生成任何调试信息  
  -g:{lines,vars,source}     只生成某些调试信息  
  -nowarn                    不生成任何警告  
  -verbose                   输出有关编译器正在执行的操作的消息  
  -deprecation               输出使用已过时的 API 的源位置  
  -classpath <路径>            指定查找用户类文件和注释处理程序的位置  
  -cp <路径>                   指定查找用户类文件和注释处理程序的位置  
  -sourcepath <路径>           指定查找输入源文件的位置  
  -bootclasspath <路径>        覆盖引导类文件的位置  
  -extdirs <目录>              覆盖安装的扩展目录的位置  
  -endorseddirs <目录>         覆盖签名的标准路径的位置  
  -proc:{none,only}          控制是否执行注释处理和/或编译。  
  -processor <class1>[,<class2>,<class3>...]要运行的注释处理程序的名称;绕过默认的搜索进程  
  -processorpath <路径>        指定查找注释处理程序的位置  
  -d <目录>                    指定存放生成的类文件的位置  
  -s <目录>                    指定存放生成的源文件的位置  
  -implicit:{none,class}     指定是否为隐式引用文件生成类文件  
  -encoding <编码>             指定源文件使用的字符编码  
  -source <版本>               提供与指定版本的源兼容性  
  -target <版本>               生成特定 VM 版本的类文件  
  -version                   版本信息  
  -help                      输出标准选项的提要  
  -Akey[=value]              传递给注释处理程序的选项  
  -X                         输出非标准选项的提要  
  -J<标志>                     直接将 <标志> 传递给运行时系统  

例子:

javac -encoding utf-8 -target 1.5 -bootclasspath E:\Androiddev\android-sdk-windows2.2\platforms\android-3\android.jar -d bin src\com\byread\reader\*.java gen\com\byread\reader\R.java 

第四步:把.class文件转化成Davik VM支持的.dex文件

将工程 bin目录下的 class 文件编译成 classes.dex,Android 虚拟机只能执行 dex 文件。

例子:

第五步:打包生成未签名的 .apk 文件

  • 【输入】打包后的资源文件、打包后类文件(.dex文件)、libs文件(包括.so文件,当然很多工程都没有这样的文件,如果你不使用C/C++开发的话)
  • 【输出】未签名的.apk文件
  • 【工具】apkbuilder工具

apkbuilder  工具用法如下:

-v      Verbose.  
-d      Debug Mode: Includes debug files in the APK file.  
-u      Creates an unsigned package.  
-storetype Forces the KeyStore type. If ommited the default is used.  
-z      Followed by the path to a zip archive.  
        Adds the content of the application package.  
-f      Followed by the path to a file.  
        Adds the file to the application package.     
-rf     Followed by the path to a source folder.  
        Adds the java resources found in that folder to the application  
        package, while keeping their path relative to the source folder.     
-rj     Followed by the path to a jar file or a folder containing  
        jar files.  
        Adds the java resources found in the jar file(s) to the application  
        package.     
-nf     Followed by the root folder containing native libraries to  
        include in the application package.

列子:

apkbuilder  ${output.apk.file} -u -z  ${packagedresource.file} -f  ${dex.file}  -rf  ${source.dir}  -rj  ${libraries.dir} 

第六步:对未签名 .apk 文件进行签名

通过 jarsigner 命令用证书文件对未签名的 APK 文件进行签名

【输入】未签名的.apk文件
【输出】签名的.apk文件
【工具】jarsigner

用法:jarsigner [选项] jar 文件别名  
           jarsigner -verify [选项] jar 文件    
[-keystore <url>]           密钥库位置    
[-storepass <口令>]         用于密钥库完整性的口令    
[-storetype <类型>]         密钥库类型    
[-keypass <口令>]           专用密钥的口令(如果不同)    
[-sigfile <文件>]           .SF/.DSA 文件的名称    
[-signedjar <文件>]         已签名的 JAR 文件的名称    
[-digestalg <算法>]    摘要算法的名称    
[-sigalg <算法>]       签名算法的名称    
[-verify]                   验证已签名的 JAR 文件    
[-verbose]                  签名/验证时输出详细信息    
[-certs]                    输出详细信息和验证时显示证书    
[-tsa <url>]                时间戳机构的位置    
[-tsacert <别名>]           时间戳机构的公共密钥证书    
[-altsigner <类>]           替代的签名机制的类名    
[-altsignerpath <路径列表>] 替代的签名机制的位置    
[-internalsf]               在签名块内包含 .SF 文件    
[-sectionsonly]             不计算整个清单的散列    
[-protected]                密钥库已保护验证路径    
[-providerName <名称>]      提供者名称    
[-providerClass <类>        加密服务提供者的名称  
[-providerArg <参数>]] ... 主类文件和构造函数参数  

只需要按步骤生成 MANIFEST.MF, CERT.RSA,CERT.SF 并放入META-INF 文件夹即可。

第七步:对签名后的.apk文件进行对齐处理

不进行对齐处理是不能发布到Google Market的

【输入】签名后的.apk文件
【输出】对齐后的.apk文件
【工具】zipalign工具

知道了这些细节之后,我们就可以实现很多我们想实现东西了,比如:自动化,我们可以使用某种脚本,像Windows下的批处理,linux下的Bash,Java下的Ant,Python、Perl这样的脚本语言,甚至直接用Java、.net这们的强类型语言也是可以的。

APK 打包 总结

以上便是APK打包的整个流程,总结一下:

  • 除了assets和res/raw资源被原装不动地打包进APK之外,其它的资源都会被编译或者处理;
  • 除了assets资源之外,其它的资源都会被赋予一个资源ID;
  • 打包工具负责编译和打包资源,编译完成之后,会生成一个resources.arsc文件和一个R.java,前者保存的是一个资源索引表,后者定义了各个资源ID常量。
  • 应用程序配置文件AndroidManifest.xml同样会被编译成二进制的XML文件,然后再打包到APK里面去。
  • 应用程序在运行时通过AssetManager来访问资源,或通过资源ID来访问,或通过文件名来访问。

APP 脱壳重打包

记一次APP脱壳重打包过程:https://bbs.pediy.com/thread-220151.htm

4、APK 安装流程

Android中APK安装流程解析:https://blog.csdn.net/mysimplelove/article/details/93619361

如果使用 WinHex 打开apk文件,从文件头就可以看出APK安装包其实就是一个zip格式的压缩包,所以我们只需将apk文件的后缀修改为 .zip 或 .rar ,就可以轻松的在电脑上打开并查看apk软件内部的文件和数据(当然你也可以使用手机R.E管理器查看)。

Android 安装 apk包的五种方式:

  • 1, 通过工具安装:R.P 或 ES等管理工具,有安装界面。
  • 2,adb install 安装,无安装界面。
  • 3,android market 在线安装,无安装界面。
  • 4,直接 copy 到系统目录 /system/app 下,无安装界面。此目录下的应用一般是系统自带的系统级 应用。
  • 5,pm(android 系统自带工具) 命令行安装,无安装界面。

apk 安装后、文件位置

apk 文件在安装到手机过程中,涉及到如下几个目录:

/system/framwork:    保存的是资源型的应用程序,它们用来打包资源文件。
/data/app-private:   保存受DRM保护的私有应用程序。
/vendor/app:         保存设备厂商提供的应用程序。
/system/app           ------- 系统自带的应用安装目录,获得 adb root权限才能删除
/data/app             ------- 用户程序安装的目录,安装时把 apk文件 复制到 此目录
/data/data            ------- 存放应用程序的数据
/data/dalvik-cache    将 apk 中的 dex文件 安装到 dalvik-cache 目录下。dex文件是dalvik虚拟机的可执行文件

/data/system:该目录下的 packages.xml 文件,类似于Windows的注册表,记录了系统的permissions,每个 apk 的 name,codePath,,version,userid 等信息主要通过解析AndroidManifest.xml 文件获取,解析完apk后将更新信息写入这个文件并保存到 flash,下次开机的时候直接从里面读取相关信息并添加到内存相关列表中。当有APK安装、升级或者删除时会更新这个文件。

/data/system/packages.xml/ 和 data/system/packages.list: packages.list指定了应用的默认存储位置/data/data/com.xxx.xxx;package.xml中包含了该应用 权限、应用包名、APK的安装位置、版本、userID等信息,并且两者都有同一个userld。之所以每个应用都要一个userId,是因为Android在系统设计上把每个应用当做Linux系统上的一个用户对待, 这样就可以利用已有的Linux用户管理机制来设计Android应用,比如应用目录,应用权限,应用进程管理等。

android文件存储路径分为三小块:

1.app 私有,用户不可见的 /data/user/0 开头的:

  • context.filesDir  -->  /data/user/0/包名/files
  • context.cacheDir  -->  /data/user/0/包名/cache
  • context.getDir("TestDir")  -->  /data/user/0/包名/app_TestDir

2.app 私有,用户可见的 /storage/emulated/0/Android/data/包名/ 开头的:

  • context.externalFilesDir(null)  -->  /storage/emulated/0/Android/data/包名/files
  • context.externalCacheDir()  -->  /storage/emulated/0/Android/data/包名/cache
  • context.externalFilesDir("TestPicture")  -->  /storage/emulated/0/Android/data/包名/files/TestPicture

3.公共目录,用户可见的/storage/emulated/0 下的:

  • api:Enviroment.getExternalStorageDirectory,Environment.getExternalStoragePublicDirectory  -->  /storage/emulated/0
  • androidQ以后不能直接访问,只能通过ContentResolver访问,具体参考别人的一遍文章,https://www.jianshu.com/p/d6e0271354d7

app 私有的都是 context 的 api,公共目录的都是 Environment 的 api 的。

android6 和 android10 都有比较大的限制变化。

android 私有目录和公有目录

https://blog.csdn.net/gc1074959890/article/details/111691682

  • /data/data/ apk 的安装目录。 如:百度地图的安装路径是/data/data/com.baidu.com/ 注意:该目录需要获取root权限才能查看
  • /system/ 存放系统应用的apk文件,即手机厂商预安装应用的apk文件 (手机厂商只需把需要预安装的apk放在该节点的相应路径下,android系统就会自己解压并安装该apk)
  • /storage/ 该节点是内置存储卡和外置SD卡的挂载点,/storage/emulated/0/是内置存储卡挂载点,/storage/sdcard1是外置SD卡(即外插入的SD卡)挂载点(不同的设备挂载节点不一样,有些设备可能会挂载到/mnt/节点)。

公有目录和私有目录是相对于一个或多个应用而言的,基于Android文件系统的权限管理。

私有目录是指仅某个应用自己可管理访问的文件目录,即该目录归属于该应用。
应用私有的目录主要分布在两个地方内部存储和外部存储。

对于每个应用,系统都会在内部存储空间中提供目录,应用可以在该存储空间中整理其文件。一个目录专为应用的持久性文件而设计,而另一个目录包含应用的缓存文件。您的应用不需要任何系统权限即可读取和写入这些目录中的文件。

如果内部存储空间不足以存储应用专属文件,请考虑改为使用外部存储空间。系统会在外部存储空间中提供目录,应用可以在该存储空间中整理仅在应用内对用户有价值的文件。一个目录专为应用的持久性文件而设计,而另一个目录包含应用的缓存文件。

app 安装过程

当新安装一个应用的时候,通常系统会执行以下流程:

  1. ---> 校验 apk 包签名
  2. ---> 复制程序包到 /data/app 目录
  3. ---> 建立 /data/data/com.xxx 数据目录
  4. ---> 提取释放lib文件 
  5. ---> dexopt 优化 classes.dex 处理后释放到 /data/dalvik-cache 目录

安装过程中 Android 系统服务还会更新以下系统文件:

  • /data/system/packages.list   --  分配存储 APP 的 uid/gid,也就是用户/所属组
  • /data/system/packages.xml  --  记录保存 APP 权限、签名证书、安装路径、版本信息等

由此可见

  • 安装好的软件程序包被原本不动存放在 /data/app 目录中,
  • 数据目录则被安放在 /data/data/pkg_name(包名) 目录中。
  • 而卸载过程则相反,进行删除相关文件处理。

整个 apk 安装流程:

  • 复制 APK 到 /data/app 目录 下,解压(会放到 data/app/包名/ 目录下面,同时 apk 中的 so 文件 也会拷贝到此目录下的 lib 文件目录中。)并扫描安装包。而系统出厂的 apk 被放到 /system 分区下,这就是没有 ROOT 手机前,我们无法删除系统app的原因。( 下面 是用 MT 管理器 (需要获取root 权限) 打开)
  • 资源管理器解析 APK 里的资源文件。
  • 解析 AndroidManifest.xml(Android应用程序的解析过程就是解析这个xml文件的过程)。解析的内容会被更新到/data/system/packages.xml 和 /data/system/packages.list中,packages.list 中指名了该应用默认存储的位置,packages.xml 中包含了该应用申请的权限、签名和代码所在位置等信息,并且两者都有一个 相同的 userId。之所以每个应用都有一个userId,是因为Android 在系统设计上把每个应用当作Linux系统上的一个用户对待,这样就可以利用已有的Linux上用户管理机制来设计Android应用,比如应用目录,应用权限,应用进程管理等。做完以上操作,就相当于应用在系统注册了,可以被系统识别。接下来就得保存应用的执行文件了,根据 packages.xml中指定的 codePath,创建一个目录,即/data/data/ 目录 下创建对应的 应用数据目录apk会被命名成 base.apk 并拷贝到此,其中 lib 目录用来存放 native 库
  • 然后对 dex 文件进行优化,并保存在 dalvik-cache目录 下。其命名规则是 apk路径+classes.dex。(此时应用就可以运行了,但如果每次应用运行还得去base.apk中取dex文件,效率就太低了。为了提升效率,Android系统在应用安装时还会做些优化操作,把所有可运行的dex文件单独提取放在一块并做些优化。在Dalvik模式下,会使用dexopt把base.apk中的dex文件优化为odex,存储在/data/dalvik-cache中,如果是ART模式,则会使用dex2oat优化成oat文件也存储在该目录下,并且文件名一样,但文件大小会大很多,因为ART模式会在安装时把dex优化为机器码,所以在ART模式下的应用运行更快,但apk安装速度相对Dalvik模式下变慢,并且会占用更多的ROM。
  • 将 AndroidManifest 文件解析出的四大组件信息注册到 PackageManagerService 中。
  • 安装完成后,发送广播。
  • 显示 icon 图标:应用程序经过 PMS 中的逻辑处理后,相当于已经注册好了,如果想要在Android 桌面上看到 icon 图标,则需要 Launcher 将系统中已经安装的程序展现在桌面上。

安装图解和过程描述:

安装过程并没有把资源文件, assets目录下文件拷贝出来,他们还在apk包里面呆着,所以,当应用要访问资源的时候,其实是从apk包里读取出来的。其过程是,首先加载apk里的resources.arsc(这个文件是存储资源Id与值的映射文件),根据资源id读取加载相应的资源。

删除安装过程:就是删除在上述三个目录下创建的文件及目录。

总体说来就两件事情:拷贝APK解析APK,解析APK主要是解析 AndroidManifest.xml,以便获得它的安装信息。在安装的过程中还会这个应用分配 Linux用户ID 和 Linux用户组ID(以便它可以在系统中获取合适的运行权限)。

涉及的三个进程

  • PackageInstaller进程:PackageInstaller事实上是一个应用,它负责APK安装以及卸载过程中与用户的交互流程。
  • SystemServer进程:该进程主要运行的是系统服务,APK的安装、卸载和查询都由PackageManagerService负责,它也是Android核心系统服务的一种,在SystemServer里初始化系统服务的时候被启动。
  • DefaultContainerService进程:DefaultContainerService也是一个单独的进程,它主要负责检查和复制设备上的文件,APK的复制就是由DefaultContainerService来完成的。

apk 解析流程

  • Android不同类型
    /system/framwork:保存的是资源型的应用程序,它们用来打包资源文件。
    /system/app:保存系统自带的应用程序。
    /data/app:保存用户安装的应用程序。
    /data/app-private:保存受DRM保护的私有应用程序。
    /vendor/app:保存设备厂商提供的应用程序。
  • DEX的dexopt流程
    dexopt操作实际上对DEX文件在执行前进行一些优化,但是不同的虚拟机操作有所不同。
    Davlik:将dex文件优化生成odex文件,这个odex文件的后缀也是dex,保存在/data/dalvik-cache目录下。
    ART:将dex文件翻译生成oat文件

apk 修改、反编译

如果只是修改程序包的一些图片/assets资源,直接在电脑上用解压缩工具,解压后就可以修改替换,但是重新打包后需要重新签名,否则没有签名或者签名校验不正确的应用是无法安装成功的。

而如果想要修改apk包中其他的已经编译后的文件,则需要反编译。由于 Android 本质上就是一个 Java 虚拟机,而 classes.dex 文件则是众多 .class 文件的打包集合,一般我们先要使用 dex2jar 将 classes.dex 解包为 Java jar 文件,然后再通过 JD-GUI 将 jar 文件的 .class 文件反编译为 .java 源码。

整个反编译dex的过程原理大致简单来说就是这样,但是实际操作起来难度不小,因为很多程序都经过了混淆加密处理(比如QQ,微信等等大公司的软件不可能让人分分钟反编译破解,否则整个安卓APP世界还不乱套了...)

5、apk 启动( 点击图标,APP加载流程 )

Android 开机 过程

Android手机在开机 Linux内核启动的时候,会加载 system/core/init/init.rc 文件,启动 init 进程,这个是 Android 特有的初始化程序,主要负责

  • 各种复杂工作
  • 负责开关机画面
  • 文件系统的创建和挂载
  • 启动Zygote(孵化器)进程
  • 启动 ServiceManager,它是Binder服务管理器,管理所有Android系统服务

fork Zygote

在启动 init 进程后会 fork Zygote 进程,它是一个孵化器进程,它的 main 函数会创建好环境并且等待孵化,接到指令后就会开始 fork 子进程

  • 创建一个 server 端的 socket, name 为 zynote,用于和客户端进程通信
  • 预加载类和资源,提高应用启动速度
  • 启动 SystemServer 进程
  • 监听 socket,当有一个应用程序启动时,就会向它发出请求,然后 zygote 进程 fock 自己来创建的一个新的子进程。

所以说 Android 内所有的应用其实都是 Zygote 的子进程

SystemServer

SystemServer是由zygote进程fork出来的第一个进程,SystemServer和Zygote是Android Framework最重要的2个进程。 系统里面重要的服务都是在这个进程里面开启的,比如ActivityManagerService(AMS)、PackageManagerService(PMS)、WindowManagerService(WMS)。

应用启动流程基本是围绕着ActivityManagerService和ActivityThread展开。

并且负责启动并且管理整个framework。

ActivityManagerService

ActivityManagerService,简称AMS,服务端对象,负责系统中所有Activity的生命周期。
并且他在SystemServer进程开启的时候,就会直接跟着一起初始化

Android手机开机启动过程

  • 1.  系统上电。当给Android系统上电,在电源接通的瞬间,CPU内的寄存器和各引脚均会被置为初始状态,CPU复位之后,程序指针会指向启动地址,从该地址读取并直接运行启动程序的可执行代码,或者将可执行代码与数据载入CPU内置的RAM中再运行。这一段代码,放在PC中,叫做BIOS,而在Android等嵌入式系统中就叫做Bootloader。
  • 2.  Bootloader引导程序。通过Bootloader引导程序,可以初始化硬件设备,建立内存空间映射图,为操作系统内核准备好环境,完成整个系统的加载启动任务。Bootloader是支持交互式操作的,一般刷机中会用到的Fastboot模式,就是在Bootloader启动之后,根据用户输入的指令(在手机上用音量键,Home健等操作),停留在当前状态。而正常情况下,Bootloader程序执行完成之后,就会开始加载Android操作系统。
  • 3.  加载内核。我们知道Android操作系统的内核其实就是Linux内核。当Bootlader引导程序启动之后,就会进入Linux的内核引导阶段。该阶段会初始化内核和设备驱动,接着启动内核,挂载文件系统,最后启动用户空间进程。

  • 4.  Init 程序。Init程序是用户空间的第一个进程。它负责解析一个叫做init.rc的配置文件,启动Android系统底层运行的一些服务进程。init.rc文件定义了不同的Action和Service。Init程序同时也会开启属性服务,存放一些关于Android系统的全局系统设置信息。最后,当所有的Action和Service都启动完成之后,Init程序就会进入一个无限的循环,监听系统中发生的事件,对一些事件进行处理,比如重启某些服务等。

  • 5.  ServiceManager。ServiceManager是Init程序启动的守护进程之一,它是Android中Binder通信机制的基础。ServiceManager提供了注册,检索服务的功能。在Zygote子进程system_server启动的每一项服务都会将其注册到ServiceManager中,由ServiceManager来统一进行管理。值得注意的是,ServiceManager本身也是一个Service,所以它也会将自己注册到自身上面。
    而ServiceManager启动的过程做了三件事:
    (1)打开Binder设备,把Binder设备映射到内核空间中,由于内核空间是所有进程共享的区域,所以借助这块区域,可以实现不同进程间资源的共享,从而达到进程间通信。
    (2)ServiceManager本身会变成一个ContextManager,即上面所说的注册到自己身上,负责各种Service的上下文管理。
    (3)最终ServiceManager也是会进入一个无限循环。

  • 6.  Zygote。Zygote也是由Init程序启动的其中一个服务,是Android中的第一个Dalvik虚拟机。在之后,所有的Dalvik虚拟机都是由Zygote孵化出来的,这是因为Android中每一个应用都有着自己的进程,而每一个进程中都着一个Dalvik虚拟机,如果每起一个应用就创建一个虚拟机,效率太低,而通过复制Zygote,不仅可以提高创建的速度,还能够共享系统和框架的资源,可以大幅度提高应用程序启动的效率。
    Zygote启动之后:
    (1)首先会创建一个socket,等待某个客户端的请求。
    (2)接着创建其第一个子进程system_server。system_server会开启Android系统的Native Service和Java Service。其中Native Service就是那些利用JNI实现的共享库,而Java Service则是通过Java语言实现的ActivityManagerService等,都是在这个阶段由system_server启动并注册到一个叫ServiceManager的服务进程中。ActivityManagerService启动之后,会进入到一个叫System Ready的状态,而在其达到System Ready的状态之前呢,就会启动一个叫做Launcher的应用,而这个Launcher就是Android系统启动成功之后见到的Home桌面了。
    而在SystemReady的状态,其会跟Zygote启动之初的Socket进行通信,告诉Zygote一切已经就绪。
    (3)Zygote知道已经启动成功,就开始进入一个无限的循环,在Socket上等待请求的到来。至此,从Android手机上电的那一刻起,到加载内核,到最后显示Home桌面的整个过程就结束了。

APK 启动 过程

要想优化 App 启动时间,第一步就是了解 App 启动进程的工作原理。 有几个基础理论:

Android Application 与其他移动平台有两个重大不同点:

  • 每个Android App 都在一个独立空间里,意味着其运行在一个单独的进程中,拥有自己的VM,被系统分配一个唯一的 user ID。
  • Android App 由很多不同组件组成,这些组件还可以启动其他 App 的组件。因此,Android App 并没有一个类似程序入口的 main() 方法。

Android Application 组件包括:

  • Activities: 前台界面, 直接面向User, 提供UI和操作.
  • Services: 后台任务.
  • Broadcast Receivers: 广播接收者.
  • Contexnt Providers: 数据提供者.

Android进程与Linux进程一样。默认情况下,每个 apk 运行在自己的 Linux 进程中。 另外,默认一个进程里面只有一个线程---主线程。这个主线程中有一个 Looper 实例,通过调用 Looper.loop() 从 Message队列里面取出 Message 来做相应的处理。

Zygote 是 Android 平台的非常基础的进程. 这个进程初始化了第一个VM, 并且预加载了framework和众多App所需要的通用资源. 然后它开启一个Socket接口来监听请求, 根据请求孵化出新的VM来管理新的App进程. 一旦收到新的请求, Zygote会基于自身预先加载的VM来孵化出一个新的VM创建一个新的进程.

启动Zygote之后, init进程会启动runtime进程. Zygote会孵化出一个超级管理进程---System Server. SystemServer会启动所有系统核心服务, 例如Activity Manager Service, 硬件相关的Service等. 到此, 系统准备好启动它的第一个App进程---Home进程了.

Android Application 启动流程分析( 点击图标,APP加载流程 )

打开应用总共涉及了三个进程:Zygote进程、AMS 进程、APP进程

App进程与AMS通过Binder机制进行跨进程通信
AMS(SystemServer进程)与zygote通过Socket进行跨进程通信。

总结:

  • 点击桌面App图标,Launcher进程采用Binder IPC向system_server进程发起startActivity请求; system_server进程接收到请求后,向zygote进程发送创建进程的请求;
  • Zygote进程fork出新的子进程,即App进程;
  • App进程,通过Binder IPC向sytem_server进程发起attachApplication请求;
  • system_server进程在收到请求后,进行一系列准备工作后,再通过binder IPC向App进程发送scheduleLaunchActivity请求;
  • App进程的binder线程(ApplicationThread)在收到请求后,通过handler向主线程发送LAUNCH_ACTIVITY消息;
  • 主线程在收到Message后,通过发射机制创建目标Activity,并回调Activity.onCreate()等方法

一个 APK 只有一个 Application。每个 Apk 运行时都需要有一个 Application对象,Application对象执行 onCreate方法时APP就开始运行

一、Zygote进程启动过程:

在Android系统中,DVM和ART、应用程序进程以及运行系统的关键服务的SystemServer进程都是由Zygote进程来创建的,我们也将它称为孵化器。

Zygote进程主要做了如下几个工作:

(1)创建AppRuntime并调用其start方法,启动Zygote进程(此过程是在init进程结束的时候执行的)

(2)创建java虚拟机并为java虚拟机注册JNI方法

此步骤主要是通过JNI进行一个从native层到java层的一个过渡。

(3)通过JNI调用ZygoteInit的main函数进入Zygote的java框架层

(4)通过registerZygoteSocket方法创建服务器Socket

通过registerZygoteSocket创建一个server端的socket,其中name为“zygote”,来等待ActivityManagerService请求Zygote来创建新的应用程序进程,同时进行预加载类和资源。

(5)启动SystemServer进程

Zygote通过fork方法创建SystemServer子进程。创建的SystemServer进程的用户id和组id为1000,进程名为:system_server,启动的类名为:com.android.server.SystemServer。

(6)通过runSelectLoop方法等待AMS的请求来创建新的应用程序进程

此方法中存在一个列表fds,用了保存AMS传送过来的要创建子进程的信息,这里面启动了一个无限循环,用来等待AMS请求Zygote进程创建新的应用进程,每次AMS来连接后会存放到fds中,创建子进程成功后,将对应连接从fds列表中删除。

二、SystemServer进程启动过程:

SystemServer进程主要用于创建系统服务,我们熟知的AMS、WMS和PMS都是由它来创建的。

SystemServer进程启动后主要做的工作如下:

(1)启动Binder线程池,这样就可以与其他进程进行通信

通过调用native层的代码nativeZygoteInit方法,来启动一个Binder线程池

(2)创建SystemServiceManager,其用于对系统的服务进程创建、启动和生命周期管理

启动Binder线程池后,紧接着会进入SystemServer的main方法(可以看做是SystemServer进程的入口),在main方法中,首先会创建一个消息looper,紧接着便会创建SystemServiceManager,它会对系统服务进行创建、启动和生命周期管理。

(3)启动各种系统服务

首先系统服务分为三种:引导服务、核心服务、其他服务。SystemServiceManager会依序将所有系统服务启动起来。对应方法为:startBootstrapServices()、startCoreServices()、startOtherServices()。

引导服务比如:PowerManagerService、ActivityManagerServer、PackageManagerService等

核心服务比如:BatteryService、UsagestatsService等

其他服务比如:WindowManagerService、CameraService、AlarmManagerService等

服务启动后会添加到mServices集合中,其中mServices是一个存储SystemService类型的ArrayList。上述所有系统服务都是SystemService的子类。Client端如果要使用某个Service,则需要查询对应的Service的相关信息,然后根据Service的额相关信息与Service所在的Server进程建立通信通路,这样Client端就可以使用Service了。

三、Launcher启动过程:

系统启动的最后一步是启动一个应用程序来显示系统中已经安装的应用程序,这个应用程序就叫做Launcher。Lanucher在启动过程中会请求PackageManagerService返回系统中已经安装的应用程序信息,并将这些信息封装成一个快捷图标列表显示在系统屏幕上。

通俗来讲Launcher就是Android系统的桌面,它的主要作用有:

(1)作为Android系统的启动器,用于启动应用程序

(2)作为Android系统的桌面,用于显示和管理应用程序的快捷图标或者其他桌面组件。

1、Launcher的的启动过程:

首先SystemServer进程会在启动的过程中启动PackageManagerService,PackageManagerService启动后会将系统中的应用程序安装完成,再此前已经启动的AMS会将Launcer启动起来,启动Launcher的入口为AMS的systemReady方法,它在Systemserver的startOtherServices(启动其他服务)方法中被调用。(注:AMS是在startBootstrapServices()方法中启动的)。

systemReady方法中最终是通过Intent进行启动的,其中action为:Intent.ACTION_MAIN、Category为Intent.CATEGORY_HOME。启动方法与普通的activity方式类似。

2、Launcher中应用图标显示过程:

首先加载所有应用信息是在内部创建了一个HandlerThread,然后通过handler循环调用来加载所以应用的。然后把allApps信息传递给一个AllAppsRecyclerView控件的adapter来进行展示的。

Launcher是用工作区的形式来显示系统安装的应用程序的快捷图标的,每一个工作区都是用来描述一个抽象桌面的,它由n个屏幕组成,每个屏幕又分为n个单元格,每个单元格用来显示一个应用程序的快捷图标。

Android 应用启动流程:

在开始分析app启动流程之前,我们先回想下平时是怎么启动一个App的:

Android系统桌面 -> 点击应用图标 -> 启动App。

在最开始先将apk启动涉及的“三个进程”,“六个大类”进行介绍一下:

  • Android系统桌面是什么?
  • 点击应用图标后Android系统执行了什么操作?

三个进程:

  • Launcher进程:整个App启动流程的起点,负责接收用户点击屏幕事件,它其实就是一个Activity,里面实现了点击事件,长按事件,触摸等事件,可以这么理解,把Launcher想象成一个总的Activity,屏幕上各种App的Icon就是这个Activity的button,当点击Icon时,会从Launcher跳转到其他页面。
  • SystemServer进程:这个进程在整个的Android进程中是非常重要的一个,地位和Zygote等同,它是属于Application Framework层的,Android中的所有服务,例如AMS, WindowsManager, PackageManagerService等等都是由这个SystemServer fork出来的。
  • App进程:你要启动的App所运行的进程。

六个大类:

  • ActivityManagerService:(AMS)AMS是Android中最核心的服务之一,主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作,其职责与操作系统中的进程管理和调度模块相类似,因此它在Android中非常重要,它本身也是一个Binder的实现类。
  • Instrumentation:监控应用程序和系统的交互。
  • ActivityThread:应用的入口类,通过调用main方法,开启消息循环队列。ActivityThread所在的线程被称为主线程。
  • ApplicationThread:ApplicationThread提供Binder通讯接口,AMS则通过代理调用此App进程的本地方法。
  • ActivityManagerProxy:AMS服务在当前进程的代理类,负责与AMS通信。
  • ApplicationThreadProxy:ApplicationThread在AMS服务中的代理类,负责与ApplicationThread通信。

可以说,启动的流程就是通过这六个大类在这三个进程之间不断通信的过程。

(1)启动的起点发生在Launcher活动中,启动一个app说简单点就是启动一个Activity,那么我们说过所有组件的启动,切换,调度都由AMS来负责的,所以第一步就是Launcher响应了用户的点击事件,然后通知AMS;

(2)AMS得到Launcher的通知,就需要响应这个通知,主要就是新建一个Task去准备启动Activity,并且告诉Launcher你可以休息了(Paused);

(3)Launcher得到AMS让自己“休息”的消息,那么就直接挂起,并告诉AMS我已经Paused了;

(4)AMS知道了Launcher已经挂起之后,就可以放心的为新的Activity准备启动工作了,首先,APP肯定需要一个新的进程去进行运行,所以需要创建一个新进程,这个过程是需要Zygote参与的,AMS通过Socket去和Zygote协商,如果需要创建进程,那么就会fork自身,创建一个线程,新的进程会导入ActivityThread类,这就是每一个应用程序都有一个ActivityThread与之对应的原因;

(5)进程创建好了,通过调用上述的ActivityThread的main方法,这是应用程序的入口,在这里开启消息循环队列,这也是主线程默认绑定Looper的原因;

(6)这时候,App还没有启动完,要永远记住,四大组建的启动都需要AMS去启动,将上述的应用进程信息注册到AMS中,AMS再在堆栈顶部取得要启动的Activity,通过一系列链式调用去完成App启动;

6、JVM、dalvik、art

  • Java 虚拟机 执行的是 java 字节码,基于栈架构
  • dalvik 虚拟机 (jit 机制) 执行的是 dalvik 字节码。基于寄存器架构。适用 Android5.0 以前版本。
  • art 虚拟机 (aot 机制)。Android5.0+ 版本。

.dex ---> dexopt ---> .odex    dalvik 加载执行的是 odex 文件

.dex ---> dexopt ---> .oat    art 加载执行的是 oat 文件

Dalvik 字节码(学习破解的基础)

Dalvik 是 google 专门为 Android 操作系统设计的一个虚拟机,经过深度优化。虽然 Android 上的程序是使用 java 来开发的,但是 Dalvik 和标准的 java 虚拟机 JVM 还是两回事。

  • Dalvik VM 是基于寄存器的,Dalvik有专属的文件执行格式 dex (dalvik executable),Dalvik VM 比 JVM 速度更快,占用空间更少。
  • JVM 是基于栈的;JVM 则执行的是 java 字节码。

通过 Dalvik 的字节码不能直接看到原来的逻辑代码,这时需要借助如 Apktool 或 dex2jar + jd-gui 工具来帮助查看。但是,我们最终修改 APK 需要操作的文件是 .smali 文件,而不是导出来的 Java 文件重新编译。

7、Smali(破解的重中之重)

Smali,Baksmali 分别是指安卓系统里的 Java 虚拟机(Dalvik)所使用的一种 dex 格式文件的汇编器,反汇编器。其语法是一种宽松式的 Jasmin / dedexer 语法,而且它实现了 .dex 格式所有功能(注解,调试信息,线路信息等)

当我们对 APK 文件进行反编译后,便会生成此类文件。在Davlik字节码中,寄存器都是32位的,能够支持任何类型,64位类型(Long/Double)用2个寄存器表示;

Dalvik字节码有两种类型:

  • 原始类型;
  • 引用类型(包括对象和数组)

方法的定义

Func-Name (Para-Type1Para-Type2Para-Type3…)Return-Type
注意参数与参数之间没有任何分隔符,举例如下:

Smali 基本语法

条件跳转分支

深入 Smali 文件

1. Smali 中的包信息

2. Smali 中的声明

一般来说,在 Smali 文件中声明如下:

3. 关于寄存器的知识补充

寄存器是什么意思呢?在 smali 里的所有操作都必须经过寄存器来进行:

  • 本地寄存器用 v 开头,数字结尾的符号来表示,如 v0、v1、v2、…
  • 参数寄存器则使用 p 开头,数字结尾的符号来表示,如 p0、p1、p2、…

特别注意的是,p0 不一定是函数中的第一个参数,在非 static 函数中,p0 代指 "this",p1 表示函数的第一个参数,p2 代表函数中的第二个参数…,而在 static 函数中 p0 才对应第一个参数(因为 Java 的 static 方法中没有 this 方法)。

4. 寄存器简单实例分析

const/4 v0, 0x1
iput-boolean v0, p0, Lcom/aaa;->IsRegistered:Z

分析上面两句 smali 代码,首先它使用了 v0 本地寄存器,并把值 0x1 存到 v0 中,然后第二句用 iput-boolean 这个指令把 v0 中的值存放到 com.aaa.IsRegistered 这个成员变量中。
即相当于:this.IsRegistered= true;(上面说过,在非static函数中p0代表的是“this”,在这里就是 com.aaa 实例)。

5. Smali 中的成员变量

成员变量格式是:.field public/private [static] [final] varName:<类型>。
对于不同的成员变量也有不同的指令。一般来说,获取的指令有:iget、sget、iget-boolean、sget-boolean、iget-object、sget-object等。

操作的指令有:iput、sput、iput-boolean、sput-boolean、iput-object、sput-object等。

  • 没有 “-object” 后缀的表示操作的成员变量对象是基本数据类型,
  • 带“-object”表示操作的成员变量是对象类型
  • boolean 类型则使用带“-boolean”的指令操作。

6. Smali成员变量指令简析

简析一

sget-object v0, Lcom/aaa;->ID:Ljava/lang/String;

sget-object 就是用来获取变量值并保存到紧接着的参数的寄存器中,本例中,它获取ID这个String类型的成员变量并放到v0这个寄存器中。
注意:前面需要该变量所属的类的类型,后面需要加一个冒号和该成员变量的类型,中间是“->”表示所属关系。

简析二

iget-object v0, p0, Lcom/aaa;->view:Lcom/aaa/view;

可以看到iget-object指令比sget-object多了一个参数,就是该变量所在类的实例,在这里就是p0即“this”。获取array的话我们用aget和aget-object,指令使用和上述一致

简析三(put指令的使用和get指令是统一的)

const/4 v3, 0x0
sput-object v3, Lcom/aaa;->timer:Lcom/aaa/timer;

相当于:this.timer= null;
注意,这里因为是赋值object 所以是null,若是boolean的话,大家想应该相当于什么呢?

简析四

.local v0, args:Landroid/os/Message;
const/4 v1, 0x12
iput v1, v0, Landroid/os/Message;->what:I

相当于:args.what = 18;(args 是 Message 的实例)

Smali 函数分析

1. Smali 中函数的调用

smali中的函数和成员变量一样也分为两种类型,分别为 direct 和 virtual 之分。

那么 direct method和 virtual method 有什么区别呢?

简单来说,direct method 就是 private 函数,其余的 public 和 protected 函数都属于 virtual method。所以在调用函数时,有invoke-direct,invoke-virtual,另外还有invoke-static、invoke-super 以及 invoke-interface 等几种不同的指令。当然其实还有 invoke-XXX/range 指令的,这是参数多于4个的时候调用的指令,比较少见,了解下即可。

(1).invoke-static:用于调用static函数,例如:

invoke-static {}, Lcom/aaa;->CheckSignature()Z

这里注意到 invoke-static 后面有一对大括号“{}”,其实是调用该方法的实例+参数列表,由于这个方法既不需参数也是static的,所以{}内为空,再看一个:

const-string v0, "NDKLIB"
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

这个是调用 static void System.loadLibrary(String) 来加载 NDK 编译的 so 库用的方法,同样也是这里 v0 就是参数”NDKLIB”了。

(2).invoke-super:调用父类方法用的指令,一般用于调用onCreate、onDestroy等方法。

(3).invoke-direct:调用private函数:

invoke-direct {p0}, Landroid/app/TabActivity;-><init>()V

这里init()就是定义在TabActivity中的一个private函数

(4).invoke-virtual:用于调用 protected 或 public 函数,同样注意修改smali时不要错用 invoke-direct 或 invoke-static:

sget-object v0, Lcom/dddd;->bbb:Lcom/ccc;
invoke-virtual {v0, v1}, Lcom/ccc;->Messages(Ljava/lang/Object;)V

这里相信大家都已经很清楚了:
v0是bbb:Lcom/ccc
v1是传递给Messages方法的Ljava/lang/Object参数。

(5).invoke-xxxxx/range:当方法的参数多于5个时(含5个),不能直接使用以上的指令,而是在后面加上“/range”,range表示范围,使用方法也有所不同:

invoke-direct/range {v0 .. v5}, Lcmb/pb/ui/PBContainerActivity;->h(ILjava/lang/CharSequence;Ljava/lang/String;Landroid/content/Intent;I)Z

需要传递v0到v5一共6个参数,这时候大括号内的参数采用省略形式,且需要连续。

Smali 中函数返回结果操作

在Java代码中调用函数和返回函数结果可以用一条语句完成,而在Smali里则需要分开来完成,在使用上述指令后,如果调用的函数返回非void,那么还需要用到 move-result(返回基本数据类型)和 move-result-object(返回对象)指令:

const-string v0, "Eric"
invoke-static {v0}, Lcmb/pbi;->t(Ljava/lang/String;)Ljava/lang/String;
move-result-object v2

v2保存的就是调用t方法返回的String字符串。

Smali 中函数实体分析–if 函数分析

猜你喜欢

转载自blog.csdn.net/freeking101/article/details/105109231