前言
隨着移動互聯網的爆發性增長,人們對移動應用的需求變得越來越復雜,企業在帶給用戶眾多便利和享受的同時,卻容易忽視應用自身的安全性問題,一旦遭受攻擊,就會給企業和用戶的經濟或聲譽帶來影響。本文主要是站在企業的角度,闡述如何通過給android SO(動態鏈接庫)加殼來提升移動APP的安全性,減少SO被逆向反匯編分析的風險。
注:本文只做單方面的總結,如果對整體提升移動應用安全性有需求的人員,可參考作者另外一份文檔:《移動應用安全開發指南v1.0(Android)》。
撰寫本文的目的:
1、 為移動應用開發人員提供安全加固技術指導,作為發布時加固的實際依據。
2、 對實施過程做詳細的記錄和總結,為需要單獨創建加固環境的人員提供具體細節,避免或少走彎路。
建議使用方法:
對於需要創建加固環境的人員,請閱讀“創建加固環境”章節,對於只需要在加固環境下對SO加固的人員(比如開發人員),只需了解“加固步驟”章節即可。
創建加固環境(X64 Linux)
1、下載UPX和依賴組件的源碼
UPX -3.92-src:https://www.pysol.org:4443/hg/upx.hg/archive/tip.tar.gz
注:v3.92為寫本文檔時的最新官方非正式發布版本(正式發布版本為v3.91)
下載入口如下:
LZMA4.43:http://nchc.dl.sourceforge.net/project/sevenzip/LZMA%20SDK/4.43/lzma443.tar.bz2
UCL1.03:http://www.oberhumer.com/opensource/ucl/download/ucl-1.03.tar.gz
2、刪除UPX殼描述信息
編輯$(UPX_SRC_ROOT)/src/packer.cpp,刪除該文件中定義的關於UPX殼的相關描述信息(詳情請參考附錄à相關問題總結5.5)。
3、編譯
3.1、編譯zlib:
tar zxvf zlib-1.2.3.tar.gz
cd zlib-1.2.3
make //編譯生成libz.a
cp libz.a /usr/lib64/libz.a //拷貝到系統默認的動態鏈接庫路徑下。
3.2、編譯UPX並執行
cd $(UPX_SRC_ROOT) //進入UPX源碼根目錄
CXX=g++ UPX_UCLDIR=/home/soft/ucl-1.03 UPX_LZMADIR=/home/soft/lzma-4.43 UPX_LZMA_VERSION=0x443 make all //編譯UPX
說明:UPX_UCLDIR和UPX_LZMADIR的值分別為UCL和LZMA解壓后的根路徑,UPX_LZMA_VERSION環境變量則指定了LZMA的版本。
編譯成功后可在$(UPX_SRC_ROOT)/src下查看到可執行文件upx.out,如下圖所示:
執行效果如下:
加固步驟
1、配置NDK集成開發環境
參考附錄《配置和使用NDK集成開發環境》的《配置步驟》章節。
2、修改native代碼
2.1、在native代碼中定義全局變量用於增加生成的二進制的體積,例如:
C:int const dummy_to_make_this_compressible[100000] = {1,2,3};
C++:extern "C" int const dummy_to_make_this_compressible[100000] = {1,2,3};
注意:如果編譯出來的庫本身足夠大,則此步驟可省略。
2.2、在native代碼中聲明_init()函數,用於在編譯時生成_init段,例如:
C:void _init(void){}
C++:extern "C" {void _init(void){}}
注意:C和C++代碼定義或聲明的方式是有所區別的,在C++中必須使用extern “C”關鍵 字進行修飾,被extern "C"修飾的變量和函數是按照C語言方式編譯和連接的
2.3、在native代碼中使用宏定義混淆函數名,用於增加靜態反匯編分析難度,例如:
#define startSimpleWifi sSW
#define sendData sD
……
3、對SO庫文件加殼
3.1、打開cygwin,進入Android工程目錄,在NDK環境中編譯native代碼(詳情可參考 附錄《配置和使用NDK集成開發環境》的《編譯native代碼》章節),也可以通過CDT 自動編譯(參考《配置和使用CDT編譯環境》),編譯通過后將在libs目錄下生成SO 動態鏈接庫文件。
3.2、將編譯生成的SO庫文件上傳到加固服務器(本文將其和UPX執行文件放同一目錄), 如下圖所示:
3.3、對SO庫進行加殼,常用命令:upx.out –o libhello-jniupx.so libhello-jni.so
4、驗證
4.1、在eclipse的Android工程中使用加殼SO替換原有的SO,如圖所示:
4.2、將使用加殼SO的Android程序安裝到設備或模擬器中執行功能驗證,若各項功能均正 常則表示加固成功。
加固前后效果對比
1、加固前反匯編:
2、加固后反匯編:
附錄
1、術語表
| 術語 |
定義 |
| UPX |
UPX是一個著名的壓縮殼,主要功能是壓縮可執行文件(比如exe,dll和elf等文件),有時候也可能被病毒用於免殺。 |
| 加殼 |
加殼的全稱應該是可執行程序資源壓縮,是保護文件的常用手段。加殼的程序經常想盡辦法阻止外部程序或軟件對加殼程序的反匯編分析或者動態分析,以達到它不可告人的目的,這種技術也常用來保護軟件版權,防止被軟件破解。 |
| 交叉編譯 |
就是在一個平台上生成另一個平台上的可執行代碼,比如在X86 Linux上編譯出可在ARM Linux上執行的程序。 |
| JNI |
JNI是Java Native Interface的縮寫,中文為JAVA本地調用,它允許Java代碼和其他語言(比如C/C++)寫的代碼進行交互。 |
| 反匯編 |
把目標代碼轉為匯編代碼的過程,也可以說是把機器語言轉換為匯編語言代碼、低級轉高級的意思,常用於軟件破解、外掛技術、病毒分析、逆向工程、軟件漢化等領域。 |
2、配置和使用NDK集成開發環境
2.1、配置步驟:
2.1.1、http://www.cygwin.com/下載cygwin並雙擊安裝。
2.1.2、選擇從internet安裝。
2.1.3、選擇一種能連上網絡的方式
2.1.4、建議選國內的鏡像站點(速度快)
2.1.5、在search中輸入”make”,選擇Devel列表中的所有package進行安裝,選擇方法是點擊package名,由keep變成install字樣即可。注:本機已安裝,故顯示為reinstall。
2.1.6、繼續下一步直至安裝完畢。
2.1.7、運行cgywin bash,輸入cygcheck -c cygwin命令,若顯示Cygwin 的package信息則表 示運行正常。
2.1.8、分別運行gcc –version、g++ --version make –version和gdb –version來檢查相關組件是 否運行正常。
2.2、編譯native代碼:
2.2.1、使用NDK編譯一個程序,首先我們要找到我們cygwin的程序安裝目錄,找到一個 home\<你的用戶名>\.bash_profile文件,如下圖所示
2.2.2、在該文件末尾添加ndk=/cygdrive/<你的盤符>/<android ndk 目錄> 例如: ndk=/cygdrive/f/android/adt-bundle-windows-x86-20131030/android-ndk-r9d
export ndk
注:"ndk"這個名字隨便起,因為后面要經常使用,建議不要太長。如下圖所示
2.2.3、之后重新打開cygwin,輸入 cd $ndk,如果進入了android ndk 目錄,證明環境變量 設置成功了
2.2.4、嘗試使用NDK編譯android NDK的樣例程序hello-jni,路徑為:
<android ndk 目 錄>/samples/hello-jni,比如:
F:\android\adt-bundle-windows-x86-20131030\android-ndk-r9d\samples\hello-jni
2.2.5、進入工程根目錄
2.2.6、執行$ndk/ndk-build命令,執行成功后它會自動生成一個libs目錄,把編譯生成的.so文件放里邊,使用file命令可查看到文件為經過交叉編譯的ARM Linux 動態鏈接庫。
注:執行$ndk/ndk-build實際等於執行NDK目錄下的ndk-build命令,如下圖所示:
3、配置和使用CDT編譯環境(非必須)
3.1、為eclipse安裝CDT插件,也可以直接下載帶CDT插件的eclipse(略)。
3.2、在eclipse中選擇右鍵選中項目,選擇Properties,在彈出的對話框中選擇Builders。
3.3、點擊對話框中的New新建一個編譯器,在新彈出對話框中選擇ProgramàOK。
3.4、對新建編譯器進行配置,如下圖:
參數解析如下:
Name:編譯器的名字,可隨便取。
Location: <你cygwin安裝路徑>\bin\bash.exe程序,即cygwin的bash程序的路徑。
Working Directory: 你cygwin安裝路徑>\bin目錄。
Arguments:給cygwin bash傳遞的參數,里面主要包含進入工程目錄並使用NDK執行編譯的命令。
3.5、接着切換到Refresh選項卡,給Refresh resources upon completion打上鈎,如圖:
3.6、最后切換到Build Options選項卡,勾選上最后三項,如下圖所示:
3.7、點擊Specify Resources按鈕,選擇資源目錄,勾選你的項目目錄即可,如下圖所示:
3.8、保存配置后將回到Properties對話框,點右邊的Up按鈕,把它排到第一位,否則C代碼的編譯晚於Java代碼的編譯,會造成你的C代碼要編譯兩次才能看到最新的修改,如圖:
3.9、在eclipse中build Android工程,可以看到同時編譯native代碼並生成了SO庫。
4、APK重打包流程
4.1、下載並安裝APK改之理,下載地址:http://www.xiaomiren.net/
4.2、啟動APK改之理,選擇項目à打開APK 打開要重打包的APK程序。
4.3、打開成功后可看到原有的工程目錄結構,打開libs目錄可以看到APK需要載入的SO庫。
4.4、刪除要替換的SO,並把修改過的SO(比如經過加殼的SO)添加進來。
4.5、重新編譯APK,即可安裝到設備中運行並觀察替換SO后的效果。
5、相關問題總結
5.1、編譯UPX出現“cannot find -lz”錯誤。
分析:原因是鏈接器LD沒有找到編譯出來的zlib庫libz.so或libz.a。
解決方法:將libz.so或libz.a拷貝到系統默認的動態鏈接庫路徑下,比如/usr/lib,/usr/lib64 等。
5.2、編譯UPX出現“CantPackException: DT_TEXTREL found; re-compile with -fPIC”錯誤。
分析:這是早期NDK版本的BUG。
解決方案:使用NDK9或以上的版本
5.3、編譯UPX出現“NotCompressibleException”錯誤。
分析:UPX對被加殼的二進制文件有最小限制,太小的文件將無法被加殼。
解決方案:在native代碼中定義足夠大的數據變量,使得編譯出來的二進制文件容易達 到UPX的要求(參考《加固步驟》之《修改native代碼》章節)。
5.4、編譯UPX出現“UnknownExecutableFormatException”錯誤。
分析:被加殼的二進制文件必須存在init段,否則UPX將無法脫殼還原原始代碼。
解決方案:在native代碼中定義_init()方法,需要注意C和C++的區別(參考《加固步 驟》之《修改native代碼》章節)。
備注:查看二進制文件是否存在init段的命令:readelf –dynamic xxx.so,如下圖:
5.5、使用UPX加殼的SO,在eclipse中啟動Android程序時出現”Fatal signal…”錯誤,如圖:
分析:此錯誤是因為UPX解析某些特殊字符處理不當導致的,該BUG已經有人提交UPX 官方解決,但是當前官方正式發布的正式版本(V3.91)並沒有fix該問題,而是在未正 式發布的V3.92才解決了該問題,因此本文檔使用源代碼版本為V3.92而非V3.91。
解決方案:下載使用V3.92源碼,下載入口請參考《創建加固環境》章節。
5.6、為何刪除UPX源碼中的軟件信息,以及如何定位查找這些信息。
分析:UPX對文件進行加殼時會把這些信息寫入殼內,通過靜態反匯編可查看到這些殼信息,進而尋找對應的脫殼機進行脫殼,使得攻擊難度降低。
解決方案:在UPX源碼中刪除這些信息,並重新編譯,步驟如下:
5.6.1、使用原始版本對文件進行加殼。
5.5.2、使用IDA反匯編加殼文件,在反匯編文件的上下文中查找UPX殼特征字符串, 如下圖所示:
5.5.3、在UPX源碼中查找這些特征字符串(建議使用Search and Replace),並一一刪除, 如下圖:
5.5.4、重新編譯UPX(參考“創建加固環境”章節)。
5.7、在沒有eclipse源碼工程的情況下如何直接替換APK的SO並觀察結果?
解決方案:參考《附錄》之《APK重打包流程》,值得注意的是,如果Android程序中 內置了檢查簽名合法性的安全機制,使用該方法的前提是先破解該簽名驗證機制。















































