感覺是要搞個《逆向分析及修復app》系列的節奏啊
故事概要
大家好,我又來了。上次給大家分享了《逆向及修復最新iOS版少數派客戶端的閃退bug》,@了一些iOS界的大神,沒想到受到了大家的轉發和關注,還真有點小激動。同時也在微博的評論下收到了稀土掘金的歡迎,還給我開了專欄許可權,希望可以到稀土掘金上分享寫作。備感榮幸的同時,突然發現,一直在稀土掘金上面看文章的我,一直沒有註冊賬戶(好像註冊了但是忘了密碼,在1Password中也沒有記錄,看來是很久之前登陸過了)。這也是這次故事的開始,收到稀土掘金的評論後,馬上開啟了早已不知道什麼時候下載的 掘金 app。果然,沒有登陸記錄,作為開發者馬上用 GitHub 授權進行登陸,尷尬的事情發生了,在授權成功後,app閃退了。好尷尬啊,好吧稀土掘金上的處女座就獻給你了。
跟之前寫《逆向及修復最新iOS版少數派客戶端的閃退bug》文章時不同,在文章寫到這裡的時候,我其實還沒有完全分析完崩潰的原因。我是分析途中決定開始寫這篇文章的,決定一邊分析一遍寫,這樣才會儘可能的保留分析的全過程,當然最後能不能分析到關鍵點,且看下去吧,因為我也不知道。
問題描述
- 最新版 4.1.3 稀土掘金 app 在使用 github 登陸成功後會 crash
- 是在登陸成功後才 crash,因為再次進入後會發現已經登陸成功
- 可能不是所有人都會遇到這個bug,根據後來的分析,可能是Github的某個個人資訊中缺少某些設定導致的授權後crash
著手分析
###找到崩潰原因
分析一個bug的開始,當然是從崩潰原因找起,前一篇文章,我們使用了lldb除錯,在觸發 app 崩潰的時候,從 Terminal 找到了關鍵詞:EXE_BAD_ACCESS
。這次,我們使用最簡單的,檢視系統日誌來定位崩潰原因,使用最新的 Mac,開啟控制檯,連線手機並清空多餘的系統日誌列印,然後觸發崩潰,觀察控制檯中輸出的內容(如果輸出日誌過多,可以使用程式名Xitu
作為關鍵詞,進行過濾)。如果可以還是不要使用關鍵詞過濾,因為不是所有的崩潰都是由於 app 本身的問題導致的,正如簽名破壞的 app 在沒有越獄的手機上會安裝失敗,這個安裝失敗的原因查詢就需要在日誌中的 SpringBoard
程式列印中進行查詢
(SpringBoard
是Apple
用來管理我們iPhone應用的一個應用,可以理解為我們 PC 端的桌面,它還負責應用的桌面排列、管理通知中心等)。
正如我們希望的一樣,崩潰的原因最常見不過了:
這個崩潰就很有意思了,開發者對NSNull
呼叫了length
方法,因為找不到該方法而崩潰!當然開發者肯定是不會主動對NSNull
型別呼叫length
方法,猜想:
- 開發不知道物件會變成
NSNull
型別,說明這個物件可能是在執行時確定的 - 呼叫
length
,說明開發者認為這個類應該是屬於NSString
型別的
大膽猜測:結合這個崩潰是發生在登陸的時候,需要從網路獲取登陸資訊,那麼會不會是程式在對獲取到的登陸資訊進行處理的時候導致的崩潰(比如登陸使用者的一些資訊沒有設定,所以程式從登陸資訊裡取到的是空值)。
分析入口
1.找到登陸介面的控制類
分析從該登陸介面出發,使用cycript注入app後,列印找到該介面的控制器:
susnms-iPhone:~ root# ps -e | grep Xitu
24240 ?? 0:00.00 (Xitu.Widget)
24241 ?? 0:00.00 (XituShare)
24242 ?? 0:00.00 (Xitu)
24283 ?? 0:02.03 /var/containers/Bundle/Application/994C4217-88DD-4F55-A016-55BDD5998C49/Xitu.app/Xitu
24288 ttys000 0:00.01 grep Xitu
susnms-iPhone:~ root# cycript -p 24283 common.cy ; cycript -p 24283
cy# currentVCWithKeyWindow ()
#"<XTGithubLoginViewController: 0x15e0ef340>"
cy#複製程式碼
currentVCWithKeyWindow() 來自我自己寫的一個指令碼,你可以前往common.cy下載。下載之後,將其放到
/var/root
目錄下即可。
使用時,只需要在cycript注入的時候加上即可。
其中還有一些好用的函式,比如快速列印UIControl
所有的target
和action
2.檢視登陸介面的呼叫流程
使用class-dump
等工具,匯出app
的所有類標頭檔案,使用logify.pl
工具生成XTGithubLoginViewController
類的所有hook
函式,使用theos
編寫安裝外掛後,觸發崩潰,然後在控制檯查詢該類的函式呼叫邏輯。如下:
可以發現,程式是在-[XTGithubLoginViewController viewWillDisappear:]
呼叫之後才收到的崩潰的通知。所以很有可能崩潰的原因不是XTGithubLoginViewController
導致的,而是在XTGithubLoginViewController
類返回登陸資訊後。
仔細研究XTGithubLoginViewController
的呼叫過程,發現了幾個有意思的方法,呼叫順序如下:
- -(void)setCallBack:
- -(id)onAuthCompleted:
- -(id)callBack
callback
!太熟悉不過了,這不就是我們經常在開發中使用的設定回撥block
的時候使用的引數名嗎!!並且,該callback
在類初始化的時候被設定,類方法-(id)onAuthCompleted:
呼叫之後才被回撥。結合這個方法名onAuthCompleted
,這個邏輯是不是很像:我們在登陸成功之後,使用block傳回了我們的登陸資訊進行處理。
分析到這裡,其實我們已經可以使用lldb除錯,列印該block
的記憶體地址,然後去掉記憶體地址偏移後,在Hopper
中檢視block的實現,看是否該block導致的崩潰。但是作為逆向的學習,我們可以先繼續深入,檢視一下這個block的傳遞呼叫過程。
繼續深入callback
的回撥過程
既然該回撥是在XTGithubLoginViewController
被初始化的時候被設定的,那麼我們先找到是誰初始化的該Github
登陸控制器:
使用cycript
注入列印該控制器類名,並嘗試github
的授權登入入口:
susnms-iPhone:~ root# cycript -p Xitu common.cy ; cycript -p Xitu
cy# currentVCWithKeyWindow ()
#"<XTLoginViewController: 0x12e43f740>"
cy# [#0x12e43f740 github
githubBt githubLogin:
cy# [#0x12e43f740 githubLogin: nil]複製程式碼
發現成功觸發登入,所以可以確定githubLogin:
方法確實是登入的入口
1.githubLogin:
實現
在Hopper
中檢視該方法的實現:
-[XTLoginViewController githubLogin:]:
0000000100194c24 sub sp, sp, #0x50 ; Objective C Implementation defined at 0x1009d2708 (instance method), DATA XREF=0x1009d2708
0000000100194c28 stp x20, x19, [sp, #0x30]
0000000100194c2c stp x29, x30, [sp, #0x40]
0000000100194c30 add x29, sp, #0x40
0000000100194c34 mov x19, x0
0000000100194c38 adrp x8, #0x100ab4000 ; @selector(detectSocketStatus)
0000000100194c3c ldr x0, [x8, #0x878] ; objc_cls_ref_LoginCloud,__objc_class_LoginCloud_class
0000000100194c40 adrp x8, #0x100aa0000 ; @selector(computeTime:)
0000000100194c44 ldr x1, [x8, #0xd0] ; "singleClass",@selector(singleClass)
0000000100194c48 bl imp___stubs__objc_msgSend ; login = [LoginCloud singleClass]
0000000100194c4c mov x29, x29
0000000100194c50 bl imp___stubs__objc_retainAutoreleasedReturnValue
0000000100194c54 mov x20, x0
0000000100194c58 adrp x8, #0x100914000
0000000100194c5c ldr x8, [x8, #0x448] ; __NSConcreteStackBlock_100914448,__NSConcreteStackBlock
0000000100194c60 str x8, [sp, #0x8]
0000000100194c64 movz w8, #0xc200
0000000100194c68 stp w8, wzr, [sp, #0x10]
0000000100194c6c adr x8, #0x100194cc4
0000000100194c70 nop
0000000100194c74 str x8, [sp, #0x18]
0000000100194c78 adrp x8, #0x100923000
0000000100194c7c add x8, x8, #0x9d0 ; 0x1009239d0
0000000100194c80 str x8, [sp, #0x20]
0000000100194c84 mov x0, x19
0000000100194c88 bl imp___stubs__objc_retain
0000000100194c8c str x0, [sp, #0x28]
0000000100194c90 adrp x8, #0x100aa7000 ; @selector(isTableViewScrolledToBottom)
0000000100194c94 ldr x1, [x8, #0x848] ; "githubLoginCallback:",@selector(githubLoginCallback:)
0000000100194c98 add x2, sp, #0x8
0000000100194c9c mov x0, x20
0000000100194ca0 bl imp___stubs__objc_msgSend ; [login githubLoginCallback: block]
0000000100194ca4 mov x0, x20
0000000100194ca8 bl imp___stubs__objc_release
0000000100194cac ldr x0, [sp, #0x28]
0000000100194cb0 bl imp___stubs__objc_release
0000000100194cb4 ldp x29, x30, [sp, #0x40]
0000000100194cb8 ldp x20, x19, [sp, #0x30]
0000000100194cbc add sp, sp, #0x50
0000000100194cc0 ret複製程式碼
內容很簡單,呼叫了單例類,並傳入回撥callback引數:
LoginCloud *login = [LoginCloud singleClass];
[login githubLoginCallBack: callback];複製程式碼
2.[LoginCloud singleClass]實現
在Hopper
檢視如下:
-[LoginCloud githubLoginCallback:]:
sub sp, sp, #0x80 ; Objective C Implementation defined at 0x1009c7cc8 (instance method), DATA XREF=0x1009c7cc8
stp x26, x25, [sp, #0x30]
stp x24, x23, [sp, #0x40]
stp x22, x21, [sp, #0x50]
stp x20, x19, [sp, #0x60]
stp x29, x30, [sp, #0x70]
add x29, sp, #0x70
mov x22, x0
mov x0, x2
bl imp___stubs__objc_retain
mov x21, x0
adrp x8, #0x100ab4000 ; @selector(detectSocketStatus)
ldr x0, [x8, #0x6a0] ; objc_cls_ref_UIStoryboard,_OBJC_CLASS_$_UIStoryboard
adrp x8, #0x100aa0000 ; @selector(computeTime:)
ldr x1, [x8, #0x788] ; "storyboardWithName:bundle:",@selector(storyboardWithName:bundle:)
adrp x2, #0x100949000 ; @"%@ %@ %@"
add x2, x2, #0x500 ; @"Login"
movz x3, #0x0
bl imp___stubs__objc_msgSend ; loginSb = [UIStoryboard storyboardWithName: @"Login" bundle: nil]
mov x29, x29
bl imp___stubs__objc_retainAutoreleasedReturnValue
mov x19, x0
adrp x8, #0x100aa0000 ; @selector(computeTime:)
ldr x1, [x8, #0x790] ; "instantiateViewControllerWithIdentifier:",@selector(instantiateViewControllerWithIdentifier:)
adrp x2, #0x10094f000 ; @"nickname"
add x2, x2, #0xd00 ; @"githubVC"
bl imp___stubs__objc_msgSend ; githubVC = [loginSb instantiateViewControllerWithIdentifier: @"githubVC"]
mov x29, x29
bl imp___stubs__objc_retainAutoreleasedReturnValue
mov x20, x0
adrp x8, #0x100914000
ldr x8, [x8, #0x448] ; __NSConcreteStackBlock_100914448,__NSConcreteStackBlock
str x8, sp
movz w8, #0xc200
stp w8, wzr, [sp, #0x8]
adr x8, #0x10015ccbc
nop
str x8, [sp, #0x10]
adrp x8, #0x100921000
add x8, x8, #0x6f0 ; 0x1009216f0
stp x8, x21, [sp, #0x18]
mov x0, x21
bl imp___stubs__objc_retain
mov x21, x0
mov x0, x22
bl imp___stubs__objc_retain
str x0, [sp, #0x28]
adrp x8, #0x100aa6000 ; @selector(hasText)
ldr x1, [x8, #0x440] ; "setCallBack:",@selector(setCallBack:)
mov x2, sp
mov x0, x20 ; [githubVC setCallBack: black]
bl imp___stubs__objc_msgSend
adrp x8, #0x100ab4000 ; @selector(detectSocketStatus)
ldr x0, [x8, #0x938] ; objc_cls_ref_UINavigationController,_OBJC_CLASS_$_UINavigationController
adrp x8, #0x100a9f000
ldr x1, [x8, #0xd78] ; "alloc",@selector(alloc)
bl imp___stubs__objc_msgSend
adrp x8, #0x100aa1000 ; @selector(setUpdatedAt:)
ldr x1, [x8, #0x948] ; "initWithRootViewController:",@selector(initWithRootViewController:)
mov x2, x20
bl imp___stubs__objc_msgSend
mov x22, x0
adrp x8, #0x100ab4000 ; @selector(detectSocketStatus)
ldr x0, [x8, #0x820] ; objc_cls_ref_UIApplication,_OBJC_CLASS_$_UIApplication
adrp x8, #0x100aa1000 ; @selector(setUpdatedAt:)
ldr x1, [x8, #0x2d8] ; "sharedApplication",@selector(sharedApplication)
bl imp___stubs__objc_msgSend
mov x29, x29
bl imp___stubs__objc_retainAutoreleasedReturnValue
mov x23, x0
adrp x8, #0x100a9f000
ldr x1, [x8, #0xf88] ; "delegate",@selector(delegate)
bl imp___stubs__objc_msgSend
mov x29, x29
bl imp___stubs__objc_retainAutoreleasedReturnValue
mov x24, x0
adrp x8, #0x100aa1000 ; @selector(setUpdatedAt:)
ldr x1, [x8, #0x930] ; "window",@selector(window)
bl imp___stubs__objc_msgSend
mov x29, x29
bl imp___stubs__objc_retainAutoreleasedReturnValue
mov x25, x0
adrp x8, #0x100aa1000 ; @selector(setUpdatedAt:)
ldr x1, [x8, #0x938] ; "rootViewController",@selector(rootViewController)
bl imp___stubs__objc_msgSend
mov x29, x29
bl imp___stubs__objc_retainAutoreleasedReturnValue
mov x26, x0
mov x0, x25
bl imp___stubs__objc_release
mov x0, x24
bl imp___stubs__objc_release
mov x0, x23
bl imp___stubs__objc_release
adrp x8, #0x100aa0000 ; @selector(computeTime:)
ldr x1, [x8, #0x220] ; "presentViewController:animated:completion:",@selector(presentViewController:animated:completion:)
orr w3, wzr, #0x1
mov x0, x26
mov x2, x22
movz x4, #0x0
bl imp___stubs__objc_msgSend
mov x0, x26
bl imp___stubs__objc_release
mov x0, x22
bl imp___stubs__objc_release
ldr x0, [sp, #0x28]
bl imp___stubs__objc_release
ldr x0, [sp, #0x20]
bl imp___stubs__objc_release
mov x0, x21
bl imp___stubs__objc_release
mov x0, x20
bl imp___stubs__objc_release
mov x0, x19
bl imp___stubs__objc_release
ldp x29, x30, [sp, #0x70]
ldp x20, x19, [sp, #0x60]
ldp x22, x21, [sp, #0x50]
ldp x24, x23, [sp, #0x40]
ldp x26, x25, [sp, #0x30]
add sp, sp, #0x80
ret複製程式碼
解釋如下:
UIStoryboard *loginSb = [UIStoryboard storyboardWithName: @"Login" bundle: nil]
UIViewController *githubVC = [loginSb instantiateViewControllerWithIdentifier: @"githubVC"]
[githubVC setCallBack: black]
UINavigatinController *nav = [[UINavigatinController alloc] initWithRootViewControler: githubVC];
UIViewController *vc = [UIApplication sharedApplication].delegate.window.rootViewController;
[vc presentViewController: nav animaed: YES completion: nil];複製程式碼
- 初始化
UIStoryBoard
,並從中初始化控制器githubVC
- 給
githubVC
控制器設定回撥block(另一個callback引數回撥) - 顯示控制器
其實這裡的githubVC
應該就是XTGithubLoginViewController
型別,我們可以使用cycript
確認一下:
cy# var sb = [UIStoryboard storyboardWithName: @"Login" bundle: nil]
#"<UIStoryboard: 0x12e0ebf00>"
cy# [#0x12e0ebf00 instantiateViewControllerWithIdentifier: @"githubVC"]
#"<XTGithubLoginViewController: 0x12e382e00>"複製程式碼
現在callback
的傳遞應該很清楚了:點選 github 登入按鈕時,呼叫單例LoginCloud
,並傳入callback
,LoginCloud
根據傳入的callback
初始化登入控制器XTGithubLoginViewController
,並設定另外的一個callback
回撥,XTGithubLoginViewController
在登陸成功後,通過該callback
進行回撥。
分析callback實現
block有點特殊,它也是一個物件型別。在這裡我們涉及到了兩個block的傳遞,為了方便之後的分析,我們可以先將兩個block的實現地址和傳遞的引數型別都列印出來。接下來的操作中可能會因為程式多次重啟導致文中上下顯示的記憶體地址不一致的情況,我每次都會進行說明。
1.獲取block的函式地址
因為我們知道這個callback
作為引數傳給了-[LoginCloud githubLoginCallback:]
方法,所以lldb連線服務(如何lldb可以看我的第一篇文章)後,我們在這個方法上打個斷點,同理設定-[XTGithubLoginViewController setCallBack:]
斷點。
根據Block
的記憶體結構可以查詢block
的函式實現地址和引數返回值型別,具體可以看這篇文章。開源就是好,節省步驟,我們可以使用facebook
開源的chisel。觸發斷點後,獲取到block
的記憶體地址後,使用pblock
列印block:
獲取到第一個 block 的函式實現地址:0x0000000100194cc4
引數及返回值:void ^(bool, NSString *)
獲取到第二個 block 的函式實現地址:0x000000010015ccbc
引數及返回值:void ^(NSDictionary *, ThirdLoginModel *, NSDictionary *)
。
2.獲取block
的具體傳回引數值
知道了 block 的引數的型別,那麼我們可以寫個 tweaks
來 hook
這個 block 然後列印一下,傳遞的這些引數內容,根據回傳順序,我們先列印第二個block
:
%hook XTGithubLoginViewController
-(callBackType)callBack {
callBackType block = ^(NSDictionary *dic1, ThirdLoginModel *model, NSDictionary *dic2) {
HBLogInfo(@"dic1: %@, model: %@, dic2: %@", dic1, [model debugDescription], dic2);
};
return block;
}
%end // end hook複製程式碼
列印結果如下:
果然發現第二個字典中,出現了多個值為null
的value
,很有可能就是我們要找的點。那麼這個字典是從哪裡來的呢?想起當時列印XTGithubLoginViewController
類的呼叫順序的時候,發現的一個函式onAuthCompleted:
,它返回的就是一個這樣的字典,那麼我們hook
一下方法,過濾掉這些值為null
的value
看看:
-(NSDictionary *)onAuthCompleted:(id)arg1 {
HBLogInfo(@"%s", __func__);
NSDictionary *result = %orig;
NSMutableDictionary *dict = [result mutableCopy];
for (NSString *key in result.allKeys) {
if ([result[key] isKindOfClass:[NSNull class]]) {
HBLogInfo(@"%s key: %@", __func__, key);
[dict removeObjectForKey:key];
}
}
HBLogInfo(@"result: %@", dict);
return dict;
}複製程式碼
打包,安裝後發現,並沒有修復這個bug,還是報找不到length
方法的日誌。看來我們還沒有找到 bug 點。
3.獲得 block 的具體實現過程
在第二個回撥上下斷點,觸發後,進入實現內部,根據lldb列印出實現內部的每個方法的方法呼叫者和函式名、引數值。過程如下(主要是找到了這個block
的實現地址後,在Hopper中找到這個程式碼塊,可以發現對於這個 block 的內部方法,Hopper是沒有註釋呼叫者、selector、引數的,我們可以對Hopper中的這些程式碼中的所有顯示為objc_msgSend
的地方下一個斷點,一一觸發,分別列印每個方法的呼叫者和呼叫方法,傳遞引數等資訊),主要過程如下:
解釋後主要是以下過程:
// swift class
ZEHud *hud = [Xitu.ZEHud sharedInstance];
[hud showHud];
[AVUser loginWithAuthData: dict platform: @"github" block: block] // block Imp: 0x000000010015cdf8 Signature: void ^(AVUser *, NSError *);複製程式碼
可以看到,其中也有一個block
,列印一下block的內容,根據Xitu
image的偏移得到block實現的地址:0x100208df8-0x00000000000ac000=0x10015CDF8
,在Hopper中找到:
在這個block上下斷點,c
執行後來到該斷點出:si
進入該實現內部,斷點定位到如圖中的唯一一個objc_msgSend
方法上,獲取其呼叫資訊:
可以看到該方法-[LoginCloud thirdLogRefreshCurrentUserDatatype:ThirdData:Error:CallBack:]
中還有一個block
引數,繼續列印:
可以發現得到的block
:0x0000000100194cc4
,void ^(bool, NSString *)
。
和我們得到的第一個block
是同一個。
既然第二個block
引數還沒有分析完,第一個block
已經迫不及待的回來了,那麼我們先在Hopper中檢視一下這個block的實現:
分析到這裡ni
後app不小心退出了,那麼我們在這裡重新啟動,既然已經知道這個block
的地址,可以直接通過檢視Hopper
內的每個objc_msgSend
的地址下斷點:
可以知道該block的呼叫如下:
login = [LoginCloud singleClass];
[login refreshGithubLogin];
[XTLoginViewController callback];複製程式碼
既然分析到了這裡,程式還沒有崩潰,那麼說程式這之前都沒有問題,那麼問題可能出在了XTLoginViewController
這個callback
中,那麼我們列印一下這個block
:
得到:0x00000001001935d0
,void ^(bool, NSString *)
。
那麼我們繼續深入,列印一下這個block
的實現,步驟如上,如果不想每個objc_msgSend
都手動打斷點,可以在這個block
上打斷點,然後si
,進入後,一步一步向下執行,等到執行到objc_msgSend
的時候,列印這個方法的內容。但是在這裡下到的斷點都沒有執行程式就已經崩潰了!!!說明什麼?說明這個傳入的callback
還沒有執行,程式已經crash了。
- 從正向開發的角度,傳入這個
block
,可能是在程式滿足一定條件的時候才會回撥這個block
,來傳遞資訊或處理一些事情。 - 那麼程式應該是崩潰在這個
block
被執行前,我們需要檢視一下這個函式的內部實現。 - 可能後知後覺了,現在仔細想想,我前面說的分析到這個函式時,使用
ni
下一步後,程式不小心退出了!!,所以應該不是不小心退出了,而是這個函式的內部實現導致了崩潰。
確定bug點
分析到了這個方法-[LoginCloud thirdLogRefreshCurrentUserDatatype:ThirdData:Error:CallBack:]
,那麼我們去Hopper
中看看它的內部實現:
果然,如圖看到的,我們看到了之前在控制檯輸出的崩潰的關鍵詞length
,既然找到了這裡,我們利用這個呼叫函式的地址:0x10015d228
來下一個斷點,看看呼叫者是誰,是不是真的是null,從而導致的bug。
重新啟動,lldb下斷點:
如圖,呼叫者為null,並且ni
後,完美的崩潰了。文章寫了這麼多,終於找到這個bug點了!!!激動啊。
找到了bug點,那麼我們應該研究一下如何修復這個bug,首先我們需要知道為什麼開發者會用這個null,呼叫了length,程式發生了什麼導致了這個呼叫者為null
。
我們來仔細看一下,這個函式呼叫所在的程式碼塊的彙編程式碼:
loc_10015d1a8:
add x8, sp, #0xb0 ; CODE XREF=-[LoginCloud thirdLogRefreshCurrentUserDatatype:ThirdData:Error:CallBack:]+660
stp xzr, x8, [sp, #0xb0]
orr w8, wzr, #0x30
stp w28, w8, [sp, #0xc0]
adr x8, #0x10015d84c
nop
fmov d0, x8
adr x8, #0x10015d85c
nop
ins v0, x8
add x8, sp, #0xb0
stur q0, [x8, #0x18]
ldr x0, [x25, #0x618]
mov x24, x27
mov x1, x24
bl imp___stubs__objc_msgSend
mov x29, x29
bl imp___stubs__objc_retainAutoreleasedReturnValue
mov x23, x0
adrp x2, #0x10094b000 ; @"Z"
add x2, x2, #0xc60 ; @"self_description"
mov x1, x26
bl imp___stubs__objc_msgSend
mov x29, x29
bl imp___stubs__objc_retainAutoreleasedReturnValue
str x0, [sp, #0xd8]
mov x0, x23
bl imp___stubs__objc_release
ldr x8, [sp, #0xb8]
ldr x0, [x8, #0x28]
adrp x8, #0x100aa0000 ; @selector(computeTime:)
ldr x1, [x8, #0x340] ; "length",@selector(length)
bl imp___stubs__objc_msgSend
ldr x20, [sp, #0x18]
cmp x0, #0x24
b.lo loc_10015d24c複製程式碼
可以看到,ldr x0, [x8, #0x28]
。這個null是從[x8, #0x28]
載入的。但是我們在這個程式碼塊中(包括整個-[LoginCloud thirdLogRefreshCurrentUserDatatype:ThirdData:Error:CallBack:]
方法內)都沒有找到。這個情況就比較特殊了,自從學習逆向以來,還是第一次碰到這種情況。該呼叫者是從一個從來沒有被寫入過內容的暫存器中讀取的。應該也是因為這個原因才導致讀取的內容為null吧。
換個思路,我們結合上下文找線索。我們翻譯一下上下文方法呼叫的彙編程式碼:
AVUser *user = [AVUser currentUser];
NSString *name = [user valueForKey: @"username"];
NSString *headImg = [user ValueForKey: @"avatar_large"];
NSString *descrip = [user ValueForKey: @"self.description"];
//...複製程式碼
我們可以使用cycript
來列印一下這個user
是什麼:
然後順便把彙編中的內容,列印一下:
果然,其中有一個列印是null
。而且正好出現在length
呼叫的上方:
所以很有可能,這個bug是因為通過key
:self.description
從user
中獲取了一個沒有的值,並對他呼叫了length
導致的。
那麼我們通過theos
建立一個外掛,然後hook
一下這個方法,返回一個我們設定了值的AVUser
物件,
%hook AVUser
+(id)currentUser {
AVUser *user = %orig;
id descri = [user valueForKey: @"self_description"];
if ([descri isKindOfClass: [NSNull class]]) {
HBLogWarn(@"the value for key: self_description is null");
[user updateValue: @"" forKey: @"self_description"];
}
return user;
}
%end複製程式碼
安裝後,再次登陸發現登陸成功,並沒有crash
,終於修復了這個bug,發現已經寫了不少字了。
總結
最後的修復bug的tweaks
可以在這裡下載XituHook。
其實在我自己分析的時候,分析的內容還要多,也比較雜。逆向分析正是這樣,我們需要順著程式執行的順序分析,但是事情往往不如我們想的這樣簡單,分析的岔路很多,特別是這裡,涉及到了多個block
的回撥,需要對block
的實現原理及其記憶體結果比較熟悉才行。
在分析的過程中,分析到的還有一些文中沒有寫到的block
和方法,並且在其中發現了length
關鍵詞,激動不已啊,但是當我深入分析的時候發現,開發者都做了防護
if ([obj isKindOfClass:[NSNull class]]) {
obj = @"";
}複製程式碼
預防出現NSNull的情況,所以都不是 crash
的罪魁禍首。我猜測self_description
是獲取的 github 上的某個個人資訊(根據名字推測是自我介紹?)。如果想分析的話也可以,需要從AVUser
這個類如果獲得這些資訊開始分析。但是我想本文分享到這裡已經差不多了。
寫在最後
求工作,求工作,求工作,重要的事情說三遍。現在的iOS就業形勢,不提也罷,興趣所致,跪著走完。