對於諸多逆向愛好者來講,給一個app脫殼是一項必作的事情。基於安全性的考慮,蘋果對上架到appstore的應用都會進行加密處理,因此若是直接逆向一個從appstore下載的應用程序時,所能看到的「源代碼」將很是的晦澀難懂。爲了能看懂應用程序的「源代碼」,就必須對應用程序進行解密,也就是所謂的脫殼。脫殼後的目的是能夠分析應用程序的一些技術實現原理,或者利用一些漏洞進行攻擊和測試。ios
這篇文章不是一篇介紹如何利用工具去進行脫殼的教程,而只是簡單的分析這些經常使用脫殼工具的實現原理。要想了解脫殼原理,就要先去了解一個被加密的應用程序是如何被運行的。下面一張圖片簡單的介紹了一個被加殼後的應用程序被加載和運行的過程:git
要對一個殼應用進行脫殼處理,無非就是採用靜態脫殼和動態脫殼兩種方法:靜態脫殼就是在已經掌握和了解到了殼應用的加密算法和邏輯後在不運行殼應用程序的前提下將殼應用程序進行解密處理。靜態脫殼的方法難度大,並且加密方發現應用被破解後就可能會改用更加高級和複雜的加密技術;動態脫殼就是從運行在進程內存空間中的可執行程序映像(image)入手,來將內存中的內容進行轉儲(dump)處理來實現脫殼處理。這種方法實現起來相對簡單,且沒必要關心使用的是何種加密技術。從上面的殼應用程序運行的過程就能夠看出不管殼程序如何被加密處理,最終運行後在進程中的代碼映像(image)始終是被解密後的原始程序二進制。因此只要一個進程內存空間中的代碼映像(image)能被讀取和訪問就能夠實現動態脫殼。下面要介紹的兩個工具就是巧妙的運用了兩種不一樣的訪問技巧來實現動態脫殼的。程序員
dumpdecrypted和frida-ios-dump都是在github上開源的項目,下載地址分別爲:github.com/stefanesser… 關於使用這兩個工具來進行脫殼的文檔很是之多。咱們知道一個應用除了有一個可執行程序外,還會連接很是多的動態庫。動態庫加載後和可執行程序共享相同的進程內存空間,並且動態庫中的代碼是能夠訪問整個進程內存空間中的有權限的區域的,包括可執行程序的image被加載到進程中的內存區域。所以只要想辦法讓應用程序加載某個特定的第三方動態庫,也就是讓這個第三方動態庫注入到應用程序的進程中去就能夠實現將被解密事後的可執行程序在進程內存中的image信息轉儲到文件中去從而實現脫殼處理。對於一個越獄後的設備來講主要能夠經過兩種方法來實現第三方動態庫的注入:github
設置環境變量DYLD_INSERT_LIBRARIES的值指向這個第三方動態庫的路徑。而後運行要脫殼的應用程序便可。 DYLD_INSERT_LIBRARIES環境變量的設置是一個操做系統提供的特性,全部運行的程序都會加載這個環境變量中所指向的動態庫文件。算法
將第三方動態庫文件保存在越獄設備的**/Library/MobileSubstrate/DynamicLibraries/**目錄下並編寫對應的庫的同名plist文件,全部plist中指定的可執行程序一旦運行就會加載對應的動態庫(此目錄即Tweak插件所在的目錄)。安全
還有一種直接修改對應mach-o格式的可執行文件內容來實現動態庫注入。bash
動態庫加載的問題解決後就須要解決動態庫中代碼運行的時機問題了。要想讓一個被加載的動態庫在加載後自動運行某一段代碼能夠有四種方法:app
創建一個C++全局對象,並在對象所屬類的構造函數中添加特定代碼。函數
創建一個OC類,並在OC類的+load方法中添加特定的代碼。工具
生成動態庫時指定一個初始化init入口函數,並在入口函數中添加特定的代碼。
在動態庫中定義一個帶有_attribute_((constructor))聲明的函數,並在函數內添加特定的代碼。
若是你想更進一步的瞭解上述那些方法的加載的原理,請參考個人文章:深刻解構iOS系統下的全局對象和初始化函數
dumpdecrypted這個工具就是經過創建一個名爲dumpdecrypted.dylib的動態庫並在庫內部定義了一個
__attribute__((constructor))
void dumptofile(int argc, const char **argv, const char **envp, const char **apple, struct ProgramVars *pvars)
複製代碼
函數來實現脫殼的。這個函數的大致實現會在後面繼續介紹。
Clutch也是一個在github上開源的項目,下載地址爲:github.com/KJCracks/Cl… 關於這個工具的使用教程也很是之多。咱們知道在unix系列的操做系統中父進程能夠經過fork或者posix_spawnp兩個函數來運行或者創建一個子進程的,這兩個函數都會返回對應的子進程ID(PID)。iOS系統則能夠經過task_for_pid函數來從進程ID獲取進程在mach內核子系統中的mach port標識。獲得mach port 標識後,就能夠藉助mach_vm_read_overwrite函數來讀取指定進程空間中的任意虛擬內存區域中所存儲的內容。所以Clutch內部的實現就是Clutch這個程序對將要進行脫殼的程序文件路徑調用posix_spawnp函數來運行從而成爲其子進程,而後藉助task_for_pid以及mach_vm_read_overwrite函數來讀取脫殼程序子進程在內存中已經被解密後的可執行程序的image所映射的內存空間來達到脫殼的目的的。
一個思考:可能在實際中並不必定要求是父子進程關係,是否只要某個具備特權的程序或者運行在root用戶上的程序只要拿到了對應進程的PID就能夠經過mach子系統提供的API來讀取其餘進程內存空間中的信息呢?
上述的兩種方法中不論是dumpdecrypted仍是Clutch最終都是將被解密後的可執行程序的image在內存中的映射寫入到一個文件中去來保存脫殼後的內容。參考dumpdecrypted中的dumptofile函數實現以及Clutch中的Dumpers目錄下的實現代碼就能夠看出:一個可執行程序image在內存中映射的內容的結構和mach-o格式的可執行文件結構基本上是保持一致的。都是有一個mach_header結構體頭還有諸多的load_command結構體組成。所以所謂的dump處理就是將內存中的這些結構和數據原封不動的寫入到文件中去即完成了脫殼中的最核心的部分。若是想仔細的閱讀這部分代碼的實現,建議先了解一下mach-o文件格式的組成。
當你瞭解了這些內部實現後,也許你會發覺其實它的原理很簡單。並且有可能你也能很快的去實現。可問題的關鍵是爲何這些方法老是別人能想到,而咱們卻想不到呢?這是否和國人的思惟以及解決問題的方式相關呢?在咱們的教育和實踐體系中更多的是拿來主義和實用主義,每每不多人會對問題進行深刻的探索研究以及進行問題的關聯性思考。希望這種狀況在將來可以獲得改進,尤爲做爲一個程序員,更加應該秉持探索求知的強烈意願而不是簡單複製和應用就知足了。
最後仍是要感謝《iOS應用逆向與安全》的做者:劉培慶。向他諮詢了逆向相關的一些知識後才得以寫出這篇文章。並推薦逆向的愛好者閱讀這本書。