彙編(X86-64)
彙編的基本語法
X86-64的暫存器組:
IA32的暫存器:
記憶體的定址模式(與暫存器結合起來看):
更加通用的定址方式:
看個例子如何計算記憶體地址:
Move指令:操作型別可以是立即數、暫存器、記憶體
取地址leap指令:
這條指令可以用來給指標賦值。
算數指令:
控制部分:
處理器的暫存器狀態:
設定條件碼:
還可以使用set進行條件碼設定。
條件跳轉的例子:
switch case的時候可以使用一種叫做跳轉表的東西進行跳轉就可以減少地址的計算。下面是直接和間接使用跳轉表的例子。間接跳轉裡面的算地址的時候就是針對於基地址的偏移,然後就可以計算出具體是跳轉表中的第幾位。
x86-64的棧組成
在程式碼執行過程中,需要注意的是這個過程中的控制轉移;引數傳遞;記憶體管理。棧是由高地址儲存到低地址進行。和棧有關的指令是pushq和popq。它們使用的地址一個在指令中給出,另外一個地址就在%rsp中。
在執行程式碼的過程中會有call label和ret。call就是跳過去呼叫函式的地址;ret就是返回到呼叫者的下一條指令的地址,這個地址是從棧中pop出來的。
在函式呼叫的時候,就會在棧中開闢一個數據幀用來儲存所需變數。
看一個函式自增的例子,以及棧中的資料如何變化。
在函式呼叫前,呼叫者把自己的資料儲存好。呼叫結束後,被呼叫者,恢復之前的各個暫存器的狀態。
x86-64的各個暫存器的用處:
看遞迴瞭解棧如何執行以及棧如何儲存呼叫者需要儲存的變數。這裡注意在每次呼叫pcount_r之前返回地址已經被壓到了棧中。
這個遞迴我覺得很巧妙,每次呼叫會把計算得到的x&1放進%rbx並放入棧中。在最終返回的時候把之前計算得到的所有的%rbx都加起來放到%rax返回。
畫棧的表示圖
x86-64的Linux系統中棧的表示:
程式碼注入
某些函式輸入字串不檢查長度,導致返回地址被覆蓋,那麼就可能被侵入。比如gets,strcpy,strcat,scanf,fscanf,sscanf。這些函式不檢查輸入的長度限制,寫過界之後就會覆蓋需要的內容。當然,現在計算機系統會檢測出這些非法地址,核心發出Segmentation Fault訊號。這樣會超界的行為我們稱為快取區溢位buffer overflow。
PS:定義結構體的時候注意對齊原則,然後使用union可以進行較好的資料格式轉換。
在使用gets以前,使這樣的:
使用gets之後:這裡使用的是ascii碼。
緩衝區溢位可以作為程式碼注入的基本方法:
將返回地址改成自己寫的部分。
防止緩衝區溢位的技巧
使用安全的函式
使用系統級保護
比如在分配棧空間的隨機分配,使得利用緩衝區溢位改寫返回地址失效。或者是給返回地址加入只讀許可權,無法改變。
使用stack canary
在buffer前面弄一個固定的識別符號,如果被修改就知道被破壞了。
但是注入程式碼的技巧變成了使用現成的程式碼進行跳轉,這就是gadget。
比如:
使用連續gadget跳到自己想要達到的地方:
PS:slide image from CMU 18-600