天天看點

[iOS 逆向 10] 實踐一

  • 裝置:iPhone 6 Plus with iOS 12.4
  • 目标:解除番茄ToDo 會員功能限制

1 脫殼

使用 dumpdecrypted 工具,把可執行檔案和 framework 都脫殼。

2 導出頭檔案

使用 class-dump 工具将可執行檔案的頭檔案導出備用。

3 分析界面

連上 Cycript,執行:

[[UIApp keyWindow] recursiveDescription].toString()

意思是遞歸列印目前 window 上的視圖樹。

[iOS 逆向 10] 實踐一

也可以連接配接 Reveal 看,更直覺:

[iOS 逆向 10] 實踐一

圖中開關按鈕”計入 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架構的動态庫,不過對于逆向分析來說并不重要。模拟器的動态庫位置非常刁鑽,如圖:

[iOS 逆向 10] 實踐一

找到 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

[iOS 逆向 10] 實踐一

在 IDA 中也能找到:

[iOS 逆向 10] 實踐一

動态調試

前面介紹過連接配接 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

,和軟體裡反彙編的結果一緻:

[iOS 逆向 10] 實踐一

解讀開始的幾條指令,已知 OC 的消息機制固定了 X0 是 self 指針,X1 是 SEL 即 const char*。先擷取 self->_mindSwitch 放到 X0,然後把 isOn 函數名放到 X1,然後發送消息。如果按鈕狀态之前是打開的,點選後是關閉,isOn 會傳回 false,于是會 CBZ 跳轉到 loc_100060B2C:

[iOS 逆向 10] 實踐一

第一個 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 一個判斷是否為會員的函數,這樣當我想關閉功能時也可以正常關閉。那就隻能看開關按鈕的另一個分支,也是目前情況下将要實際執行的分支。中間一段不重要的代碼跳過,找到核心的分支語句,如圖

[iOS 逆向 10] 實踐一

這段代碼相當于判斷:

[SystemUtil shouldSH] || [CommonUtil isActive]

二者任意一個正确都可以,那麼分别檢視這兩個函數的代碼:

[iOS 逆向 10] 實踐一
[iOS 逆向 10] 實踐一

可以看出,第一個是讀記憶體中的值;第二個是從 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代碼十分簡單:

[iOS 逆向 10] 實踐一

然後依次執行編譯、打包、安裝指令,即可快速安裝到越獄裝置。如果要安裝到非越獄裝置,先用第三種動态庫注入方式修改可執行檔案,然後對 App 重簽名後才能安裝。會員權限使用成功的兩個例子如圖 ,分别是可以無限使用海報、自動寫入 iOS 健康。

總結

  • dumpdecrypted 砸殼
  • class-dump 導出頭檔案
  • Cycript / Reveal 分析界面
  • 對照 IDA 反彙編出的代碼進行 LLDB 調試,摸清邏輯
  • Theos 寫插件,和普通 iOS 開發一樣。

注意

有可能番茄 ToDo 作者看到本文就會修改邏輯,不能保證過一段時間後本文的代碼還能用,但是思路肯定是能用的;

第十一章是另一個 app 的逆向實踐,沒意思,沒寫,直接看第12章

繼續閱讀