記憶體與指標

返回位址

當你暫停讀書去應門時,你會用手指按住那一行,好知道從哪裡接著讀。返回位址(return address)就是電腦按在書裡的那根手指:當一個函式呼叫另一個函式時,它存下「被呼叫函式結束後該繼續執行的指令位址」,這樣控制權才能回到正確的位置。

機制上,呼叫指令把「下一條指令的位址」(也就是呼叫之後緊接著的那個位置)推上堆疊,然後跳到被呼叫的函式。那個存下的位址成為新框的一部分。函式做完時,返回指令把返回位址從堆疊彈出並跳過去,讓呼叫者正好回到它離開的地方。被重新載入的是 CPU 的指令指標(x86-64 上是 rip)。這就是巢狀與遞迴呼叫得以成立的原因:每個框各自帶著自己的返回位址,所以一連串呼叫能一路正確地展開回去。

返回位址也是攻擊者最愛的目標,這正是它的意義超越機制之處。因為它就坐落在堆疊上,常常緊鄰區域陣列,一個被寫過頭的緩衝區可能蓋掉存下的返回位址——經典的堆疊緩衝區溢位——接著一個返回就跳到攻擊者安排的任何地方。這一個事實推動了這個領域的許多東西:堆疊金絲雀、不可執行的堆疊,以及定址空間佈局隨機化,大多是為了保護這一個存下的值而存在。

若 main 在位於 0x4011a0 的指令處呼叫 f,這次呼叫會把 0x4011a5(下一條指令)推入當作返回位址;f 結束時,它的返回把 0x4011a5 彈出,於是執行回到 main 的那裡繼續。

呼叫存下下一條指令的位址;返回把它彈出並跳回去。

返回位址是堆疊上的普通資料,不是受保護的特殊欄位——這正是為什麼把鄰近的區域陣列寫爆就能蓋掉它並挾持控制權。堆疊金絲雀、不可執行堆疊等防禦措施,大多就是為了守護這一個值而存在。

又稱
saved instruction pointer返回位址