1. 程式人生 > >記一次android程式反編譯並二次打包的過程

記一次android程式反編譯並二次打包的過程

在安全界有一句話叫不懂攻,焉知防。

前幾天看到有一個問題在問Android端目前防止二次打包的方法有哪些?我想從攻擊的角度來說這個問題。在分析過程中講解每一步都有哪些防範手段。下面以一個市面上大公司的app為例,講一下我是怎麼繞過它的防範機制,修改程式碼(彈出一個提示框),並進行二次打包,重新簽名,執行的。在寫這篇文的過程中,我也遇到了一些問題,我在文章的最後進行了整理提問,希望這方面經驗豐富的開發者可以一起交流。

另外,繞過程式的防二次打包機制畢竟不是一件好事,搞不好做這個程式的程式設計師要背鍋,所以文章中程式碼都是以圖片形式給出,關鍵識別位置都打了馬賽克,但是我想一些有心人還是可以看出這是什麼程式,你看出來就看出來吧,就不要說出來了,好嗎。

好,以下是正文

工欲善其事,必先利其器。首先準備好工具:

反編譯工具

  • apktool 反編譯利器

  • dex2jar 將dex檔案反編譯成jar檔案(Java程式碼)工具,用於解讀程式碼

  • gui 開啟jar檔案工具

簽名工具

  • apksign給java程式簽名的工具

  • testkey.pk8 teskkey.x509.pem用於簽名的檔案

首先下載好apk

用ApkTool反編譯android程式

用apktool反編譯,命令如下

成功後會在同級目錄生成一個test資料夾

這就是反編譯之後的Android程式了,可以看出,這個目錄結構跟我們編寫android程式碼時的目錄結構非常相似,除了java程式碼是以smali的格式呈現之外,其他都基本是原來的程式碼。其實有很多人抄介面,到這一步就可以抄出完整的介面了。如manifest檔案,裡面的Activity定義都可以看的很清楚了。然後layout檔案,各種res都可以看見了。

其實寫到這,我就有個問題了,這一步怎麼防?我不知道,願請教一二。

如果我們要參考(chao)一個程式的介面,到這一步已經夠了,以為所有的res和layout檔案已經能看到了。

改程式碼重新編譯也是要在這個資料夾中改smali檔案的,所以smali的語法還是要熟悉一點。但是看程式碼邏輯我們不用去看晦澀難懂的smali語言,這就是下一步要做的工作。反編譯出java程式碼。

用dex2jar反編譯出java原始碼

第一步做的工作先放在這,我們需要重新操作apk檔案,其實apk檔案就是一種壓縮包,所以我們把字尾名改成rar,用解壓縮工具開啟。

看到這裡,有人會問,為什麼不直接解壓縮,跟我們剛才用apktool反編譯出來的不一樣嗎,你可以試一下。

這裡其他檔案在apktool那一步已經反編譯出來了,我們需要的僅僅是class檔案,這是java程式碼編譯後生成的檔案,用dex2jar這個工具就可以反編譯出原始碼(java格式)了。把這個class檔案解壓出來,放在dex2jar的同級目錄下。

命令如上,成功之後就會在同級目錄下生成jar檔案了。

用gui檢視程式碼

還記得一開始我們說過的工具gui,通過gui開啟jar檔案,就能看到java程式碼了

這裡所有的引入的包程式碼都會有,那麼怎麼尋找我們要的主程式程式碼呢,這就要依賴在第一步我們反編譯出的manifest檔案,熟悉android的朋友知道,在manifest檔案中有兩個資訊比較重要。

一是包名,也就是主程式的路徑,在manifest的最開始一行。

第二個資訊是入口activity,這個很簡單,只要找到有launcher標識的activity就是入口activity。

現在你就可以去gui裡面找到這個入口類了

程式碼有混淆,但是混淆只是替換了一些變數名或者類名而已,增加了程式碼閱讀的困難性,並不會修改程式邏輯本身,所以只要靜下心來慢慢看,還是看到懂得。

至此,反編譯的過程就結束了,你想看到一個程式的邏輯或者一個程式的介面邏輯都可以看的到的。

重新打包,簽名,執行

下面,開始進行最重要的工作,修改程式碼,二次打包。其實這裡你可以什麼程式碼先都別改,只重新打包一次,看看程式是否能夠正常執行,如果不能,看看程式是哪一步阻止了執行,這也方便你後期定位簽名驗證的位置。目前我見過的簽名驗證有以下幾種:

  • 直接丟擲異常,禁止執行
  • 彈出提示框提示使用者,提示框消失後,退出程式
  • 跟伺服器互動傳遞簽名信息,如果不正確則伺服器不返回資料

重新打包是這樣的,還要用到apktool,記得在第一步反編譯出的那個資料夾嗎,就是用這些檔案再重新打包。打包命令如下:

成功後,在同級目錄下會看到test1.apk檔案,這裡只是打包成功了,程式還沒有簽名,沒有簽名的程式是無法安裝到手機上的。簽名用的的是apksign這個工具,這是java提供給開發者用於程式簽名的工具,android的各類IDE也是用這個工具在簽名。使用方法如下,將signapk.jar,testkey.pk8,testkey.x509.pem放在一個目錄下,寫一個signapk.bat檔案,如下

java -jar signapk.jar testkey.x509.pem testkey.pk8 %1 %2

然後執行命令

成功後會在同級目錄下生成一個簽過名的apk檔案,這個檔案我們需要的最終檔案,只要你改過程式碼並且簽完名後這個apk可以正常安裝執行,那麼本次的任務就算完成了。現在安裝一下,看看會發生什麼。

程式啟動,然後彈出提示框

程式彈出提示,點選確認後退出程式,看來這個app的簽名驗證是用了我說的上面第二種方法,下面來進行一些嘗試來繞過這個簽名驗證。

繞過程式防二次打包機制

首先,我建議大家先全域性搜一下signatures這個字串,因為程式要獲取app的簽名就要通過packageInfo.signatures這種方式,如果在這裡我們不讓程式獲取到真正的簽名,而是直接返回給它那個“正確”的簽名,豈不是瞞天過海,一步搞定。當然了,你必須要有原來那個程式的“正確”簽名,不過這個簡單,android系統並不阻止你去獲取其他程式的簽名,所以我們可以寫個小的test程式,然後安裝原來的apk,去獲取一次正確的簽名,記錄下來。

獲取其他程式簽名程式碼如下

private static String getSignture(Application paramApplication) {
    try {
        String packageName = "packageName";
        List<PackageInfo> packages = paramApplication.getPackageManager().getInstalledPackages(PackageManager.GET_SIGNATURES);
        for (PackageInfo packageInfo : packages) {
            Signature[] signs = packageInfo.signatures;
            Signature sign = signs[0];
            String signString = sign.toCharsString();
            System.out.println(signString);
            return signString;
        }
    } catch (Exception e) {
        return "";
    }
    return "";
}

先裝上原來從正常渠道下載的程式,然後改一下包名,執行這個程式,就能得到正確程式的正確簽名了,記錄一下簽名,然後去我們反編譯的程式碼裡面找signatures相關的程式碼,看在哪裡獲取了簽名並驗證。

程式中一共有三個地方,MainActivity裡是程式用到的,另外兩個是第三方庫的簽名校驗,像微信支付這種第三方庫都會校驗簽名,這個可以暫時不管,所以要管的其實就只有MainActivity裡這個了,看這個方法:

是不是跟我寫的那個方法完全一樣,這個方法其實是獲取程式的本來的簽名的,這就好說了,我們直接返回剛才記錄的“正確”簽名就可以瞞過程式了。

好,第一次嘗試,去apktool反編譯出的檔案中的smali資料夾下找到這個類MainActivity,如下

這是smali的語法,挺複雜的,感興趣的朋友可以自己再翻閱一下資料。這裡我們把這個方法全部注掉,直接返回“正確”的簽名。如下

按照前面說的簽名的方法,重新打包,簽名,安裝。

我們會發現,程式第一次進入是不行的,還是會提示,簽名驗證失敗,第二次之後就可以正常進入了,這不是我們要的完美效果,思考一下,為什麼會有這個情況,我想到以下幾種原因:

  • 第一次的時候signinfo還沒有獲取,為空,所以認為是非法的
  • 除了這裡,程式在另外的地方做了二次驗證,而且這個二次驗證並不一定每次都能執行成功,這個很像是一個網路請求方法,跟伺服器做驗證,所以根據網路情況,並不一定每次都成功。

如果是第一種情況,為什麼正常的程式沒有問題,我們就只是讓返回值變了一下,其他並沒有改變邏輯。我推測是時間差,因為原來的方法執行獲取簽名需要較長的時間,而直接返回正確簽名很快,難道是這個時間差的影響?我決定把原來那個方法改回來,只修改返回值。如下:

只修改返回值,原來的邏輯不變,時間差應該也排除了,重新打包簽名執行。好吧,很明顯不是,而且情況更嚴重了,前面這些只是我的經驗之談,你在完全不瞭解邏輯的情況下,可以這樣先試一下,我想能繞過30%的app吧。如果是上面說的第二種情況,我們還是來看一下程式碼邏輯吧。

全域性搜一下應用簽名驗證失敗這句話,看看什麼情況下會觸發。

一共有兩處,我們先看第一處

其實混淆後的程式碼挺噁心的,你看這個邏輯好像是如果LoginActivity的c方法為null就執行,但是你去看c方法就會發現根本就沒有返回值,穩穩的null。這裡程式碼其實是這樣看的,要跳出前面那個while,所以我們去loginActivity找what值是19的情況。

往前看,可以發現他呼叫了一個方法

看來驗證應該是在這裡了,而且是一個網路請求驗證,所以這個app的防二次打包的機制已經做的比較好的。研究下這個方法,混淆程式碼不是很容易看,我先用抓包工具抓了一下包。

發現程式在啟動的時候發了兩個用來驗證的請求,第一個請求沒有引數,伺服器返回如下欄位

第二次請求帶有如下引數

正常的包伺服器返回的是status=1,而我重新打包後伺服器返回的是status=0

這是一種典型的challenge-response的方法,伺服器發來challenge,然後程式用自身特性的一個字串加密後再返回response,如果正確,則通過驗證,反之則阻止執行。

這裡我想的是,我找的加密challenge的那一段演算法,看他是用什麼方式加密的,用的是程式的哪一段特徵值,然後像前面改簽名一樣,用“正確”的特徵值替換下。

但是,恕我愚鈍,看不懂程式碼,這裡我貼一下邏輯,有大神對混淆比較瞭解的可以跟我交流下。

首先loginActivity調了這個Post請求,第一次呼叫引數為空,伺服器會返回challenge 四個字串

程式會把這四個字串交給一個handler處理

抱歉我追到這就追不下去了,因為中間這幾個不管a還是b都因為混淆無法直接找到,我也沒想出什麼能間接找到的方法。

是不是到這就束手無策了呢,其實也不是,前面的分析是希望在最上游解決問題,如果我們能在最上游把問題解決了,下面不管什麼邏輯都不用擔心了,但是現在最上游無解了,那麼我們就往下找一找,前面說過, 簽名驗證失敗彈框是在伺服器返回後根據伺服器返回資訊來判斷的,那麼我們可以把判斷的邏輯改掉。

將這個程式碼改成永true

我們去smali找到LoginActivity裡的f類,smali編譯時會把所有的內部類編成一個單獨的檔案,所有我們去找LoginActivity$f這個檔案

這段程式碼是比較status和1,如果為0則跳到cond_2,cond_2就是會給message19的那部分程式碼,這裡我們不讓他跳轉,所以刪掉這一句即可。另外MainActivity裡也有一個同樣的校驗,一起改掉就行了。

現在打包,簽名,執行

程式正常啟動,沒有彈出任何異常提醒,試試其他功能,都正常。既然簽名驗證我們搞定,現在往裡面加一句彈toast的程式碼,輕而易舉,我準備加在MainActivity的onCreate的時候,找到這部分程式碼。

注意要加在super.onCreate之後。彈框程式碼如下

加完程式碼之後如下

打包,簽名,執行

效果如上,至此,這篇文章就結束了,我們繞過了這個app的防二次打包機制,併成功的修改了程式碼。

總結一下

1, 混淆確實是有用處的,雖然混淆後的邏輯仍然可以看懂,但是如果你想去追蹤一些細節邏輯,很難,當然,我混淆程式碼研究的太少,經驗太少也是一個方面。

2, App層面上的簽名驗證基本是無效的的,比如一開始我們說的getSignature這裡。

3, 採用challenge-response的方式跟伺服器驗證,如果使用不恰當,基本也是完全無效的,比如該應用,成功與否只判斷伺服器返回的一個字串,而且判斷語句是在本地,這個完全是可以繞過的。

至於更好的方法,我查資料的時候,網上看到這樣一個方法,同樣是跟伺服器驗證,但是伺服器不是返回一個欄位,而是返回一段核心程式碼,然後程式動態執行這段核心程式碼。我覺得采用這種方法,難度會上升一個層級。但還是無法有效避免二次打包。

幾個問題:

1, 跟伺服器驗證的時候,驗證的是什麼東西,前面講了因為那段程式碼沒跟出來,所以不知道實現邏輯。以我的經驗,二次打包唯一變動的應該就是簽名了,但是簽名我們已經繞過去了,不知道還有什麼可以拿來驗證的東西。

2, Android資源層面的東西有沒有防反編譯的方法,我是說res,layout這些。

ok,洋洋灑灑的終於寫完了,我是覺得自己寫得已經很詳細了,已經到了讀者完全可以複製過程的程度。但難免有一些地方我覺得可以省略,但是讀者不懂,可以在評論區提問,我會回答的。

另外,再次強調一下,繞過程式的防二次打包機制畢竟不是一件好事,搞不好做這個程式的程式設計師要背鍋,所以文章中程式碼都是以圖片形式給出,關鍵識別位置都打了馬賽克,但是我想一些有心人還是可以看出這是什麼程式,你看出來就看出來吧,就不要說出來了,好嗎。

如果這樣還有任何侵犯到開發方權利的地方,開發方可以向我提出,我換個程式繼續搞,哈哈,開玩笑,我會和你們協商如果處理的。