Android的逆向入門篇 - 編譯,打包,安裝

Android 編譯器 Java XML Android Studio DEX Google 賽虎學院 2019-06-29

引言

本片作為Android的逆向入門篇,只有先了解APK包的信息,才可以進一步來逆向它

APK打包

Android的逆向入門篇 - 編譯,打包,安裝

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

相關推薦

推薦中...