iOS逆向-從匯編代碼理解函數(shù)調(diào)用棧

程序的棧空間有什么特點(diǎn)呢?首先會(huì)想到的就是师坎,棧空間是往低地址增長的臂拓,當(dāng)調(diào)用一個(gè)函數(shù)時(shí),先開辟棧空間,用來存放當(dāng)前函數(shù)的參數(shù)和局部變量敲茄;執(zhí)行函數(shù)之前還需要先保護(hù)現(xiàn)場,當(dāng)函數(shù)執(zhí)行完之后會(huì)恢復(fù)現(xiàn)場搁宾。那么函數(shù)調(diào)用的過程中內(nèi)存和CPU的寄存器到底發(fā)生了什么變化呢折汞?這是本篇要探討的問題。

先看一段C代碼盖腿,定義了三個(gè)函數(shù),調(diào)用關(guān)系為:funcA調(diào)用funcB, funcB調(diào)用funcC损同。

int funcA(int a, int b) {
    int ret = funcB(a, b);
    return ret;
}

int funcB(int a, int b) {
    return funcC(a, b);
}

int funcC(int a, int b) {
    int c = a + b;
    return c;
}

從函數(shù)是否還調(diào)用了其他函數(shù)的角度看翩腐,函數(shù)可分為葉子函數(shù)非葉子函數(shù)
葉子函數(shù):函數(shù)內(nèi)部沒有調(diào)用其他函數(shù)了膏燃,例如上面的funcC
非葉子函數(shù):函數(shù)內(nèi)部還調(diào)用了其他的函數(shù)茂卦,例如上面的funcA、funcB

為什么要這么劃分呢组哩?因?yàn)?strong>葉子函數(shù)與非葉子函數(shù)生成的匯編代碼有所不同等龙,這也是本篇要分析的一個(gè)點(diǎn)。

獲取匯編代碼


得到對應(yīng)的匯編代碼有兩種方式

方式一:
用clang編譯器把C代碼編譯為匯編代碼伶贰,編譯函數(shù)所在的.c文件即可

xcrun -sdk iphoneos clang -S -arch arm64 Ctest.c

指定架構(gòu)為arm64蛛砰,執(zhí)行命令后會(huì)得到Ctest.s文件,就是對應(yīng)的匯編代碼了黍衙。

方式二:
新建一個(gè)iOS項(xiàng)目泥畅,并且運(yùn)行在真機(jī)設(shè)備上,在真機(jī)上才是arm64架構(gòu)的匯編哦琅翻。在三個(gè)函數(shù)分別打斷點(diǎn)位仁,把Xcodedebug模式設(shè)置為顯示匯編模式柑贞,在Debug -> Debug Workflow -> Aways Show Disassembly設(shè)置。當(dāng)函數(shù)斷住時(shí)聂抢,會(huì)顯示當(dāng)前函數(shù)的匯編代碼钧嘶。

這里采用方式二,得到的匯編代碼如下:

funcA

 Assembly`funcA:
 0x100086ab8 <+0>:  sub    sp, sp, #0x20             ; =0x20
 0x100086abc <+4>:  stp    x29, x30, [sp, #0x10]
 0x100086ac0 <+8>:  add    x29, sp, #0x10            ; =0x10
 0x100086ac4 <+12>: stur   w0, [x29, #-0x4]
 0x100086ac8 <+16>: str    w1, [sp, #0x8]
 0x100086acc <+20>: ldur   w0, [x29, #-0x4]
 0x100086ad0 <+24>: ldr    w1, [sp, #0x8]
 0x100086ad4 <+28>: bl     0x100086aec               ; funcB at Ctest.c:57
 0x100086ad8 <+32>: str    w0, [sp, #0x4]
 0x100086adc <+36>: ldr    w0, [sp, #0x4]
 0x100086ae0 <+40>: ldp    x29, x30, [sp, #0x10]
 0x100086ae4 <+44>: add    sp, sp, #0x20             ; =0x20
 0x100086ae8 <+48>: ret

funcB

 Assembly`funcB:
 0x100086aec <+0>:  sub    sp, sp, #0x20             ; =0x20
 0x100086af0 <+4>:  stp    x29, x30, [sp, #0x10]
 0x100086af4 <+8>:  add    x29, sp, #0x10            ; =0x10
 0x100086af8 <+12>: stur   w0, [x29, #-0x4]
 0x100086afc <+16>: str    w1, [sp, #0x8]
 0x100086b00 <+20>: ldur   w0, [x29, #-0x4]
 0x100086b04 <+24>: ldr    w1, [sp, #0x8]
 0x100086b08 <+28>: bl     0x100086b18               ; funcC at Ctest.c:75
 0x100086b0c <+32>: ldp    x29, x30, [sp, #0x10]
 0x100086b10 <+36>: add    sp, sp, #0x20             ; =0x20
 0x100086b14 <+40>: ret

funcC

 Assembly`funcC:
 0x100086b18 <+0>:  sub    sp, sp, #0x10             ; =0x10
 0x100086b1c <+4>:  str    w0, [sp, #0xc]
 0x100086b20 <+8>:  str    w1, [sp, #0x8]
 0x100086b24 <+12>: ldr    w0, [sp, #0xc]
 0x100086b28 <+16>: ldr    w1, [sp, #0x8]
 0x100086b2c <+20>: add    w0, w0, w1
 0x100086b30 <+24>: str    w0, [sp, #0x4]
 0x100086b34 <+28>: ldr    w0, [sp, #0x4]
 0x100086b38 <+32>: add    sp, sp, #0x10             ; =0x10
 0x100086b3c <+36>: ret

匯編分析


先回憶一下幾個(gè)關(guān)鍵寄存器和指令琳疏,
sp (Stack Point) :寄存器r31康辑,指向函數(shù)調(diào)用棧的棧頂
fp (Frame Point):寄存器r29,指向當(dāng)前正在執(zhí)行函數(shù)棧幀的棧底
lr (Link Register) :寄存器r30轿亮,存儲(chǔ)的是函數(shù)返回地址疮薇,用于當(dāng)函數(shù)結(jié)束時(shí),返回函數(shù)調(diào)用方繼續(xù)往下執(zhí)行我注。
bl:跳轉(zhuǎn)指令按咒,它做了兩件事情,
1. 把下一條指令的地址存儲(chǔ)到lr寄存器中
2. 跳轉(zhuǎn)到標(biāo)記處執(zhí)行指令
ret:函數(shù)返回但骨,返回到lr保存的地址繼續(xù)執(zhí)行指令励七。
更多的寄存器和指令學(xué)習(xí)可參考iOS逆向-arm64匯編學(xué)習(xí)

由于funcAfuncB都是非葉子函數(shù)奔缠,生成的主要匯編代碼大致相同掠抬,所以下面只分析funcBfuncC對應(yīng)的匯編代碼。

Assembly funcB:

 Assembly`funcB:
// 第一部分:開辟椥0ィ空間两波,保護(hù)現(xiàn)場
 0x100086aec <+0>:  sub    sp, sp, #0x20           ;// sp指針往下移動(dòng)0x20個(gè)字節(jié),開辟椕贫撸空間腰奋。
 0x100086af0 <+4>:  stp    x29, x30, [sp, #0x10]   ;// x29(fp)、x30(lr)寄存器中的內(nèi)容存放內(nèi)存中
 0x100086af4 <+8>:  add    x29, sp, #0x10          ;// x29(fp) 棧底指針往下移動(dòng)0x10個(gè)字節(jié)抱怔。

// 第二部分:函數(shù)邏輯
 0x100086af8 <+12>: stur   w0, [x29, #-0x4]        ;// 把第一個(gè)參數(shù)存入內(nèi)存中
 0x100086afc <+16>: str    w1, [sp, #0x8]          ;// 把第二個(gè)參數(shù)存入內(nèi)存中
 0x100086b00 <+20>: ldur   w0, [x29, #-0x4]        ;// 從內(nèi)存中讀取到w0中劣坊,也就是第一個(gè)參數(shù)
 0x100086b04 <+24>: ldr    w1, [sp, #0x8]          ;// 從內(nèi)存中讀取到w1中,也就是第二個(gè)參數(shù)
 0x100086b08 <+28>: bl     0x100086b18             ;// 調(diào)用 funC 函數(shù)

// 第三部分:恢復(fù)現(xiàn)場屈留,回收椌直空間
 0x100086b0c <+32>: ldp    x29, x30, [sp, #0x10]   ;// 從內(nèi)存中讀取數(shù)據(jù)到x29、x30灌危,恢復(fù) x29康二、x30的值。
 0x100086b10 <+36>: add    sp, sp, #0x20           ;// 函數(shù)執(zhí)行完畢乍狐,回收椩。空間
 0x100086b14 <+40>: ret                            ;// 返回,跳轉(zhuǎn)回上一個(gè)函數(shù)(funcA)執(zhí)行

Assembly funcC

 Assembly`funcC:
// 第一部分:開辟棧空間
 0x100086b18 <+0>:  sub    sp, sp, #0x10             ;// sp指針往下移動(dòng)0x20個(gè)字節(jié)藕帜,開辟椞陶郑空間。

// 第二部分:函數(shù)邏輯
 0x100086b1c <+4>:  str    w0, [sp, #0xc]
 0x100086b20 <+8>:  str    w1, [sp, #0x8]
 0x100086b24 <+12>: ldr    w0, [sp, #0xc]
 0x100086b28 <+16>: ldr    w1, [sp, #0x8]
 0x100086b2c <+20>: add    w0, w0, w1
 0x100086b30 <+24>: str    w0, [sp, #0x4]
 0x100086b34 <+28>: ldr    w0, [sp, #0x4]

// 第三部分:回收椙⒐剩空間
 0x100086b38 <+32>: add    sp, sp, #0x10             ;// 函數(shù)執(zhí)行完畢贝攒,回收棧空間
 0x100086b3c <+36>: ret                              ;// 返回时甚,跳轉(zhuǎn)回上一個(gè)函數(shù)(funcB)執(zhí)行

經(jīng)過上面的分析隘弊,Assembly funcB:Assembly funcC:最大的卻別是在Assembly funcB:中有對x29 (fp)、x30 (lr)的內(nèi)容存儲(chǔ)到內(nèi)存荒适,進(jìn)行保護(hù)梨熙,并在結(jié)束時(shí)恢復(fù)其原來的值;而在Assembly funcC:中沒有這樣的操作刀诬。

原因分析:

  1. Assembly funcC:bl指令咽扇,bl指令會(huì)修改x30( lr )寄存器的值,所以需要在真正執(zhí)行函數(shù)之前保存陕壹。
  2. 那為什么要保存x29的值呢质欲?從匯編代碼看,x29是被修改了糠馆,但是不修改也可以的嘶伟,其他指令并沒有修改x29的值;單獨(dú)通過sp也可以完成所有的尋址(訪問具體的內(nèi)存空間)工作又碌。說說我個(gè)人的理解
    2.1)通過x29 (fp)x30(sp)可以確定當(dāng)前函數(shù)的棧幀的地址范圍九昧,尋址時(shí)不可以超出這個(gè)范圍。訪問超出這個(gè)范圍地址的數(shù)據(jù)可能是臟數(shù)據(jù)赠橙,至少對當(dāng)前函數(shù)來說是臟數(shù)據(jù)耽装。
    2.2)有了x29寄存器,尋址的時(shí)候可以用x29的值做一個(gè)偏移定位到要訪問的地址期揪,也可以用sp的值做一個(gè)偏移找到要訪問的地址」娓觯靠近棧底的用x29做偏移凤薛,靠近棧頂?shù)挠?code>sp做偏移,這樣可以提高尋址的速度诞仓。那CPU怎么知道要訪問的地址是靠近x29還是靠近sp呢缤苫?CPU是不知道的,但編譯器知道墅拭,要訪問什么地址活玲,在程序編譯的時(shí)候就確定了。
    2.3)那Assembly funcC:為什么沒有棧底指針x29,有的話也可以提高尋址速度啊舒憾。那為什么沒有自己的棧底指針呢镀钓?目前還沒找到答案,也是自己的一個(gè)疑惑镀迂。

好了丁溅,上面啰嗦了那么多,下面通過圖來看函數(shù)調(diào)用時(shí)椞阶瘢空間寄存器的變化情況窟赏,

stack.png

對圖稍微說明一下,棧是高地址往低地址增長的箱季,圖中每一行表示4個(gè)字節(jié)涯穷。左邊是函數(shù)調(diào)用時(shí)spfp的變化過程,右邊是函數(shù)調(diào)用結(jié)束后spfp的變化過程藏雏。通過觀察拷况,開辟棧空間時(shí)诉稍,不是需要多大的內(nèi)存就開閉多少的內(nèi)存空間蝠嘉,而是16的倍數(shù)的字節(jié)數(shù)。這要做有助于提高CPU訪問內(nèi)存的速度杯巨。

總結(jié)


通過上面的分析蚤告,理解了函數(shù)調(diào)用時(shí)棧空間寄存器的變化情況服爷,以及為什么需要那樣變化杜恰。但還留下了一個(gè)疑問,葉子函數(shù)為什么沒有自己fp仍源,知道的大神麻煩告知心褐。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市笼踩,隨后出現(xiàn)的幾起案子逗爹,更是在濱河造成了極大的恐慌,老刑警劉巖嚎于,帶你破解...
    沈念sama閱讀 216,496評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掘而,死亡現(xiàn)場離奇詭異,居然都是意外死亡于购,警方通過查閱死者的電腦和手機(jī)袍睡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肋僧,“玉大人斑胜,你說我怎么就攤上這事控淡。” “怎么了止潘?”我有些...
    開封第一講書人閱讀 162,632評論 0 353
  • 文/不壞的土叔 我叫張陵掺炭,是天一觀的道長。 經(jīng)常有香客問我覆山,道長竹伸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評論 1 292
  • 正文 為了忘掉前任簇宽,我火速辦了婚禮勋篓,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘魏割。我一直安慰自己譬嚣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評論 6 388
  • 文/花漫 我一把揭開白布钞它。 她就那樣靜靜地躺著拜银,像睡著了一般。 火紅的嫁衣襯著肌膚如雪遭垛。 梳的紋絲不亂的頭發(fā)上尼桶,一...
    開封第一講書人閱讀 51,165評論 1 299
  • 那天,我揣著相機(jī)與錄音锯仪,去河邊找鬼泵督。 笑死,一個(gè)胖子當(dāng)著我的面吹牛庶喜,可吹牛的內(nèi)容都是我干的小腊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼久窟,長吁一口氣:“原來是場噩夢啊……” “哼秩冈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起斥扛,我...
    開封第一講書人閱讀 38,910評論 0 274
  • 序言:老撾萬榮一對情侶失蹤入问,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后稀颁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體队他,經(jīng)...
    沈念sama閱讀 45,324評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評論 2 332
  • 正文 我和宋清朗相戀三年峻村,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锡凝。...
    茶點(diǎn)故事閱讀 39,711評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡粘昨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情张肾,我是刑警寧澤芭析,帶...
    沈念sama閱讀 35,424評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站吞瞪,受9級特大地震影響馁启,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜芍秆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評論 3 326
  • 文/蒙蒙 一惯疙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧妖啥,春花似錦霉颠、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至怀读,卻和暖如春诉位,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背菜枷。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評論 1 269
  • 我被黑心中介騙來泰國打工苍糠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人犁跪。 一個(gè)月前我還...
    沈念sama閱讀 47,722評論 2 368
  • 正文 我出身青樓椿息,卻偏偏與公主長得像,于是被迫代替她去往敵國和親坷衍。 傳聞我的和親對象是個(gè)殘疾皇子寝优,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評論 2 353

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