[英]How does Visual Studio 2013 detect buffer overrun
Visual Studio 2013 C ++項目具有/GS
開關,可在運行時啟用緩沖區安全檢查驗證。 自升級到VS 2013以來,我們遇到了更多STATUS_STACK_BUFFER_OVERRUN錯誤,並懷疑它與改進的新編譯器中緩沖區溢出檢查有關。 我一直在嘗試驗證這一點,並更好地了解如何檢測緩沖區溢出。 即使由語句更新的內存僅更改同一范圍內堆棧上另一個局部變量的內容,也會報告緩沖區溢出這一事實讓我感到困惑! 因此,它必須不僅檢查更改不會破壞本地變量“不擁有”的內存,而且還要檢查更改不會影響除分配給單個更新語句引用的那個之外的任何本地變量。 這是如何運作的? 自VS 2010以來它有變化嗎?
編輯:這是一個例子,說明了Mysticial的解釋未涉及的案例:
void TestFunc1();
int _tmain(int argc, _TCHAR* argv[])
{
TestFunc1();
return 0;
}
void TestFunc1()
{
char buffer1[4] = ("123");
char buffer2[4] = ("456");
int diff = buffer1 - buffer2;
printf("%d\n", diff);
getchar();
buffer2[4] = '\0';
}
輸出為4
表示即將被覆蓋的內存在buffer1
的范圍內(緊接在buffer2
之后),但程序終止時緩沖區溢出。 從技術上講,它應該被認為是緩沖區溢出,但我不知道它是如何被檢測到的,因為它仍然在局部變量的存儲內,並沒有真正破壞局部變量之外的任何東西。
帶有內存布局的屏幕截圖證明了這一點。 步進一行后,程序因緩沖區溢出錯誤而中止。
我只是想相同的代碼在VS 2010中,雖然調試模式陷入緩沖區溢出(帶緩沖12的偏移量),在發行模式下,它沒有趕上它(帶緩沖的8偏移)。 所以我認為VS 2013收緊了/GS
交換機的行為。
編輯2:我設法用這段代碼偷偷溜過VS 2013范圍檢查。 它仍然沒有檢測到更新一個局部變量的嘗試實際更新了另一個:
void TestFunc()
{
char buffer1[4] = "123";
char buffer2[4] = "456";
int diff;
if (buffer1 < buffer2)
{
puts("Sequence 1,2");
diff = buffer2 - buffer1;
}
else
{
puts("Sequence 2,1");
diff = buffer1 - buffer2;
}
printf("Offset: %d\n", diff);
switch (getchar())
{
case '1':
puts("Updating buffer 1");
buffer1[diff] = '!';
break;
case '2':
puts("Updating buffer 2");
buffer2[diff] = '!';
break;
}
getchar(); // Eat enter keypress
printf("%s,%s\n", buffer1, buffer2);
}
您正在看到/ GS機制的改進,首先添加到VS2012。 最初/ GS可以檢測到緩沖區溢出但是仍然存在一個循環漏洞,攻擊代碼可以攻擊堆棧但繞過cookie。 大概是這樣的:
void foo(int index, char value) {
char buf[256];
buf[index] = value;
}
如果攻擊者可以操縱索引的值,則cookie無效。 此代碼現在重寫為:
void foo(int index, char value) {
char buf[256];
buf[index] = value;
if (index >= 256) __report_rangefailure();
}
只是簡單的索引檢查。 當觸發時,如果沒有附加調試器,則會立即使用__fastfail()終止應用程序。 Backgrounder 就在這里 。
從Visual Studio 2013中/GS
上的MSDN頁面 :
安全檢查
在編譯器識別為緩沖區溢出問題的函數上,編譯器在返回地址之前在堆棧上分配空間。 在函數入口上,分配的空間加載了一個安全cookie,該cookie在模塊加載時計算一次。 在函數退出時,以及在64位操作系統上的幀展開期間,將調用輔助函數以確保cookie的值仍然相同。 不同的值表示可能發生了堆棧的覆蓋。 如果檢測到不同的值,則終止該過程。
有關更多詳細信息,同一頁面指的是編譯器安全檢查深度 :
什么/ GS做
/ GS開關在緩沖區和返回地址之間提供“減速帶”或cookie。 如果溢出寫入返回地址,則必須覆蓋放在它和緩沖區之間的cookie,從而產生新的堆棧布局:
- 功能參數
- 功能返回地址
- 幀指針
- 曲奇餅
- 異常處理程序框架
- 本地聲明的變量和緩沖區
- Callee保存寄存器
稍后將更詳細地檢查cookie。 函數的執行確實隨着這些安全檢查而改變。 首先,當調用函數時,要執行的第一個指令位於函數的序言中。 prolog至少為堆棧上的局部變量分配空間,例如以下指令:
sub esp, 20h
該指令留出32個字節供函數中的局部變量使用。 當使用/ GS編譯函數時,函數prolog將預留另外四個字節並添加另外三個指令,如下所示:
sub esp,24h
mov eax,dword ptr [___security_cookie (408040h)]
xor eax,dword ptr [esp+24h]
mov dword ptr [esp+20h],eax
prolog包含一條指令,用於獲取cookie的副本,然后是執行cookie的邏輯xor和返回地址的指令,最后是一條指令,用於將cookie存儲在返回地址正下方的堆棧中。 從現在開始,該函數將按正常方式執行。 當一個函數返回時,最后要執行的是函數的epilog,它與prolog相反。 如果沒有安全檢查,它將回收堆棧空間並返回,如下面的說明:
add esp,20h
ret
使用/ GS編譯時,安全檢查也會放在epilog中:
mov ecx,dword ptr [esp+20h]
xor ecx,dword ptr [esp+24h]
add esp,24h
jmp __security_check_cookie (4010B2h)
檢索堆棧的cookie副本,然后使用帶有返回地址的XOR指令進行檢索。 ECX寄存器應包含與__security_cookie變量中存儲的原始cookie匹配的值。 然后回收堆棧空間,然后執行__security_check_cookie例程的JMP指令,而不是執行RET指令。
__security_check_cookie例程很簡單:如果cookie沒有改變,它會執行RET指令並結束函數調用。 如果cookie無法匹配,則例程調用report_failure。 然后,report_failure函數調用__security_error_handler(_SECERR_BUFFER_OVERRUN,NULL)。 這兩個函數都在C運行時(CRT)源文件的seccook.c文件中定義。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.