逆向分析及修復(fù)稀土掘金iOS版客戶端閃退bug

感覺是要搞個(gè)《逆向分析及修復(fù)app》系列的節(jié)奏啊

故事概要

大家好妇穴,我又來了。上次給大家分享了《逆向及修復(fù)最新iOS版少數(shù)派客戶端的閃退bug》,@了一些iOS界的大神材原,沒想到受到了大家的轉(zhuǎn)發(fā)和關(guān)注练俐,還真有點(diǎn)小激動(dòng)袖迎。同時(shí)也在微博的評(píng)論下收到了稀土掘金的歡迎,還給我開了專欄權(quán)限腺晾,希望可以到稀土掘金上分享寫作燕锥。備感榮幸的同時(shí),突然發(fā)現(xiàn)悯蝉,一直在稀土掘金上面看文章的我归形,一直沒有注冊(cè)賬戶(好像注冊(cè)了但是忘了密碼,在1Password中也沒有記錄鼻由,看來是很久之前登陸過了)暇榴。這也是這次故事的開始,收到稀土掘金的評(píng)論后蕉世,馬上打開了早已不知道什么時(shí)候下載的 掘金 app蔼紧。果然,沒有登陸記錄讨彼,作為開發(fā)者馬上用 GitHub 授權(quán)進(jìn)行登陸歉井,尷尬的事情發(fā)生了,在授權(quán)成功后哈误,app閃退了哩至。好尷尬啊,好吧稀土掘金上的處女座就獻(xiàn)給你了蜜自。

跟之前寫《逆向及修復(fù)最新iOS版少數(shù)派客戶端的閃退bug》文章時(shí)不同菩貌,在文章寫到這里的時(shí)候,我其實(shí)還沒有完全分析完崩潰的原因重荠。我是分析途中決定開始寫這篇文章的箭阶,決定一邊分析一遍寫,這樣才會(huì)盡可能的保留分析的全過程戈鲁,當(dāng)然最后能不能分析到關(guān)鍵點(diǎn)仇参,且看下去吧,因?yàn)槲乙膊恢馈?/p>

問題描述

  • 最新版 4.1.3 稀土掘金 app 在使用 github 登陸成功后會(huì) crash
  • 是在登陸成功后才 crash婆殿,因?yàn)樵俅芜M(jìn)入后會(huì)發(fā)現(xiàn)已經(jīng)登陸成功
  • 可能不是所有人都會(huì)遇到這個(gè)bug诈乒,根據(jù)后來的分析,可能是Github的某個(gè)個(gè)人信息中缺少某些設(shè)置導(dǎo)致的授權(quán)后crash

著手分析

找到崩潰原因

分析一個(gè)bug的開始婆芦,當(dāng)然是從崩潰原因找起怕磨,前一篇文章喂饥,我們使用了lldb調(diào)試,在觸發(fā) app 崩潰的時(shí)候肠鲫,從 Terminal 找到了關(guān)鍵詞:EXE_BAD_ACCESS员帮。這次,我們使用最簡(jiǎn)單的导饲,查看系統(tǒng)日志來定位崩潰原因捞高,使用最新的 Mac,打開控制臺(tái)帜消,連接手機(jī)并清空多余的系統(tǒng)日志打印棠枉,然后觸發(fā)崩潰,觀察控制臺(tái)中輸出的內(nèi)容(如果輸出日志過多泡挺,可以使用進(jìn)程名Xitu作為關(guān)鍵詞,進(jìn)行過濾)命浴。如果可以還是不要使用關(guān)鍵詞過濾娄猫,因?yàn)椴皇撬械谋罎⒍际怯捎?app 本身的問題導(dǎo)致的,正如簽名破壞的 app 在沒有越獄的手機(jī)上會(huì)安裝失敗生闲,這個(gè)安裝失敗的原因查找就需要在日志中的 SpringBoard 進(jìn)程打印中進(jìn)行查找
SpringBoardApple用來管理我們iPhone應(yīng)用的一個(gè)應(yīng)用媳溺,可以理解為我們 PC 端的桌面,它還負(fù)責(zé)應(yīng)用的桌面排列碍讯、管理通知中心等)悬蔽。

正如我們希望的一樣,崩潰的原因最常見不過了:


這個(gè)崩潰就很有意思了捉兴,開發(fā)者對(duì)NSNull調(diào)用了length方法蝎困,因?yàn)檎也坏皆摲椒ǘ罎ⅲ‘?dāng)然開發(fā)者肯定是不會(huì)主動(dòng)對(duì)NSNull類型調(diào)用length方法倍啥,猜想:

  • 開發(fā)不知道對(duì)象會(huì)變成NSNull類型禾乘,說明這個(gè)對(duì)象可能是在運(yùn)行時(shí)確定的
  • 調(diào)用length,說明開發(fā)者認(rèn)為這個(gè)類應(yīng)該是屬于NSString類型的

大膽猜測(cè):結(jié)合這個(gè)崩潰是發(fā)生在登陸的時(shí)候虽缕,需要從網(wǎng)絡(luò)獲取登陸信息始藕,那么會(huì)不會(huì)是程序在對(duì)獲取到的登陸信息進(jìn)行處理的時(shí)候?qū)е碌谋罎ⅲū热绲顷懹脩舻囊恍┬畔]有設(shè)置,所以程序從登陸信息里取到的是空值)氮趋。

分析入口

1.找到登陸界面的控制類


分析從該登陸界面出發(fā)伍派,使用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() 來自我自己寫的一個(gè)腳本剩胁,你可以前往common.cy下載诉植。下載之后,將其放到/var/root目錄下即可摧冀。
使用時(shí)倍踪,只需要在cycript注入的時(shí)候加上即可系宫。
其中還有一些好用的函數(shù),比如快速打印UIControl所有的targetaction

2.查看登陸界面的調(diào)用流程

使用class-dump等工具建车,導(dǎo)出app的所有類頭文件扩借,使用logify.pl工具生成XTGithubLoginViewController類的所有hook函數(shù),使用theos編寫安裝插件后缤至,觸發(fā)崩潰潮罪,然后在控制臺(tái)查找該類的函數(shù)調(diào)用邏輯。如下:

可以發(fā)現(xiàn)领斥,程序是在-[XTGithubLoginViewController viewWillDisappear:]調(diào)用之后才收到的崩潰的通知嫉到。所以很有可能崩潰的原因不是XTGithubLoginViewController導(dǎo)致的,而是在XTGithubLoginViewController類返回登陸信息后月洛。

仔細(xì)研究XTGithubLoginViewController的調(diào)用過程何恶,發(fā)現(xiàn)了幾個(gè)有意思的方法,調(diào)用順序如下:

  • -(void)setCallBack:
  • -(id)onAuthCompleted:
  • -(id)callBack

callback嚼黔!太熟悉不過了细层,這不就是我們經(jīng)常在開發(fā)中使用的設(shè)置回調(diào)block的時(shí)候使用的參數(shù)名嗎!唬涧!并且疫赎,該callback在類初始化的時(shí)候被設(shè)置,類方法-(id)onAuthCompleted:調(diào)用之后才被回調(diào)碎节。結(jié)合這個(gè)方法名onAuthCompleted捧搞,這個(gè)邏輯是不是很像:我們?cè)诘顷懗晒χ螅褂胋lock傳回了我們的登陸信息進(jìn)行處理狮荔。

分析到這里胎撇,其實(shí)我們已經(jīng)可以使用lldb調(diào)試,打印該block的內(nèi)存地址轴合,然后去掉內(nèi)存地址偏移后创坞,在Hopper中查看block的實(shí)現(xiàn),看是否該block導(dǎo)致的崩潰受葛。但是作為逆向的學(xué)習(xí)题涨,我們可以先繼續(xù)深入,查看一下這個(gè)block的傳遞調(diào)用過程总滩。

繼續(xù)深入callback的回調(diào)過程

既然該回調(diào)是在XTGithubLoginViewController被初始化的時(shí)候被設(shè)置的纲堵,那么我們先找到是誰初始化的該Github登陸控制器:

使用cycript注入打印該控制器類名,并嘗試github的授權(quán)登錄入口:

susnms-iPhone:~ root# cycript -p Xitu common.cy ; cycript -p Xitu
cy# currentVCWithKeyWindow ()
#"<XTLoginViewController: 0x12e43f740>"
cy# [#0x12e43f740 github
githubBt      githubLogin:
cy# [#0x12e43f740 githubLogin: nil]

發(fā)現(xiàn)成功觸發(fā)登錄闰渔,所以可以確定githubLogin:方法確實(shí)是登錄的入口

1.githubLogin:實(shí)現(xiàn)

Hopper中查看該方法的實(shí)現(xiàn):

-[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

內(nèi)容很簡(jiǎn)單席函,調(diào)用了單例類,并傳入回調(diào)callback參數(shù):

LoginCloud *login = [LoginCloud singleClass];
[login githubLoginCallBack: callback];

2.[LoginCloud singleClass]實(shí)現(xiàn)

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控制器設(shè)置回調(diào)block(另一個(gè)callback參數(shù)回調(diào))
  • 顯示控制器

其實(shí)這里的githubVC應(yīng)該就是XTGithubLoginViewController類型茂附,我們可以使用cycript確認(rèn)一下:

cy# var sb = [UIStoryboard storyboardWithName: @"Login" bundle: nil]
#"<UIStoryboard: 0x12e0ebf00>"
cy# [#0x12e0ebf00 instantiateViewControllerWithIdentifier: @"githubVC"]
#"<XTGithubLoginViewController: 0x12e382e00>"

現(xiàn)在callback的傳遞應(yīng)該很清楚了:點(diǎn)擊 github 登錄按鈕時(shí)正蛙,調(diào)用單例LoginCloud,并傳入callback营曼,LoginCloud根據(jù)傳入的callback初始化登錄控制器XTGithubLoginViewController乒验,并設(shè)置另外的一個(gè)callback回調(diào),XTGithubLoginViewController在登陸成功后蒂阱,通過該callback進(jìn)行回調(diào)锻全。

分析callback實(shí)現(xiàn)

block有點(diǎn)特殊,它也是一個(gè)對(duì)象類型录煤。在這里我們涉及到了兩個(gè)block的傳遞鳄厌,為了方便之后的分析,我們可以先將兩個(gè)block的實(shí)現(xiàn)地址和傳遞的參數(shù)類型都打印出來妈踊。接下來的操作中可能會(huì)因?yàn)槌绦蚨啻沃貑?dǎo)致文中上下顯示的內(nèi)存地址不一致的情況了嚎,我每次都會(huì)進(jìn)行說明。

1.獲取block的函數(shù)地址

因?yàn)槲覀冎肋@個(gè)callback作為參數(shù)傳給了-[LoginCloud githubLoginCallback:]方法响委,所以lldb連接服務(wù)(如何lldb可以看我的第一篇文章)后新思,我們?cè)谶@個(gè)方法上打個(gè)斷點(diǎn),同理設(shè)置-[XTGithubLoginViewController setCallBack:]斷點(diǎn)赘风。
根據(jù)Block的內(nèi)存結(jié)構(gòu)可以查找block的函數(shù)實(shí)現(xiàn)地址和參數(shù)返回值類型,具體可以看這篇文章纵刘。開源就是好邀窃,節(jié)省步驟,我們可以使用facebook開源的chisel假哎。觸發(fā)斷點(diǎn)后瞬捕,獲取到block的內(nèi)存地址后,使用pblock打印block:

獲取到第一個(gè) block 的函數(shù)實(shí)現(xiàn)地址:0x0000000100194cc4
參數(shù)及返回值:void ^(bool, NSString *)

獲取到第二個(gè) block 的函數(shù)實(shí)現(xiàn)地址:0x000000010015ccbc
參數(shù)及返回值:void ^(NSDictionary *, ThirdLoginModel *, NSDictionary *)舵抹。

2.獲取block的具體傳回參數(shù)值

知道了 block 的參數(shù)的類型肪虎,那么我們可以寫個(gè) tweakshook 這個(gè) block 然后打印一下,傳遞的這些參數(shù)內(nèi)容惧蛹,根據(jù)回傳順序扇救,我們先打印第二個(gè)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

打印結(jié)果如下:


果然發(fā)現(xiàn)第二個(gè)字典中,出現(xiàn)了多個(gè)值為nullvalue香嗓,很有可能就是我們要找的點(diǎn)迅腔。那么這個(gè)字典是從哪里來的呢?想起當(dāng)時(shí)打印XTGithubLoginViewController類的調(diào)用順序的時(shí)候靠娱,發(fā)現(xiàn)的一個(gè)函數(shù)onAuthCompleted:沧烈,它返回的就是一個(gè)這樣的字典,那么我們hook一下方法像云,過濾掉這些值為nullvalue看看:

-(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;
}

打包锌雀,安裝后發(fā)現(xiàn)蚂夕,并沒有修復(fù)這個(gè)bug,還是報(bào)找不到length方法的日志腋逆⌒鲭梗看來我們還沒有找到 bug 點(diǎn)。

3.獲得 block 的具體實(shí)現(xiàn)過程

在第二個(gè)回調(diào)上下斷點(diǎn)闲礼,觸發(fā)后牍汹,進(jìn)入實(shí)現(xiàn)內(nèi)部,根據(jù)lldb打印出實(shí)現(xiàn)內(nèi)部的每個(gè)方法的方法調(diào)用者和函數(shù)名柬泽、參數(shù)值慎菲。過程如下(主要是找到了這個(gè)block的實(shí)現(xiàn)地址后,在Hopper中找到這個(gè)代碼塊锨并,可以發(fā)現(xiàn)對(duì)于這個(gè) block 的內(nèi)部方法露该,Hopper是沒有注釋調(diào)用者、selector第煮、參數(shù)的解幼,我們可以對(duì)Hopper中的這些代碼中的所有顯示為objc_msgSend的地方下一個(gè)斷點(diǎn),一一觸發(fā)包警,分別打印每個(gè)方法的調(diào)用者和調(diào)用方法撵摆,傳遞參數(shù)等信息),主要過程如下:

解釋后主要是以下過程:

// swift class
ZEHud *hud = [Xitu.ZEHud sharedInstance];
[hud showHud];
[AVUser loginWithAuthData:  dict platform: @"github" block: block] // block Imp: 0x000000010015cdf8    Signature: void ^(AVUser *, NSError *);

可以看到害晦,其中也有一個(gè)block特铝,打印一下block的內(nèi)容,根據(jù)Xituimage的偏移得到block實(shí)現(xiàn)的地址:0x100208df8-0x00000000000ac000=0x10015CDF8壹瘟,在Hopper中找到:

在這個(gè)block上下斷點(diǎn)鲫剿,c運(yùn)行后來到該斷點(diǎn)出:si進(jìn)入該實(shí)現(xiàn)內(nèi)部勘畔,斷點(diǎn)定位到如圖中的唯一一個(gè)objc_msgSend方法上炒嘲,獲取其調(diào)用信息:

可以看到該方法-[LoginCloud thirdLogRefreshCurrentUserDatatype:ThirdData:Error:CallBack:]中還有一個(gè)block參數(shù)稽鞭,繼續(xù)打影妓琛:

可以發(fā)現(xiàn)得到的block:
0x0000000100194cc4,void ^(bool, NSString *)吟策。
和我們得到的第一個(gè)block是同一個(gè)核行。

既然第二個(gè)block參數(shù)還沒有分析完精算,第一個(gè)block已經(jīng)迫不及待的回來了伯病,那么我們先在Hopper中查看一下這個(gè)block的實(shí)現(xiàn):

分析到這里ni后app不小心退出了粱挡,那么我們?cè)谶@里重新啟動(dòng)赠幕,既然已經(jīng)知道這個(gè)block的地址,可以直接通過查看Hopper內(nèi)的每個(gè)objc_msgSend的地址下斷點(diǎn):


可以知道該block的調(diào)用如下:

login = [LoginCloud singleClass];
[login refreshGithubLogin];
[XTLoginViewController callback];

既然分析到了這里询筏,程序還沒有崩潰榕堰,那么說程序這之前都沒有問題,那么問題可能出在了XTLoginViewController這個(gè)callback中,那么我們打印一下這個(gè)block

得到:0x00000001001935d0,void ^(bool, NSString *)逆屡。

那么我們繼續(xù)深入圾旨,打印一下這個(gè)block的實(shí)現(xiàn),步驟如上魏蔗,如果不想每個(gè)objc_msgSend都手動(dòng)打斷點(diǎn)砍的,可以在這個(gè)block上打斷點(diǎn),然后si莺治,進(jìn)入后廓鞠,一步一步向下執(zhí)行,等到運(yùn)行到objc_msgSend的時(shí)候谣旁,打印這個(gè)方法的內(nèi)容床佳。但是在這里下到的斷點(diǎn)都沒有執(zhí)行程序就已經(jīng)崩潰了!i蟆砌们!說明什么?說明這個(gè)傳入的callback還沒有執(zhí)行搁进,程序已經(jīng)crash了浪感。

  • 從正向開發(fā)的角度,傳入這個(gè)block饼问,可能是在程序滿足一定條件的時(shí)候才會(huì)回調(diào)這個(gè)block影兽,來傳遞信息或處理一些事情。
  • 那么程序應(yīng)該是崩潰在這個(gè)block被執(zhí)行前莱革,我們需要查看一下這個(gè)函數(shù)的內(nèi)部實(shí)現(xiàn)赢笨。
  • 可能后知后覺了,現(xiàn)在仔細(xì)想想驮吱,我前面說的分析到這個(gè)函數(shù)時(shí),使用ni下一步后萧吠,程序不小心退出了W蠖!纸型,所以應(yīng)該不是不小心退出了拇砰,而是這個(gè)函數(shù)的內(nèi)部實(shí)現(xiàn)導(dǎo)致了崩潰。

確定bug點(diǎn)

分析到了這個(gè)方法-[LoginCloud thirdLogRefreshCurrentUserDatatype:ThirdData:Error:CallBack:]狰腌,那么我們?nèi)?code>Hopper中看看它的內(nèi)部實(shí)現(xiàn):

果然除破,如圖看到的,我們看到了之前在控制臺(tái)輸出的崩潰的關(guān)鍵詞length琼腔,既然找到了這里瑰枫,我們利用這個(gè)調(diào)用函數(shù)的地址:0x10015d228來下一個(gè)斷點(diǎn),看看調(diào)用者是誰,是不是真的是null光坝,從而導(dǎo)致的bug尸诽。

重新啟動(dòng),lldb下斷點(diǎn):

如圖盯另,調(diào)用者為null性含,并且ni后,完美的崩潰了鸳惯。文章寫了這么多商蕴,終于找到這個(gè)bug點(diǎn)了!Vシⅰ绪商!激動(dòng)啊。

找到了bug點(diǎn)后德,那么我們應(yīng)該研究一下如何修復(fù)這個(gè)bug部宿,首先我們需要知道為什么開發(fā)者會(huì)用這個(gè)null,調(diào)用了length瓢湃,程序發(fā)生了什么導(dǎo)致了這個(gè)調(diào)用者為null理张。

我們來仔細(xì)看一下,這個(gè)函數(shù)調(diào)用所在的代碼塊的匯編代碼:

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]雾叭。這個(gè)null是從[x8, #0x28]加載的。但是我們?cè)谶@個(gè)代碼塊中(包括整個(gè)-[LoginCloud thirdLogRefreshCurrentUserDatatype:ThirdData:Error:CallBack:]方法內(nèi))都沒有找到落蝙。這個(gè)情況就比較特殊了织狐,自從學(xué)習(xí)逆向以來,還是第一次碰到這種情況筏勒。該調(diào)用者是從一個(gè)從來沒有被寫入過內(nèi)容的寄存器中讀取的移迫。應(yīng)該也是因?yàn)檫@個(gè)原因才導(dǎo)致讀取的內(nèi)容為null吧。

換個(gè)思路管行,我們結(jié)合上下文找線索厨埋。我們翻譯一下上下文方法調(diào)用的匯編代碼:

AVUser *user = [AVUser currentUser];
NSString *name = [user valueForKey: @"username"];
NSString *headImg = [user ValueForKey: @"avatar_large"];
NSString *descrip = [user ValueForKey: @"self.description"];
//...

我們可以使用cycript來打印一下這個(gè)user是什么:

然后順便把匯編中的內(nèi)容,打印一下:

果然捐顷,其中有一個(gè)打印是null荡陷。而且正好出現(xiàn)在length調(diào)用的上方:

所以很有可能,這個(gè)bug是因?yàn)橥ㄟ^keyself.descriptionuser中獲取了一個(gè)沒有的值迅涮,并對(duì)他調(diào)用了length導(dǎo)致的废赞。

那么我們通過theos創(chuàng)建一個(gè)插件,然后hook一下這個(gè)方法叮姑,返回一個(gè)我們?cè)O(shè)置了值的AVUser對(duì)象唉地,

%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

安裝后,再次登陸發(fā)現(xiàn)登陸成功,并沒有crash渣蜗,終于修復(fù)了這個(gè)bug屠尊,發(fā)現(xiàn)已經(jīng)寫了不少字了。

總結(jié)

最后的修復(fù)bug的tweaks可以在這里下載XituHook耕拷。

其實(shí)在我自己分析的時(shí)候讼昆,分析的內(nèi)容還要多,也比較雜骚烧。逆向分析正是這樣浸赫,我們需要順著程序執(zhí)行的順序分析,但是事情往往不如我們想的這樣簡(jiǎn)單赃绊,分析的岔路很多既峡,特別是這里,涉及到了多個(gè)block的回調(diào)碧查,需要對(duì)block的實(shí)現(xiàn)原理及其內(nèi)存結(jié)果比較熟悉才行运敢。

在分析的過程中,分析到的還有一些文中沒有寫到的block和方法忠售,并且在其中發(fā)現(xiàn)了length關(guān)鍵詞传惠,激動(dòng)不已啊,但是當(dāng)我深入分析的時(shí)候發(fā)現(xiàn)稻扬,開發(fā)者都做了防護(hù)

if ([obj isKindOfClass:[NSNull class]]) {
    obj = @"";
}

預(yù)防出現(xiàn)NSNull的情況卦方,所以都不是 crash 的罪魁禍?zhǔn)住N也聹y(cè)self_description是獲取的 github 上的某個(gè)個(gè)人信息(根據(jù)名字推測(cè)是自我介紹泰佳?)盼砍。如果想分析的話也可以,需要從AVUser這個(gè)類如果獲得這些信息開始分析逝她。但是我想本文分享到這里已經(jīng)差不多了浇坐。

寫在最后

求工作,求工作黔宛,求工作吗跋,重要的事情說三遍。現(xiàn)在的iOS就業(yè)形勢(shì)宁昭,不提也罷,興趣所致酗宋,跪著走完积仗。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蜕猫,隨后出現(xiàn)的幾起案子寂曹,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隆圆,死亡現(xiàn)場(chǎng)離奇詭異漱挚,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)渺氧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門旨涝,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人侣背,你說我怎么就攤上這事白华。” “怎么了贩耐?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵弧腥,是天一觀的道長。 經(jīng)常有香客問我潮太,道長管搪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任铡买,我火速辦了婚禮更鲁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘寻狂。我一直安慰自己岁经,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布蛇券。 她就那樣靜靜地躺著缀壤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪纠亚。 梳的紋絲不亂的頭發(fā)上塘慕,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音蒂胞,去河邊找鬼图呢。 笑死,一個(gè)胖子當(dāng)著我的面吹牛骗随,可吹牛的內(nèi)容都是我干的蛤织。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鸿染,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼指蚜!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起涨椒,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤摊鸡,失蹤者是張志新(化名)和其女友劉穎绽媒,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體免猾,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡是辕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了猎提。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片获三。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖忧侧,靈堂內(nèi)的尸體忽然破棺而出石窑,到底是詐尸還是另有隱情,我是刑警寧澤蚓炬,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布松逊,位于F島的核電站,受9級(jí)特大地震影響肯夏,放射性物質(zhì)發(fā)生泄漏经宏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一驯击、第九天 我趴在偏房一處隱蔽的房頂上張望烁兰。 院中可真熱鬧,春花似錦徊都、人聲如沸沪斟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽主之。三九已至,卻和暖如春李根,著一層夾襖步出監(jiān)牢的瞬間槽奕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國打工房轿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留粤攒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓囱持,卻偏偏與公主長得像夯接,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子纷妆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容