首先我們先觀察最干凈的函數(shù)刃鳄,做了什么
這個函數(shù)什么都沒做
void _func_1_(){
}
我沒來看看這個函數(shù)的調(diào)用到推,與函數(shù)的里面的匯編是咋樣的
函數(shù)調(diào)用的匯編
0x1004228a0 <+24>: bl 0x100422824 ; _func_1_ at main.m:13
函數(shù)執(zhí)行的匯編
02-匯編-函數(shù)`_func_1_:
-> 0x100422824 <+0>: ret
函數(shù)調(diào)用的匯編就是這么簡單,通過bl/ret指令實現(xiàn)代碼塊的跳轉(zhuǎn)實現(xiàn)是钥。
首先我們回顧一下bl/ret指令的功能
- bl指令 跳轉(zhuǎn)讥巡,將下一條執(zhí)行的指令放入lr(X30)寄存器
- ret 返回到lr寄存器所保存的地址 執(zhí)行代碼
我們可以直接查看寄存器看看是不是真的
當(dāng)然因為這個是空的函數(shù)上荡,所以才會看起來那么簡單
接下來我們在函數(shù)里面調(diào)用別的函數(shù)試試
先看C代碼
void _func_2_(){
_func_1_();
}
函數(shù)的調(diào)用都是一樣的趴樱,我們?nèi)タ纯春瘮?shù)里面發(fā)什么了什么變化
02-匯編-函數(shù)`_func_2_:
0x1022e6810 <+0>: stp x29, x30, [sp, #-0x10]!
0x1022e6814 <+4>: mov x29, sp
0x1022e6818 <+8>: bl 0x1022e680c ; _func_1_ at main.m:13
0x1022e681c <+12>: ldp x29, x30, [sp], #0x10
0x1022e6820 <+16>: ret
接下來我們仔細(xì)觀察函數(shù)發(fā)生的變化,多了以下,兩句話
stp x29, x30, [sp, #-0x10]! // 1.把x29 和 x30存方到sp-8 sp-10的位置 2.sp -= 0x16
ldp x29, x30, [sp], #0x10 // 1.將sp-8 sp-10位置的值取出來,放入x29 和 x30酪捡,2.sp += 0x16
通過堆棧的操作在函數(shù)的開頭保存了x30(lr),x29(fp),兩個寄存器纳账,在函數(shù)結(jié)尾的時候逛薇,又把這些值取出來,為什么要怎么做呢疏虫?
反過來想永罚,如果不這么做,會導(dǎo)致什么問題卧秘?會出現(xiàn)一個很嚴(yán)重的問題呢袱!【下面純文字解釋,請耐心理解】
當(dāng)我們在執(zhí)行這句話的 bl 0x1022e680c 會把x30寄存器改成 0x1022e681c地址翅敌,那么 0x1022e6820這個位置的時候執(zhí)行ret羞福,代碼會跳回到main函數(shù)嗎?肯定是不會的蚯涮,它只會跳到x30指向的位置治专。那么代碼就會在 0x1022e681c 0x1022e6820 這兩個位置不斷執(zhí)行卖陵,知道把堆棧弄炸,程序奔潰~~~~~~~
所以這兩句匯編是為了保護(hù)代碼回家的的路张峰,以后我們在逆向別人的代碼的時候泪蔫,請不要不破壞~~~
接下來我們繼續(xù)深入理解函數(shù)參數(shù)的傳遞,及返回值的返回
先上個簡單的C代碼
int _func_3_(int a,int b,int c,int d,int e,int f,int g,int h,int i,int j,int k,int l){
return a+b;
}
接著上調(diào)用函數(shù)的匯編解釋
02-匯編-函數(shù)`main:
0x1028f2888 <+0>: sub sp, sp, #0x40 ; =0x40
0x1028f288c <+4>: stp x29, x30, [sp, #0x30]
0x1028f2890 <+8>: add x29, sp, #0x30 ; =0x30
0x1028f2894 <+12>: stur wzr, [x29, #-0x4]
0x1028f2898 <+16>: stur w0, [x29, #-0x8]
0x1028f289c <+20>: stur x1, [x29, #-0x10]
0x1028f28a0 <+24>: bl 0x1028f2818 ; _func_1_ at main.m:13
0x1028f28a4 <+28>: bl 0x1028f281c ; _func_2_ at main.m:15
0x1028f28a8 <+32>: orr w0, wzr, #0x1 ; ----華麗分割線-----
0x1028f28ac <+36>: orr w1, wzr, #0x2
0x1028f28b0 <+40>: orr w2, wzr, #0x3
0x1028f28b4 <+44>: orr w3, wzr, #0x4
0x1028f28b8 <+48>: mov w4, #0x5
0x1028f28bc <+52>: orr w5, wzr, #0x6
0x1028f28c0 <+56>: orr w6, wzr, #0x7
0x1028f28c4 <+60>: orr w7, wzr, #0x8
0x1028f28c8 <+64>: mov w8, #0x9
0x1028f28cc <+68>: mov w9, #0xa
0x1028f28d0 <+72>: mov w10, #0xb
0x1028f28d4 <+76>: orr w11, wzr, #0xc
-> 0x1028f28d8 <+80>: str w8, [sp]
0x1028f28dc <+84>: str w9, [sp, #0x4]
0x1028f28e0 <+88>: str w10, [sp, #0x8]
0x1028f28e4 <+92>: str w11, [sp, #0xc] ; ----華麗分割線-----
0x1028f28e8 <+96>: bl 0x1028f2830 ; 在這里調(diào)用_func_3_函數(shù)
0x1028f28ec <+100>: stur w0, [x29, #-0x14]
0x1028f28f0 <+104>: ldur w8, [x29, #-0x14]
0x1028f28f4 <+108>: mov x30, x8
0x1028f28f8 <+112>: mov x12, sp
0x1028f28fc <+116>: str x30, [x12]
0x1028f2900 <+120>: adrp x0, 1
0x1028f2904 <+124>: add x0, x0, #0xf14 ; =0xf14
0x1028f2908 <+128>: bl 0x1028f2bfc ; symbol stub for: printf
0x1028f290c <+132>: mov w8, #0x0
0x1028f2910 <+136>: str w0, [sp, #0x18]
0x1028f2914 <+140>: mov x0, x8
0x1028f2918 <+144>: ldp x29, x30, [sp, #0x30]
0x1028f291c <+148>: add sp, sp, #0x40 ; =0x40
0x1028f2920 <+152>: ret
兩段華麗的分割線展示了函數(shù)的參數(shù)是如何傳遞的
細(xì)致不多看喘批,直接看結(jié)果
-> 0x1028f28d8 <+80>: str w8, [sp]
0x1028f28dc <+84>: str w9, [sp, #0x4]
0x1028f28e0 <+88>: str w10, [sp, #0x8]
0x1028f28e4 <+92>: str w11, [sp, #0xc]
他們把第1-第8個參數(shù)放到了x0-x7八個寄存器撩荣,第9-第12個參數(shù),存到堆棧進(jìn)行傳輸參數(shù)
接著我們看看返回值
02-匯編-函數(shù)`_func_3_:
0x104d46830 <+0>: sub sp, sp, #0x30 ; =0x30
0x104d46834 <+4>: ldr w8, [sp, #0x3c]
0x104d46838 <+8>: ldr w9, [sp, #0x38]
0x104d4683c <+12>: ldr w10, [sp, #0x34]
0x104d46840 <+16>: ldr w11, [sp, #0x30]
0x104d46844 <+20>: str w0, [sp, #0x2c]
0x104d46848 <+24>: str w1, [sp, #0x28]
0x104d4684c <+28>: str w2, [sp, #0x24]
0x104d46850 <+32>: str w3, [sp, #0x20]
0x104d46854 <+36>: str w4, [sp, #0x1c]
0x104d46858 <+40>: str w5, [sp, #0x18]
0x104d4685c <+44>: str w6, [sp, #0x14]
0x104d46860 <+48>: str w7, [sp, #0x10]
-> 0x104d46864 <+52>: ldr w0, [sp, #0x2c]
0x104d46868 <+56>: ldr w1, [sp, #0x28]
0x104d4686c <+60>: add w0, w0, w1 ; 把 w0+w1的值放到w0,也就是參數(shù)1+參數(shù)2
0x104d46870 <+64>: str w11, [sp, #0xc]
0x104d46874 <+68>: str w10, [sp, #0x8]
0x104d46878 <+72>: str w8, [sp, #0x4]
0x104d4687c <+76>: str w9, [sp]
0x104d46880 <+80>: add sp, sp, #0x30 ; =0x30
0x104d46884 <+84>: ret
根據(jù)我上述的備注饶深,他們吧結(jié)果放到w0餐曹,之后返回
所以我這邊做個簡單的總結(jié):
- 參數(shù)會存放到X0到X7(W0到W7)這8個寄存器里面,如果超過8個參數(shù),就會入棧粥喜,返回值是通過x0進(jìn)行返回獲取
- 由于操作堆棧會比操作寄存器更消耗性能凸主,所以我們的參數(shù)盡可能不要超過8個
拓展:
好像了解了函數(shù)的原理之后,沒啥用额湘?不是的卿吐,這對之后的逆向很有幫助,下面簡單說一下最大的用處在哪
對于了解過OC runtime的開發(fā)者來說 ,都知道OC調(diào)用函數(shù)锋华,都是通過 objc_msgSend 來實現(xiàn)的
objc_msgSend的第一個參數(shù)就是對象本身嗡官,第二個參數(shù)就是SEL,后面就是傳入這個SEL的具體參數(shù)
那么對于我們進(jìn)行動態(tài)調(diào)試得是有多大的益處呀~~嘿嘿