簡體   English   中英

預期的緩沖區溢出並不總是導致程序崩潰

[英]An intended buffer overflow that does not always cause the program to crash

考慮以下最小C程序:

案例編號1

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

void foo(char* s)
{
    char buffer[10];
    strcpy(buffer,s);
}

int main(void)
{
    foo("01234567890134567");
}

這不會導致崩潰轉儲

如果只添加一個字符,那么新的主要是:

案例2

void main()
{
    foo("012345678901345678");
                          ^   
}

程序因Segmentation故障而崩潰。

除了堆棧中保留的10個字符外,還有一個額外的空間可容納8個額外字符。 因此第一個程序不會崩潰。 但是,如果再添​​加一個字符,則會開始訪問無效內存。 我的問題是:

  1. 為什么我們在堆棧中保留了這些額外的8個字符?
  2. 這與內存中的char數據類型對齊有何關聯?

我在這種情況下的另一個疑問是操作系統(在這種情況下是Windows)如何檢測到錯誤的內存訪問? 通常,根據Windows文檔,默認堆棧大小為1MB 堆棧大小 所以我沒有看到操作系統如何檢測到被訪問的地址是否在進程內存之外,特別是當最小頁面大小通常為4k時。 在這種情況下操作系統是否使用SP來檢查地址?

PD:我正在使用以下環境進行測試
Cygwin的
GCC 4.8.3
Windows 7操作系統

編輯

這是從http://gcc.godbolt.org/#生成的程序集,但是使用GCC 4.8.2,我在可用的編譯器中看不到GCC 4.8.3。 但我想生成的代碼應該是相似的。 我沒有任何標志構建代碼。 我希望擁有匯編專業知識的人能夠了解foo函數中發生的事情以及為什么額外的char會導致seg錯誤

    foo(char*):
    pushq   %rbp
    movq    %rsp, %rbp
    subq    $48, %rsp
    movq    %rdi, -40(%rbp)
    movq    %fs:40, %rax
    movq    %rax, -8(%rbp)
    xorl    %eax, %eax
    movq    -40(%rbp), %rdx
    leaq    -32(%rbp), %rax
    movq    %rdx, %rsi
    movq    %rax, %rdi
    call    strcpy
    movq    -8(%rbp), %rax
    xorq    %fs:40, %rax
    je  .L2
    call    __stack_chk_fail
.L2:
    leave
    ret
.LC0:
    .string "01234567890134567"
main:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $.LC0, %edi
    call    foo(char*)
    movl    $0, %eax
    popq    %rbp
    ret

我相信你明白你已經實現了導致未定義行為的東西。 所以很難回答為什么它失敗的額外字符串而不是原始字符串。 它可能與內部編譯器實現+受編譯標志(如對齊,優化等)的影響有關。

您可以嘗試反匯編二進制文件或創建匯編代碼,並查看緩沖區在堆棧中的確切位置。 您可以使用不同的優化級別執行相同操作,以檢查程序集代碼和行為中的更改。

操作系統(在這種情況下是Windows)如何檢測到錯誤的內存訪問? 通常,根據Windows文檔,默認堆棧大小為1MB堆棧大小。 所以我沒有看到操作系統如何檢測到被訪問的地址是否在進程內存之外,特別是當最小頁面大小通常為4k時。 在這種情況下操作系統是否使用SP來檢查地址?

操作系統不會監視您執行的代碼。 HW(CPU)執行(因為它執行此代碼)。 一旦您的代碼嘗試訪問未為您的進程分配的地址(未您的程序的OS 映射 ),操作系統將獲得指示,因為HW將觸發#PF(頁面錯誤)異常。 另一種情況是您嘗試訪問為您分配但具有不正確權限的地址(例如,您嘗試從沒有'執行'權限的DATA頁面執行二進制數據)或轉到CODE頁面但是有錯誤offset和你讀的指令不存在或者(甚至更糟)它存在並解碼為你不期望的東西(我們之前是否說過Undefined Behavior?)。

一般來說,你的代碼很可能不會在strcpy失敗(如果你寫了足夠的數據來訪問一些禁止的地址,但很可能不是這種情況) - 當它從foo函數返回時它會失敗。 strcpy只是覆蓋了指向foo函數后指向下一條指令的下一條指令指針。 因此,指令指針用“012345678901345678”字符串中的數據填充,並嘗試從'junky'地址獲取下一條指令,並由於上述原因而失敗。

這種“方法”/錯誤被稱為“ 緩沖區溢出攻擊 ”,並且在黑客中廣泛使用以使您的代碼(以及更常見的以更高權限執行的OS / BIOS / VMM / SMM代碼)執行黑客提供的惡意代碼。 只需確保用您預先准備的代碼的地址覆蓋指令指針。

官方,系統無關的答案是:

您的代碼將數據寫入目標數組的末尾,行為未定義,任何事情都可能發生,包括任何內容空間探測器在火星表面上崩潰 您的觀察結果在緩沖區末端之外的8個字節內沒有明顯影響,並且超出此范圍的分段故障崩潰可能是未定義行為的影響,完全在預期結果范圍內。

您感興趣的額外實施細節:

實際行為取決於許多情況,例如您使用的編譯器,OS和ABI(應用程序二進制接口)等。

您的程序在64位Windows環境中編譯和執行。 在這個環境中,堆棧在64位邊界或可能的16字節邊界上保持對齊,以允許從/向堆棧位置直接加載和存儲MMX寄存器。 數組buffer[10]在堆棧上占用16個字節。 給定如何在此處理器上建立堆棧,它將位於函數foo使用的位置下方,以將任何已保存的寄存器和返回地址存儲到調用函數main 額外的6個字節是在數組之前還是之后是編譯器的選擇。 它可以將此空間用於其他局部變量,或者只是忽略它。

如果填充在數組之后,超出buffer末尾的buffer對於最多6個字節可能是無害的,對於另外8個字節可能沒有任何明顯的影響(破壞保存的rbp寄存器,在調用之后在main未使用),但是將會開始產生不良副作用,因為你將覆蓋返回地址。

當您覆蓋返回地址時,處理器不會從函數foo返回到調用者main ,而是返回到存儲在堆棧中的任何地址,並且被違規代碼破壞。 如果這個損壞的地址指向可執行代碼,那么該代碼將被執行並帶來潛在的有害后果......黑客正是這樣做的:他們非常謹慎地制作一個漏洞,設法將某些有害代碼存儲在可執行內存中的已知位置並利用緩沖區溢出代碼,用於將所述代碼的地址存儲在返回地址的堆棧位置中。

在您的情況下,損壞的返回地址指向的位置可能無法執行,從而觸發您觀察到的分段錯誤。

我建議您嘗試在此站點上編譯代碼,以查看在各種編譯器選項下生成的實際匯編代碼: http//gcc.godbolt.org/#

堆棧中的下一個條目是64位系統中的函數地址,必須與8對齊,因此有足夠的空間容納16個字符。

您可以通過在數組后聲明一個int變量來驗證這一點。 Int將與4對齊,並且字符空間將減少,因此程序將在較低的數字上崩潰。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM