ttyn727 发表于 2019-9-21 18:32:00

安卓的逆向入门篇 - 编译,打包,安装

## 引言

本片作为Android的逆向入门篇,只有先了解APK包的信息,才可以进一步来逆向它

## APK打包



APK打包的内容主要有:**应用模块**也。就是自己开发的用到的源代码,资源文件,AIDL接口文件,就是还有**依赖模块**即源代码用到的第三方依赖库如:AAR,罐子,所以文件。

从图中可以看出主要分为以下几步:

 **第一步:编译,打包**

目录结构类似下面所示:

```
android-project/├── AndroidManifest.xml├── gen/├── lib/│ └── android-support-v4.jar├── out/├── res/│ ├── drawable-xhdpi/│ │ └── icon.png│ ├── drawable-xxhdpi/│ │ └── icon.png│ ├── drawable-xxxhdpi/│ │ └── icon.png│ └── layout/│ └── activity_main.xml└── src/ └── cn/ └── androidblog/ └── testbuild/ └── MainActivity.java
```

**流程**

1. 打包资源文件生成R.java,编译AIDL生成的Java接口文件
2. 将源代码编译成DEX(Dalvik Executable)文件(其中包括Android设备上运行的字节码)。
3. 将编译后的文件打包成一个APK压缩包

**工具**

**aapt.exe / aapt2.exe**:资源打包工具

**javac.exe**:将java转成类

**dx.jar**:将类转成dex文件

**资源打包**

**AAPT**

通过AAPT工具生成R.java和打包到压缩包中的各种编译好的XML文件,未编译的文件,ARSC文件。

资源文件中值的文件夹中的文件生成了resource.arsc和R.java

```
aapt.exe p -M AndroidManifest.xml -S ./main/res -I android.jar -J ./ -F ./out.apk
```

- 电话号码:打包
- -M:AndroidManifest.xml中文件路径
- -S:RES目录路径
- -A:资产目录路径
- -I:的android.jar路径,会用到的一些系统库
- -J指定生成的R.java的输出目录
- -F具体指定apk文件的输出

**aapt2**

**编译**

从Android Studio 3.0开始,google默认开启了aapt2作为资源编译的编译器,aapt2的出现,为资源的增量编译提供了支持。当然使用过程中**也会遇到**一些问题,我们可以通过在**gradle.properties**中配置**android.enableAapt2 = FALSE**来关闭aapt2。

- 编译整个目录中的所有资源文件到一个压缩包中,并且全部编译成平面文件

```
aapt2.exe compile -o base.apk -dir E:\AndroidStudioProjects\TestJni\app\src\main\res
```

- 编译单个文件,多个文件到指定目录中

```
aapt2.exe compile -o E:\ E:\AndroidStudioProjects\TesttJni\app\src\main\res\mipmap-xxxhdpi\ic_launcher_round.png
```

**链接**

- -o:链接进指定压缩包内
- -i:指定的android.jar路径
- --manifest:指定的AndroidManifest.xml路径
- --java:指定目录生成R.java(包含包路径,例如包名是com.test,则会生成到./com/test目录下)

需要把所有的平面文件加载后面

```
aapt2.exe link -o .\out.apk -I .\Sdk\platforms\android-28\android.jar --manifest E:\AndroidStudioProjects\TestJni\app\src\main\AndroidManifest.xml .\values_styles.arsc.flat .\mipmap-hdpi_ic_launcher_round.png.flat --java ./
```

**编译AIDL文件**

SDK \构建工具目录下的aidl.exe工具

- -I指定import语句的搜索路径,注意-I与目录之间一定不要有空格
- -p指定系统类的import语句路径,如果是要用到android.os.Bundle系统的类,一定要设置sdk的framework.aidl路径
- -o生成java文件的目录,注意-o与目录之间一定不要有空格,而且这设置项一定要在aidl文件路径之前设置

```
aidl -Iaidl -pD:/Android/Sdk/platforms/android-27/framework.aidl -obuild aidl/com/android/vending/billing/IInAppBillingService.aidl
```

**编译源代码**

**toClass**

javac的:JDK自带工具

- -target:生成特定VM版本的类文件,也就是SDK版本
- -bootclasspath:表示编译需要用到的系统库
- -d:生成的类文件存放的目录位置

最后将需要编译的java的文件放在文件末尾

```
javac -target 1.8 -bootclasspath platforms\android-28\android.jar -d e:/ java\com\testjni\*.java
```

**todex**

SDK \构建工具下的LIB目录下的dx.jar工具

- --dex:将类文件转成DEX文件
- --output:指定生成DEX文件到具体位置

```
java -jar dx.jar --dex --ouput=.\classes.dex .\com\testjni\*.class
```

**打包**

由于apkbuilder工具被废弃了,我们可以手动将文件放到aapt生成的apk文件中

**第二步:签名**

使用签名工具对打包好的压缩包签名后才可以被android系统安装,签名后的证书文件放在META-INF目录下

公钥证书(也称为数字证书或身份证书)包含公钥/私钥对的公钥,对apk的签名也就是将公钥附加在apk上,充当指纹的作用,用来将APK唯一关联到开发者手上的私钥上。

密钥库是一种包含一个或多个私钥的二进制文件。我们先构建自己的密钥库

**构建密钥库**

- genkey:生成密钥库
- alias:密钥库别名
- keyalg:密钥算法RSA
- validity:证书有效期40000天
- keystore:密钥库名称

```
keytool -genkey -alias demo.keystore -keyalg RSA -validity 40000 -keystore demo.keystore
```

接着会让输入密钥密码,证书信息等,下面命令是查看自己这个密钥库中的详细信息的命令

```
keytool -list -keystore demo.keystore -v
```

**apksigner**

- sign:签署数字证书
- --ks:指定密钥库

```
java -jar apksigner.jar sign --ks demo.keystore demo.apk
```

**jarsigner**

jdk工具:jarsigner.exe

- keystore:指定密钥库文件置
- signedjar:签名后文件存储的位置

```
jarsigner.exe -verbose -keystore <密钥库> -signedjar <签名后的apk名称> <需要被签名的apk文件> <密钥库的别名>
```

**第三步 zipalign对齐**

为了减少RAM的使用,目的确保所有未压缩的数据在4字节边界上对其,根据不同签名工具,具体对齐时间不定

- apksigner签名apk,在签名之前进行对齐,否则会致使签名无效
- jarsigner签名apk,在签名之后进行对齐
- 4:表示4字节对齐

```
zipalign.exe 4 base.apk aligned.apk
```

## APK安装

**/system/app**:系统自带的应用程序,需要ROOT权限方可删除

**/data/app**:用户安装应程序时,将apk文件复制到这里

**/vendor/app**:设备商的应用程序

**/data/app-private**:受DRM保护(数字版权管理)的app

**/data/data**:应用存放数据的地方

**/data/dalvik-cache**:将apk中的dex文件安装到这里

**系统安装(放入就安装)**

- 将安装包放入/system/app、/data/app、/data/app,/data/app-private目录中即可实现自动安装
- 如果删除/system/app、/data/app、/data/app、/data/app-private目录中的安装包,即可实现应用删除操作
- /system/app和/vendor/system下的应用需要ROOT权限方可删除

安装功能主要有systemServer的子类PackageManagerService来实现,如下面这里会注册一个观察者mSystemInstallObserver,监听/system/app内的应用安装包情况,一旦有安装包放入这个目录,就会触发事件来调用scanPackageLI方法进行apk的安装操作。

上面这些app安装目录的监听原理是大致相同的

```
// Collect ordinary system packages. File systemAppDir = new File(Environment.getRootDirectory(), "app"); mSystemInstallObserver = new AppDirObserver( systemAppDir.getPath(), OBSERVER_EVENTS, true, false); mSystemInstallObserver.startWatching(); scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0);private final class AppDirObserver extends FileObserver { .... if ((event&ADD_EVENTS) != 0) { .... p = scanPackageLI(fullPath, flags, SCAN_MONITOR | SCAN_NO_PATHS | SCAN_UPDATE_TIME, System.currentTimeMillis(), UserHandle.ALL);
```

在scanPackageLI方法中,具体在这里进行了安装操作

```
//invoke installer to do the actual installation int ret = mInstaller.install(pkgName, pkg.applicationInfo.uid, pkg.applicationInfo.uid);
```

**网络下载安装**

从网络上下载下来apk安装包不管是手动点击安装还是应用检测安装,一般都会使用**PackageManagerService**的installPackage方法进行安装,这个方法向内部查看几层发现是调用了installPackageWithVerificationAndEncryption方法

```
/* Called when a downloaded package installation has been confirmed by the user */ public void installPackage( final Uri packageURI, final IPackageInstallObserver observer, final int flags) { installPackage(packageURI, observer, flags, null); }
```

从截取的片段可以看到,这个方法出了实例化一个观察者外,主要通过传递安装参数给handle,下面我们看一下下handle的处理方法

```
public void installPackageWithVerificationAndEncryption(Uri packageURI, IPackageInstallObserver observer, int flags, String installerPackageName, VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) ....... observer.packageInstalled("", PackageManager.INSTALL_FAILED_USER_RESTRICTED); ....... final Message msg = mHandler.obtainMessage(INIT_COPY); msg.obj = new InstallParams(packageURI, observer, filteredFlags, installerPackageName, verificationParams, encryptionParams, user); mHandler.sendMessage(msg);
```

而处理message的hanle方法主要,将初始化安装参数,并接着发送标识为MCS_BOUND的message,而在处理这个message的时候,调用了startCopy,接着调用handleReturnCode,最终installPackageL内部是installNewPackageLI->scanPackageLI,这样回到了系统安装应用的步骤了

```
void doHandleMessage(Message msg) { switch (msg.what) { case INIT_COPY: { ...... mPendingInstalls.add(idx, params); // Already bound to the service. Just make // sure we trigger off processing the first request. if (idx == 0) { mHandler.sendEmptyMessage(MCS_BOUND); }case MCS_BOUND: {} else if (mPendingInstalls.size() > 0) { HandlerParams params = mPendingInstalls.get(0); if (params != null) { if (params.startCopy()) {private void installPackageLI(InstallArgs args, boolean newInstall, PackageInstalledInfo res) { ............. final PackageParser.Package pkg = pp.parsePackage(tmpPackageFile, null, mMetrics, parseFlags);
```

**adb安装**

通过adb命令安装APK安装包,主要分为两步:

- adb push xxx.apk /data/local/tmp,先将apk文件传送到设备临时目录下
- pm install /data/local/tmp/xxx.apk

这里用到了pm类的runinstall方法,内部调用了installPackageWithVerification,通过下面这个跨进程接口调用了安装包服务来执行安装操作,也就回到了网络下载后的安装执行流程中

```
mPm = IPackageManager 。存根。asInterface (的ServiceManager 。的getService (“包” ));
```

## 小结

【1】打包过程中,需要先把资源,AIDL文件转成java的文件,然后同所有的java源码一起打包成的.class再到DEX

## 参考

【1】谷歌官方打包流程:https://developer.android.com/studio/build?hl = zh-cn

【2】apktool网官https://ibotpeaches.github.io/Apktool/

【3】APK过程安装原理及详解https://blog.csdn.net/hdhd588/article/details/6739281
页: [1]
查看完整版本: 安卓的逆向入门篇 - 编译,打包,安装