3.10 在機器級程序中將控制與數據結合起來
3.10.1 理解指針
指針以一種統一方式,對不同數據結構中的元素產生引用。
1) 每個指針都對應一個類型。(指針類型不是機器代碼中的一部分;只是C語言提供的一種抽象,幫助程序員避免尋址錯誤。)
2) 每個指針都有一個值。這個值是某個指定類型的對象的地址。特殊的 NULL(0)
值代表該指針沒有指向任何地方。
3) 指針用‘&’運算符創建。
4) 操作符是用來間接引用指針。
5) 數組與指針緊密聯繫。一個數組的名字可以像指針變量一樣引用(但是不能修改)。如,a[3]
等價於*(a+3)
。
數組引用和指針運算都需要用對象大小對偏移量進行伸縮。
6) 將指針從一種類型強制轉換成另一種類型,只改變它的類型,而不改變它的值。強制類型轉換的一個效果是改變指針運算的伸縮。
例如:
char *p = 'c' ;
(int *)p + 7 的結果爲 p + 28;
(int *)(p + 7) 的結果爲 p + 7。
(強制類型轉換的優先級高於加法)
7) 指針也可以指向函數。這提供了一個很強大的存儲和像代碼傳遞引用的功能,這些引用可以被程序的某個其他部分調用。
例如:
int fun(int x, int *p);
/** 聲明指針 fp ,將它賦值爲這個函數 */
int (*fp)(int, int *);
fp = fun;
/** 函數調用 */
int y = 1;
int result = fp(3, &y);
函數指針的值是該函數機器代碼表示中第一條指令的地址。
3.10.2 應用:使用 GDB 調試器
先運行OBJDUMP 來獲得程序的反彙編版本。如下命令行來啓動 GDB:
linux> gdb prog
通常的方法是在程序感興趣的地方附近設置斷點。
命令 | 效果 |
---|---|
quit |
退出GDB |
run |
運行程序(在此給出命令行參數) |
kill |
停止程序 |
break sth |
在函數sth 入口處設置斷點 |
break *0x400540 |
在地址0x400540 處設置斷點 |
delete 1 |
刪除斷點1 |
delete |
刪除所有斷點 |
stepi |
執行1 條指令 |
stepi 4 |
執行4 條指令 |
nexti |
類似stepi ,但以函數調用爲單位 |
continue |
繼續執行 |
finish |
運行到當前函數返回 |
disas sth |
反彙編函數sth |
disas 0x400540 |
反彙編0x400540 附近的函數 |
disas 0x400540.0x40054d |
反彙編指定範圍內的代碼 |
print /x $rip |
以十六進制輸出程序計數器(%rip) 的值 |
print $rip |
以十進制輸出程序計數器(%rip) 的值 |
print /t $rip |
以二進制輸出程序計數器(%rip) 的值 |
print 0x100 |
輸出0x100 的十進制 |
print /x 555 |
輸出555 的十六進制 |
print /x ($rsp + 8) |
以十六進制輸出%rsp + 8 |
print *(long *) 0x7fff ffff e818 |
輸出位於地址0x7fff ffff e818 的長整數 |
print *(long *) (%rsp + 8) |
輸出位於地址%rsp + 8 的長整數 |
x/2g 0x7fffffffe818 |
檢查從地址0x7fffffffe818 開始的雙字(8 字節) |
x/20b sth |
檢查函數sth 的前20 個字節 |
info frame |
有當前棧幀的信息 |
info registers |
所有寄存器的值 |
help |
獲取有關GDB 的信息 |
3.10.3 內存越界引用和緩衝區溢出
緩衝區溢出(buffer overflow
)。在棧中分配某個字符數組來保存字符串,但是字符串的長度超出了爲數組分配的空間。
示例:
echo
對應的彙編:
越界會破壞的信息:
緩衝區溢出的一個更加致命的使用就是讓程序執行它本來不願意執行的函數。這是一種最常見的通過計算機網絡攻擊系統安全的方法。
3.10.4 對抗緩衝區溢出攻擊
1. 棧隨機化
爲了在系統中插入攻擊代碼,攻擊者既要插入代碼,也要插入指向這段代碼的指針,這個指針也是攻擊字符串的一部分。產生這個指針需要知道這個字符串放置的棧地址。
棧隨機化的思想使得棧的位置在程序每次運行時都有變化。這類技術稱爲地址空間佈局隨機化(Address-Space Layout Randomization
),簡稱ASLR。
通常攻擊者使用空操作雪橇(nop sled
),使程序”滑過“目標序列,即在實際攻擊代碼前插入一段很長的nop
(讀作“no op”
,no operation
的縮寫)指令。
示例:
個字節的nop sled
能破解的棧隨機化需要枚舉 次。
2. 棧破壞檢測
計算機的第二道防線是能夠檢測到何時棧已經被破壞。
GCC提供一種棧保護者機制,來檢測緩衝區越界。其思想是在棧幀中任何局部緩衝區與棧狀態之間存儲一個特殊的金絲雀(canary
),也稱爲哨兵值(guard value
),是在程序每次運行時隨機產生的。
echo
函數示例:
程序第三行通過段地址%fs:40
從內存讀入金絲雀的值,保存在棧中;
程序第十一行取出該值與原地址的值作比較,不相等則棧異常。
tips
:
容易越界的參數儘可能放置在棧底,以保護其他參數。
示例:
圖(b
)中,參數v
比數組參數buf
更靠近棧頂,vbuf
緩衝越界不會破壞v
。
3. 限制可執行代碼區域
最後一招是消除攻擊者向系統插入可執行代碼的能力。
隨機化、棧保護和限制哪部分內存可以存儲可執行代碼——是用於最小化程序緩衝區溢出攻擊漏洞三種最常見的機制。
3.10.5 支持變長棧幀
前面所講的各種函數的機器級代碼,都有一個共同點,即編譯器能夠預先確定需要爲棧幀分配多少空間。
下面示例爲局部存儲是變長的。
%rbp
稱爲幀指針(frame pointer
)有時稱爲基址幀(base pointer
);
leave
指令將棧幀指針恢復到它之前的值(第20行)。等價於:
movq %rbp, %rsp ;Set stack pointer to begining of frame
popq %rbp ;Restore saved %rbp and set stack ptr to end of caller's frame
重點習題:
答案: