2017-04-12 92 views
0

這是我編譯的C代碼:爲什麼緩衝區不會溢出這個代碼?

#include <stdio.h> 
#include <stdlib.h> 

int main(){ 
long val=0x41414141; 
char buf[20]; 

printf("Correct val's value from 0x41414141 -> 0xdeadbeef!\n"); 
printf("Here is your chance: "); 
scanf("%24s",&buf); 

printf("buf: %s\n",buf); 
printf("val: 0x%08x\n",val); 

if(val==0xdeadbeef) 
system("/bin/sh"); 
else { 
printf("WAY OFF!!!!\n"); 
exit(1); 
} 

return 0; 
} 

在這裏,我期待的溢出long val如果用戶輸入字符串24個字符長,在val更改值。但即使字符串足夠長,它也不會溢出。有人可以解釋這種行爲嗎?

我在macOS上。這是gcc -v吐出:

Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/usr/include/c++/4.2.1 
Apple LLVM version 8.0.0 (clang-800.0.42.1) 
Target: x86_64-apple-darwin16.0.0 
Thread model: posix 
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin 

此外,谷歌上搜索了一下後,我試着用這些標誌GCC:

gcc -g -fno-stack-protector -D_FORTIFY_SOURCE=0 -o overflow_example overflow_example.c 

的是,結果是一樣的。

此代碼是narniaoverthewire的戰爭遊戲挑戰的一部分。我設法在他們的遠程外殼上解決了這個挑戰,它的行爲和預期的一樣。現在,我試圖在我的本地系統上重現這個相同的挑戰並面臨這個問題。請幫忙。

編輯:對於UB大喊大叫的人:就像我說的,這是overthewire上要解決的挑戰之一,所以它不能有UB。有一些博客(here's on I found)爲這個挑戰提供了演練,併爲提供了合理的邏輯解釋,爲什麼代碼的行爲方式與此相同,我同意這一點。我也明白,編譯後的二進制文件是依賴於平臺的。 那麼,我該怎麼做才能生成這個在本地系統上可能溢出的二進制文件?

+5

未定義的行爲=未定義的行爲。無法保證這些變量在內存中分配的位置。 – Lundin

+0

@Lundin先生,請強調這兩點不是相關的。 –

+5

您正在利用*未定義行爲*,未定義行爲的定義未定義。另外,由編譯器創建的堆棧佈局很大程度上取決於編譯器,而像這樣的破解可能無法在所有編譯器上運行。特別是如果有一些優化正在進行(這可能完全優化'val'變量)。 –

回答

2

顯然,變量會在您的mac上以不同的方式佈局。

將它們包裝在一個結構中將確保它們按您想要的順序放置。

由於存在填充的可能性,我們將其關閉。對於gcc,預編譯器directe #pragma pack控制結構打包。

int main(){ 
#pragma pack(1) 
    struct { 
    char buf[20]; 
    long val=0x41414141; 
    } s; 
#pragma pack() 
    printf("Correct val's value from 0x41414141 -> 0xdeadbeef!\n"); 
    printf("Here is your chance: "); 
    scanf("%24s",&s.buf); 

    printf("buf: %s\n",s.buf); 
    printf("val: 0x%08x\n",s.val); 

    if(s.val==0xdeadbeef) 
    system("/bin/sh"); 
    else { 
    printf("WAY OFF!!!!\n"); 
    exit(1); 
    } 

    return 0; 
} 
+0

是的,包裝他們確保他們的地方。我這樣做了,在這兩種情況下都打印了他們的地址('printf(「%p \ n」,(void *)&a);'),這兩個地址之間的差異是24字節,這是預期的。即使如此,輸入24個字符的長字符串也不會覆蓋'val' –

+0

我期望地址之間的差異爲20個字節,而不是24個。有時編譯器會在使用調試標誌時在變量之間插入額外的字節,因此請嘗試跳過編譯命令中的'-g' –

+0

是的,我同意差異應該是20個字節,即使刪除了-g標誌,它仍然是24個字節,而不是掃描24個字符,如果我掃描28個字符並輸入28字符長的字符串,最後4個字節覆蓋'val'(這是有道理的)現在我明白了爲什麼二進制的行爲是id的方式,這很有幫助,現在如果你能解釋這一點,爲什麼我的編譯器在這兩個變量之間插入額外的4個字節? –

3

這是未定義的行爲,因爲C函數不檢查一個參數是否對於它的緩衝區是否太大。

+0

這不是未定義的行爲。預計此代碼會導致溢出。就像我提到的,這是來自overthewire.org的挑戰。我設法在他們的遠程外殼上解決它。我理解了內存佈局,預測了輸入,手工輸入了輸入,果然,var被覆蓋(用0xdeadbeef)產生了shell!我的問題是:爲什麼我無法在本地系統上重現這個相同的問題?我錯過了什麼?我應該從我的編譯器中刪除什麼樣的優化(如果有的話)?另外,對於scanf函數,它將讀取24個字符,不多不少。 –

+2

@JayBhavsar:實際上它*是*未定義的行爲 - 事實上,你在一個場景中實現了這個漏洞利用工作並沒有證明什麼 - 你對於未定義的行爲如何發揮作出了很多假設,在一個案例中碰巧爲你工作,但在一般情況下不能依賴。 –

0

我不確定你的意思。

1)你是否擔心你的輸入字符串會產生一個數字,這個數字太長而無法存儲。這種情況不會發生,數字將簡單地包裹起來。 2)你是否擔心你會在buf的界限之外讀到內存?在C中,這會產生未定義的行爲,不一定是崩潰。

buf位於堆棧上(可能與堆一樣),因此您可以從buf啓動的地址繼續寫入內存。編譯器將生成不會爲您進行邊界檢查的代碼。所以如果你超過了20byte,你最終會開始覆蓋其他不屬於你爲buf預留的內存塊的內存部分。