如何防止 Android 應用被二次打包?

防止二次打包一般會用到如下幾種手段:

1. Java 代碼中加入簽名校驗(直接修改smali文件繞過)

2. NDK 中加入簽名校驗(ida查看直接hex修改就可以繞過)

3. 利用二次打包工具本身的缺陷阻止打包(manifest 欺騙,圖片修改後綴等等)

以上方式其實都挺容易繞過去的,希望能和大家一起從技術層面上來探討如何防止二次打包(使用梆梆等加固服務不在討論範圍,但討論其原理還是非常歡迎的~~)


講個好玩的

這個 App 在 Jar =&> Dex 這步用了自定義的混淆方式,將類名、函數名和變數名都替換成了繁體中文(替換的 token 都來自一本叫做 《難經本義》的古醫書,相當漲姿勢)

目前 baksmali 對 utf8 字元的處理存在問題,無法處理這樣的 Dex(apktool 依賴於 baksmali,所以也不能正常工作),從而提高了二次打包的門檻


謝邀

題主的問題是問怎麼防止,我可以給出的答案是不可能,只是難度問題。

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

我想從攻擊的角度來說這個問題。以一個市面上大公司的app為例,講一下我是怎麼繞過它的防範機制,修改代碼(彈出一個提示框),並進行二次打包,重新簽名,運行的。在寫這篇文的過程中,我也遇到了一些問題,我在文章的最後進行了整理提問,希望這方面經驗豐富的開發者可以一起交流。

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

好,以下是正文

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

反編譯工具

1, apktool反編譯利器

2, dex2jar將dex文件反編譯成jar文件(java代碼)工具,用於解讀代碼

3, gui 打開jar文件工具

簽名工具

1, apksign給java程序簽名的工具

2, 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& 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的方式跟伺服器驗證,如果使用不恰當,基本也是完全無效的,比如該應用,成功與否只判斷伺服器返回的一個字元串,而且判斷語句是在本地,這個完全是可以繞過的。

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

【原創】Android程序的簽名保護及繞過方法

幾個問題:

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

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

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

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

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

嗯。看到這,你也該小手一抖,點個贊了吧。


主要邏輯以及驗證完整性的部分全部丟到so模塊里,然後對so模塊進行各種桌面軟體常用的加殼、花指令、多重自校驗、自修改代碼等防篡改處理

不過好像java部分只是個殼的Android應用數量很少吧,基本都是遊戲、模擬器一類

或者把整個程序插件化,把完整性檢測丟到插件里


你們都弱暴了, 看我怎麼混淆的, 點開圖片仔細看

混淆字典開源, 有人給star嘛

https://github.com/qbeenslee/gradle-proguard-dictionary


應用加固,很多產商都有可直接用。

如下:

1、梆梆;

2、通付盾;

3、360加固保;

4、愛加密;

5、騰訊;

6、娜迦

來源


登錄時伺服器發送隨機密鑰,利用ndk實時計算文件簽名,並使用獲得的密鑰加密,然後上報到伺服器,由伺服器校驗簽名是否有效,決定是否允許該軟體登錄。核心業務邏輯放伺服器上,不允許繞過伺服器單機運行。

如此一來,因為沒有任何簽名數據寫在程序中,所以hex修改的方法無效。


不要想了,你過了反編譯或加固還有沙盤(droidplugin)等著你吶


1、為什麼要防止二次打包

2、二次打包有哪些可能用到的技術

3、如何防止二次打包

關鍵代碼用native c寫,然後給so加殼,動態載入.


代碼混淆加jni驗證 包名 改包名就無法使用 不改包名 應用商店上不去


你可以不斷升級版本


其實從移動應用安全形度來看的話,我們可以從如何提高破解成本,如何提高二次打包的難度的角度來考慮。首先我們來看二次打包會做哪些操作:

1)修改包名,但也有部分是不改包名的,直接把他想要的內容插進去

2)簽名信息修改,由於修改了apk信息,需要重新簽名,簽名信息自然改了

3)修改原apk的部分功能或邏輯,因為要添加自己的代碼嘛

4)修改資源,比如圖標,字元串,app名等

其它還包括篡改伺服器地址,去除廣告等等。

那麼從保護角度來看,要如何防止呢:

1)包名校驗,一般校驗前面幾個欄位,因為很多應用最後一個欄位一般是渠道的

2)簽名校驗,這個應該是最有效最直接的,發現簽名非法,不讓運行

3)代碼被篡改這塊很難直接防,可以使用apk加固進行保護

4)資源文件校驗也可以做,比如app名是否是目標名,圖標什麼的進行校驗

這些校驗建議可以放在so層來做,當然這個so本身又是app核心功能不能缺失的,然後再對so進行加固、混淆保護。

經過几上層層安全校驗和保護,apk的安全性肯定是大大提高,起碼可以經得起攻擊了,而不是原來的不堪一擊。


androidAPK或者說java的特殊性很容易反編譯出來,不過一般用混淆工具混淆防止源碼被破譯,不過我下載過一個遊戲被360打包過以後我用一般的反編譯工具編不出來,希望知道的朋友指點下


應用加固


你試試二次打包QQ。

然後順著這個思路走


所有的混淆編譯,都是沒什麼用處的邪道,確保應用發布只有通過「數字簽名」一條路,培養用戶驗證簽名的習慣更重要


推薦閱讀:

在華強北有沒有可能組裝一部手機?
為什麼 Moto X 銷量慘淡?
如何評價摩托羅拉於 2015 年 7 月 28 日發布的兩款 Moto X 新品以及 Moto G?

TAG:數據安全 | Android開發 | Android手機 | Android |