只需雙擊,老鐵!debugserver快速啟動插件

只需雙擊,老鐵!debugserver快速啟動插件

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/TalkingData/tap2debug

運行界面

只需雙擊,老鐵!debugserver快速啟動插件

我們所用的開發環境是iOS 13.3,但是並沒有用到特殊版本的API,低版本手機應該也OK。

下面簡單分享開發過程:

0x01 通過圖標找到應用執行路徑

從界面找邏輯,逆向發現SpringBoard的圖標是SBIconView。並且有一個叫屬性 applicationBundleIdentifierForShortcuts 返回的是圖標對應的App的Bundle ID。通過Bundle ID構造LSApplicationProxy對象,並且獲得canonicalExecutablePath屬性,也就是應用的可執行文件路徑。

<code>Class LSApplicationProxy_class = objc_getClass("LSApplicationProxy");
NSObject* proxyObj = [LSApplicationProxy_class performSelector:@selector(applicationProxyForIdentifier:) withObject:bundle];
NSString * canonicalExecutablePath = [proxyObj performSelector:@selector(canonicalExecutablePath)];
/<code>

0x02 尋找注入點添加擴展

接續看SBIconView,圖標上有兩個手勢對象:

  • 單擊,用來啟動App。
  • 長按,進入編輯狀態,執行刪除和排列圖標等操作。

所以,我們來給圖標交互加個雙擊擴展。

%hook SBIconView

<code>- (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];
}
}
}
/<code>

這裡額外說一句,[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,沒毛病。找個函數調用一下:

  1. system函數
  2. posix_spawn函數
  3. NSTask ,面向對象方便管理,異步執行,不會block UI,就用它了。

代碼如下:

<code>task = [[NSTask alloc]init];
[task setLaunchPath:bin_serverpath];
[task setArguments:args];
[task launch];
/<code>

每次server在launch之前,要把之前的task結束掉。

<code>- (void)interrupt; // Not always possible. Sends SIGINT.
/<code>


<code>- (void)terminate; // Not always possible. Sends SIGTERM.
/<code>

NSTask

頭文件裡竟然告訴我 Not always possible。事實上,在調用的時候,還真的不怎麼possible,實際測試第一次server正常啟動,後續由於沒成功關閉,所以第二次就沒法啟動了。

所以還是換種方式關閉吧。簡單粗暴的 kill 函數:

<code>NSTask * task = [TaskManager sharedManager].runningTask;
if(task){
kill(task.processIdentifier,SIGKILL);
task = nil;
}
/<code>

0x04 添加UI交互

直接用Alert,又有按鈕又有輸入框,不過UIAlertView已經被廢棄掉了,需要用UIAlertController。由於彈出Controller需要父Controller,通過View找到當前的Controller,做正向的應該都寫過這段代碼吧:

<code>@implementation UIView(find)
-(UIViewController*)findViewController
{
UIResponder* target= self;
while (target) {
target = target.nextResponder;
if ([target isKindOfClass:[UIViewController class]]) {
break;
}
}
return (UIViewController*)target;
}
@end
/<code>

0x05 優化一下用戶體驗

輸入框裡的IP和debugserver的path,每個人都不一樣,所以在第一次輸入完成之後,把這些值用NSUserDefault持久化存儲起來,下次直接讀取填充。

0x06後記

之前在相關技術論壇讀到討論用Root身份運行App的帖子,學習完帖子裡的技巧,增強對操作系統的理解以及實踐之後,發現如果真的想RootApp運行,其實SpringBoard本身就是一個RootApp,我們把SpringBoard當做RootViewController,很容易把一些系統工具做出界面,從而提升生產力。比如砸殼、重籤、拷貝App等。


作者:TalkingData小張同學


分享到:


相關文章: