[TOC]
本片作为Android逆向入门篇,只有先了解APK包的信息,才可以进一步来逆向它
APK打包的内容主要有: 应用模块 也就是自己开发的用到的源代码、资源文件、aidl接口文件,还有就是 依赖模块 即源代码用到的第三方依赖库如:aar、jar、so文件。
从图中可以看出主要分为以下几步:
目录结构类似下面所示:
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
aapt.exe/aapt2.exe:资源打包工具
javac.exe:将java转成class
dx.jar:将class转成dex文件
通过aapt工具生成R.java和打包到压缩包中的各种编译好的xml文件、未编译的文件、arsc文件。
资源文件中values文件夹中的文件生成了resource.arsc和R.java
aapt.exe p -M AndroidManifest.xml -S ./main/res -I android.jar -J ./ -F ./out.apk
从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
需要把所有的flat文件加载后面
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 ./
sdk/build-tools目录下的aidl.exe工具
aidl -Iaidl -pD:/Android/Sdk/platforms/android-27/framework.aidl -obuild aidl/com/android/vending/billing/IInAppBillingService.aidl
javac:jdk自带工具
最后将需要编译的java文件放在文件末尾
javac -target 1.8 -bootclasspath platforms/android-28/android.jar -d e:/ java/com/testjni/*.java
sdk/build-tools下的lib目录下的dx.jar工具
java -jar dx.jar --dex --ouput=./classes.dex ./com/testjni/*.class
由于apkbuilder工具被废弃了,我们可以手动将文件放到aapt生成的apk文件中
使用签名工具对打包好的压缩包签名后才可以被android系统安装,签名后的证书文件放在META-INF目录下
公钥证书(也称为数字证书或身份证书)包含公钥/私钥对的公钥,对apk的签名也就是将公钥附加在apk上,充当指纹的作用,用来将APK唯一关联到开发者手上的私钥上。
密钥库是一种包含一个或多个私钥的二进制文件。我们先构建自己的密钥库
keytool -genkey -alias demo.keystore -keyalg RSA -validity 40000 -keystore demo.keystore
接着会让输入密钥密码,证书信息等,下面命令是查看自己这个密钥库中的详细信息的命令
keytool -list -keystore demo.keystore -v
java -jar apksigner.jar sign --ks demo.keystore demo.apk
jdk工具:jarsigner.exe
jarsigner.exe -verbose -keystore <密钥库> -signedjar <签名后的apk名称> <需要被签名的apk文件> <密钥库的别名>
为了减少RAM的使用,目的确保所有未压缩的数据在4字节边界上对其,根据不同签名工具,具体对齐时间不定
apksigner签名apk,在签名之前进行对齐,否则会致使签名无效
jarsigner签名apk,在签名之后进行对齐
zipalign.exe 4 base.apk aligned.apk
/system/app:系统自带的应用程序,需要ROOT权限方可删除
/data/app:用户安装应程序时,将apk文件复制到这里
/vendor/app:设备商的应用程序
/data/app-private:受DRM保护(数字版权管理)的app
/data/data:应用存放数据的地方
/data/dalvik-cache:将apk中的dex文件安装到这里
安装功能主要有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命令安装APK安装包,主要分为两步:
这里用到了pm类的runinstall方法,内部调用了installPackageWithVerification,通过下面这个跨进程接口调用了安装包服务来执行安装操作,也就回到了网络下载后的安装执行流程中
mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
【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