debugserver+lldb很好用,但啟動起來太麻煩?我們開發了一款iOS SpringBoard tweak小外掛,簡化debugserver啟動過程。老鐵,請雙擊!
0x00 懶是第一生產力
我們經常要通過debugserver對App進行除錯,有書籍和論壇對相關的技術和實踐進行了說明,但實際應用起來還是有些麻煩。先要重籤拷貝,再要啟動終端ssh到iPhone啟動debugserver,各種ls加grep找到想除錯的應用,敲命令啟動debugserver,然後Mac本地終端啟動lldb。這樣折騰下來,至少要開兩個終端,有的時候甚至更多。GitHub上有個issh工具對上述操作有封裝和優化,但是還是需要敲命令找App,再執行debugserver。
所以做個tweak提升一下生產力。只需雙擊應用圖示,即可一鍵啟動debugserver。
程式碼見:Github
https://github.com/TalkingDat...
執行介面
我們所用的開發環境是iOS 13.3,但是並沒有用到特殊版本的API,低版本手機應該也OK。
下面簡單分享開發過程:
0x01 通過圖示找到應用執行路徑
從介面找邏輯,逆向發現SpringBoard的圖示是SBIconView。並且有一個叫屬性 applicationBundleIdentifierForShortcuts 返回的是圖示對應的App的Bundle ID。通過Bundle ID構造LSApplicationProxy物件,並且獲得canonicalExecutablePath屬性,也就是應用的可執行檔案路徑。
Class LSApplicationProxy_class = objc_getClass("LSApplicationProxy");NSObject* proxyObj = [LSApplicationProxy_class performSelector:@selector(applicationProxyForIdentifier:) withObject:bundle];NSString * canonicalExecutablePath = [proxyObj performSelector:@selector(canonicalExecutablePath)];複製程式碼
0x02 尋找注入點新增擴充套件
接續看SBIconView,圖示上有兩個手勢物件:
- 單擊,用來啟動App。
- 長按,進入編輯狀態,執行刪除和排列圖示等操作。
所以,我們來給圖示互動加個雙擊擴充套件。
%hook SBIconView
- (void)didMoveToWindow{ %orig; UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(handleDoubleClick:)]; [doubleTap setNumberOfTapsRequired:2]; [self addGestureRecognizer:doubleTap]; NSArray * ges = self.gestureRecognizers; for(UITapGestureRecognizer * each in ges){ if([each isKindOfClass:[UITapGestureRecognizer class]]){ [each requireGestureRecognizerToFail: doubleTap]; } }}複製程式碼
這裡額外說一句,[each requireGestureRecognizerToFail: doubleTap]新增了雙擊手勢指揮,由於iOS內部維護了手勢的狀態機,我們進行單擊操作的時候,其實產生了兩種Possible State。第一種是識別為單擊,然後結束。第二種是識別為雙擊的第一下並等待第二下的發生,然後根據兩次點選之時間間隔閾值來判斷是不是合法的雙擊。
所以我們手動增加了約束,相當於指定了識別的優先順序,只有雙擊失敗了,才繼續執行單擊回撥。這種操作會帶來一點幾乎無感的瑕疵:單擊後等待雙擊識別失敗的延遲,延遲的值就是雙擊識別執行的閾值(大約零點幾秒)。
0x03 debugserver啟動和關閉
debugserver是一個二進位制檔案,狗神的教程裡有如何重籤,issh把這些過程給簡化了。先看一下debugserver的許可權:
-rwxr-xr-x 1 root admin 9876848 Jan 19 11:28 /iOSRE/tools/debugserver*
再來看一下SpringBoard的許可權:
-rwxr-xr-x 1 root wheel 71264 Dec 5 13:15 SpringBoard*
屬主使用者都是root,沒毛病。找個函式呼叫一下:
- system函式
- posix_spawn函式
- NSTask ,物件導向方便管理,非同步執行,不會block UI,就用它了。
程式碼如下:
task = [[NSTask alloc]init];[task setLaunchPath:bin_serverpath];[task setArguments:args];[task launch];複製程式碼
每次server在launch之前,要把之前的task結束掉。
- (void)interrupt; // Not always possible. Sends SIGINT.複製程式碼
- (void)terminate; // Not always possible. Sends SIGTERM.複製程式碼
NSTask標頭檔案裡竟然告訴我 Not always possible。事實上,在呼叫的時候,還真的不怎麼possible,實際測試第一次server正常啟動,後續由於沒成功關閉,所以第二次就沒法啟動了。
所以還是換種方式關閉吧。簡單粗暴的 kill 函式:
NSTask * task = [TaskManager sharedManager].runningTask;if(task){ kill(task.processIdentifier,SIGKILL); task = nil;}複製程式碼
0x04 新增UI互動
直接用Alert,又有按鈕又有輸入框,不過UIAlertView已經被廢棄掉了,需要用UIAlertController。由於彈出Controller需要父Controller,通過View找到當前的Controller,做正向的應該都寫過這段程式碼吧:
@implementation UIView(find)-(UIViewController*)findViewController{ UIResponder* target= self; while (target) { target = target.nextResponder; if ([target isKindOfClass:[UIViewController class]]) { break; } } return (UIViewController*)target;}@end複製程式碼
0x05 優化一下使用者體驗
輸入框裡的IP和debugserver的path,每個人都不一樣,所以在第一次輸入完成之後,把這些值用NSUserDefault持久化儲存起來,下次直接讀取填充。
0x06後記
之前在相關技術論壇讀到討論用Root身份執行App的帖子,學習完帖子裡的技巧,增強對作業系統的理解以及實踐之後,發現如果真的想RootApp執行,其實SpringBoard本身就是一個RootApp,我們把SpringBoard當做RootViewController,很容易把一些系統工具做出介面,從而提升生產力。比如砸殼、重籤、拷貝App等。
**
作者:TalkingData小張同學
本文版權歸TalkingData所有,如需轉載請註明來源**