更爲複雜C程序的運行時結構

運行環境 win 10 企業版 1809 17763.194,MinGW V3.14 32位,Bundled V3.13.2,Bundled GDB V8.2。編程

在C語言中,棧的方向是從高地址向低地址延伸,而數組中數據在棧中的存儲方向與此正好相反。字符串拷貝等數組操做是不對數據長度作審覈的,若是實際的數據長度超過了棧中預留的空間,就會將棧中其餘數據覆蓋,這種現象被稱爲「棧溢出」。棧溢出可能致使一個不可預期的錯誤,也可能致使一個精心策劃的執行流程發生改變。可見,是否可以對本身所寫程序的運行時狀態作到心中有數,是可否寫出高質量、安全代碼的前提保證。數組

以上兩節介紹的運行時結構都是由C程序所對應的指令,在內存中執行,驅動數據變化而產生的。C程序只有通過編譯,才能生成目標代碼。目標代碼將與指令和全局數據一一對應。編譯的最終目標就是能讓C程序的設計意圖體如今運行時結構中,這也使得編譯的每一個階段的中心任務都要爲造成運行時結構着想。下一節咱們將概述編譯的過程。安全

1.2 更爲複雜C程序的運行時結構(1)函數

在實際編程過程當中會遇到更爲複雜的問題。要解決這樣的問題,更加依賴對運行時結構的瞭解。下面咱們來看一個比較複雜的案例,案例的兩個程序分別以下:spa

#include <stdio.h>
#include <string.h>

void fun1() {
    int m = 10;
    char num[4];
    strcpy(num, "bbbb");
}

void fun2() {
    printf("You were attacked!!!\n");
}

int main() {
    fun1();
    printf("over");
    return 0;
}
#include <stdio.h>
#include <string.h>

void fun1() {
    int m = 10;
    char num[4];
    strcpy(num, "bbbbbbbbbbbb\x12\x13\x40\x00");
}

void fun2() {
    printf("You were attacked!!!\n");
}

int main() {
    int address = (int) fun2;
    printf("%08x\n", address);

    fun1();

    return 0;
}

 

這個案例中的兩個程序在代碼上只有微小的差異,但執行結果卻不一樣,尤爲是左邊的程序,執行結果以下所示:設計

這些字符顯然是fun2函數被調用時纔會輸出的,但fun2這個函數在本程序中沒有被調用過,這樣的輸出結果顯得有些難以想象了,程序執行時到底發生了什麼呢?下面咱們一步一步地對比分析這個案例。咱們先來看main函數調用fun1函數時的情景,fun1函數執行後的返回地址被壓入棧中,跳轉到fun1函數執行,此時兩邊程序的執行沒有差別,情景如圖1-29所示。3d

以後保存了main函數棧底的地址值,ebp被騰出來,指向fun1函數的棧底,此時兩邊也沒有差別。情景如圖1-30所示。code

m入棧,初始化爲10,爲num數組開闢了棧空間,此時仍然沒有差別,情景如圖1-31所示。blog

下面差別產生了。調用strcpy函數,執行的目的是把指定的字符串拷貝到num數組中,指定多少,拷貝多少。咱們先來看右邊的程序。該程序會把指定的字符串拷貝給num數組,其長度恰好填滿num數組,情景如圖1-32所示。ip

再看左邊程序,指定的字符串長度已經超出了num數組的長度,因此在拷貝的時候,會把棧中前面的數據覆蓋掉,包括num的數組、main函數棧底地址值直至fun1函數執行後的返回地址,所有被覆蓋,情景如圖1-33所示。

覆蓋的結果使得fun1函數在返回並恢復現場時出現了問題。

咱們先來看右邊的程序,跳轉回main函數,正常恢復,情景如圖1-34所示。

再看左邊的程序,棧底地址值被覆蓋了,ebp會獲得一個亂值,再也不指向main函數的棧底,另外,因爲fun1函數執行後返回地址已經被覆蓋,並且覆蓋的數值正好是fun2函數的起始地址,將這個數據傳遞給eip,那麼eip天然跳轉到fun2函數執行,至關於調用了fun2函數,也就輸出了fun2函數的打印信息。同時,ebp成了亂值,程序最終將產生段錯誤,情景如圖1-35所示。

相關文章
相關標籤/搜索