前提
眾所周知,iOS系統安全性非常高,很少出現漏洞,幾乎不會中毒。大家認為蘋果系統的封閉性會使iOS APP安全性比較高,但是實際上iOS應用本身被破解的難度并不高,一旦在越獄設備上,ipa被分析就會變得很容易。對于iOS開發者來說,有必要了解一些APP加固的方法,用以提高破解的難度,特別是針對一些金融、游戲類APP的開發。
iOS代碼保護
在大多數iOS應用中,一些工具,比如Clutch,class-dump,cycript,lldb,theos.對應用程序的結構,代碼邏輯,運行流程,可以做到很容易的分析。然后進行應用的破解,篡改,重簽名??梢詮哪嫦蚍治龅姆绞阶龃a保護的思路:
- 1.靜態分析:針對這種情況可以把字符串加密,類名方法名混淆,代碼混淆
- 2.調試 :反調試
- 3.注入 :反注入
- 4.中間人攻擊 :https, 證書驗證, 數據加密
一,靜態分析
靜態分析是指用工具對程序結構,代碼邏輯的分析。很大程度上取決關鍵字,通過關鍵字找到敏感代碼,進行破解。所以靜態分析的防護主要是代碼混淆。
1,混淆硬編碼的明文字符串
明文字符串可直接在二進制包搜索到,常常是作為逆向分析的切入口,隱藏明文字符串可有效提升靜態分析的難度。在源代碼中將字符串加密,運行時先解密在使用,如果直接在代碼中寫加密后字符串,代碼的可讀性會變得非常差。網上又一個方法不是很優雅,但有效,私以為不錯:
- 將源代碼中的字符串通過函數宏手動標記
- 打包的時候拷貝源代碼副本
- 執行腳本,將副本代碼中所有標記過的字符串,替換成decrypt("密文")的形式
- 在適當的位置,插入decrypt函數的實現(或者事先在源代碼中寫好)
- 編譯
通過函數宏手動標記字符串:
執行加密腳本后:
這里的加密僅僅是和0xAA做了一個簡單的異或運算,解密函數內聯編譯到代碼中。
上圖是未做混淆前的反匯編代碼,可以直接看到明文字符串。
下面是經過混淆的反匯編代碼,已經看不到明文字符串了。
2,objective C代碼混淆
網上方法較多,其中尤以念茜大神的方法為佳。大抵是在編譯前執行混淆腳本,對OC函數(消息)進行混淆。但是又不能全部混淆,有一些是SDK的代理回掉,如tableView的UITableViewDataSource,混淆后將不會調用,如繼承子類的init,混淆后也不會調用。解決辦法有很多,這里介紹兩個匹配的辦法:
- 1,建立一個索引文件,將需要混淆的函數寫入索引文件,混淆腳本讀取索引文件的函數名進行匹配。
- 2,在代碼中以前綴或后綴的方式標識需要混淆的函數,混淆腳本通過這些標識進行自動混淆。
匹配函數名后,將混淆過的函數寫入數據庫中記錄下來,以便在后續在分析app Crash的時候找對應的函數,快速定位到對應的問題代碼.
未做混淆的代碼經過class-dump或者直接在hopper中可直接看到函數名,逆向者以此猜測程序功能,快速切入,找到hock點。
混淆腳本設置
混淆后的代碼,無法通過函數名猜測到程序功能,可大大增加逆向難度。
代碼混淆除了函數名的混淆,還有類名,協議名,文件名的混淆等。此外,敏感代碼可以用C函數來實現,可以避免在class-dump等工具中倒出,但是在nm等工具中還是可以看到一些符號表的信息。
小結,靜態分析的防護手段主要是作代碼的混淆,已到達提升逆向的難度。此外還有代碼邏輯的混淆,通過在代碼中加入大量無用的邏輯判斷,增加程序結構的復雜性,以此提升程序在ida,hopper等工具分析中的難度。但是此方法對源代碼的改動性較大,使代碼的可讀性變得極差。
二,動態分析 反調試
逆向者不僅可以靜態分析程序,也可以通過debugserver,lldb等工具動態分析程序,通過在程序中打斷點,修改內存中的變量等方式分析,改變程序的行為。以此達到分析,hock程序的目的。
1,反調試之 ptrace
談到debug,首先會想到的一個系統調用是ptrace,它主要用于實現斷點調試和系統調用跟蹤。 PT_DENY_ATTACH是蘋果增加的一個ptrace選項,用以防止gdb等調試器依附到某進程。代碼如下:
#ifndef PT_DENY_ATTACH
#define PT_DENY_ATTACH 31
#endif
typedef int (*ptrace_ptr_t)(int _request, pid_t _pid, caddr_t _addr, int _data);
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
void *handle = dlopen(0, RTLD_GLOBAL | RTLD_NOW);
ptrace_ptr_t ptrace_ptr = (ptrace_ptr_t)dlsym(handle, "ptrace");
ptrace_ptr(PT_DENY_ATTACH, 0, 0, 0);
}
手邊沒有越獄機器,直接通過xcode debug,應用會Crash掉,安裝到手機后可正常打開。
針對這種ptrace的反反調試方法其實很簡單,通過下斷點,然后修改ptrace的參數或者用hook函數去掉反調試保護就可以搞定。也可在程序多個處調用來增加crash,提高逆向難度。
2,反調試之 sysctl
思路是通過sysctl查看信息進程里的標記,判斷自己是否正在被調試。sysctl是用以查詢內核狀態的接口,并允許具備相應權限的進程設置內核狀態。其定義如下:
int sysctl(int *name, u_int namelen, void *old, size_t *oldlen, void *newp, size_t newlen);
name參數是一個用以指定查詢的信息數組;
namelen用以指定name數組的元素個數;
old是用以函數返回的緩沖區;
oldlen用以指定oldp緩沖區長度;
newp和newlen在設置時使用;
當進程被調試器依附時,kinfo_proc結構下有一個kp_proc結構域,kp_proc的p_flag的被調試標識將被設置,即會進行類似如下的設置:
kinfo_proc. kp_proc. p_flag & P_TRACED
其中P_TRACED的定義如下:
#define P_TRACED 0x00000800 /* Debugged process being traced */
我們可以通過sysctl查詢進程相應的kinfo_proc信息,查詢函數的實現可以這樣:
static int is_debugged() __attribute__((always_inline));
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
if (is_debugged() == YES) {
self.label.text = @"is_debugged ";
exit(-1);
}else{
self.label.text = @"not is_debugged";
}
}
static int is_debugged(){
int name[4] = {CTL_KERN,KERN_PROC,KERN_PROC_PID,getpid()};
struct kinfo_proc Kproc;
size_t kproc_size = sizeof(Kproc);
memset((void*)&Kproc, 0, kproc_size);
if (sysctl(name, 4, &Kproc, &kproc_size, NULL, 0) == -1) {
perror("sysctl error \n ");
exit(-1);
}
return (Kproc.kp_proc.p_flag & P_TRACED) ? 1 : 0;
}
針對sysctl的反反調試的思路其實很簡單,只需要在函數返回時清除p_flag標識位即可,根據sysctl.h文件中的定義:
#define CTL_KERN 1
#define KERN_PROC 14
#define KERN_PROC_PID 1
以及sysctl的第二個參數為4,對sysctl下條件斷點,在sysctl返回后,根據反編譯二進制文件找到kproc的首地址,接下來找到p_flag相對kproc首地址的偏移,最后修改對應內存地址的值就OK了。
動態分析的防護還有很多,如:dylib注入檢測,越獄檢測等。當然,也有相應的反反調試手段,且許多檢測方法涉及到iOS的私有API,存在一定的上架風險。
三,總結
總體來說,iOS系統安全性是很高的,且大多數iOS應用都沒做混淆,反調試等。對于金融類,游戲類的應用防護套路來一點還是能增加逆向破解的難度的。當然,要完全防止程序被調試或者被逆向,理論上來說是不可能的。
參考文章
iOS App的加固保護原理 http://www.cocoachina.com/ios/20170324/18955.html
iOS代碼混淆 http://xelz.info/blog/2016/11/20/ios-code-obfuscation/
阿里 iOS Anti-Debug https://jaq.alibaba.com/blog.htm?id=53
關于反調試&反反調試那些事 http://bbs.iosre.com/t/topic/8179
看雪論壇 iOS加固淺談之字符串加密 http://bbs.pediy.com/thread-217991.htm