1、強(qiáng)大的lldb
上文我們說(shuō)到了調(diào)試液荸。在iOS逆向中,很多人推薦debugserver + lldb 其實(shí)調(diào)試只需要lldb就夠了脱篙。
debugserver配置的文章有很多娇钱,從14年到18年不等伤柄,但大部分都過(guò)時(shí)了。所以我也沒(méi)用文搂。那 只用lldb該怎么斷點(diǎn)調(diào)試三方app呢适刀?我們先來(lái)簡(jiǎn)單看下lldb的斷點(diǎn)命令。
1.1 LLDB斷點(diǎn)命令:
1.1.1 設(shè)置斷點(diǎn) 下斷點(diǎn)命令我們只需要先會(huì)兩個(gè)就夠了:
a: 給指定內(nèi)存地址下斷點(diǎn): br set -a 0x00000000
全拼我記不住
b: 給某方法下斷點(diǎn):b -n "[ClassName methodName]"
全拼同樣記不住
關(guān)于a煤蹭,怎么定位方法內(nèi)存笔喉,一會(huì)再講。
關(guān)于b硝皂,很多app打App Store包的時(shí)候常挚,是去掉了符號(hào)表的編譯配置項(xiàng)的。所以稽物,我們暫時(shí)打不了三方app的方法斷點(diǎn)奄毡。不過(guò)不用急,后面我們會(huì)教大家如何還原三方app的符號(hào)表姨裸。
1.1.2 查看斷點(diǎn)
br list
這個(gè)全拼我能記籽砬恪:breakpoint list
1.1.3 刪除斷點(diǎn)
br del n
這里的n是上一步br list
列出的斷點(diǎn)的序號(hào)。根據(jù)對(duì)應(yīng)的序號(hào)刪除想刪的斷點(diǎn)傀缩。當(dāng)然你也可以直接br del
那先,然后lldb會(huì)問(wèn)你是否要?jiǎng)h除全部斷點(diǎn)。[Y/n] ?
輸入Y即可赡艰。
1.2 如何定位函數(shù)地址
好 我們先來(lái)看看如何找到想斷點(diǎn)的函數(shù)的地址售淡。為了降低被黑客攻擊的風(fēng)險(xiǎn),操作系統(tǒng)大都采用ASLR(地址空間布局隨機(jī)化) 技術(shù): 詳細(xì)解釋請(qǐng)看這 這個(gè)人的內(nèi)存管理講的不錯(cuò)慷垮,一共7篇揖闸,建議大家有時(shí)間看看。
ASLR是一種避免類(lèi)似攻擊的有效保護(hù)料身。進(jìn)程每一次啟動(dòng)時(shí)汤纸,地址空間都會(huì)被簡(jiǎn)單地隨機(jī)化:進(jìn)行整體的地址偏移,而不是攪亂芹血。通過(guò)內(nèi)核將整個(gè)進(jìn)程的內(nèi)存空間“平移”某個(gè)隨機(jī)數(shù)贮泞,進(jìn)程的基本內(nèi)存布局如程序文本、數(shù)據(jù)幔烛、庫(kù)等相對(duì)位置仍然是一樣的啃擦,但其具體的地址都不同了。
簡(jiǎn)單來(lái)說(shuō)饿悬,ASLR會(huì)在進(jìn)程啟動(dòng)時(shí)候隨機(jī)一個(gè)基地址令蛉。
在lldb中的查看命令是image list
或image list -o -f
。
如過(guò)命令后面不加參數(shù)狡恬,它打印的就是每個(gè)image(鏡像)的虛擬內(nèi)存地址珠叔。
如果加了-o參數(shù)蝎宇,打印的就是image的偏移量(相對(duì)于誰(shuí),暫時(shí)還沒(méi)研究)运杭。
具體區(qū)別如下:
image list
打印的基地址: 0x102a94000
image list -o -f
的地址: 0x002a94000
看出區(qū)別了么夫啊?第9位差了一個(gè)1。
這個(gè)1正好跟IDA工具中的0x 1 0000 0000對(duì)上了辆憔。
原因終于找到了,在:楊瀟玉大哥的這篇文章报嵌。main()調(diào)用之前會(huì)調(diào)用exec()
虱咧,exec()
是一個(gè)系統(tǒng)調(diào)用。系統(tǒng)內(nèi)核把應(yīng)用映射到新的地址空間锚国,且每次起始位置都是隨機(jī)的(因?yàn)槭褂?ASLR)腕巡。并將起始位置到 0x000000
這段范圍的進(jìn)程權(quán)限都標(biāo)記為不可讀寫(xiě)不可執(zhí)行。如果是 32 位進(jìn)程血筑,這個(gè)范圍至少是 4KB绘沉;對(duì)于 64 位進(jìn)程則至少是4GB。NULL 指針引用和指針截?cái)嗾`差都是會(huì)被它捕獲豺总。4GB = 232,用二進(jìn)制表示就是:第32+1位為1车伞,剩下的32位為0。而4位二進(jìn)制位可以表示一個(gè)十六進(jìn)制位喻喳,所以十六進(jìn)制表示就是:0x 1 0000 0000另玖。這樣就滿(mǎn)足了64位進(jìn)程至少4GB的最小偏移范圍了。
這里我們提到的地址是虛擬地址表伦,為什么不是物理地址谦去?
原因是:操作系統(tǒng)將我們(程序猿)跟物理內(nèi)存劃分了一個(gè)安全界限,我們程序猿只需要用虛擬內(nèi)存跟操作系統(tǒng)打交道即可蹦哼,操作系統(tǒng)調(diào)用底層硬件api鳄哭,跟物理內(nèi)存打交道。如果程序猿直接跟物理內(nèi)存交流纲熏,恐怕不是那么安全的妆丘。程序一旦出了bug,可能會(huì)導(dǎo)致系統(tǒng)癱瘓赤套。
基地址有了飘痛,在前面ASLR技術(shù)中我們提到內(nèi)核將整個(gè)進(jìn)程的內(nèi)存“平移”,所以數(shù)據(jù)段容握、代碼段等等內(nèi)存的偏移量其實(shí)是固定的宣脉,他們相對(duì)于mach-o header部分的偏移量是個(gè)常亮,那既然如此剔氏,我們的IDA工具又派上用場(chǎng)了:
這里列出的偏移地址塑猖,就是函數(shù)相對(duì)于mach-o的起始地址的偏移量竹祷,所以:
虛擬地址 = 基地址 + 偏移地址。
那么羊苟,我們就可以在lldb用
br set -a 0x102a94000
下斷點(diǎn)了塑陵。在這里在交給大家兩個(gè)好用的命令:
讀內(nèi)存命令:
x 0x102a94000
全拼是:memory read 0x102a94000
反匯編命令:
dis -s 0x102a94000
dis是dissamble的簡(jiǎn)寫(xiě)。
我們調(diào)試別人的app蜡励,是看不到源碼的令花,只能看匯編。所以這里建議大家學(xué)一下匯編凉倚。我知道很多人聽(tīng)到匯編就頭大兼都,不過(guò)不要緊,找對(duì)了教材稽寒,匯編真的可以通俗易懂扮碧。比如這本:《匯編語(yǔ)言第3版》王爽著。學(xué)習(xí)時(shí)長(zhǎng)兩三天就夠杏糙。不信你試試慎王。
我是從網(wǎng)上下的影印版PDF,閱讀效果不太好宏侍,大家可以買(mǎi)正版書(shū)或者正版PDF來(lái)學(xué)習(xí)赖淤。
學(xué)完了匯編,CPU工作原理你就基本了解了负芋,再跟內(nèi)存交流起來(lái)就方便多了漫蛔。不過(guò)你可能還是看不懂xcode中出現(xiàn)的上古語(yǔ)言。因?yàn)閮烧叩膮R編指令集不一樣旧蛾,書(shū)里的CPU是古老的8086莽龟,是地址總線(xiàn)20位,數(shù)據(jù)總線(xiàn)16位的16位機(jī)器锨天。而iPhone 5s之后都是arm64 CPU毯盈,命令不太一樣,總線(xiàn)位數(shù)也不一樣病袄,不過(guò)如果你理解了CPU的工作原理搂赋,arm64其實(shí)只是換了一種語(yǔ)法而已。而且CPU尋址操作也變得更簡(jiǎn)單益缠。畢竟arm64數(shù)據(jù)總線(xiàn)跟地址總線(xiàn)位數(shù)相同(皆為64位)脑奠,CPU不再需要地址加法器計(jì)算地址了。
這些都掌握了之后幅慌,我們可以看一下runtime源碼宋欺,重點(diǎn)看一下objc/message部分。
目前蘋(píng)果官方最新的是objc4-762版本,為了方便調(diào)試齿诞,我從github上下載了objc4-750版本酸休,就差一個(gè)版本,不影響我們理解原理祷杈。
runtime 非官方Git地址
可以結(jié)合這篇文章進(jìn)行理解斑司。作者梳理的很好,從objc_msgSend的匯編代碼入口開(kāi)始但汞,梳理到最終的runtime消息轉(zhuǎn)發(fā)機(jī)制宿刮。
有了這些基礎(chǔ),下面我們?cè)龠M(jìn)行斷點(diǎn)調(diào)試的時(shí)候特占,就會(huì)方便很多糙置。
比如說(shuō),我們給沒(méi)有隱藏符號(hào)表的app下斷點(diǎn):
未隱藏符號(hào)表的程序是目,斷點(diǎn)的時(shí)候,函數(shù)名也會(huì)顯示出來(lái)标捺。
接下來(lái)我們將用到一個(gè)進(jìn)階命令:register read
查看寄存器信息:
前面如果你研究了runtime的objc_msgSend函數(shù)懊纳,你就會(huì)知道,該函數(shù)有兩個(gè)默認(rèn)參數(shù)
(receiver, cmd)
亡容,第一個(gè)參數(shù)是消息接收者嗤疯,第二個(gè)是函數(shù)地址。在iOS arm64 CPU中闺兢,通用寄存器中的x0~x7
寄存器 用于參數(shù)傳遞茂缚。所已x0
寄存器的值就是我們?cè)摵瘮?shù)的第一個(gè)參數(shù):消息接收者,也就是DJHomeViewController類(lèi)型的一個(gè)對(duì)象屋谭。結(jié)合po命令脚囊,我們就可以查看很多東西了。第二個(gè)參數(shù)是函數(shù)地址桐磁,對(duì)應(yīng)
x1
寄存器悔耘。objc_msgSend中第三個(gè)參數(shù)(
x2
寄存器),就是viewDidAppear:后面的傳參了我擂,我們看到這個(gè)值是0. 回頭看看class-dump
衬以,可以看到這個(gè)參數(shù)要求傳一個(gè)布爾值,那么 我們就可以猜出該函數(shù)入?yún)⑹莊alse校摩。有了對(duì)象和參數(shù)地址看峻,你就擁有了一切。
比如衙吩,我們通過(guò)class-dump 看到該類(lèi)有這么一個(gè)屬性互妓,那我們就可以直接訪問(wèn)!
我們可以用
p
命令 輸出一下對(duì)象,該功能有點(diǎn)類(lèi)似于expression
命令车猬。此時(shí)會(huì)生成一個(gè)
$
符號(hào)開(kāi)頭的變量霉猛,這個(gè)變量可以在后續(xù)lldb中使用。那么接下來(lái)我們就可以利用kvc進(jìn)行訪問(wèn)任何內(nèi)容了珠闰。也可以直接像寫(xiě)OC代碼一樣惜浅,在lldb中寫(xiě)方法調(diào)用。例如:
po [$10 class]
;lldb常用命令可以看這里伏嗜、或這里坛悉。網(wǎng)上有很多這類(lèi)文章,大家可以自行查找承绸。
到這里裸影,我們可以看到,假如調(diào)試的時(shí)候能看到函數(shù)名军熏,那我們逆向就沒(méi)有任何阻礙轩猩。所以,符號(hào)表是我們的必爭(zhēng)之地荡澎!
如何還原符號(hào)表均践,請(qǐng)看下集:iOS逆向資料(四):還原符號(hào)表,再無(wú)障礙摩幔。