感覺是要搞個(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)行查找
(SpringBoard
是Apple
用來管理我們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
所有的target
和action
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è) tweaks
來 hook
這個(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è)值為null
的value
香嗓,很有可能就是我們要找的點(diǎn)迅腔。那么這個(gè)字典是從哪里來的呢?想起當(dāng)時(shí)打印XTGithubLoginViewController
類的調(diào)用順序的時(shí)候靠娱,發(fā)現(xiàn)的一個(gè)函數(shù)onAuthCompleted:
沧烈,它返回的就是一個(gè)這樣的字典,那么我們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;
}
打包锌雀,安裝后發(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ù)Xitu
image的偏移得到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)橥ㄟ^key
:self.description
從user
中獲取了一個(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ì)宁昭,不提也罷,興趣所致酗宋,跪著走完积仗。