iOS逆向之旅(基礎篇) — 彙編(四) — 彙編下的函式

洪呵呵發表於2018-10-25

首先我們先觀察最乾淨的函式,做了什麼

這個函式什麼都沒做

void _func_1_(){
}
複製程式碼

我沒來看看這個函式的呼叫,與函式的裡面的彙編是咋樣的 函式呼叫的彙編

0x1004228a0 <+24>:  bl     0x100422824               ; _func_1_ at main.m:13
複製程式碼

函式執行的彙編

02-彙編-函式`_func_1_:
->  0x100422824 <+0>: ret 
複製程式碼

函式呼叫的彙編就是這麼簡單,通過bl/ret指令實現程式碼塊的跳轉實現。 首先我們回顧一下bl/ret指令的功能

  • bl指令 跳轉,將下一條執行的指令放入lr(X30)暫存器
  • ret 返回到lr暫存器所儲存的地址 執行程式碼

我們可以直接檢視暫存器看看是不是真的

iOS逆向之旅(基礎篇) — 彙編(四) — 彙編下的函式
當然因為這個是空的函式,所以才會看起來那麼簡單

####接下來我們在函式裡面呼叫別的函式試試 先看C程式碼

void _func_2_(){
    _func_1_();
}
複製程式碼

函式的呼叫都是一樣的,我們去看看函式裡面發什麼了什麼變化

02-彙編-函式`_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 
複製程式碼

接下來我們仔細觀察函式發生的變化,多了以下,兩句話

  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
複製程式碼

通過堆疊的操作在函式的開頭儲存了x30(lr),x29(fp),兩個暫存器,在函式結尾的時候,又把這些值取出來,為什麼要怎麼做呢? 反過來想,如果不這麼做,會導致什麼問題?會出現一個很嚴重的問題!【下面純文字解釋,請耐心理解】

當我們在執行這句話的 bl  0x1022e680c  會把x30暫存器改成 0x1022e681c地址,那麼 0x1022e6820這個位置的時候執行ret,程式碼會跳回到main函式嗎?肯定是不會的,它只會跳到x30指向的位置。那麼程式碼就會在 0x1022e681c 0x1022e6820 這兩個位置不斷執行,知道把堆疊弄炸,程式奔潰~~~~~~~

所以這兩句彙編是為了保護程式碼回家的的路,以後我們在逆向別人的程式碼的時候,請不要不破壞~~~

####接下來我們繼續深入理解函式引數的傳遞,及返回值的返回 先上個簡單的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;
}
複製程式碼

接著上呼叫函式的彙編解釋

02-彙編-函式`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               ; 在這裡呼叫_func_3_函式
    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  
複製程式碼

兩段華麗的分割線展示了函式的引數是如何傳遞的 細緻不多看,直接看結果

4.png

->  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個引數放到了x0-x7八個暫存器,第9-第12個引數,存到堆疊進行傳輸引數 接著我們看看返回值

02-彙編-函式`_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,也就是引數1+引數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    
複製程式碼

根據我上述的備註,他們吧結果放到w0,之後返回 所以我這邊做個簡單的總結:

  • 引數會存放到X0到X7(W0到W7)這8個暫存器裡面,如果超過8個引數,就會入棧,返回值是通過x0進行返回獲取
  • 由於操作堆疊會比操作暫存器更消耗效能,所以我們的引數儘可能不要超過8個

####擴充: 好像瞭解了函式的原理之後,沒啥用?不是的,這對之後的逆向很有幫助,下面簡單說一下最大的用處在哪 對於瞭解過OC runtime的開發者來說 ,都知道OC呼叫函式,都是通過 objc_msgSend 來實現的

5.png
objc_msgSend的第一個引數就是物件本身,第二個引數就是SEL,後面就是傳入這個SEL的具體引數 那麼對於我們進行動態除錯得是有多大的益處呀~~嘿嘿

資原始碼

相關文章