引言
本片作為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
流程
- 打包資源文件生成R.java,編譯AIDL生成的Java接口文件
- 將源代碼編譯成DEX(Dalvik Executable)文件(其中包括Android設備上運行的字節碼)。
- 將編譯後的文件打包成一個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