iOS 逆向指南:靜態分析

靜態分析是指對二進制包進行反編譯,分析靜態的代碼邏輯。

本文內容包括:app 砸殼過程、工具和環境的坑、導出 OC 頭文件、使用 hopper 和 IDA 反編譯、arm 寄存器功能、靜態分析經驗、推薦的 IDA 插件、如何分析系統庫。

對 app 砸殼解密

從 App Store 下載的 app 是經過加密的,需要對其進行解密後,才能進行分析。如果你懶得砸殼,可以直接去各種蘋果助手下載越獄版 app,那些是已經解密過的。但是如果要找的 app 在助手上沒有,就只能自己砸殼了。

砸殼可以使用 dumpdecrypted,也可以使用更簡單的 clutch。這裏用 dumpdecrypted 講解。步驟如下。

1.下載 dumpdecrypted

https://github.com/AloneMonkey/dumpdecrypted下載源碼,編譯出一個 dumpdecrypted.dylib 文件。這個版本的 dumpdecrypted 添加了對 framework 的 dump。

2.安裝 openSSH

iOS 9及以下系統,在 Cydia 裏安裝 openSSH 即可。

iOS 10越獄自帶了 openSSH,但是默認是關閉的,需要做一點修改。

如果是用的 yalu 越獄:

  • 1.用蘋果助手或者其他工具進入 iOS 的/private/var/containers/Bundle/Application/yalu102/yalu102.app/
  • 2.用文本編輯器打開 dropbear.plist 文件。
  • 3.替換 127.0.0.1:22 爲 22。
  • 4.重啓設備,重新使用越獄工具恢復越獄。

參考:http://bbs.iosre.com/t/make-package-ssh-ios10-2/7564

或者直接去 Cydia 裏安裝 dropbear 插件。

3.連接到 iOS 設備

iOS 設備安裝了 openSSH 後,在 Mac 端打開終端,確保 Mac 和 iOS 設備連接到同一網絡,在終端裏輸入命令:ssh root@iOSIP。iOS 設備的 ip 地址:

iOS IP

在終端中輸入命令:ssh [email protected],回車,接着輸入 ssh 的默認密碼alpine後即可連接到 iOS 設備。

4.找到需要砸殼的 app

找到 app 所在目錄,格式爲/var/mobile/Containers/Data/Application/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/,可以使用同步助手、itools 等工具查找。

也可以在 Cydia 裏安裝 ps 命令行工具後,使用ps –e命令查找,方法是 ssh 成功後,關閉所有 app,打開需要砸殼的 app,輸入ps –e命令,即可打印出所有進程,/var/mobile開頭的那個目錄就是 app 所在的目錄。

dump path

5.進行砸殼

下面的砸殼是舊版 dumpdecrypted 的方法,比較繁瑣。AloneMonkey 的 這個 https://github.com/AloneMonkey/dumpdecrypted 更加簡單。

  • dumpdecrypted.dylib拷貝到/usr/lib。 iOS 9之前是拷貝到 app 的 Document 目錄的, iOS 9 之後出現了權限問題,所以拷貝到/usr/lib
  • 修改 user 爲mobilesu 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 設備上,重新進行砸殼。

砸殼完畢後,在當前目錄會生成一個.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 砸殼的方法:https://github.com/CarinaTT/dumpdecrypted

使用 class-dump 導出 app 的頭文件

Class-dump 是一個可以導出 Objective-C 頭文件的工具,官網:http://stevenygard.com

通過分析頭文件裏的 API,可以簡單地分析一個類的實現,或者查找一些私有 API。

class-dump 官網上的版本不能導出用 swift 編寫的工程的頭文件,當出現Error: Cannot find offset for address 0x3a546a04 in dataOffsetForAddress:這樣的錯誤時,就說明這個 app 可能是用 swift 編寫的。

建議去 github 上手動編譯最新版的 class-dump,或者使用 class-dump-z 代替,下載地址:https://code.google.com/archive/p/networkpx/downloads

把下載到的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 定位到的類名,即可找到對應的文件,查看類裏面的方法。

class-dump

可以看到,在掃一掃界面,微信使用了- (void)captureOutput: didOutputSampleBuffer: fromConnection:這個方法,說明它是截取了視頻流的幀圖像,再對圖像進行二維碼分析,而不是用AVFoundiation提供的二維碼識別方法。

如果還想進一步查看方法的邏輯,可以使用Hopper Disassembler對 .decrypted 文件進行反編譯。

使用使用 Hopper Disassembler 靜態分析

一個專門反編譯 OC 程序的工具。官網:http://www.hopperapp.com。試用版有功能限制,30分鐘退出一次,不能保存和導入反編譯後的文件,不能動態調試等。

打開 Hopper Disassembler,直接將 .decrypted 文件拖入,選擇對應的 CPU 架構類型即可,例如這個.decrypted 是從 iPad mini2 上生成的,那麼就是 arm64。

打開後會自動進行分析,列出方法名、字符串等信息,但是大多數都是彙編語言。閱讀彙編語言,還需要了解對應架構寄存器功能的知識。

在左側可以搜索類名,方法名。

hopper1

右側的 is referenced by 和 have reference to 可以看到方法之間的的交叉引用關係:

hopper2

按空格鍵可以彈出方法的邏輯跳轉圖:

hopper3

Hopper Disassembler 可以將彙編語言轉換爲 OC 風格的僞代碼,但是舊版的 hopper 不能對 arm64 文件使用這個功能。建議使用 armv7s 以下的 iOS 設備的原因就在這裏。以下是使用 iPad2 越獄設備反編譯後,生成的彙編代碼和對應的僞代碼,由於微信的代碼比較複雜,這裏選用的是另外一個更簡單的二維碼 app 的代碼:

hopper4
hopper5

可以看到aptureOutput: didOutputSampleBuffer: fromConnection:裏,首先用取到的幀生成了一張圖片,再用createRotatedImage:degrees:對圖片做了一次處理,最後用decodeImage:cgimg:對圖片進行二維碼分析。要想查看這些方法,只需要再搜索對應的方法名就可以了。最新版 hopper 也可以雙擊直接跳轉。

另外一個反編譯工具 IDA 也可以反編譯 armv7 的 app ,使用方法類似,可以和 Hopper Disassembler 對照着看。需要注意的是 IDA 的 Pro 版才支持 arm64 的 app,而 Pro 版不支持免費試用。

靜態分析經驗總結

追蹤調用流程

  • 對於靜態函數,直接用交叉引用功能is referenced by查看函數在哪裏被引用。注意 hopper 面板裏列出的引用不是完整的,可以用快捷鍵x列出完整的引用
  • 對於 OC 方法,由於 runtime 在調用時不是直接引用方法,而是引用了 selector,所以需要搜索方法名字符串和 selector,然後再用is referenced by查找哪些地址引用了此字符串或者 selector,來查找方法調用
  • 通過寄存器的賦值操作回溯參數的傳遞
  • 通過查找某些關鍵字符串,回溯到關鍵函數

注意,反彙編工具有時候會分析出錯誤的指令,所以有些函數體是丟失的,需要在反編譯時手動 undefined。

分析彙編代碼

  • 使用 hopper 的僞代碼轉換功能,可以將 OC 方法的彙編代碼轉換爲 OC 風格的僞代碼。此功能對 arm64 的支持不是很好,建議使用 armv7 或者 armv7s 的越獄機
  • 在函數的開始,32 位 arm 上前四個參數存放在 r0-r3 中,其他參數存放在棧中,結束後,返回值放在 r0 中;在 arm64 上,前7個參數存放在 x0–x7 中,返回值存在 x0 中
  • 有些代碼是被開發者故意混效過的,例如打亂執行流程、加入冗餘代碼,可以藉助一些 IDA 插件處理後再分析,例如 CrowdDetox、optimice python plugin,不過只是分析 iOS 的話,很少會遇到這種情況

基本的彙編知識

你並不需要花時間理解每一條彙編指令,只需要梳理出關鍵點就能理清代碼的邏輯。

逆向中關鍵的指令:

  • ldrmov,讀取指令,從地址讀取數據到寄存器。
  • 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 插件:hopperref

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

hopperref 插件是源自 一個 IDA 的插件 idaref

idaref.py拷貝到your_ida_path/ida.app/Contents/MacOS/plugins/下,把archs文件夾拷貝到your_ida_path/ida.app/Contents/MacOS/plugins/archsarchs文件夾裏是彙編指令的文檔x86-64.sql``x86-64_old.sql``arm.sql``mips32.sql``xtensa.sql

之後打開 IDA,就可以在Edit菜單中多出了idaref選項,選擇Start Idaref就開啓了自動提示,

idaref

當選中彙編指令時,對應的文檔就會顯示在Instruction Reference窗口中。

idaref output

IDA 插件:FRIEND

除了 idaref,還有另一個插件 FRIEND 也提供了彙編指令和寄存器的文檔功能。只要把鼠標停在指令或者寄存器上就會顯示文檔懸浮窗。

需要注意的是,編譯出來的 IDA dylib 插件是對應 IDA 版本的,如果要使用不同版本的 IDA,就需要重新編譯。把對應版本的FRIEND.dylibFRIEND64.dylib拷貝到your_ida_path/ida.app/Contents/MacOS/plugins/下,再打開 IDA 就會在Edit->Plugins中多出FRIEND選項。

ida FRIEND

點擊選項,打開 FRIEND 的設置。需要加載 FRIEND 提供的 XML 配置文件,對應二進制文件的 x86_64 或者 arm 平臺。例如x86_64.xml配置中提供了x86_64 instructions項,選中後,勾上下面的四個功能選項,點擊 OK 保存。

ida FRIEND settings

之後,當鼠標停在指令或者寄存器上就會顯示文檔懸浮窗。

ida FRIEND instructions

識別庫函數

很多時候,二進制文件中的函數都被去掉了符號,因此只能看到很多sub_100017D90這樣的函數,難以直觀分析。而程序會使用到很多第三方庫,例如加密庫、壓縮庫、網絡庫,這些第三方庫一般都是開源的,可以得到函數符號,如果能恢復這部分函數的符號,就能避免浪費時間在分析這些開源代碼上,也能通過分析開源庫的交叉引用,追蹤程序自身的邏輯。

這部分代碼一般都是 C 和 C++ 函數,OC 方法的名字都保存在 Mach-O 文件的符號表中,不會被去除符號。如果你需要分析 C++ 程序,可以使用下面的工具進行輔助。

FLIRT:庫快速識別和鑑定技術

IDA 提供了FLIRT Signature功能,FLIRT 全稱是庫快速識別和鑑定技術,可以爲帶有符號的庫文件中的函數生成簽名,再把簽名文件導入到分析後的 app 中,就會識別出匹配到的函數,重命名爲正確的符號。

但是生成正確的簽名並不容易。用於生成簽名的庫文件,編譯時的編譯器版本、配置和 app 中用到的庫的編譯器版本、配置需要相同。這樣才能生成相同的代碼,從而生成相同的代碼簽名。

具體的使用方法,可以在書籍IDA Pro 權威指南中找到。

識別加密函數

類似的,有些 IDA 插件可以識別程序中用到的加密常數、加密方法和壓縮方法。例如 Find Crypt 可以尋找常用加密算法中的常數,IDA signsrch 可以尋找二進制文件所使用的加密、壓縮算法,IDA scope 可以自動識別 windows 函數和壓縮、加密算法。

可以從這些關鍵函數入手,尋找程序中的關鍵邏輯。

如何分析系統庫

有時候在分析某個 crash 時,或者對某個系統功能感興趣時,會需要分析特定版本的 iOS 系統庫的實現,例如UIKit.framework Foundiation.framework

絕大部分時候,只需要分析模擬器版本的系統庫就可以了。因爲模擬器的系統庫保留了所有的符號,查找交叉引用更直接。

不過有些系統庫只在真機上纔有,或者你需要特定版本的庫用於分析 crash 時,可以從這裏下載對應的系統庫。

真機的系統庫和模擬器的有些差別。系統庫在真機上經過了很多編譯優化,去除了大部分私有的函數符號,交叉引用也不像模擬器版本的那樣直接。真機上的所有系統 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文件,不過這樣做需要反彙編整個文件,耗時很大。

結尾

靜態分析的整個流程如上,剩下的就是積累經驗了。通過靜態分析查看一些簡單函數的實現,在大部分情況下都足夠了。不過靜態分析的信息是有限的,有時候很難找到想要的函數,這時候就需要動態分析上場了。下一篇文章將講解動態分析。

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章