1. 程式人生 > >利用OLLVM混淆Android Native程式碼篇一

利用OLLVM混淆Android Native程式碼篇一

Author: GeneBlue

這裡將會用兩篇文章解釋OLLVM混淆Android Native程式碼的方法和原理。篇一主要聚焦NDK中OLLVM的編譯構建和主要混淆模式的使用,並簡要解釋各混淆模式的效果;篇二主要研究預設混淆模式的實現並嘗試編寫除錯自定義Pass。

0X01 OLLVM簡介

OLLVM(Obfuscator-LLVM)是瑞士西北應用科技大學安全實驗室於2010年6月份發起的一個專案,該專案旨在提供一套開源的針對LLVM的程式碼混淆工具,以增加對逆向工程的難度。目前,OLLVM已經支援LLVM-3.6.1版本。

LLVM是一個優秀的編譯器框架,它也採用經典的三段式設計。前端可以使用不同的編譯工具對程式碼檔案做詞法分析以形成抽象語法樹AST,然後將分析好的程式碼轉換成LLVM的中間表示IR(intermediate representation);中間部分的優化器只對中間表示IR操作,通過一系列的Pass對IR做優化;後端負責將優化好的IR解釋成對應平臺的機器碼。LLVM的優點在於,中間表示IR程式碼編寫良好,而且不同的前端語言最終都轉換成同一種的IR。

LLVM IR是LLVM的中間表示,優化器就是對IR進行操作的,具體的優化操作由一些列的Pass來完成,當前端生成初級IR後,Pass會依次對IR進行處理,最終生成後端可用的IR。下圖可以說明這個過程。

OLLVM的混淆操作就是在中間表示IR層,通過編寫Pass來混淆IR,然後後端依據IR來生成的目的碼也就被混淆了。得益於LLVM的設計,OLLVM適用LLVM支援的所有語言(C, C++, Objective-C, Ada 和 Fortran)和目標平臺(x86, x86-64, PowerPC, PowerPC-64, ARM, Thumb, SPARC, Alpha, CellSPU, MIPS, MSP430, SystemZ, 和 XCore)。

0X02 OLLVM Android編譯環境搭建

以下,介紹OLLVM Android編譯環境的搭建過程。環境資訊:ndk(android-ndk-r10e)、LLVM(llvm-3.6.1)

首先下載原始碼,編譯OLLVM混淆器,這裡採用LLVM的版本是3.6.1。下載編譯過程如下:

git clone -b llvm-3.6.1 https://github.com/obfuscator-llvm/obfuscator.git
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE:String=Release../obfuscator/
make -j5

下載的原始碼裡已經包含了LLVM和Clang,編譯完成後,編譯好的二進位制程式都存放在build/bin目錄下。

依據github上的 wiki ,bin目錄下編譯好的工具鏈可以直接用來編譯混淆linux下的程式,就像我們常用的gcc那樣。若想使用OLLVM來混淆Android Native程式,還需將bin目錄下的工具鏈整合進ndk環境中。

按照ndk編譯工具鏈的組織結構,我們照樣子新建一條工具鏈即可。在toolchains目錄下新建obfuscator-llvm-3.6目錄,並將llvm-3.6目錄下的config.mk、setup.mk和setup-common.mk拷貝到obfuscator-llvm-3.6目錄中,不做任何修改。然後,把原始碼編譯好的bin目錄和lib目錄按照llvm-3.6中prebuilt/linux-x86_64的目錄格式拷貝。接著,在toolchains目錄下分別建立arm-linux-androideabi-obfuscator3.6, mipsel-linux-android-obfuscator3.6, x86-obfuscator3.6目錄,注意資料夾的字首要與原toolchains中的目錄保持一致,然後把arm-linux-androideabi-clang3.6, mipsel-linux-android-clang3.6, x86-clang3.6資料夾下的 config.mk 和 setup.mk 對應拷貝到上述三個資料夾中,此時要分別修改 setup.mk 中的 LLVM_NAME ,即將其指定到開始建立的obfuscator-llvm-3.6目錄。

LLVM_NAME := obfuscator-llvm-$(LLVM_VERSION)

若編譯64位版本的so,也要按照上面的格式依次配置x86_64-obfuscator3.6,mips64el-linux-android-obfuscator3.6,aarch64-linux-android-obfuscator3.6三個資料夾。還要修改$NDK_PATH/build/core/setup-toolchain.mk檔案,在NDK_64BIT_TOOLCHAIN_LIST := 加入 obfuscator 對應的NDK_TOOLCHAIN_VERSION

NDK_64BIT_TOOLCHAIN_LIST := obfuscator3.6 clang3.6 clang3.5 clang3.44.9

至此,新增加的具備OLLVM混淆的編譯工具鏈就新增完成了,在編譯native程式時,在Android.mk和Application.mk中配置編譯引數即可。

在Application.mk中指定編譯器名字:

NDK_TOOLCHAIN_VERSION := obfuscator3.6

在Android.mk中設定混淆引數:

LOCAL_CFLAGS +=-mllvm -sub-mllvm -bcf -mllvm -fla

這樣配置完成後,使用ndk-build命令即可編譯出混淆程式碼。程式碼在64位機器上執行正常,內容僅僅是一條if-else語句,混淆的效果確實不錯。

0X03 OLLVM混淆使用

通過簡單的demo可以看出OLLVM的混淆功能確實強大。本小節將介紹混淆功能的具體使用。

OLLVM預設支援 -fla -sub -bcf 三個混淆引數,這三個引數可以單獨使用,也可以配合著使用。-fla 引數表示使用控制流平展(Control Flow Flattening)模式,-sub引數表示使用指令替換(Instructions Substitution)模式,-bcf引數表示使用控制流偽造(Bogus Control Flow)模式。下述if-else分支程式碼用於驗證各混淆模式的效果。

int main(){int a =1;int b =0;int c =0;if(a > b){
        a =100;  
        b =50;  
        c = a - b;int d = a + b;int e = a & b;int f = a ^ b;  
        printf("c = %d\n",c);  
        printf("d = %d\n",d);  
        printf("e = %d\n",e);  
        printf("f = %d\n",f);  
        printf("a > b\n");}else{
        printf("a < b\n");}return0;}

Control Flow Flattening

控制流平展模式可以完全改變程式原本的控制流圖。如下示例程式碼是簡單的if-else分支語句,正常編譯後其控制流圖在IDA中如圖6所示,是正常的if-else分支,使用 -mllvm -fla 引數混淆後,在IDA中顯示的控制流圖如圖7所示。

經FLA模式混淆後,程式的執行流程已經被打亂,出現許多程式碼分支。通過仔細對比程式混淆前後,可以發現上圖著色區域是相對應的,也就是說,FLA模式只會去更改程式碼分支,而不會對單個程式碼塊做處理。用IDA反編譯的混淆後的偽碼如下:

int __cdecl main(int argc,constchar**argv,constchar**envp){signedint v4;// [sp+38h] [bp-28h]@1

    v4 =1837851710;while(1){while( v4 <=1496444127){if( v4 ==1258601415){  
                v4 =1496444128;  
                printf("a < b\n", argv, envp);}}if( v4 ==1496444128)break;if( v4 ==1806065225){
                  printf("c = %d\n",50LL, envp);
                  printf("d = %d\n",150LL);
                  argv =(constchar**)86;
                  printf("e = %d\n",32LL);
                  printf("f = %d\n",86LL);
                  v4 =1496444128;
                  printf("a > b\n");}elseif( v4 ==1837851710){
                  envp =(constchar**)1;
                  argv =0LL;
                  v4 =1806065225;}}return0;}

原程式中,if-else的程式碼塊是順序執行的,混淆後代碼塊的執行由while迴圈和變數v4動態計算而得。

Instructions Substitution

指令替換模式主要是將正常的運算操作(+,-,&,|等)替換成功能相等但表述更復雜的形式。比如,對於表示式 a = b + c,它的等價式可以有 a = - ( -b - c), a = b - (-c) 或 a = -(-b) + c 等,原表示式可以替換成任意相等式,或者通過隨機數在多個相等式中做選擇。SUB模式目前只支援整數運算操作,支援 + , - , & , | 和 ^ 操作,還是比較侷限的。編譯時,使用 -mllvm -sub 引數即可。

Bogus Control Flow

控制流偽造模式也是對程式的控制流做操作,不同的是,BCF模式會在原始碼塊的前後隨機插入新的程式碼塊,新插入的程式碼塊不是確定的,然後新程式碼塊再通過條件判斷跳轉到原始碼塊中。更要命地是,原始碼塊可能會被克隆並插入隨機的垃圾指令。這麼多不確定性,就導致對同一份程式碼多次做BCF模式的混淆時,得到的是不同的混淆效果。可見,BCF混淆模式還是很強大的,不同於FLA那種較確定的混淆模式。使用BCF模式編譯時配置引數 -mllvm -bcf 即可,此外,BCF模式還支援其它幾個引數,下面引數與 -mllvm -bcf 引數配合使用。

-mllvm -perBCF=20:對所有函式都混淆的概率是20%,預設100%-mllvm -boguscf-loop=3:對函式做3次混淆,預設1-mllvm -boguscf-prob=40:程式碼塊被混淆的概率是40%,預設30%

如上圖,下面兩個著色的程式碼塊就是有上面兩個程式碼塊克隆而來,而且其中被插入了一些垃圾指令,類似於這樣:

mov     ecx, ds:x
mov     edx, ds:y
mov     esi, ecx
sub     esi,1
imul    ecx, esi
and     ecx,1
cmp     ecx,0
setz    r8b
cmp     edx,0Ah
setl    r9b
or      r8b, r9b
test    r8b,1

當然,上述介紹的三種混淆模式可以搭配使用,同時使用三個引數混淆後,原本簡單的if-else分支程式碼將會變得異常複雜,這無疑給逆向分析增加巨大的難度。

Functions annotations

有的時候,由於效率或其它原因的考慮,我們只想給指定的函式混淆,OLLVM也提供了對這一特性的支援。比如,想對函式func()使用bcf混淆,只需要給函式func()增加bcf屬性即可。

int func() __attribute((__annotate__(("bcf"))));

fla,sub和bcf三個屬性可以搭配使用。如果不想對func()函式使用bcf屬性,那標記為“nobcf”即可。

0X04 參考