基于Smali即時編譯的DEX靜態補丁技術實現
時常有人建議實現一個能對APK進行修改和重打包的功能,其實可以實現重打包的工具不少,想來也是多此一舉,因此長時間沒有搭理。直到前一段時間,有小伙伴反饋重打包某APP,始終失敗,幾乎放棄,原因是DEX中出現了很多非法花指令,他解包時所有的DEX都需要反編譯,由于非法指令太多,回編譯后的APK始終無法正常使用。隨著字節碼指令級的對抗升級,這種問題或許會越來越普遍。針對這種問題,最終極的解決方法就是不反編譯,直接給DEX文件中的指令打補丁,但其中牽扯到指令格式、局部寄存器、各種索引問題需要解決,并非易事。因此為了實現這種解決方法,便有了本文,這種技術我稱為“smali即時編譯靜態補丁技術”:無需采用傳統反編譯手段對整個APK進行反編譯后做代碼修改工作,僅僅針對某一條或多條smali指令進行修改并將修改指令直接patch到DEX文件中,也不需要對所有代碼做二次編譯。
Contribution:
◆實現全新輕量級smali編譯器LiteSmali
◆指令語法提示器
◆花指令清除器
◆廣譜匹配的花指令全局識別器
下載地址:
https://github.com/charles2gan/GDA-android-reversing-Tool
GDA主頁-亞洲首款現代交互式Android反編譯器
(http://www.gda.wiki:9090/)
演示:
1、修改DEX文件中的字符串

2、 修改跳轉指令吐出Flag:

一 簡述
為了實現APK的插樁和修改,通常做法是使用backsmali工具將目標DEX/APK反匯編成為smali代碼文件,然后搜索要修改的文件并對感興趣的smali代碼進行修改,完成代碼修改后再通過smali工具將其編譯回去(其他集成這些核心工具的軟件底層原理也類似)。我們知道一款商業化的APP,其內部可能包含著上萬個類以及上千萬級的字節碼指令,這里面存在著各種對抗的可能性,這些對抗手段極容易導致類似于apktool/backsmali/smali工具在工作過程中出現錯誤。尤其在處理一些被加固保護過的APP,或者被混淆或者采用了各種對抗手段的APP時,錯誤率會顯著提高,哪怕只要有一個小段代碼處理不當,或者某一個需要反編譯的文件(DEX文件/XML文件/資源文件)無法正常反編譯,就會導致整個插樁或patch過程失敗。
因此,實現一種直接對DEX文件打補丁的技術能夠很好簡化整個過程,避免這類情況的出現。直接補丁技術可以完全脫離一系列工具鏈的依賴,同時也不需要反編譯和編譯整個APK文件及其內部文件(包括XML文件、資源文件、DEX文件等等),節省了系統開銷、簡化了patch過程、避免了不可期的錯誤。
本文旨在介紹新版GDA反編譯器中引入的一種可以直接對DEX文件進行smali代碼級patch的技術,并通過一些簡單的例子來分享其使用方法。即使你不熟悉smali也能夠方便的自定義自己的代碼,并通過簡單的操作就能夠將所有修改內容方便地回寫到APK文件中,并自動實現APK的簽名和安裝。你可以一邊修改代碼,一邊查看執行結果。
注意:由于修改后APK需要簽名,因此我不得不引入apksinger.jar,所以需要你系統中有java環境,如果GDA自帶的簽名工具簽名失敗,你可以用自己的簽名工具對未簽名成功的APK進行簽名。

二 實現原理
我們這里談及的DEX Patch實際上指的是對類的方法代碼進行修改,直接對字節碼做修改并不是我們期望的目標,因此這里實現的要求是對任意位置的二進制數據實現smali輸入、smali編譯和字節碼生成,最后將生成的字節碼回寫到dex文件中。

原理其實很簡單,這整個過程最核心最難的部分就是smali編譯器,常規smali編譯器其實并不滿足我的需求:一則類似于smali這樣開源編譯器(準確說叫匯編器)大而復雜;我這里是需要嚴重依賴DEX文件來生成字節碼,其中class descriptor, method index, field index, string index這些都需要從DEX文件中去提取;三則是smali編譯器很難去完成獨立于上下文的單個smali編譯的工作。因此再造輪子長遠來看更劃算,而且通過c++實現能夠能夠更好的和GDA的GUI交互,效率更高也更容易維護。于是,一個可逐條編譯且上下文無關的smali編譯器誕生了,這是一個非常輕量級的smali編譯器,沒有龐大的詞法/語法器。我將其命名為LiteSmali。
當然,有了LiteSmali還遠遠不夠,要做好與GDA反編譯器之間的交互以及與Android設備交互,還需要做大量的工作比如smali指令實時提示、批量編譯、DEX回寫、校驗簽名以及APK安裝。為了延續GDA原有的代碼編輯,單個指令編輯依然放到smali文本框中實現,當然也提供批量smali代碼的輸入,并且實現從指令修改到APK安裝的一站式操作,簡化了代碼patch到代碼執行的過程。整個patch和運行的過程不需要任何第三方工具介入。GDA的liteSmali支持的交互操作如下:
1) Smali指令自動提示:支持所有smali指令的實時格式提示(包括指令、操作碼、寄存器)。
2) 對象自動提示:根據你的輸入,程序從當前dex空間中搜索可用的對象給你使用,其中包括string, class/type, method,field。
3) 一鍵patch: 通過回車鍵實時將smali代碼patch回代碼中。
4) 字符串patch:可以對dex中的字符串進行修改。
5) DEX自動校驗:修改完后,自動更新DEX文件的signature和checksum。
6) 回寫DEX到APK并自動簽名:一鍵實現將已修改的DEX文件回寫到APK文件中,通過GDA自帶的證書文件或自定義的證書對APK文件進行簽名。
三 使用方法
這里我以一個crackme為例介紹該功能如何使用,該crackme是一個以輸入密碼釋放正確來彈出flag的程序。該APK來自github,我把這個crackme放在本文的附件中。這是一個非常簡單的crackme,本文主要用來演示如何編輯指令。
1、 破解crackme演示

在上述動畫里,鼠標單擊你所需要編輯的指令,按M鍵,該指令的smali代碼便會顯示在右上角smali編輯框中,編輯好后按下回車鍵生效,GDA會patch內存中DEX文件,同時會自動獲取下一條指令等待你的編輯,如果不需要忽略即可。最后,你可以按下R鍵將修改后的DEX文件保存到磁盤中同時更新checksum和signature,然后回寫到原APK文件中,最后簽名APK并安裝到Android設備中。
2、 編輯以及恢復任意smali
如果想返回編輯前的文件,你可以通過CTRL+Z恢復上一次編輯的指令。下圖中,首先連續編輯幾條指令后,通過快捷鍵逐條恢復指令。

3、批量編譯smali
如果你想將多條smali代碼一次性的寫入DEX文件中,你可以首先將代碼寫如文件中,然后右鍵菜單->Multi-Dex Edit,導入你的文件,進行編輯,注意:smali代碼支持的格式包含如下三種情況:
1) 純smali代碼
2) 帶偏移的smali代碼
3) 帶字節碼的smali代碼
對于跳轉指令,你需要在GDA中修正跳轉偏移地址。

4、函數調用注意事項
在函數調用時應該注意,你要調用的方法是否是私有方法,雖然GDA能夠編譯通過,但是你無法啟動APP。

5、關于構造smali shellcode
雖然直接編輯DEX文件無法引入新的字符串和API,但如果我們選取一個空間足夠的方法來引入,也是可行的。具體做法也很簡單,我們通過建立一個數組,將字符串逐個填充即可,如果是API也可以先填充APK字符串,然后通過反射調用來實現。最后提取出shellcode字節碼即可,以下是我構造的一段shellcode代碼。

6、關于花指令清除與恢復
GDA的花指令清楚有兩種方式,一種是直接對選擇的指令行進行nop,可以通過CTR+Z恢復,一種是通過廣譜匹配的方式實現進行指令清除,可以在右鍵菜單中恢復。

上面動畫演示直接NOP掉你所識別出來的花指令,花指令清楚后,反編譯代碼恢復正常。
通過廣譜匹配清除所有花指令,需要編輯匹配規則,注意確認生效后,當前頁面需要刷新以后才能看到。

其中規則中兩個問號代表一個任意字節匹配,通過大括號來指定任意值的長度,規則非常簡單易用。如 1a05??{2}6e101b??{3} 可以匹配如下指令:

每個匹配都會保存在Junk Manager中,你可以點擊每個規則以恢復被該規則清除的指令,如圖。

7、恢復所有被編輯過的DEX到原始DEX
如果需要將所有修改一次性恢復到原始狀態,可以通過點擊菜單欄中的如下菜單實現。

四 總結
當然,基于DEX文件的代碼編輯,并不是那么完美,依然有很多限制,比如難以引入新的字符串和API,許多資源受限于DEX本身,不管如何這也是一項值得嘗試的技術,比如構造shellcode等等,正是得益于GDA反編譯器保留指令級別的特性,使得該技術得以實現和應用,使用如有問題,可以到GDA的github主頁去留言反饋問題,同時希望小伙伴們start我的GDA反編譯器項目。
歡迎star: GDA github(https://github.com/charles2gan/GDA-android-reversing-Tool)