[iOS 逆向 10] 實踐一

  • 設備: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章

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