校驗數字簽名防止 apk 被二次打包 - Java層校驗(大衆點評爲例)

原文鏈接:  http://kiya-z.github.io/2015/11/12/check-signature-for-avoiding-fake-app-java-level-check/
測試環境
Ubuntu 14.04
Lenovo Android 5.1
Lenovo Android 4.2.2
Android Studio

普及簽名包名知識

包名 (Package Name) 相當於「應用的身份證」,是系統用來區分不同應用的字段,重複的包名會被認爲是同一款應用。
簽名文件相當於「開發者的身份證」,目的是爲了檢驗應用是否被人更改過(應用必須簽名過才能正常安裝)。

包名相同簽名相同時,會發生 替換安裝 / 應用升級;
包名相同簽名不同時,安裝失敗;
包名不同簽名相同時,相當於同一開發者的兩個應用,互相不衝突。

簽名的注意事項
所有的Android應用都必須有數字簽名,沒有不存在數字簽名的應用,包括模擬器上運行的。Android系統不會安裝沒有數字證書的應用。
簽名的數字證書不需要權威機構來認證,是開發者自己產生的數字證書,即所謂的自簽名。
正式發佈一個Android應用時,必須使用一個合適的私鑰生成的數字證書來給程序簽名,不能使用ADT插件或者ANT工具生成的調試證書來發布。
Android將數字證書用來標識應用程序的作者和在應用程序之間建立信任關係,而不是用來決定最終用戶可以安裝哪些應用程序。


爲大衆點評換籤名

按照常規步驟使用 apktool + signapk 反編譯、編譯、簽名並安裝到手機上(沒有修改任何代碼),打開 app 選擇城市後界面如下圖並很快退出:

dianping-crash

說明點評對簽名進行了校驗 。


分析校驗方法

怎麼退出的?

打開 apktool 反編譯得到的文件夾下的 AndroidManifest.xml ,得到程序包名:com.dianping.v1 。
清除大衆點評的數據,打開 as,連上手機,log 的過濾條件設爲 com.dianping ,在選擇城市之前清一下 log ,在 log 裏搜索 “die”,比較明顯的是有四處:

進程死亡:

1
2
3
Process com.dianping.v1 (pid 19182) has died
Process com.dianping.v1 (pid 19586) has died
Process com.dianping.v1 (pid 19650) has died

app 死亡:

1
Force removing ActivityRecord{266e5efd u0 com.dianping.v1/.NovaMainActivity t14010}: app died, no saved state

其中前兩個進程死亡之後都有開啓進程的操作,說明第一次校驗失敗後重試了兩次:

1
startProcess: name=com.dianping.v1 app=null knownToBeDead=true thread=null pid=-1

1
startProcess: name=com.dianping.v1 app=null knownToBeDead=true thread=null pid=-1

最後一個直接殺死了 app,沒有再繼續創建進程。

在進程結束之前,發生錯誤的調用記錄:

1
2
3
4
5
6
9586-19586/? D/AccessibilityManager:     at com.dianping.base.app.NovaActivity.setContentView(NovaActivity.java:722)
 9586-19586/? D/AccessibilityManager:     at com.dianping.main.guide.MainActivity.setOnContentView(MainActivity.java:339)
 9586-19586/? D/AccessibilityManager:     at com.dianping.base.basic.FragmentTabActivity.onCreate(FragmentTabActivity.java:51)
 9586-19586/? D/AccessibilityManager:     at com.dianping.base.widget.NovaFragmentTabActivity.onCreate(NovaFragmentTabActivity.java:26)
 9586-19586/? D/AccessibilityManager:     at com.dianping.main.guide.MainActivity.onCreate(MainActivity.java:169)
 9586-19586/? D/AccessibilityManager:     at com.dianping.v1.NovaMainActivity.onCreate(NovaMainActivity.java:15)

代碼探索

解壓 apk 文件,發現有 3 個 dex 文件,先拿第一個下手,JD-GUI 打開發現代碼沒有混淆!

調用記錄中的文件從上往下過一遍,發現在 com.dianping.main.guide.MainActivity.onCreate() 方法中有校驗簽名的函數:

1
2
3
if (!checkSignature()) {    
      Process.killProcess(Process.myPid());
    }

checkSignature 函數:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private boolean checkSignature()
{
 try
 {
   Signature[] arrayOfSignature = getPackageManager().getPackageInfo(getPackageName(), 64).signatures;     //獲得簽名數組
   if (arrayOfSignature != null)
   {
     if (arrayOfSignature.length == 0) {
       return false;
     }
     int j = arrayOfSignature.length;
     int i = 0;
     while (i < j)   //如果數組中的某個元素值與 'ac6fc3fe' 相等,返回校驗成功;如果直到結束也沒有相等的元素,返回失敗
     {               //只比較一個特定的元素,可能也是爲了不把整個簽名泄露出來,同時也做到了一定程度的校驗
       String str = Integer.toHexString(arrayOfSignature[i].toCharsString().hashCode());
       if (!"ac6fc3fe".equalsIgnoreCase(str))   
       {
         boolean bool = "600cf559".equalsIgnoreCase(str);       //這個比較好像沒用
         if (!bool) {}
       }
       else
       {
         return true;
       }
       i += 1;
     }
   }
   return false;
 }
 catch (Exception localException) {}
}

相關 API:

public Signature[] signatures
Array of all signatures read from the package file. This is only filled in if the flag GET_SIGNATURES was set.

public static final int GET_SIGNATURES
PackageInfo flag: return information about the signatures included in the package.
Constant Value: 64 (0x00000040)

public boolean equalsIgnoreCase (String string)
Compares the given string to this string ignoring case.
The strings are compared one char at a time.

流程修改

在 smali/com/dianping/main/guide/MainActivity.smali 中搜索 ac6fc3fe :

1
2
3
4
5
6
7
8
9
.line 358
    .local v4, "myHash":Ljava/lang/String;
    const-string v9, "ac6fc3fe"

    invoke-virtual {v9, v4}, Ljava/lang/String;->equalsIgnoreCase(Ljava/lang/String;)Z

    move-result v9

    if-nez v9, :cond_2      //if(!equal(..)) return 1

找到 con_2 的定義:

1
2
3
4
5
.line 359
:cond_2
const/4 v8, 0x1

goto :goto_0

1
2
:goto_0
    return v8

所以把 if-nez v9, :cond_2 改成 if-eqz v9, :cond_2 就可以了,當然,修改方法還有很多。

打包簽名

點評可以正常打開,正常登錄,正常使用了。


番外:
而另一臺手機 (Lenovo Android 4.2.2) 測試進程會不斷重新創建。
應用 crash 之後 App 對應的 Process 都被殺死,然後安排重啓 Service,重新啓動 Task 棧頂的 Activity 。


Reference

Android軟件安全與逆向分析
洗白白手記:繞開 Android 應用開發的那些「坑」
給 Android 應用程序簽名

發佈了272 篇原創文章 · 獲贊 59 · 訪問量 43萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章