- 裝置:iPhone 6 Plus with iOS 12.4
- 目标:解除番茄ToDo 會員功能限制
1 脫殼
使用 dumpdecrypted 工具,把可執行檔案和 framework 都脫殼。
2 導出頭檔案
使用 class-dump 工具将可執行檔案的頭檔案導出備用。
3 分析界面
連上 Cycript,執行:
[[UIApp keyWindow] recursiveDescription].toString()
意思是遞歸列印目前 window 上的視圖樹。

也可以連接配接 Reveal 看,更直覺:
圖中開關按鈕”計入 iOS 正念時間“點選後就告訴我們沒開會員不能用,那麼我們隻需要讓判斷是否是會員的函數傳回 true 就可以打開了。這是一個 UISwitch 控件,可以得到位址為 0x13dde0c20。
找到響應事件
在 Cycript 中執行:
cy# [#0x13dde0c20 allTargets]
[NSSet setWithArray:@[#"<MyController: 0x13e059a00>"]]]
cy# [#0x13dde0c20 valueForKey:@"targetActions"]
@[#"<UIControlTargetAction: 0x2823ab780>"]
有 iOS 開發經驗的話,第一句擷取 allTargets 屬性是沒有疑問的,官方文檔中明确寫了;第二句利用 iOS Runtime KVC 特性,擷取名為 targetActions 的屬性,但是 targetActions 屬性沒在官方文檔中提到過,是怎麼知道的?在正向開發中,我們可以給一個按鈕添加多個響應事件,由此可以想到肯定有一個數組儲存了所有的響應關系,但是在官方文檔中并沒有發現。是以該屬性極有可能是一個 private 屬性,為了驗證這一點,我們得先搞到系統動态庫。
裝置第一次連接配接到Xcode時,會自動提取系統庫等資料到 ~/Library/Developer/Xcode/iOS DeviceSupport/ 目錄下,進入某一版本的Symbols/System/Library/PrivateFrameworks/ 目錄即可找到原始的動态庫。
模拟器也是iOS系統,可以直接從電腦上找到模拟器的所有系統庫,但由于模拟器是運作在x86_64的CPU上,是以都是x86_64架構的動态庫,不過對于逆向分析來說并不重要。模拟器的動态庫位置非常刁鑽,如圖:
找到 UIKit 架構中的 UIKitCore,用 class-dump 提取頭檔案。UISwitch 的父類為 UIControl,檢視 UIControl 類的頭檔案,發現果然有一個私有數組屬性 _targetActions!利用 KVC 獲得到對象的私有屬性 targetActions 為 0x2823ab780,列印該屬性的内容
cy# *(#0x2823ab780)
{
isa:UIControlTargetAction,
_target:#"<MyController: 0x11606da00>",
_action:@selector(onMindClicked:),
_eventMask:64,
_cancelled:false
}
isa 指向 UIControlTargetAction 類;也得到了 UISwitch 的響應事件:MyController 類的 onMindClicked: 方法。
分析代碼
打開 Hopper,打開之前脫殼的可執行檔案,搜尋 onMindClick
在 IDA 中也能找到:
動态調試
前面介紹過連接配接 debugserver,連上後,執行
image list -o -f
可以看到所有 image 資訊,分别是加載的序号,基位址,實際加載位址(子產品的基位址+加載的起始位址)
第一個 image 是 App 的可執行檔案,基位址是 0x0000000001084000,而 onMindClicked 函數偏移量為 0x0000000100060968,是以打斷點:
breakpoint set -a 0x0000000001084000+0x0000000100060968
繼續執行,我點選界面上的開關按鈕,程式暫停,LLDB 輸出:
Process 625 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
frame #0: 0x00000001010e4968 TomatoTime`___lldb_unnamed_symbol1415$$TomatoTime
TomatoTime`___lldb_unnamed_symbol1415$$TomatoTime:
-> 0x1010e4968 <+0>: sub sp, sp, #0xb0 ; =0xb0
0x1010e496c <+4>: stp x24, x23, [sp, #0x70]
0x1010e4970 <+8>: stp x22, x21, [sp, #0x80]
0x1010e4974 <+12>: stp x20, x19, [sp, #0x90]
Target 0: (TomatoTime) stopped.
目前指令停在
sub sp, sp, #0xb0
,和軟體裡反彙編的結果一緻:
解讀開始的幾條指令,已知 OC 的消息機制固定了 X0 是 self 指針,X1 是 SEL 即 const char*。先擷取 self->_mindSwitch 放到 X0,然後把 isOn 函數名放到 X1,然後發送消息。如果按鈕狀态之前是打開的,點選後是關閉,isOn 會傳回 false,于是會 CBZ 跳轉到 loc_100060B2C:
第一個 objc_msgSend 是調用了 MTA 類的一個靜态方法,我百度了一下,MTA 是騰訊的一個移動資料分析的工具。這裡是上傳一些日常資料,暫時不用管。第二個 objc_msgSend 的參數中,X0 是GVUserDefaults 類對象,X1 是字元串 standardUserDefaults,可以聯想到系統類 NSUserDefaults,但類名對應不上,于是查找之前提取出來的頭檔案,發現隻是對 NSUserDefaults的封裝,負責 App 内持久化存儲的類。第三個 objc_msgSend 給 isOpenMind 屬性設定值為 0,在頭檔案中也找到了相應的動态屬性:@property(nonatomic) _Bool isOpenMind; // @dynamic isOpenMind;
到這裡,我感覺隻需要給 GVUserDefaults 類添加 isOpenMind 的 getter 實作,且始終傳回 true,這樣别的地方判斷這個屬性進而執行某些功能時,始終能夠執行。但這樣不完美,因為理想情況下是 hook 一個判斷是否為會員的函數,這樣當我想關閉功能時也可以正常關閉。那就隻能看開關按鈕的另一個分支,也是目前情況下将要實際執行的分支。中間一段不重要的代碼跳過,找到核心的分支語句,如圖
這段代碼相當于判斷:
[SystemUtil shouldSH] || [CommonUtil isActive]
二者任意一個正确都可以,那麼分别檢視這兩個函數的代碼:
可以看出,第一個是讀記憶體中的值;第二個是從 GVUserDefaults 裡面讀 isActive 鍵的值,是從硬碟裡讀(我猜測記憶體中的值應該是初始化時讀硬碟值做緩存),我在第一個函數内打斷點:
breakpoint set -a 0x0000000001084000+0x0000000100024794
然後執行,我點選開關按鈕後,程式停在:
TomatoTime`___lldb_unnamed_symbol429$$TomatoTime:
-> 0x1010a4794 <+0>: adrp x8, 1756
0x1010a4798 <+4>: ldrb w0, [x8, #0xc60]
0x1010a479c <+8>: ret
單步執行到 ret 指令時檢視寄存器,register read x0 結果不出所料是 0,因為我沒開會員。然後在這裡修改寄存器,register write x0 1,修改後繼續程式,此時按鈕被成功打開了,沒有彈窗提示沒有權限,并且跳轉到請求系統授權通路健康。我用上面同樣的方法嘗試了其他幾個會員功能,發現 [SystemUtil shouldSH] 隻能開啟上面這個功能,而 [CommonUtil isActive] 可以打開任意功能。現在确定了,[CommonUtil isActive] 就是要 hook 的目标函數。
再回顧一下有沒有疏忽的點。前面提到過,該 App 中有用 MTA 送出使用者日志的代碼,是以為了不被發現異常操作,我們還需要把 MTA 記錄使用者記錄檔的方法 hook 掉。
Hook
使用 theos 建立一個項目,編寫 Tweak。本項目一共要攔截3個方法,前兩個是權限判斷函數,直接傳回true;第三個是記錄使用者日志函數,直接傳回。要寫的hook代碼十分簡單:
然後依次執行編譯、打包、安裝指令,即可快速安裝到越獄裝置。如果要安裝到非越獄裝置,先用第三種動态庫注入方式修改可執行檔案,然後對 App 重簽名後才能安裝。會員權限使用成功的兩個例子如圖 ,分别是可以無限使用海報、自動寫入 iOS 健康。
總結
- dumpdecrypted 砸殼
- class-dump 導出頭檔案
- Cycript / Reveal 分析界面
- 對照 IDA 反彙編出的代碼進行 LLDB 調試,摸清邏輯
- Theos 寫插件,和普通 iOS 開發一樣。
注意
有可能番茄 ToDo 作者看到本文就會修改邏輯,不能保證過一段時間後本文的代碼還能用,但是思路肯定是能用的;
第十一章是另一個 app 的逆向實踐,沒意思,沒寫,直接看第12章