[iOS 逆向 4] 開發儲備

做逆向必須先熟悉 iOS 開發,默認都會,所以本文都是簡述。

1 界面、交互、調試

視圖樹

Xcode 可以點擊 Debug View Hierarchy 查看視圖樹,例如:
在這裏插入圖片描述
明確地展示了視圖層級關係,點擊某個視圖可以查看非常詳細的屬性。
上篇文章中的 Reveal 提供了更強大的功能。

內存圖

Xcode 可以點擊 Debug Memory Graph 查看對象引用關係、內存申請情況等,例如:
在這裏插入圖片描述
1 可以十分明確地看出對象間引用關係:圖中的 UINavigationController 通過屬性 _childViewControllers 引用了一個 NSMutableArray,可變數組對象,數組偏移8字節處,也就是數組的第二個元素,引用的是一個自定義的 Controller 對象。
內存管理的核心就是引用計數,對象間引用關係利用這個工具可以一目瞭然。
圖中的引用關係太簡單,看不出優點,可以自己寫一個引用較混亂的看一下。只要得到某個對象的地址,比如是從 Debug View Hierarchy 看到的地址,在內存圖中直接搜索該地址,就能看到對應的內存和引用關係。

2 以圖中的 _childViewController 爲例,點擊後右側可以看到它作爲成員變量的偏移量: <UINavigationController 0x7fcce800c200> [1536] +416,lldb 用 po 命令查看某地址上的對象,便於調試,這樣就可以便捷查看對象或某個成員了。後面會詳細介紹 LLDB。

響應者鏈

如果想複習的話,這裏有響應者鏈的文章鏈接,不看也行。

2 存儲相關

沙盒

iOS 的沙盒(Sandbox)包含:

  • Bundle Container:存儲的是 .app,不可寫,否則破壞了簽名
  • Data Container:真正的“沙盒”,隨便讀寫。默認包含 Documents Library Temp 三個文件夾
  • iCloud Container:需要去蘋果開發者中心添加、項目內打開,然後才能讀寫。

前面有文章介紹了前兩個 Container 的路徑,iCloud Container 具體路徑我忘了,但可以用 [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil] 獲取(項目必須打開 iCloud 存儲能力)

持久化方式

在沙盒內直接創建、讀寫文件即可實現持久化。除了自定義的格式,常見的有:
property list,settings bundle,數據庫文件等。

property list 文件分三種,通過 file 命令可以查看具體格式。分別是 XML(最常見的 xxx.plist 就是),binary,ASCII。binary 格式的無法查看,可以用 plutil 命令轉換爲 XML 格式的查看,如 plutil -convert xml1 xxx.log -o xxx.plist 。前面提到的內存圖就可以導出爲文件,格式就是 binary property list。User Defaults 就是 XML 格式的 xx.plist。

settings bundle 就是系統設置裏可以對 app 進行的設置。有一套規則,但一般 App 都沒做,只有蘋果的 App 實現了。

數據庫類的 SQLite、CoreData(本質是封裝的 SQLite)的 .db 文件,可以通過 brew cask install db-browser-for-sqlite 安裝的軟件查看數據庫有哪些表 等信息。

讀取鑰匙串(非業務讀寫):下載 keychain_dumper,複製(scp 命令)到手機,chmod +r /var/Keychains/keychain-2.db 確保文件可讀,然後執行 keychain_dumper,就能看到各個 App 存到 keychain 的信息了!

3 runtime 基礎

基礎

C/C++ 基礎,數據結構基礎,操作系統基礎,主要就體現在對代碼段、數據段、堆、棧的理解。如果學過彙編,這些基本沒問題。
ARM 彙編、LLDB、DYLD、Mach-O 後面的文章會詳細介紹。

runtime 源碼

做逆向的話,下面列的源碼的具體實現不一定要讀,但要熟悉部分 API,比如根據字符串獲得類對象,創建、註冊一個類,給類添加方法等。如果要做大廠 iOS 開發工程師,以下全得讀。源碼下載

逆向必看的部分:
熟悉對象與類:objc-private.h 和 objc-runtime-new.h
熟練使用 API:runtime.h 和 NSObject.h

iOS 中/高級開發的部分:
對象與類
實現類過程(realizeClass)
讀取 image 過程(_read_images)(load_images)
消息機制,方法緩存
引用計數管理,弱引用管理,自動釋放池
關聯對象
@synchronized
runtime 外的比較重要的源碼:
block 源碼
Foundation 中 Runloop 的源碼
GCD 一部分源碼

上面源碼有些在我 [iOS 理解] 系列文章中解讀過,有興趣的可以看一下。

實踐

典型:KVO 原理,Method Swizzling,Zombie 對象原理,各種 __attribute__ 實現原理…

Method Swizzling 是常見的 Hook 手段,所以要熟練使用 runtime API。後面文章會仔細介紹 Hook。
總之,必須得跟 runtime API 混個眼熟,熟悉內存。

4 App 構建

App 獲取

iTunes 12.7 之後沒有 App Store 了,不能再下載應用包。iTunes 下載下來的是 .ipa 格式的壓縮包,其實就是 .zip 格式的,解壓後是 Payload 文件夾內有一個 xxx.app(其他可忽略)。雖然可以下載舊版 iTunes 再下載 App,但完全可以通過越獄設備安裝後再複製出來 xxx.app,放入 Payload 文件夾再壓縮,是一樣的。

如果想下載舊版本的 App,可以用 Charles 抓包,得到各個版本號,修改請求某個版本,挺麻煩。Cydia 上有插件實現了這個過程,可以從 App Store 選擇 App 舊版本下載,插件叫 AppStore++ 降級神器。

獲取到 xxx.app,觀察 app 包內容,包含:可執行文件、庫文件、配置文件、資源文件等。

App 構建

先用 Xcode Build 一個項目,點擊 navigator 最後一個,就是查看具體過程的。
觀察構建過程可以發現,實際只是 Xcode 幫我們執行了一系列命令,大致過程:
1 編譯代碼
2 鏈接
3 編譯 storyboard/xib 文件
4 複製 Provisioning Profile
5 生成 entitlements
6 簽名

先簡單介紹一下這一系列過程中涉及到的東西。

clang

clang 是一個編譯器,兼容 gcc,所以基礎用法和 gcc 相同。

經常用 gcc 在命令行中編譯 C 程序的話,下面幾個命令應該很熟悉:

1 gcc -c 生成可重定位文件(俗稱目標文件。按規範,目標文件包含 可重定位文件、共享目標文件即動態鏈接庫、可執行文件)
2 nm 命令查看目標文件中的全部符號。可以看到符號的信息:虛擬地址(可重定位文件中全0);所在位置:代碼段/數據段/未定義(外部定義,只找到聲明)。通過參數可以分類查看:可被外部引用的符號;調試程序加進去的符號;只看/不看外部定義的符號
3 file 命令查看文件格式 relocatable/executable 等基礎信息
4 ld 命令鏈接,編排地址,即重定位,生成可執行文件。-Ttext 指定最終生成的可執行文件的起始虛擬地址;-e 或 --entry 指定程序從哪裏開始執行,參數可以是數字地址,可以是符號名,默認值是符號 _start。

這些是編譯、鏈接的基礎概念,clang 可能會加很多參數,但核心的編譯鏈接流程是相同的。

xcrun

xcrun 是 Xcode 提供的用於編譯打包的命令,比 clang 更好用一些。常見使用方法:

1 編譯
xcrun	-sdk iphoneos clang 	(真機 sdk)
		-arch arm64 			(arm64 架構)
		-mios-version-min=11.0 	(支持的最低版本)
		-F UIKit				(鏈接哪些 framework)
		-fobjc-arc 				(開啓 ARC)
		-c main.m 				(僅編譯源文件)
		-o main.o 				(輸出可重定位文件)
2 鏈接
xcrun	-sdk iphoneos clang
		main.o file1.o file2.o
		-arch arm64
		-mios-version-min=11.0
		-fobjc-arc
		-framework UIKit
		-o exec	(輸出可執行文件)
ibtool

ibtool 用來編譯 storyboard/xib 文件。

ibtool	a.storyboard 
		--compile a.storyboardc
	 
ibtool	a.xib 
		--compile a.nib 
		 
actool

actool 用於打包項目中保存在 Assets.xcassets 中的資源文件,該命令保存在 /Applications/Xcode.app/Contents/Developer/usr/bin/actool 位置。

actool	Assets.xcassets 
		--compile outputDir		(需要提前創建輸出文件夾)
		--platform iphoneos 	(目標設備類型)
		--minimum-deployment-target 8.0

執行後,Assets.car 被輸出到指定文件夾內。

下載 Provisioning Profile

複習第一節,Provisioning Profile 文件即 .mobileprovision 後綴的文件,內容包含:
(App ID + 測試設備 IDs + App 權限)+ 簽名,是從蘋果服務器請求籤名後下載的,用於安裝 App 時驗證。

對於免費的開發者,好像無法從開發者網站下載,但如果僅僅嘗試手動打包過程,可以先在 Xcode 裏配置項目的 Team、Bundle Identifier,然後 Xcode 會自動請求配置文件,並下載到 ~/Library/MobileDevice/Provisioning Profiles/xxx.mobileprovision,直接拿來用就行了。

可以通過 security cms -D -i xxx.mobileprovision 查看該文件內容。

xxx.app 文件夾

上面提到的 可執行文件,storyboardc 文件,Assets.car 文件,nib 文件和 plist 文件,第三方 framework 文件,Provisioning Profile,其他資源文件,都拷貝到一個文件夾內,文件夾命名爲 xxx.app。需要注意 info.plist 可以直接複製項目裏的,但要把裏面值爲 $() 的選項手動填上;注意多語言文件夾,比如 storyboardc 默認放在 Base.lproj 文件夾內。
在這裏插入圖片描述

dsym

.dSYM 文件,是二進制地址與源代碼符號映射表,可以從崩潰信息中的二進制地址解析出源代碼的符號。

dsymutil -arch arm64 鏈接生成的可執行文件

這一步也可以去掉。

codesign

在項目裏打開某項能力時,比如打開 iCloud 容器存儲,會自動生成一個 xxx.entitlements,就是這個 app 的權限文件,如果這個能力是不允許免費開發者用的,編譯時會報錯:
Your development team does not support the iCloud capability.
如果這個能力是訪問相機的,則可以編譯成功。

現在要手動生成這個文件,首先文件類型是 property list,XML 子格式,隨便找一個 plist 文件複製內容,然後修改吧。。。內容是一個字典,先添加兩個鍵值對:
application-identifier -> App ID
get-task-allow -> YES
然後添加其他能力。
用 Xcode 編輯可以快一點,如果手寫 XML,比如添加蘋果登陸能力:

<key>com.apple.developer.applesignin</key>
	<array>
		<string>使用蘋果 ID 登陸</string>
	</array>

執行簽名:

codesign
		-fs '證書名'
		--entitlements entitlements文件
		xxx.app路徑

此時如果再修改 app 包內容,需要重新簽名。此時 xxx.app 內會出現一個 _CodeSignature 文件夾,裏面包含了資源文件的簽名;目標文件(可執行文件、動態庫)的簽名會寫到目標文件中,後面 Mach-O 文件格式中有詳細介紹 。
執行後,新建 Payload 文件夾,把 xxx.app 放進去。把 Payload 壓縮成 zip,改後綴爲 ipa,即打包完成。

命令行構建實踐

如果要構建多個項目,用命令行每次都得輸入同樣的命令。所以用 makefile 或其他腳本提高效率。
我寫了一個測試項目,名字 CA_MaskLayer,makefile 如下。(各個步驟沒能連起來一次性執行,只是單獨的各個步驟的目標)

這個 makefile 裏包含了上文大部分步驟,但有的步驟的命令沒寫,比如壓縮命令。

PROJECT 	=$(PWD)
BUILD_DIR   =$(PROJECT)/output
PRODUCT_NAME=CA_MaskLayer
APP_NAME  	=$(PRODUCT_NAME).app

BIN_DIR 	=$(BUILD_DIR)/$(APP_NAME)
SRC_DIR 	=$(PROJECT)/CA_MaskLayer
SB_DIR		=$(SRC_DIR)/Base.lproj
ASSETS_DIR	=$(SRC_DIR)/Assets.xcassets
ENTITLEMENTS=$(BUILD_DIR)/entitlements.plist


SOURCES	 	=$(wildcard $(SRC_DIR)/*.m)
OBJ			=$(patsubst %.m, %.o, $(SOURCES))
TARGET		=$(PRODUCT_NAME)
 
SDK 		=iphoneos
CC			=@xcrun -sdk $(SDK) clang

STORYBOARDS =$(wildcard $(SB_DIR)/*.storyboard)
STORYBOARDCS=$(patsubst %.storyboard, %.storyboardc, $(STORYBOARDS))
ASSETS_CAR	=$()
IBTOOL 		=@ibtool
IBFLAGS		= --compile
ACTOOL 		=@/Applications/Xcode.app/Contents/Developer/usr/bin/actool
ACFLAGS		=--platform iphoneos --minimum-deployment-target 8.0

DSYM 		= dsymutil
CERTIFICATE ='iPhone Developer: [email protected] (AFB945DAJK)'

CFLAGS		= -Wall -arch arm64 -mios-version-min=11.0 -F UIKit -F QuartzCore -fobjc-arc
LDFLAGS		= -arch arm64 -mios-version-min=11.0 -framework UIKit -framework QuartzCore -fobjc-arc

# 鏈接生成可執行文件
link:$(TARGET)
$(TARGET):$(OBJ)
	@mkdir -p $(BIN_DIR)
	$(CC) $(OBJ) $(LIB_PATH) $(LIB_NAMES) $(LDFLAGS) -o $(BIN_DIR)/$(TARGET)
	
# 編譯爲可重定位文件
compile:$(OBJ)
%.o: %.m
	$(CC) $(CFLAGS) $(INCLUDES) -c  $< -o $@

# 編譯 sb 文件
storyboard:$(STORYBOARDCS)
%.storyboardc: %.storyboard
	$(IBTOOL) $(IBFLAGS) $@ $<

assets:$(ASSETS_DIR)
 	$(ACTOOL) $< --compile $(BIN_DIR) $(ACFLAGS) 
 
dsymbol:$(TARGET)
	$(DSYM) $(BIN_DIR)/$(TARGET)

# TODO: - fix dependency
codesign:$(TARGET)
	@codesign -fs $(CERTIFICATE) --entitlements $(ENTITLEMENTS) $(BIN_DIR)

rm-obj:
	@rm $(OBJ)
rm-sb:
	@rm $(STORYBOARDCS)
rm-all: rm-obj rm-sb

clean:
	rm -rf $(BUILD_DIR) rm-all 

按照上面的過程構建時,只需要:
make compile
make link
make storyboard
make assets
複製並修改 info.plist
複製各種資源文件
make dsymbol
make codesign
移動到 Payload
壓縮

效率勉強提高了一些。

5 重簽名

前面文章介紹了 app 包內容、打包過程和簽名過程。同時也可以得出,一旦改變了應用的二進制文件,或者修改了應用裏面的資源文件,應用的簽名就會驗證失敗,無法安裝到手機上。這時就需要對應用重新簽名。這部分屬於後面文章的內容,不過上面提到了打包簽名,這裏趁熱打鐵把重簽名一併介紹。

對於我們自己開發的 App,直接執行上面的 make codesign 就行了。那麼第三方的 App 呢?我們沒有 App 開發者的私鑰怎麼簽名?可以換一種思路來實現。

重新簽名首先本地需要有一個開發者證書。可以通過鑰匙串打開查看,也可以執行 security find-identity -v -p codesigning 在終端打印出來。新建一個 iOS 工程,讓 Xcode 幫忙生成一個 Provisioning Profile 文件 xxx.mobileprovision。執行 security cms -D -i xxx.mobileprovision > xxx.plist 生成一個 xxx.plist 文件備用。

將一個砸殼後的 app 中 Framework 目錄下的 framework、Plugins 目錄下的插件、Watch 目錄下的 extension 都重新簽名:codesign -fs "iPhone Developer: xxx" xxx.framework。修改此 app 內的 Info.plist 中的 Bundle Identifier 爲新建的項目的 Bundle Identifier。

對 app 包重新簽名:codesign -fs "iPhone Developer: xxx" --no-strict --entitlements=xxx.plist xxx.app。然後打包,即可成功安裝到手機上。

我個人理解,這個流程就像是是把 App 的所有文件“據爲己有”,盜版了一個項目簽了自己的名。

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