靜態分析是指對二進制包進行反編譯,分析靜態的代碼邏輯,從而找到關鍵的代碼所在。找到關鍵代碼也就基本實現了逆向的目的,能夠經過修改二進制對關鍵代碼作出自定義修改,達到破解的目的。python
本文內容包括:app 砸殼過程、工具和環境的坑、導出 OC 頭文件、使用 hopper 和 IDA 反編譯、arm 寄存器功能、靜態分析經驗、推薦的 IDA 插件、如何分析系統庫。ios
從 App Store 下載的 app 是通過加密的,須要對其進行解密後,才能進行分析。若是你懶得砸殼,能夠直接去各類蘋果助手下載越獄版 app,那些是已經解密過的。可是若是要找的 app 在助手上沒有,就只能本身砸殼了。git
砸殼可使用 dumpdecrypted,也可使用更簡單的 clutch。這裏用 dumpdecrypted 講解。步驟以下。github
從github.com/AloneMonkey…下載源碼,編譯出一個 dumpdecrypted.dylib 文件。這個版本的 dumpdecrypted 添加了對 framework 的 dump。算法
iOS 9及如下系統,在 Cydia 裏安裝 openSSH 便可。sql
iOS 10越獄自帶了 openSSH,可是默認是關閉的,須要作一點修改。swift
若是是用的 yalu 越獄:windows
/private/var/containers/Bundle/Application/yalu102/yalu102.app/
。dropbear.plist
文件。參考:bbs.iosre.com/t/make-pack…sass
或者直接去 Cydia 裏安裝 dropbear 插件。微信
iOS 設備安裝了 openSSH 後,在 Mac 端打開終端,確保 Mac 和 iOS 設備鏈接到同一網絡,在終端裏輸入命令:ssh root@iOSIP。iOS 設備的 ip 地址:
在終端中輸入命令:ssh root@10.5.53.182
,回車,接着輸入 ssh 的默認密碼alpine
後便可鏈接到 iOS 設備。
找到 app 所在目錄,格式爲/var/mobile/Containers/Data/Application/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/
,可使用同步助手、itools 等工具查找。
也能夠在 Cydia 裏安裝 ps 命令行工具後,使用ps –e
命令查找,方法是 ssh 成功後,關閉全部 app,打開須要砸殼的 app,輸入ps –e
命令,便可打印出全部進程,/var/mobile
開頭的那個目錄就是 app 所在的目錄。
下面的砸殼是舊版 dumpdecrypted 的方法,比較繁瑣。AloneMonkey 的 這個 github.com/AloneMonkey… 更加簡單。
dumpdecrypted.dylib
拷貝到/usr/lib
。 iOS 9以前是拷貝到 app 的 Document 目錄的, iOS 9 以後出現了權限問題,因此拷貝到/usr/lib
mobile
:su mobile
cd /var/mobile/Documents
DYLD_INSERT_LIBRARIES
加載動態庫到 app 上,格式爲DYLD_INSERT_LIBRARIES='dumpdecrypted.dylib的目錄' '須要砸殼的app執行文件的目錄'
,例如:DYLD_INSERT_LIBRARIES=/usr/lib/dumpdecrypted.dylib /var/mobile/Applications/F7753B03-3F06-4524-A735-5BF5B398C730/WeChat.app/WeChat
。這是系統的 dyld 提供的加載動態庫的功能,能夠在 dyld 源代碼中看到這部分邏輯。若是出現dyld: could not load inserted library 'dumpdecrypted.dylib' because no suitable image found. Did find: dumpdecrypted.dylib: required code signature missing for 'dumpdecrypted.dylib'
,須要對 dumpdecrypted.dylib 進行簽名。
在 Mac 上列出證書:security find-identity -v -p codesigning
,用列出的證書籤名: codesign --force --verify --verbose --sign "iPhone Developer: xxx xxxx (xxxxxxxxxx)" dumpdecrypted.dylib
。把簽名後的dumpdecrypted.dylib
從新拷到 iOS 設備上,從新進行砸殼。
砸殼完畢後,在當前目錄或者 app 的Documents
沙盒目錄會生成一個.decrypted
後綴的文件,這就是砸殼後的文件,將其拷貝到 Mac 上便可導入其頭文件、用反編譯工具打開分析。能夠在 Mac 上使用 scp 命令拷貝越獄機上的文件:scp -P 端口號(默認22) root@iOSIP:/var/mobile/Documents/xxx.decrypted ~/Documents/xxx.decrypted
。若是拷貝的是文件夾,加上-r
參數。
dumpdecrypted
原理是 app 啓動後會被系統解密,所以能夠把解密後的內存 dump 出來。可是若是要對 app extension 進行砸殼,因爲 extension 是依賴於主 app 的,不能獨立啓動,因此砸殼方法就失效了。能夠參考這個改進版對 extension 砸殼的方法:github.com/CarinaTT/du…
Class-dump 是一個能夠導出 Objective-C 頭文件的工具,官網:stevenygard.com。
經過分析頭文件裏的 API,能夠簡單地分析一個類的實現,或者查找一些私有 API。
class-dump 官網上的版本不能導出用 swift 編寫的工程的頭文件,當出現Error: Cannot find offset for address 0x3a546a04 in dataOffsetForAddress:
這樣的錯誤時,就說明這個 app 多是用 swift 編寫的。
建議去 github 上手動編譯最新版的 class-dump,或者使用 class-dump-z 代替,下載地址:code.google.com/archive/p/n…。
把下載到的class-dump-z
執行文件放到/usr/local/bin/
,賦予執行權限chmod +x /usr/local/bin/class-dump-z
。這樣就能夠在終端使用 class-dump 命令了:class-dump-z –H '須要導出頭文件的app目錄' –o '導出頭文件的存放目錄'
。
例如要 dump 系統自帶的計算器,導出它的頭文件,命令以下: class-dump-z -H /Applications/Calculator.app -o ~/Documents/headers
。
拿到砸殼後的 .decrypted 文件後,直接使用class-dump-z
便可導出頭文件。
此時,使用以前 reveal 定位到的類名,便可找到對應的文件,查看類裏面的方法。
能夠看到,在掃一掃界面,微信使用了- (void)captureOutput: didOutputSampleBuffer: fromConnection:
這個方法,說明它是截取了視頻流的幀圖像,再對圖像進行二維碼分析,而不是用AVFoundiation
提供的二維碼識別方法。
若是還想進一步查看方法的邏輯,可使用Hopper Disassembler
對 .decrypted 文件進行反編譯。
一個專門反編譯 OC 程序的工具。官網:www.hopperapp.com。試用版有功能限制,30分鐘退出一次,不能保存和導入反編譯後的文件,不能動態調試等。
打開 Hopper Disassembler,直接將 .decrypted 文件拖入,選擇對應的 CPU 架構類型便可,例如這個.decrypted 是從 iPad mini2 上生成的,那麼就是 arm64。
打開後會自動進行分析,列出方法名、字符串等信息,可是大多數都是彙編語言。閱讀彙編語言,還須要瞭解對應架構寄存器功能的知識。
在左側能夠搜索類名,方法名。
右側的 is referenced by 和 have reference to 能夠看到方法之間的的交叉引用關係:
按空格鍵能夠彈出方法的邏輯跳轉圖:
Hopper Disassembler 能夠將彙編語言轉換爲 OC 風格的僞代碼,可是舊版的 hopper 不能對 arm64 文件使用這個功能。建議使用 armv7s 如下的 iOS 設備的緣由就在這裏。如下是使用 iPad2 越獄設備反編譯後,生成的彙編代碼和對應的僞代碼,因爲微信的代碼比較複雜,這裏選用的是另一個更簡單的二維碼 app 的代碼:
能夠看到aptureOutput: didOutputSampleBuffer: fromConnection:
裏,首先用取到的幀生成了一張圖片,再用createRotatedImage:degrees:
對圖片作了一次處理,最後用decodeImage:cgimg:
對圖片進行二維碼分析。要想查看這些方法,只須要再搜索對應的方法名就能夠了。最新版 hopper 也能夠雙擊直接跳轉。
另一個反編譯工具 IDA 也能夠反編譯 armv7 的 app ,使用方法相似,能夠和 Hopper Disassembler 對照着看。須要注意的是 IDA 的 Pro 版才支持 arm64 的 app,而 Pro 版不支持免費試用。
is referenced by
查看函數在哪裏被引用。注意 hopper 面板裏列出的引用不是完整的,能夠用快捷鍵x
列出完整的引用is referenced by
查找哪些地址引用了此字符串或者 selector,來查找方法調用注意,反彙編工具備時候會分析出錯誤的指令,因此有些函數體是丟失的,須要在反編譯時手動 undefined。
你並不須要花時間理解每一條彙編指令,只須要梳理出關鍵點就能理清代碼的邏輯。
逆向中關鍵的指令:
ldr
,mov
,讀取指令,從地址讀取數據到寄存器。str
,保存指令,保存數據到寄存器。b
,跳轉指令,跳轉到某個地址。cmp
,比較指令,說明這裏有分支。32 位 arm 的調用約定:
寄存器 | 描述 |
---|---|
r0-r3 | 傳遞參數與返回值。若是斷點在 OC 方法的第一行,那 r0 就是 self,r1 就是 cmd。若是超過四個參數,或者一些例如結構體的參數超過了32位 bit,那麼參數將會經過棧來傳遞;返回值通常都在 r0 上 |
r4-r6, r8, r10-r11 | 沒有特殊規定,通用寄存器 |
r7 | 棧幀指針寄存器(Frame Pointer),指向前一個保存的棧幀(stack frame)和連接寄存器(link register, lr)在棧上的地址 |
r9 | 操做系統保留 |
r12 | IP 寄存器(intra-procedure scratch) |
r13 | SP 寄存器(stack pointer),是棧頂指針 |
r14 | LR 寄存器(link register),存放函數返回後須要繼續執行的指令地址 |
r15 | PC 寄存器(program counter),指向當前指令地址 |
CPSR | 當前程序狀態寄存器(Current Program State Register),在用戶狀態下存放像 condition 標誌中斷禁用等標誌 |
arm64 的調用約定:
arm64有 r0 - r30 是31個通用整形寄存器,PC 不能再做爲寄存器直接訪問。每一個寄存器能夠存取一個64位大小的數。 當使用 x0 - x30 訪問時,它就是一個64位的數。當使用 w0 - w30 訪問時,訪問的是這些寄存器的低32位。
寄存器 | 描述 |
---|---|
x0–x7 | 傳遞參數與返回值。若是參數個數超過了8個,多餘的參數會存在棧上;返回值通常都在 x0 上 |
x29 | 棧幀指針寄存器(Frame Pointer),指向前一個保存的棧幀(stack frame)和連接寄存器(link register, lr)在棧上的地址 |
x31 | SP 寄存器(stack pointer),是棧頂指針;根據不一樣指令,也有多是 zero register |
x30 | LR 寄存器(link register),存放函數的返回地址 |
CPSR | 當前程序狀態寄存器(Current Program State Register),在用戶狀態下存放像 condition 標誌中斷禁用等標誌 |
x86-64 的調用約定:
x86-64 有16個64位寄存器,分別是:
rax,rbx,rcx,rdx,esi,edi,rbp,rsp,r8,r9,r10,r11,r12,r13,r14,r15
寄存器 | 描述 |
---|---|
rax | 做爲函數返回值使用 |
rsp | 棧指針寄存器,指向棧頂 |
rdi,rsi,rdx,rcx,r8,r9 | 依次用做函數參數;若是斷點在 OC 方法的第一行,那 rdi 就是 self,rsi 就是 cmd |
rbx,rbp,r10,r11,r12,r13,r14,r15 | 通用寄存器 |
棧幀相關的知識,能夠參考:iOS開發同窗的arm64彙編入門
有許多頗有用的插件能夠對靜態分析提供幫助。
有時候看到不了解的彙編指令,每次都去 Google 查找,是一件很低效的事。能夠安裝插件,直接在 hopper 和 IDA 中顯示指令的功能。
Hopper 可使用 Python 編寫的擴展插件。安裝插件hopperref,把Show Instruction Reference.py``arm.sql``x86-64.sql
拷貝到~/Library/Application Support/Hopper/Scripts/
目錄下便可。以後就能在 hopper 界面的菜單欄Scripts
中找到Show Instruction Reference
選項,點擊便可輸出選中指令的詳細文檔。
mov
指令的文檔:
hopperref 插件是源自 一個 IDA 的插件 idaref。
把idaref.py
拷貝到your_ida_path/ida.app/Contents/MacOS/plugins/
下,把archs
文件夾拷貝到your_ida_path/ida.app/Contents/MacOS/plugins/archs
。archs
文件夾裏是彙編指令的文檔x86-64.sql``x86-64_old.sql``arm.sql``mips32.sql``xtensa.sql
。
以後打開 IDA,就能夠在Edit
菜單中多出了idaref
選項,選擇Start Idaref
就開啓了自動提示,
當選中彙編指令時,對應的文檔就會顯示在Instruction Reference
窗口中。
除了 idaref,還有另外一個插件 FRIEND 也提供了彙編指令和寄存器的文檔功能。只要把鼠標停在指令或者寄存器上就會顯示文檔懸浮窗。
須要注意的是,編譯出來的 IDA dylib 插件是對應 IDA 版本的,若是要使用不一樣版本的 IDA,就須要從新編譯。把對應版本的FRIEND.dylib
和FRIEND64.dylib
拷貝到your_ida_path/ida.app/Contents/MacOS/plugins/
下,再打開 IDA 就會在Edit->Plugins
中多出FRIEND
選項。
點擊選項,打開 FRIEND 的設置。須要加載 FRIEND 提供的 XML 配置文件,對應二進制文件的 x86_64 或者 arm 平臺。例如x86_64.xml
配置中提供了x86_64 instructions
項,選中後,勾上下面的四個功能選項,點擊 OK 保存。
以後,當鼠標停在指令或者寄存器上就會顯示文檔懸浮窗。
不少時候,二進制文件中的函數都被去掉了符號,所以只能看到不少sub_100017D90
這樣的函數,難以直觀分析。而程序會使用到不少第三方庫,例如加密庫、壓縮庫、網絡庫,這些第三方庫通常都是開源的,能夠獲得函數符號,若是能恢復這部分函數的符號,就能避免浪費時間在分析這些開源代碼上,也能經過分析開源庫的交叉引用,追蹤程序自身的邏輯。
這部分代碼通常都是 C 和 C++ 函數,OC 方法的名字都保存在 Mach-O 文件的符號表中,不會被去除符號。若是你須要分析 C++ 程序,可使用下面的工具進行輔助。
IDA 提供了FLIRT Signature
功能,FLIRT 全稱是庫快速識別和鑑定技術,能夠爲帶有符號的庫文件中的函數生成簽名,再把簽名文件導入到分析後的 app 中,就會識別出匹配到的函數,重命名爲正確的符號。
可是生成正確的簽名並不容易。用於生成簽名的庫文件,編譯時的編譯器版本、配置和 app 中用到的庫的編譯器版本、配置須要相同。這樣才能生成相同的代碼,從而生成相同的代碼簽名。
具體的使用方法,能夠在書籍IDA Pro 權威指南
中找到。
相似的,有些 IDA 插件能夠識別程序中用到的加密常數、加密方法和壓縮方法。例如 Find Crypt 能夠尋找經常使用加密算法中的常數,IDA signsrch 能夠尋找二進制文件所使用的加密、壓縮算法,IDA scope 能夠自動識別 windows 函數和壓縮、加密算法。
能夠從這些關鍵函數入手,尋找程序中的關鍵邏輯。
有時候在分析某個 crash 時,或者對某個系統功能感興趣時,會須要分析特定版本的 iOS 系統庫的實現,例如UIKit.framework
Foundiation.framework
。
絕大部分時候,只須要分析模擬器版本的系統庫就能夠了。由於模擬器的系統庫保留了全部的符號,查找交叉引用更直接。
不過有些系統庫只在真機上纔有,或者你須要特定版本的庫用於分析 crash 時,能夠在iOS-System-Symbols下載對應的系統庫。
真機的系統庫和模擬器的有些差異。系統庫在真機上通過了不少編譯優化,去除了大部分私有的函數符號,交叉引用也不像模擬器版本的那樣直接。真機上的全部系統 framework 都被整合成了一個大文件,名爲dyld_shared_cache_arm64
或者dyld_shared_cache_armv7
。函數在尋址時,是基於整個dyld_shared_cache_xxx
文件進行尋址的。
當你把真機鏈接到 Xcode,Xcode 會把真機上的系統庫拷貝到~/Library/Developer/Xcode/iOS DeviceSupport
,從dyld_shared_cache_xxx
中切分出每一個單獨的 framework。可是當你反編譯這些 framework 時,會發現代碼裏會使用不少無效地址的函數指針,難以分析。這是由於在dyld_shared_cache_xxx
中,一個 framework 引用另外一個 framework 中的函數時,是至關於在一個庫中直接引用的,直接跳轉到對應的地址,而不是再用函數符號通過 lazy binder 進行調用。當 framework 從dyld_shared_cache_xxx
中切分出來後,這些函數調用的地址就會指向 framework 外,沒法追蹤。
因此在分析真機的系統庫時,最好是配合模擬器版本的系統庫輔助分析,能夠看到私有的符號,也能夠看到更明確的交叉引用。或者用 IDA 直接分析整個 dyld_shared_cache_xxx
文件,不過這樣作須要反彙編整個文件,耗時很大。
靜態庫是由 .o 文件組成的,拖到 hopper 裏只能逐個查看 .o。能夠按下列步驟把 .o 整合成一個文件。
lipo 靜態庫文件 -thin arm64 -output libfile
導出想要分析的架構ar -x libfile
導出全部的 object 文件grep "符號名" -rn ./
在全部 object 文件中搜索符號otool -l libobject.o | grep bitcode
檢查是否有 bitcodeld -r -arch arm64 -syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk ./*.o -o ../outputlibfile
把全部 .o 文件整合成一個文件,若是有 bitcode,則須要加上-bitcode_bundle
靜態分析的整個流程如上,剩下的就是積累經驗了。經過靜態分析查看一些簡單函數的實現,在大部分狀況下都足夠了。不過靜態分析的信息是有限的,有時候很難找到想要的函數,這時候就須要動態分析上場了。下一篇文章將講解動態分析。