1. 加固的緣由?
我們都知道,在越獄機型上,如果程序的可執行文件被獲取到,就可以通過一些逆向工具來反編譯我們的程序,從而可以實現:
任意讀寫文件系統數據
HTTP(S)實時被監測
重新打包ipa
暴露的函數符號
未加密的靜態字符
篡改程序邏輯控制流
攔截系統框架API
逆向加密邏輯
跟蹤函數調用過程(objc_msgSend)
可見視圖的具體實現
偽造設備標識
可用的URL schemes
runtime任意方法調用
2. 編譯過程
其實使用 Xcode 構建一個程序,就是把源文件 ( .h
和 .m
.swift
) 文件轉換為一個可執行文件。這個Mach-O文件中包含的字節碼會將被 CPU (包括iOS 設備中的 ARM 處理器或 Mac 上的 Intel 處理器) 執行。大致流程如下:
預處理
- 符號化 (Tokenization)
- 宏定義的展開
-
#include
的展開
語法和語義分析
- 將符號化后的內容轉化為一棵解析樹 (parse tree)
- 解析樹做語義分析
- 輸出一棵抽象語法樹(Abstract Syntax Tree* (AST))
生成代碼和優化
- 將 AST 轉換為更低級的中間碼 (LLVM IR)
- 對生成的中間碼做優化
- 生成特定目標代碼
- 輸出匯編代碼
匯編器
- 將匯編代碼轉換為目標對象文件。
鏈接器
- 將多個目標對象文件合并為一個可執行文件 (或者一個動態庫)
Objective-C 早期采用GCC,從Xcode5之后采用 Clang 作為前端,而 Swift 則采用 swift() 作為前端(Swift的語法分析器是一個簡單的,對整體通過遞歸向下的方式進行語法分析的手工編碼詞法分析器,他是在lib/Parse內實現的。該分析器負責生成不包含語義和類型信息的抽象語法樹AST
(Abstract Syntax Tree)。這個階段生成的AST
也不包含警告和錯誤的注入,因為 swift 在編譯時就完成了方法綁定直接通過地址調用屬于強類型語言,方法調用不再是像OC那樣的消息發送,這樣編譯就可以獲得更多的信息用在后面的后端優化上),二者都是用 LLVM(Low level vritual machine) 作為編譯器后端。
對應前端編譯器地址如下:
# Objective-C
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang
# Swift
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift
GCC
(GNU Compiler Collection)縮寫,一個編程語言編譯器,是GNU(自由軟件理事會)的關鍵部分。也是GNU工具鏈的一部分。GCC常被認為是跨平臺編譯器的事實標準,特別是它的C語言編譯器。GCC原本只能處理C語言。但是面對Clang的競爭,很快作出了擴展,現在已經可以處理C++,Fortran、Pascal、Object-C、Java、Ada,以及Go語言。許多操作系統,包括許多Unix系統,如Linux及BSD家族都采用GCC作為標準編譯器。MacOSX也是采用這個編譯器。
LLVM
是Low Level Virtual Machine的簡稱。這個庫提供了與編譯器相關的支持,能夠進行程序語言的編譯期優化、鏈接優化、在線編譯優化、代碼生成。可以作為多種語言編譯器的后臺來使用。
LLVM是一個優秀的編譯器框架,如圖:
它采用經典的三段式設計。前端可以使用不同的編譯工具對代碼文件做詞法分析以形成抽象語法樹AST,然后將分析好的代碼轉換成LLVM的中間表示IR(intermediate representation);它既不是源代碼,也不是機器碼。從代碼組織結構上看它比較接近機器碼,但是在函數和指令層面使用了很多高級語言的特性。
IR代碼是由一個個Module組成的,每個Module之間互相聯系,而Module又是由一個個Function組成,Function又是由一個個BasicBlock組成,在BasicBlock中又包含了一條條Instruction。
中間部分的優化器只對中間表示IR操作,通過一系列的Pass對IR做優化;后端負責將優化好的IR解釋成對應平臺的機器碼。LLVM的優點在于,中間表示IR代碼編寫良好,而且不同的前端語言最終都轉換成同一種的IR。
Clang
Clang 是 LLVM 的子項目,是 C,C++ 和 Objective-C 編譯器,目的是提供驚人的快速編譯,比 GCC 快3倍,其中的 clang static analyzer 主要是進行語法分析,語義分析和生成中間代碼,當然這個過程會對代碼進行檢查,出錯的和需要警告的會標注出來。
前端編譯器
編譯器前端的任務是進行:語法分析,語義分析,生成中間代碼(intermediate representation )。在這個過程中,會進行類型檢查,如果發現錯誤或者警告會標注出來在哪一行。
后端編譯器
編譯器后端會進行機器無關的代碼優化,生成機器語言,并且進行機器相關的代碼優化。iOS的編譯過程,后端的處理如下
LVVM 優化器會進行 BitCode 的生成,鏈接期優化等等。
3. 加固類型
1.字符串混淆
對應用程序中使用到的字符串進行加密,保證源碼被逆向后不能看出字符串的直觀含義。
2.類名、方法名混淆
對應用程序的方法名和方法體進行混淆,保證源碼被逆向后很難明白它的真正功能。
3.程序結構混淆加密
對應用程序邏輯結構進行打亂混排,保證源碼可讀性降到最低。
4.反調試、反注入等一些主動保護策略
這是一些主動保護策略,增大破解者調試、分析App的門檻。
4. 逆向工具??
1. class-dump
class-dump 利用 Objective-C runtime 特性,將存儲在 Mach-O 文件結構里 data 部分的類屬性和方法等信息提取出來,并生成對應的 .h 文件的工具。
在 class-dump 官網下載 dmg,將 dmg 里面的 class-dump
拷貝到 /usr/local/bin
文件夾下,然后就可以在終端中使用 class-dump
命令了。
簡單使用:
class-dump -H [需要被導出的 Mach-O 文件路徑] -o [頭文件輸出目錄地址]
2. hopper
Hopper 是一種適用于 OS X 和 Linux 的逆向工程工具,可以用于反匯編、反編譯和調試 32位/64位英特爾處理器的 Mac、Linux、Windows 和 iOS 可執行程序。
3. IDA
是目前最棒的一個靜態反編譯軟件,為眾多0day世界的成員和ShellCode安全分析人士不可缺少的利器。
就其本質而言,IDA是一種遞歸下降反匯編器。但是,為了提高遞歸下降過程的效率,IDA的開發者付出了巨大的努力,來為這個過程開發邏輯。為了克服遞歸下降的一個最大的缺點,IDA在區分數據與代碼的同時,還設法確定這些數據的類型。雖然你在IDA中看到的是匯編語言形式的代碼,但IDA的主要目標之一,在于呈現盡可能接近源代碼的代碼。此外,IDA不僅使用數據類型信息,而且通過派生的變量和函數名稱來盡其所能地注釋生成的反匯編代碼。這些注釋將原始十六進制代碼的數量減到最少,并顯著增加了向用戶提供的符號化信息的數量。
5. OLLVM
O-llvm是基于llvm進行編寫的一個開源項目(https://github.com/obfuscator-llvm/obfuscator),它的作用是對前端語言生成的中間代碼進行混淆。
O-llvm總體構架和llvm是一致的:
其中IR(intermediate representation)中間代碼表示,也是Pass(總的來說,所有的pass大致可以分為兩類:分析和轉換;分析類的pass以提供信息為主,轉換類的會修改中間代碼)操作的對象,它主要包含四個部分:
(1)Module:比如一個.c或者.cpp文件。
(2)Function:代表文件中的一個函數。
(3)BasicBlock:每個函數會被劃分為一些block,它的劃分標準是:一個block只有一個入口和一個出口。
(4)Instruction:具體的指令。
對于OLLVM的每個pass,其主要的工作繼承對應的pass類,就是對相應的方法進行重寫,例如SplitBasicBlock的實現,它繼承自FunctionPass,并重寫了runOnFunction方法。
O-llvm包含有三個pass,分別是BogusControlFlow、Flattening 和 Instruction Substitution。它們是O-llvm實現混淆功能的核心,具體實現位于llvm/lib/Transforms/Obfuscation/目錄下。
控制流扁平化
這個模式主要是把一些if-else語句,嵌套成do-while語句
-mllvm -fla:激活控制流扁平化
-mllvm -split:激活基本塊分割。在一起使用時改善展平。
-mllvm -split_num=3:如果激活了傳遞,則在每個基本塊上應用3次。默認值:1
指令替換
這個模式主要用功能上等效但更復雜的指令序列替換標準二元運算符(+ , – , & , | 和 ^)
-mllvm -sub:激活指令替換
-mllvm -sub_loop=3:如果激活了傳遞,則在函數上應用3次。默認值:1
虛假控制流程
這個模式主要嵌套幾層判斷邏輯,一個簡單的運算都會在外面包幾層if-else,所以這個模式加上編譯速度會慢很多因為要做幾層假的邏輯包裹真正有用的代碼。
另外說一下這個模式編譯的時候要浪費相當長時間包哪幾層不是鬧得!
-mllvm -bcf:激活虛假控制流程
-mllvm -bcf_loop=3:如果激活了傳遞,則在函數上應用3次。默認值:1
-mllvm -bcf_prob=40:如果激活了傳遞,基本塊將以40%的概率進行模糊處理。默認值:30
6. Hikari
HikariObfuscator/Hikari 是一個基于 Obfuscator-LLVM 對 Xcode9的適配。
如果需要自行編譯,需要安裝 cmake
and ninja
以及 SWIG
。
cmake : CMake 是一個跨平臺的自動化建構系統,它使用一個名為 CMakeLists.txt 的文件來描述構建過程,可以產生標準的構建文件,如 Unix 的 Makefile 或 Windows Visual C++ 的 projects/workspaces 。文件 CMakeLists.txt 需要手工編寫,也可以通過編寫腳本進行半自動的生成。CMake 提供了比 autoconfig 更簡潔的語法。一些使用 CMake 作為項目架構系統的知名開源項目有 VTK、ITK、KDE、OpenCV、OSG 等。
在 linux 平臺下使用 CMake 生成 Makefile 并編譯的流程如下:
- 編寫 CMake 配置文件 CMakeLists.txt 。
- 執行命令
cmake PATH
或者ccmake PATH
生成 Makefile。(ccmake
和cmake
的區別在于前者提供了一個交互式的界面)其中,PATH
是 CMakeLists.txt 所在的目錄。 - 使用
make
命令進行編譯。
下載地址: https://cmake.org/download/
在終端安裝:
sudo "/Applications/CMake.app/Contents/bin/cmake-gui" --install
Ninja : Ninja是一種類似GNU make的編譯系統。 就像make有Makefile,它也有自己的編譯配置文件。 相對來說,Ninja文件沒有分支、循環的流程控制,本質上就是純粹的配置文件,所以要比Makefile簡單得多。
Ninja目前主要應用在Google Chrome,部分Android系統,LLVM以及CMake的Ninja后端中。
可通過官網下載安裝release版本,也可通過homebrew或者npm包管理工具安裝 brew install Ninja
SWIG :Simplified Wrapper and Interface Generator,SWIG完整支持ANSI C,支持除嵌套類外的所有C++特性。SWIG是一個接口編譯器,旨在為C/C++方便地提供腳本語言接口。SWIG不僅可以為C/C++程序生成 Python接口,目前可以生成CLISP,Java,Lua,PHP,Ruby,Tcl等19種語言的接口。SWIG被Subversion, wxPython, Xapian等項目使用。值得一提的是,Google也使用SWIG。
SWIG本質上是個代碼生成器,為C/C++程序生成到其他語言的包裝代碼(wrapper code),這些包裝代碼里會利用各語言提供的C API,將C/C++程序中的內容暴露給相應語言。為了生成這些包裝代碼,SWIG需要一個接口描述文件,描述將什么樣的接口暴露給其他語言。
SWIG的 接口描述文件可以包含以下內容:
- ANSI C函數原型聲明
- ANSI C變量聲明
- SWIG指示器(directive)相關內容
利用SWIG,可以現實以下功能:
- 用Python調用C/C++庫
- 用Python繼承C++類,并在Python中使用該繼承類
- C++使用Python擴展(通過文檔描述應該可以支持,未驗證)
自行安裝需要在/Hikari/tools/xcode-toolchain/CMakeLists.txt中加上set(CMAKE_CXX_FLAGS "-std=c++11 ${CMAKE_CXX_FLAGS}")才能編譯通過。另如果執行完成報錯 lldb_codesign: no identity found
,則是需要添加一個lldb_codesign證書,然后信任,具體可參照 這里 或 編譯mac下的lldb
安裝完成之后執行腳本,到生成Toolchains我這邊大約花了2小時:
git clone --recursive -b release_80 https://github.com/HikariObfuscator/Hikari.git Hikari && cd Hikari && git submodule update --remote --recursive && cd ../ && mkdir Build && cd Build && cmake -G "Ninja" -DLLDB_CODESIGN_IDENTITY='' -DCMAKE_BUILD_TYPE=MinSizeRel -DLLVM_APPEND_VC_REV=on -DLLVM_CREATE_XCODE_TOOLCHAIN=on -DCMAKE_INSTALL_PREFIX=~/Library/Developer/ ../Hikari && ninja &&ninja install-xcode-toolchain && git clone https://github.com/HikariObfuscator/Resources.git ~/Hikari && rsync -a --ignore-existing /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/ ~/Library/Developer/Toolchains/Hikari.xctoolchain/ && rm ~/Library/Developer/Toolchains/Hikari.xctoolchain/ToolchainInfo.plist
另外也可以直接下載安裝作者提供的release版本 pkg 文件:
1 https://github.com/HikariObfuscator/Hikari/releases
重啟Xcode就能在Toolchains里面看到除了系統默認的編譯工具還多了一個Hikari編譯工具。
-
Xcode
->Toolchains
->Hikari
將混淆工具和項目關聯 - 將所有與要運行的 target 相關的 target(包括pod進來的庫)
Enable Index-While-Building
的值改為 NO。 -
Optimization Level
的值設置為None[-O0]
- 在
Build Settings
->Other C Flags
中加入混淆標記
在 Wiki 中可以看到用法:
The following flags are supported
-enable-bcfobf 啟用偽控制流
-enable-cffobf 啟用控制流平坦化
-enable-splitobf 啟用基本塊分割
-enable-subobf 啟用指令替換
-enable-acdobf 啟用反class-dump
-enable-indibran 啟用基于寄存器的相對跳轉,配合其他加固可以徹底破壞IDA/Hopper的偽代碼(俗稱F5)
-enable-strcry 啟用字符串加密
-enable-funcwra 啟用函數封裝
7. 代碼虛擬化
使用一套自定義的字節碼來替換掉程序中原有的native指令,而字節碼在執行的時候又由程序中的解釋器來解釋執行。自定義的字節碼是只有解釋器才能識別的,所以一般的工具是無法識別我們自定義的字節碼,也是因為這一點,基于虛擬機的保護相對其他保護而言要更加難破解。
目前很多地方都會用到虛擬化技術,比如sandbox、程序保護殼等。很多時候為了防止惡意代碼對我們的系統造成破壞,我們需要一個sandbox,使程序運行在sandbox中,即使惡意代碼破壞系統也只是破壞了sandbox而不會對我們的系統造成影響。
基于虛擬機的代碼保護也可以算是代碼混淆技術的一種。代碼混淆的目的就是防止代碼被逆向分析,但是所有的混淆技術都不是完全不能被分析出來,只是增加了分析的難度或者加長了分析的時間,雖然這些技術對保護代碼很有效果,但是也存在著副作用,比如會或多或少的降低程序效率,這一點在基于虛擬機的保護中格外突出,所以大多基于虛擬機的保護都只是保護了其中比較重要的部分。
LLVM
The Compiler
CMake入門實戰
Mach-O 可執行文件
在linux下使用CMake構建應用程序
利用 SWIG 對 C++ 庫進行 Python 包裝