Apk去簽名校驗詳解


  某些apk為了防止重打包,使用了簽名校驗。所以在破解的時候我們需要破解簽名校驗。在定位簽名校驗位置時常用的關鍵詞有sign,signature,checkSign,signCheck,getPackageManager,getPackageInfo,verify,same等。
  java層簽名校驗代碼示例:
 1 //原簽名信息
 2 private static final String SIGNATURE = "478yYkKAQF+KST8y4ATKvHkYibo=";
 3 private static final int VALID = 0;
 4 private static final int INVALID = 1;
 5 
 6 public static int checkAppSignature(Context context) {
 7     try {
 8         PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(),PackageManager.GET_SIGNATURES);
 9 
10         for (Signature signature : packageInfo.signatures) {
11             byte[] signatureBytes = signature.toByteArray();
12             MessageDigest md = MessageDigest.getInstance("SHA");
13             md.update(signature.toByteArray());
14             
15             final String currentSignature = Base64.encodeToString(md.digest(), Base64.DEFAULT);
16 
17             Log.d("REMOVE_ME", "Include this string as a value for SIGNATURE:" + currentSignature);
18 
19             //compare signatures
20             if (SIGNATURE.equals(currentSignature)){
21                 return VALID;
22             };
23         }
24     } catch (Exception e) {
25         //assumes an issue in checking signature., but we let the caller decide on what to do.
26 
27     }
28     return INVALID;
29 }

    最近遇到一個進行了簽名校驗的apk,它是在客戶端獲取簽名信息然后在服務器端進行簽名比對的,而且簽名的獲取是在native代碼中實現的。這種簽名校驗的破解思路一般是在sd卡中保存一個原apk包,修改apk路徑,讓程序獲取原apk的簽名信息。修改辦法一般有三種,下面一一介紹。

(一)在java層修改context進行破解

  首先安裝apk,啟動DDMS查看log信息:
 
 
 
 
 
根據這個“verifyHashByC”這個字符串猜測跟簽名校驗有關系,於是在java層搜索verifyHashByC,沒有找到對應的log打印處,只發現同名的native函數調用:
 
 
 
 
 
 
猜測此log打印信息是在so中,於是找到該native函數對應的so加載處,發現加載的是libAppVerify.so這個動態庫:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
用ida打開libAppVerify.so,搜索verifyHashByC,在VerifyHash_BySha函數中發現了log日志打印的代碼:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
光看函數名稱VerifyHash_BySha就知道是通過sha算法驗證Hash,但是這是不是用於簽名校驗的呢,利用ida的交叉引用查看函數調用關系,發現這個函數在VerifyHash(_JNIEnv *, char *, char const*, int)函數中被調用。而VerifyHash則是由前面提到的native函數verifyHashByC來調用的。
 
 
 
 
 
VerifyHash函數先獲取應用包名,然后調用GetApkMFData函數:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
在該函數中獲取應用路徑,然后解壓應用安裝包,讀取里面的MANIFEST.MF文件內容,然后再進行簽名比對:
 
 
 
 
 
 
 
  獲取apk路徑的示例代碼如下:
 1 private static String getApkPath(String pkgName) {  
 2     PackageManager pm = mContext.getPackageManager();  
 3     ApplicationInfo pi = null;  
 4     try {  
 5         pi = pm.getApplicationInfo(pkgName,PackageManager.GET_UNINSTALLED_PACKAGES);  
 6         if(pi != null)  
 7             return pi.sourceDir;  
 8         else   
 9             return null;  
10     } catch (NameNotFoundException e) {  
11         e.printStackTrace();  
12         return null;  
13     }  
14 }  

sourceDir保存了apk的完整路徑:

 

 
 
 
接下來,解壓apk,讀取MANIFEST.MF文件:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
下圖為MF文件中的內容,與上圖中內存單元的內容一致:
 
 
 
 
 
 
 
 
 
 
 
 
 
  分析了這么多,如何破解呢?通過前面的分析可知在so中獲取apk包的路徑的流程是:
 
 
 
 
傳入context,通過ApplicationInfo的sourceDir獲取到apk路徑,如果我們修改sourceDir讓它始終指向原apk,那么我們的目的就達到了。
通過反編譯java代碼,找到調用libAppVerify.so的地方,如圖所示,context就是從這里傳入的:
 
 
 
 
 
 
 
 
 
 
 
 
我們要做的就是修改這個context,使它最后指向的sourceDir為原apk路徑。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
  根據上圖原理,我們自己實現一個ApkApplicationInfo類指定sourceDir為原apk(SD卡中apk)路徑,ApkApplicationInfo在getApplicationInfo函數中實例化,由於getApplicationInfo函數為PackageManager類的成員函數,所以需要自己實現一個ApkPackageManager類(繼承自PackageManager類)來調用getApplicationInfo函數。ApkPackageManager類需要在getPackageManager函數中得到實例化,getPackageManager函數為Context的成員函數,所以還需要自己實現一個繼承自Context的類ApkContext。
  於是,創建一個Android工程,創建ApkApplicationInfo、ApkPackageManager和ApkContext這三個類,設置sourceDir為“/sdcard/myapk.apk”。編譯后,將生成的apk反編譯,得到這的三個類的smali代碼文件,如下圖所示:
 
 
com.zip
 
 
 
然后,將原apk反編譯,把上述三個文件放入反編譯后的apk smali文件夾中,在Lcom/rytong/emp/security/AppVerify類的verifyHash函數中插入下述代碼,如圖所示:
 
 
 
 
 
 
 
重打包,安裝運行,成功!

(二)使用hook技術

  我們知道進行簽名校驗是在libAppVerify.so中,所以我們只需hook這個so中的函數,更改傳入的apk路徑就行了。通過分析可知,該so是使用MINIZIP進行apk文件解壓縮,然后獲取簽名信息的。
  使用MINIZIP進行apk文件解壓縮的示例代碼如下:
 1 void uncrypt_test()
 2 {
 3   //采用MINIZIP進行文件解壓縮 
 4   unzFile uf=NULL;
 5     unzFile data[1200];
 6     unz_global_info64 gi;
 7     unz_file_info64 FileInfo;  
 8  
 9     //打開zip文件 
10     uf = unzOpen64("D:\\myfile.zip");
11     int result=unzGetGlobalInfo64(uf, &gi);
12     if (result != UNZ_OK)          
13         throw "文件錯誤";
14  
15     //循環解壓縮文件 
16     for(int i=0;i<gi.number_entry;++i)
17     {
18         if (unzGetCurrentFileInfo64(uf, &FileInfo, 0, 0,NULL,0,NULL,0)!= UNZ_OK)
19              throw "文件錯誤";
20  
21         if(!(FileInfo.external_fa & FILE_ATTRIBUTE_DIRECTORY)) //文件,否則為目錄 
22             //打開文件
23             //result=unzOpenCurrentFile(uf);/* 無密碼 */
24             result=unzOpenCurrentFilePassword(uf,"123"); /* 有密碼 */
25   
26         //讀取內容
27         int size= unzReadCurrentFile(uf,data,sizeof(data));                    
28  
29         //關閉當前文件
30         unzCloseCurrentFile(uf);   
31  
32         //出錯
33         if(i < gi.number_entry - 1 && unzGoToNextFile(uf) != UNZ_OK)
34           throw "error";        
35     }
36  
37     //關閉流
38     unzClose(uf);
39 }

  從上述代碼可知,解壓過程中,apk路徑以參數形式傳入unzOpen64函數。所以我們需要hook這個函數。但是如何知道libAppVerify.so何時被加載呢?我們知道系統通過dvmLoadNativeCode函數從指定的路徑加載so,如果對系統函數dvmLoadNativeCode進行hook,當它加載libAppVerify.so的時候,再hook unzOpen64函數,修改apk路徑,不就行了?

  dvmLoadNativeCode函數原型:  

bool dvmLoadNativeCode(constchar* pathName, Object* classLoader, char** detail)

  這里我們使用cydia substrate這個hook框架,關鍵代碼如下:

 
將編譯生成的so文件放到原apk lib目錄下。再通過java層進行加載,加載代碼如下:
1     const-string/jumbo v0, "substrate"
2     invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
3     const-string/jumbo v0, "substrate-dvm"
4     invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
5     const-string/jumbo v0, "HookVerify.cy"
6     invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

由於需要在簽名校驗代碼運行之前加載這些so,所以我們在Lcom/cgbchina/xpt/EMPView類的構造函數中添加上述代碼,如下圖所示:

重打包簽名,安裝運行成功!
HookVerify.zip

(3)直接修改so文件

由於簽名驗證驗證的是MANIFEST.MF文件,我們將原apk中MANIFEST.MF文件改名為SIGNFILE.MF放到修改了的apk META-INF文件夾下,然后將so中驗證的文件名改為SIGNFILE.MF,如圖所示:
保存,替換原so,重打包安裝,運行成功!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM