前言
這篇文章,是爲了介紹緩衝區溢出攻擊做準備。
在開始之前,我們希望具備以下背景知識。
(gdb我用的比較少,只會簡單的,還沒熟練。我們的界面或許會有些不一樣,因爲插件的緣故,但不影響)
看起來,略微有些複雜,需要自己調試一遍。
- 虛擬內存的概念
- 程序在內存中的佈局
- 簡單的gdb使用:100個gdb小技巧
- 簡單的彙編語言
環境
ubuntu18.04+gcc7.5+gdb8.1
摘要和總結
我們先給出gdb的調試方法。
然後結合棧幀信息和彙編代碼,分析函數調用過程中的棧(棧幀)變化情況。
調試過程
調試的代碼
//我們用一個最簡單的程序:memery_layout.c
/**
* 使用gdb調試該程序,展示內存佈局
*/
#include <stdio.h>
void func(int a,int b){
int x,y;
x = a + b;
y = a - b;
}
int main(void){
int x=0;
int y=0;
func(4,3);
return 0;
}
調試過程
-
在開始調試之前,我們先進行編譯:
gcc -g -o memery_layout memery_layout.c
-
開始調試:
gdb -q memery_layout
-
顯示程序:
l
-
打斷點:
break func
、break 10
-
運行到斷點:
run
-
顯示程序的調用棧信息:
bt
(backtrace) -
顯示當前棧幀的詳細信息:
info frame
(關於這個棧幀信息的解讀,我們放在後面)
-
跳轉到上一個棧幀:
up
-
顯示當前棧幀的詳細信息:
info frame
-
跳轉到下一個棧棧:
down
-
繼續運行到下一個斷點:
c
-
查看全部局部變量信息:
info locals
-
查看x變量內容、地址:
print x
、print &x
-
查看寄存器內容:
info registers
-
查看指定寄存器(rbp)內容:
print $rbp
-
查看彙編代碼:
disassemble /rm
-
查看內存內容(0x7fffffffdc70地址,5個單元/16十六進制格式顯示/g表示八字節):
x /5xg 0x7fffffffdc70
棧幀信息分析
棧幀結構
關於調試過程上面已經介紹,下面便不再贅述。
參考文章:x86_64架構下的函數調用及棧幀原理
因爲調試的現象和它的不一樣。所以我在下面畫出我的棧幀圖片。
因爲參數只有兩個,通過寄存器傳參,所以沒有畫參數壓棧的情況。
我按照info frame
的信息劃分棧幀。
我們先看下棧幀的基本結構。
寄存器介紹
-
%rax :通常存儲函數調用的返回結果,也被用在
idiv
(除法)和imul
(乘法)命令中。 -
%rsp :堆棧指針寄存器,指向棧頂位置。pop操作通過增大rsp的值實現出棧,push操作通過減小rsp的值實現入棧。
-
%rbp :棧幀指針。
標識當前棧幀的起始位置。。並不是當前棧幀開始的地方 -
%rdi, %rsi, %rdx, %rcx,%r8, %r9 :六個寄存器,當參數少於7個時, 參數從左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9;當參數爲7個以上時,前 6 個與前面一樣, 但後面的依次從 “右向左” 放入棧中,即和32位彙編一樣。
棧幀信息分析
-
藍色方框:標明我們現在在第0層。
-
綠色方框,是上一個棧幀的信息,保存在當前棧幀。
called by frame at 0x7fffffffdcb0
:由0x7fffffffdcb0
位置開始的棧幀調用saved rip = 0x555555554640
:當前棧幀要保存上一個棧幀將要執行的地址0x555555554640
rip at 0x7fffffffdc88
:返回地址(上一個棧幀將要執行的地址),保存在0x7fffffffdc88
rbp at 0x7fffffffdc80
:上一個棧幀指針內容保存在0x7fffffffdc80
Previous frame's sp is 0x7fffffffdc90
:上一個棧幀的棧頂指針。(這我畫成紅框,框錯了。。)
-
紅色方框是當前棧幀信息
frame at 0x7fffffffdc90
:當前棧幀的起始地址0x7fffffffdc90
Locals at 0x7fffffffdc80
:局部變量從0x7fffffffdc80
開始。
代碼分析
那麼當前的rbp值是什麼?局部變量爲甚從上面 0x7fffffffdc80的地方開始?
僅從棧幀的信息,是無法驗證。這時候,我們來看看它的彙編代碼。
補充下壓棧:push指令可以分解爲=%rsp棧頂指下移+mov 源數據
-
綠色方框
- 通過壓棧方式,保存上一個棧幀指針。由上一節,我們知道,它被保存在
0x7fffffffdc80
- 將當前棧頂,保存在rbp中。即,設置棧幀指針。所以如上面結構圖所示,當前棧幀位置,是保存上一個棧幀的內存位置。
- 通過壓棧方式,保存上一個棧幀指針。由上一節,我們知道,它被保存在
-
藍色方框
從寄存器中取出參數,放入內存。
-
紅色方框
進行計算,計算的結果放入局部變量局部變量的起始地址,從rbp指向的內存的下面開始。
-
還原
- 將上一個棧幀地址,還原到rbp中
- ret 返回調用(下面介紹)
函數的調用與返回
這裏,我們僅僅着重看下call
和ret
我們也看下main
函數的彙編代碼。
我們可以看到,兩個參數,從右向左,傳遞給寄存器。
然後,調用0x5555555545fa
位置的func
函數。
通過上一張圖,我們能看出0x5555555545fa
是func函數的起始位置。
call
還幹了一件事,把下一條要執行的地址,壓入棧中。我們可以通過內存查看。
-
綠色方框 —> 兩個局部變量的值
-
藍色方框 —> 上一個棧幀的值
-
紅色方框 —> 返回地址
棧幀指針-不是棧幀開始的地方
棧幀寄存器存儲的不是棧幀開始的地方。
我們用第0層的棧幀來看:frame at 0x7fffffffdc90
+ Saved registers: rbp at 0x7fffffffdc80
當前棧幀開始的地方是0x7fffffffdc90
,而棧幀寄存器裏面的值是0x7fffffffdc80
而且,我傾向與將返回地址,劃分在當前棧幀,而不是上一個棧幀。
0x7fffffffdc90
這個內存中的內容,我不知道代表什麼。不是寄存器的值
我們可以看出0x7fffffffdc98
裏面是兩個int 0,是上一個棧幀的內容。
我們可以通過,frame 0x7fffffffdc90
跳轉到當前棧幀。