前言
相信大部分的第一感覺就是覺得jni居然也可以混淆到踏?!尚猿!是的窝稿,在沒有接觸這塊的時候我和大家是一樣的懵逼,居然還有這種操作凿掂!對于混淆來說伴榔,做的最多的還是app端的代碼混淆,那么對應so文件我的印象還是比較安全的庄萎,沒有想到還有混淆so文件的情況踪少。但是so真的那么安全嗎?其實不見得糠涛,如果你的so文件沒有經(jīng)過任何包裝援奢,打出so文件直接使用,那么通過不能混淆的native方法和反編譯so庫脱羡,就能大致知曉你要調的方法甚至是你的代碼實現(xiàn)萝究。不信免都?那就來看看吧。
為何要對jni混淆帆竹?
我們先看個例子:
jni代碼:
JNICALL
Java_com_zdu_nativedemo_MainActivity_stringFromJNI(JNIEnv *env, jobject /* this */) {
int a = 2, b = 3;
int c = a * b;
char str[20];
sprintf(str,"%d",c);
return env->NewStringUTF(str);
}
很簡單的一個計算绕娘,輸出a*b的值,我們編譯成so文件后栽连,使用反編譯工具IDA看下:
雖然我們看不懂偽代碼险领,但對比我們寫的jni會發(fā)現(xiàn),邏輯步驟基本一樣秒紧,大神們甚至可以通過這些偽代碼來還原我們真實的實現(xiàn)绢陌。
如此,你還會覺得so文件很安全嗎熔恢?
對于so文件的安全性上有兩個方面可以研究:
一個是so文件的加殼技術脐湾,簡單的說替換jni方法名,反編譯后找不到具體的實現(xiàn)方法叙淌,這個研究是我另一個同事雷某某在搞秤掌,等這家伙寫完博客后我會貼上;
另一個方面就是對jni里的方法實現(xiàn)混淆鹰霍,使代碼非常難以讀懂闻鉴,增大反編譯的難度。
編譯OLLVM
不知道OLLVM的同學自己google去吧茂洒,簡單的說就是一種混淆jni文件的一種工具孟岛。
編譯OLLVM之前需要做些準備:
首先,你要準備一臺ubuntu系統(tǒng)的電腦督勺,MAC也行渠羞。如果是虛擬機的話,內(nèi)存設置最好4g以上玷氏,硬盤30g堵未,cpu核心跟你電腦的核心一樣就行。重點:不要試圖在window系統(tǒng)上編譯OLLVM盏触,不要試圖在window系統(tǒng)上編譯OLLVM渗蟹,不要試圖在window系統(tǒng)上編譯OLLVM,無論你做了多少工作赞辩,安裝了多少需要的工具雌芽,編譯到100%的時候肯定會報錯,
mingw32-make: *** [Makefile:151: all] Error 2
辨嗽。我是搞不定世落,如果你能搞定,懇請大神回復咋搞定的糟需。
其次:需要安裝好幾個工具:git屉佳,cmake谷朝,make,gcc武花,g++圆凰。注意:cmake不要直接使用命令行安裝,版本太低了体箕,需要到官網(wǎng)上下載安裝专钉,不會的同學移步:ubuntu安裝CMake的幾種方式
最后:安裝好AS以及NDK,移步:Ubuntu下安裝AndroidStudio累铅。建議使用AS內(nèi)嵌的NDK跃须。
PS:如果使用內(nèi)嵌的SDK編譯報錯clang++:......"-nostdlib++"這樣的錯誤,放棄吧騷年娃兽,我已經(jīng)趟了很久的坑了菇民,然而并沒有解決,百@度不到任何有用的資料换薄,只能去Google玉雾,最后發(fā)現(xiàn)是NDK內(nèi)部編譯器的錯誤,18e版本也沒有修復轻要,但神奇的是直接使用AS的cmake語句去編譯ndk,卻沒有問題垦缅,我沒有搞明白冲泥。幸運的是我找到了其他的方式,只使用NDK的r10e版本壁涎。
移步:Ubuntu下最新Android NDK安裝
準備工作都做完了凡恍,那么我們可以按照官方的知道來編譯OLLVM了:
$ git clone -b llvm-4.0 https://github.com/obfuscator-llvm/obfuscator.git
$ mkdir build
$ cd build
$ cmake -DCMAKE_BUILD_TYPE=Release ../obfuscator/
這一步如果報錯,修改參數(shù)為:
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=OFF ../obfuscator/
$ make -j7
下載obfuscator的比較慢怔球,速度大概10k左右嚼酝,慢慢等等吧。編譯OLLVM根據(jù)你的電腦配置竟坛,30分鐘到幾個小時不等闽巩,要有耐心。
編譯完成后担汤,只有bin
和lib
文件是有用的涎跨,其他用不著可以刪掉。附上ubuntu下編譯好的obfuscator:不點贊你好意思下載嗎 提取碼: bcqf
集成到NDK
ubuntu系統(tǒng)下的二進制疊加包我已經(jīng)給大家準備好崭歧,ubuntu下NDK二進制疊加包提取碼: 6v3n隅很,說明一下,這是NDK-r10版本的二進制疊加包率碾,建議直接使用此版本叔营。
(什么是二進制疊加包屋彪?知道了又有什么卵用,跟著步驟走就完全ojbk了)(其實就是一些預設好的配置文件)
傻瓜式安裝步驟:
第一步:解壓縮绒尊。
只有一個toolchains
文件夾畜挥,內(nèi)涵兩個文件夾arm-linux-androideabi-clang3.4-obfuscator
和obfuscator-llvm-3.4
,注意這個版本都是3.4的垒酬。
第二步:復制此文件夾到ndk根目錄砰嘁,覆蓋即可;
到NDK/toolchains的目錄下勘究,你會發(fā)現(xiàn)矮湘,llvm最低是3.5版,而我們復制過來的是3.4版口糕,這個沒有關系缅阳,一樣可以使用,注意區(qū)分版本就行了景描。
第三步:軟鏈編譯器
沒啥好解釋的十办,照著命令執(zhí)行就行。
cp -r $NDK_PATH/toolchains/arm-linux-androideabi-clang3.5 $NDK_PATH/toolchains/arm-linux-androideabi-clang3.4-obfuscator
命令執(zhí)行完超棺,查看arm-linux-androideabi-clang3.4-obfuscator
文件夾
如果有箭頭標記的帶鎖的那個文件夾向族,說明已經(jīng)軟鏈成功了。
第四步:修改setup.mk文件
打開復制到NKD目錄下的arm-linux-androideabi-clang3.4-obfuscator
文件夾下的setup.mk
文件棠绘,找到這幾行代碼:
TARGET_CC := $(LLVM_TOOLCHAIN_PREFIX)clang$(HOST_EXEEXT)
TARGET_CXX := $(LLVM_TOOLCHAIN_PREFIX)clang++$(HOST_EXEEXT)
并將其替換成:
LLVM_TOOLCHAIN_PATH := <PATH_TO_OBFUSCATOR_REPO>/build/bin/
TARGET_CC := $(LLVM_TOOLCHAIN_PATH)clang$(HOST_EXEEXT)
TARGET_CXX := $(LLVM_TOOLCHAIN_PATH)clang++$(HOST_EXEEXT)
LLVM_TOOLCHAIN_PATH
指向的是你編譯完成build文件夾下的bin的全路徑件相,比如說我的就是這樣設置的:LLVM_TOOLCHAIN_PATH := /home/du/Desktop/hunxiao/FileName/build/bin/
至此,就完成了全部的準備工作了氧苍。
混淆JNI
首先得說明一點:本文使用的是NDK-build的方式來完成混淆夜矗,不是cmake。
創(chuàng)建jni目錄让虐,并創(chuàng)建我們的jni類紊撕,名字就叫native-lib.cpp
,代碼還是使用文章剛開始的那樣:
JNICALL
Java_com_zdu_nativedemo_MainActivity_stringFromJNI(JNIEnv *env, jobject /* this */) {
int a = 2, b = 3;
int c = a * b;
char str[20];
sprintf(str,"%d",c);
return env->NewStringUTF(str);
}
編寫Application.mk
:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
APP_ABI := armeabi-v7a
NDK_TOOLCHAIN_VERSION := clang3.4-obfuscator #指定編譯器
APP_PLATFORM = android-16
APP_STL := c++_shared #llvm編譯需此編譯庫
include $(BUILD_EXECUTABLE)
編寫Android.mk
:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := nativeDemo
LOCAL_LDLIBS += -llog -landroid -lc
LOCAL_SRC_FILES := native-lib.cpp
LOCAL_CFLAGS := -mllvm -bcf -mllvm -sub -mllvm -fla #開啟三種混淆方式
include $(BUILD_SHARED_LIBRARY)
最終如圖:
打開命令行赡突,執(zhí)行ndk-build
对扶,完成編譯,建議是在root環(huán)境下麸俘,省去權限問題了辩稽。如圖:
最終在lib文件夾里編譯打包出我們的
libnativeDemo.so
文件。我們先看看混淆后的成果从媚,打開IDA反編譯器逞泄,找到我們的方法打開一看:
看一下圖表概覽:
一個簡單的運算被增加了那么多邏輯,效果太驚人了。
對比下原來的偽代碼:
再對比下我們的實際代碼:
JNICALL
Java_com_zdu_nativedemo_MainActivity_stringFromJNI(JNIEnv *env, jobject /* this */) {
int a = 2, b = 3;
int c = a * b;
char str[20];
sprintf(str,"%d",c);
return env->NewStringUTF(str);
}
沒有混淆前的偽代碼大致的還能看的出是什么意思喷众,但是混淆后的代碼各谚,我相信沒有幾個人能看的懂,就算是大神費了九牛二虎之力看懂到千,結果發(fā)現(xiàn)只是求a*b的值這么簡單的運算昌渤,是不是有種不想活的感覺了。
混淆后的文件13.5k憔四,沒有混淆的9.4k膀息,增加了43%的大小。那么對于一個正常使用的so庫的話了赵,使用三種混淆方式混淆的話潜支,so文件大小大概會增大50%左右,畢竟增加了那么多的代碼啊柿汛。
實際項目使用中遇到的問題
在實際項目中應用的時候會發(fā)現(xiàn)冗酿,三種混淆方式全都使用的話,會有一個問題络断,那就是編譯時間會特別特別長裁替,甚至根本就打不出so文件。長會長的什么程度呢貌笨?有一次我用項目中的jni混淆弱判,一個算是比較簡單的,只有3個cpp類锥惋,編譯了大概1個小時才編譯出來裕循;用我們項目中比較復雜的,十幾個cpp文件的jni净刮,編譯了4個小時,依然沒有編譯完成硅则,預計是不可能編譯出來了淹父。
那么遇到這種問題又該怎么辦呢?我先不說結果怎虫,咱們先分別使用三種混淆jni方式混淆下暑认,看看單獨的效果如何。
只使用控制流扁平化: -mllvm -fla
編譯完成發(fā)現(xiàn)大小只有9.4k大审,跟沒有混淆過一樣蘸际,反編譯后發(fā)現(xiàn):
居然跟沒有混淆過一模一樣,為什么會這樣呢徒扶?難度說
-fla
根本不起作用嗎粮彤?其實并不是這樣,看下官方wiki:
-fla
表示使用控制流平展模式,最直觀的感受就是簡單的if-else語句导坟,被嵌套成了while-switch語句屿良,出現(xiàn)了很多干擾無用的分支,增加閱讀難度惫周。
我們的代碼中又沒有if-else語句尘惧,當然沒有任何效果了。
只使用指令替換: -mllvm -sub
結果跟-fla
混淆一樣递递,跟沒有混淆過的代碼一樣喷橙。看下官方wiki:
這種混淆技術的目標僅僅在于用功能上等效但更復雜的指令序列替換標準二元運算符(如加法登舞,減法或布爾運算符)贰逾。當有幾個等效的指令序列可用時,隨機選擇一個逊躁。
這種混淆是相當簡單的似踱,并沒有增加很多安全性,因為它可以通過重新優(yōu)化生成的代碼輕松刪除稽煤。然而核芽,假設偽隨機生成器以不同的值接種,則指令替換在所產(chǎn)生的二進制中帶來多樣性酵熙。
目前轧简,只有整數(shù)運算符可用,因為在浮點值上替換運算符會帶來舍入誤差和不必要的數(shù)值誤差匾二。
我們代碼中沒有 + , – , & , | 和 ^ 操作哮独,只有*
,但還不支持混淆察藐,所以沒有任何效果皮璧。
只使用虛假控制流程: -mllvm -bcf
這也是最后一個混淆方式,可以說肯定是有效果的分飞,上圖:
混淆后13.1k悴务。代碼通過bcf增加了很多虛假控制的代碼,然后這些代碼又經(jīng)過平流展開fla和指令替換sub的方式增加額外混淆譬猫,就成了我們最終三種混淆結果讯檐。
三種混淆方式單獨使用后我們可以得出結論:混淆效果最好的指令是虛假控制流程-bcf
,而平流展開-fla
和指令替換-sub
在我們的測試demo中并沒有體現(xiàn)出很大的效果染服,但如何使用相信大家心里已經(jīng)很清楚了
擴展1:
平流展開-fla
-mllvm -fla:激活控制流扁平化
-mllvm -split:激活基本塊分割别洪。在一起使用時改善展平。
-mllvm -split_num=3:如果激活了傳遞柳刮,則在每個基本塊上應用3次挖垛。默認值:1
指令替換-sub
-mllvm -sub:激活指令替換
-mllvm -sub_loop=3:如果激活了傳遞痒钝,則在函數(shù)上應用3次。默認值:1晕换。
虛假控制-bcf
-mllvm -bcf:激活虛假控制流程
-mllvm -bcf_loop=3:如果激活了傳遞午乓,則在函數(shù)上應用3次。默認值:1
-mllvm -bcf_prob=40:如果激活了傳遞闸准,基本塊將以40%的概率進行模糊處理益愈。默認值:30
舉例:如果要使用平流展開并應用3次:
LOCAL_CFLAGS := -mllvm -fla -mllvm -split -mllvm -split_num=3
PS:每個混淆擴展的作用都是非常的大,舉例平流展開夷家,我們demo單獨使用沒有任何效果蒸其,但如果激活-split就會有效果,再應用次數(shù)變化的話库快,效果也很明顯摸袁,而且so文件大小也不是很大,這里就不貼圖了义屏,大家自己編譯看看吧靠汁。
擴展2:字符串混淆
上述三種以及其擴展混淆都無法對字符串進行任何有效的混淆,反編譯后無論怎么混淆都能看到實際代碼中的字符串闽铐,如果一些key保存到so庫中蝶怔,這無形中就暴露了,所以我們還需要對字符串進行混淆兄墅。
字符串混淆的技術是孤挺花(Armariris) -- 由上海交通大學密碼與計算機安全實驗室維護的LLVM混淆框架踢星,目前只放出了字符串混淆技術,但正好整合到我們的OLLVM中隙咸,實現(xiàn)4種方式混淆沐悦。先貼上孤挺花(Armariris)的github地址:孤挺花(Armariris) -- 由上海交通大學密碼與計算機安全實驗室維護的LLVM混淆框架。
額外說一點:我曾單獨編譯過Armariris的llvm五督,但發(fā)現(xiàn)缺少cmakelists.txt文件藏否,無法完成編譯,不知道是我操作有誤還是其他問題充包,但這不影響我們提取Armariris的字符串混淆技術秕岛。
集成
1.在孤挺花(Armariris)的github中找到位于include/llvm/Transforms/Obfuscation
文件夾下的StringObfuscation.h,復制到你的ollvm相對應的目錄下
2.找到位于lib/Transforms/Obfuscation
文件夾下的StringObfuscation.cpp误证,復制到對應的OLLVM目錄下,并修改對應目錄下的cmakelists.txt
文件修壕,將StringObfuscation.cpp
添加到編譯庫中:
3.找到位于
lib/Transforms/IPO
文件夾下的PassManagerBuilder.cpp
愈捅,添加引用:#include "llvm/Transforms/Obfuscation/StringObfuscation.h"
如圖:4.還是PassManagerBuilder.cpp
,按照圖示位置添加兩句代碼慈鸠,編譯時的編譯參數(shù)-mllvm -sobf:
static cl::opt<std::string> Seed("seed", cl::init(""), cl::desc("seed for the random"));
static cl::opt<bool> StringObf("sobf", cl::init(false), cl::desc("Enable the string obfuscation"));
如圖:
5.還是PassManagerBuilder.cpp
蓝谨,找到PassManagerBuilder::PassManagerBuilder()
,在構造函數(shù)中添加隨機數(shù)因子的初始化代碼:
//添加隨機數(shù)因子的初始化
if(!Seed.empty()) {
if(!llvm::cryptoutils->prng_seed(Seed.c_str()))
exit(1);
}
如圖:
6.還是PassManagerBuilder.cpp
,找到void PassManagerBuilder::populateModulePassManager
浆熔,添加代碼:
MPM.add(createStringObfuscation(StringObf));
如圖:
完成上述6個步驟后树灶,重新編譯一遍ollvm即可甸箱。這個編譯好的文件我也有,這次就不放出來了诱贿,大家總歸還是要親自動手體會下才有印象。
開啟字符串混淆的方式也很簡單咕缎,在Android.mk中添加:LOCAL_CFLAGS += -mllvm -sobf
即可珠十。開啟后混淆字符串,反編譯后你將找不到任何字符串凭豪,全部被混淆成二進制了焙蹭。
踩坑記錄
在完成擴展2中添加字符串混淆配置后,開始重新編譯ollvm嫂伞,編譯到30-40%時會報錯孔厉,mingw32-make: *** [Makefile:151: all] Error 2
,參考了大神的博客解決了該問題:Win10 OLLVM添加字符串混淆踩坑篇by sudami帖努,簡單的說就是將StringObfuscation.h
修改為以下代碼:
#ifndef _STRINGOBFUSCATION_H_
#define _STRINGOBFUSCATION_H_
// LLVM include
#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/Instructions.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/Transforms/IPO.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/CryptoUtils.h"
// Namespace
using namespace llvm;
using namespace std;
namespace llvm {
Pass* createStringObfuscation(bool flag);
}
#endif
保存撰豺,再次執(zhí)行編譯,等待100%編譯完成即可然磷。
結語
本篇在國慶前就已經(jīng)完成了70%郑趁,假期都背著電腦回家了,就想著在家搞完姿搜,結果你們都懂得了寡润。幸好間隔不是很久,記憶還都在舅柜,趕緊寫下來梭纹,以便日后查詢,只有把別人的知識掌握了致份,才算是自己的技術变抽。