反編譯、分析、閱讀 Android 應用程式原始碼,以 17直播為例

有些 Android 應用程式會要求過多的權限,如果不給它的話還不讓你使用應用程式,但明明這權限就很不合理啊。

以 17 直播為例,看個直播幹嘛要存取我的檔案?

https://dl2.pushbulletusercontent.com/N86YBB9oAlvoEPmGO6QyO9NJvSDoFD9c/Screenshot_20180225-141150.png

看了就很不爽,就是這樣的應用程式訓練出毫不眨眼就隨便給出權限的使用者,所以今天拿它來開刀。

找出顯示中的 Activity

首先用 adb shell dumpsys activity 瞭解一下目前是哪一個 activity :

ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)
Display #0 (activities from top to bottom):
  Stack #1:
    Task id #7379
    * TaskRecord{b876752 #7379 A=com.machipopo.media17 U=0 sz=1}
      userId=0 effectiveUid=u0a204 mCallingUid=u0a204 mCallingPackage=com.machipopo.media17
      affinity=com.machipopo.media17
      intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.machipopo
.media17/.StartActivity bnds=[48,156][126,234]}
      realActivity=com.machipopo.media17/.StartActivity
      autoRemoveRecents=false isPersistable=true numFullscreen=1 taskType=0 mTaskToReturnTo=1
      rootWasReset=true mNeverRelinquishIdentity=true mReuseTask=false mLockTaskAuth=LOCK_TASK_AUTH_PINNABLE
      Activities=[ActivityRecord{822c1b8 u0 com.machipopo.media17/.activity.MenuActivity_V2 t7379}]
      askedCompatMode=false inRecents=true isAvailable=true
      lastThumbnail=android.graphics.Bitmap@80a2cec lastThumbnailFile=/data/system/recent_images/7379_task_thumbnail
.png
      stackId=1
      hasBeenVisible=true mResizeable=false firstActiveTime=1519539613444 lastActiveTime=1519539613444 (inactive for
 26s)

主要是看 Activities= 那一行,就知道目前的 activity 是什麼。

反編譯取得原始碼

我已經裝好了 Mobile Security Framework ,直接上傳 APK 檔案就可以反組譯,還會順便做一些其他分析。

到安裝 MobSF 的目錄底下有個 uploads 資料夾, APK 的 checksum 就是資料夾名稱,進去之後會看到幾個資料夾:

接下來的重點就是看 java_sourcesmali_source 這兩個資料夾了。

閱讀 Java 原始碼

打開 MenuActivity_V2.java ,往下捲一下很快就看到:

顯然就是檢查有沒有寫入外部儲存空間權限的函式,我猜測它的行為如下:

  1. 呼叫 android.support.v4 API ,檢查使用者是否已經授權存取外部儲存空間(269 行)
  2. 如果沒有授權的話,把字串 android.permission.WRITE_EXTERNAL_STORAGE 加進 arrayList 。也就是說,arrayList 裡面的字串代表應用程式還需要的權限。
  3. arrayList 的長度,設為 n2 ,然後回傳。

再找一下 F() 它的 caller 有誰,搜尋了一下,只有這個函式:

在 1410 行的 if 檢查了 this.F() 是不是回傳零,也就是「應用程式所需要的權限數量是不是零」,如果不是零的話,執行 1411~1414 this.n 的相關操作。

看到它在 1411 行檢查了 this.n 是否為空,如果不為空的話呼叫 this.n.dismiss() ,初步猜測 this.n 就是上面截圖中對話框的控制 class ,而 1411~1412 則是在檢查目前有沒有已經開啟的對話框,如果有的話,把它關閉 (dismiss) 。

在檔案開始處 MenuActivity_V2 class 的屬性型別宣告證實了我的猜想:

從 207 行,我們得知 this.n 的型別是 com.machipopo.media17.fragment.dialog.e ,看起來就是個對話框 (dialog) 。

至於對話框 class 要怎麼操作,現階段不是很重要,畢竟我們只需要把對話關掉(應該說是完全不要呼叫顯示對話框的函式)就好了。

關掉對話框有很多種方法,但由於我們實際上要修改應用程式的行為光是改 java code 是沒用的,需要修改 smali code ,因此我想還是先看看它的 smali code 長什麼樣子,再思考要怎麼修改比較方便。

閱讀 smali 原始碼

一樣在 uploads 資料夾裡面,可以找到 smali_source/com/machipopo/media17/activity/MenuActivity_V2.smali 這個檔案,也就是對應 MenuActivity_V2.java 的 smali 原始碼。

首先我們需要先找到 F() 的 caller ,也就是 onStart()

雖然說我根本就沒學過 smali (應該只有研究 Java VM 的人才需要瞭解 smali 吧),但稍微看了整個 smali 原始碼,還是可以看到一些規律,加上一些背景知識就可以看懂一段 smali code 的行為。

smali 看來還是蠻高階的東西,簡單搜尋了一下 onStart 字串,就找到這一段:

什麼 Singleton , AppLogic 的,跟 Java code 裡面的名字一樣,因此確定從 5621 行就是 onStart() 沒錯。

往下到 5648 行,就看到呼叫 F() 的指令:

5649 行的行為,猜測大概就是呼叫 F() ,而 F 會回傳一個整數 (I) ,這個回傳值會放在 {p0} 裡面。

5651 行把 {p0} 裡面的結果移動到 v0

5653 行是重點,判斷 v0 是否為零 (equal to zero, eqz) ,如果是的話 jump 到 :cond_81 ,如果不是的話繼續執行

以上 5649~5653 這段 smali code ,對應的 Java code 是 1410 行 if (this.F() != 0 && Build.VERSION.SDK_INT >= 23) { && 前面的東西。

瞭解了以上行為之後,看得出來,如果在使用者有給出所有權限的狀況下:

  1. F() 會回傳零
  2. 在 5653 的判斷會成功,直接跳到 :cond_81

因此 :cond_81 之後就是我們想要的行為(回歸正常操作),而 5653 ~ :cond_81 這段則會跳出對話框,不斷阻擋我們操作。

看一下 :cond_81 會做什麼:

5768~5772 對應到 Java code 1416 的 com.machipopo.media17.business.AppLogic.a().o((Context)this);

最後的 goto 則是 1418 行 if (com.machipopo.media17.business.AppLogic.a().b(.......

綜合以上判斷,我們需要修改的地方,就是把 5648 ~ :cond_81 這段行為,完全刪除,如此一來,無論 F() 的回傳值為何, 1416 行檢查權限成功之後的行為都會照常執行。

重新組裝 APK

關於整個反編譯和重新 build apk 的流程,可以參考 Huli 的這篇《人人都會的 apk 反編譯》。

用 apktool 反編譯原始碼(其實直接用 MobSF 反編譯出來的應該也可以用,但為了避免 apktool 沒辦法把 MobSF 反編譯的原始碼包回去,省點 debug 的力氣,還是就另外用一次 apktool),然後照著剛剛的規劃,刪掉 5648 ~ :cond_81 的東西。需要特別注意的是,用 apktool 反編譯出來的原始碼會跟 MobSF 的有點不一樣,主要是 symbol 名稱不同,像是 cond_81 可能會是其他數字,但程式碼的結構會是一樣的,修改的時候注意一下就可以。

修改過 smali code 之後,用 apktool b xxx -o new_17.apk 它包回 apk 檔案,

然後它就發生了錯誤…:

I: Using Apktool 2.2.3
I: Checking whether sources has changed...
I: Smaling smali folder into classes.dex...
base/smali/com/machipopo/media17/activity/MenuActivity_V2.smali[5659,0] Cannot get the location of a label that hasn't been placed yet.
Exception in thread "main" brut.androlib.AndrolibException: Could not smali file: com/machipopo/media17/activity/MenuActivity_V2.smali

不確定是什麼原因(畢竟我不是很懂 smali),我猜大概是中間刪掉的 code 有什麼重要意義,所以導致後面呼叫錯誤,但沒關係,還有其他方法可以修改行為:

直接在 5651 行它把結果存入 v0 之後,把 v0 覆蓋為零(5653 行是我加入的),至於問我我怎麼知道 const/4 v0, 0x0 這種寫法?我是往上稍微捲一下, 5637 行就有這種指令,我直接改了寫上 5653 行。

解決這問題之後,使用 apktool b 的時候還有遇到另一個錯誤,照著連結中的解法,成功封裝起來了。

接下來的步驟是 zipalign 打包好的 apk,然後用 apksigner 簽章(產生 keystore 的方法見這裡),方法可以直接參考 Google 的文件

安裝問題

接下來我就把 apk 放進手機裡,然後點開安裝,它卻只會跳出「套件剖析錯誤」的提示,從 adb logcat 看詳細錯誤看到: Parse error when parsing manifest. Discontinuing installation

查了一下,照著這篇改用 adb install 安裝就可以了。

成功

https://dl2.pushbulletusercontent.com/BxEUwLrfTKMNj8HUojseWOQgRLD2rRh9/Screenshot_20180225-225223.png

再也不會有惱人的提示框啦!

參考

學習 smali 語法: http://blog.csdn.net/wdaming1986/article/details/8299996

來龍去脈

其實 17 的 app 在更早以前的版本不只會要外部儲存空間的權限,應用程式一打開,就會向你要求麥克風、相機和簡訊的存取權限,這個是 2017 年 7 月左右版本的原始碼:

當時看這個 app 我就更不爽了,應用程式一打開什麼功能都還沒開始用就跟你要一大堆權限,不給權限的話還整個不給你用,真的是非常不負責任的開發者行為。