緩衝區溢出攻擊與堆疊保護

作者 | 陸小風

來源 | 碼農的荒島求生

在上一篇文章《進程切換的本質是什麼?》中舉了一個示例,也就是這段程式碼:

    #include <stdio.h>#include <stdlib.h>
    void funcC() { printf("jump to funcC !!!\n") ; exit(-1) ;}
    void funcB() { long *p = NULL ; p = (long*)&p ; *(p+2) = (long)funcC ;}
    void funcA() { funcB();}
    int main() { funcA() ; return 0 ;}

    有同學在微信群裡問不能在自己的機器上覆現,並給出了編譯後的機器指令:

      00000000004005ee <funcB>: 4005ee: 55 push %rbp 4005ef: 48 89 e5 mov %rsp,%rbp 4005f2: 48 83 ec 10 sub $0x10,%rsp 4005f6: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax 4005fd: 00 00 4005ff: 48 89 45 f8 mov %rax,-0x8(%rbp) 400603: 31 c0 xor %eax,%eax 400605: 48 c7 45 f0 00 00 00 movq $0x0,-0x10(%rbp) 40060c: 00 40060d: 48 8d 45 f0 lea -0x10(%rbp),%rax 400611: 48 89 45 f0 mov %rax,-0x10(%rbp) 400615: 48 8b 45 f0 mov -0x10(%rbp),%rax 400619: 48 83 c0 10 add $0x10,%rax 40061d: ba d6 05 40 00 mov $0x4005d6,%edx 400622: 48 89 10 mov %rdx,(%rax) 400625: 90 nop 400626: 48 8b 45 f8 mov -0x8(%rbp),%rax 40062a: 64 48 33 04 25 28 00 xor %fs:0x28,%rax 400631: 00 00 400633: 74 05 je 40063a <funcB+0x4c> 400635: e8 66 fe ff ff callq 4004a0 <__stack_chk_fail@plt> 40063a: c9 leaveq 40063b: c3 retq

      仔細看這段程式碼,有這樣一段可疑的指令:

        mov %fs:0x28,%raxmov %rax,-0x8(%rbp)

        這兩行指令將fs:[0x28] (段定址的方式)處的值push到了調用棧上(%rbp偏移8位元組),並在函數即將返回的時候又檢查了一遍該值有沒有被修改:

          mov -0x8(%rbp),%raxxor %fs:0x28,%rax

          接下來如果保存到棧上的值不等於fs:[0x28]處的值(xor指令進行比較)那麼跳轉到__stack_chk_fail函數,我們的疑問是為什麼要有這麼一遍檢查呢?

          本質上我們在開頭給出的程式碼相對於緩衝區溢出攻擊,做法是修改上一個棧幀的返回地址,將其修改為某個特定地址(駭客希望跳轉到的地方);

          在開頭的這段程式碼中本來funcA函數調用完funcB後需要返回funcA,但在我們的「精心」設計下調用完funcB後卻跳轉到了funcC,那麼我們有沒有辦法防範這種攻擊呢?

          答案是肯定的,這種方法要追溯到很久很久以前。

          在上世紀初,煤礦開採是一項很危險的工作,因為煤礦中的有毒氣體通常極難被人類察覺,這給礦工的生命帶來很大的威脅,而金絲雀對毒氣非常敏感,這樣礦工可以利用金絲雀來監控礦區,從而提早發現險情。

          這裡也是一樣的道理,我們可以在棧區中放置一個「金絲雀」(fs:[0x28]處的值):

          當函數返回時我們會再次拿fs:[0x28]處的值與棧上的「金絲雀」進行對比,一旦發現這兩個值不同我們就可以認為當前的棧已經被破壞了,由於棧上的資料已然不可信,因此我們必須及早撤離礦區,也就是調用__stack_chk_fail函數提前終止進程。

          而金絲雀也就是fs:[0x28]是隨機產生的(每次程序運行時都不一樣),因此攻擊者很難提前知道該值是多少。

          當然我們也可以看到,添加堆疊保護功能需要增加額外的機器指令,這些也會稍稍對性能產生影響,代價就是需要額外多執行一部分機器指令。

          這就是編譯器的堆疊保護功能,當然這個功能也是可以去掉的,編譯時添加-fno-stack-protector編譯選項(在這裡感謝小風哥微信技術群裡同學的提示),這樣即可關閉堆疊保護功能,生成的程式碼就可以復現上一篇文章《進程切換的本質是什麼?》中提到的效果了。

          怎麼樣,想成為駭客還是沒那麼容易吧,就好比只有真正理解法律才能鑽空子一樣,只有真正理解計算機的工作原理才能hack它,當然,想成為頂尖駭客只有對計算機的理解還不夠,你還需要有想象力。

          點點贊

          點點贊

          點在看

          點在看

          相關文章

          震驚!C 語言字串處理有很多坑?

          震驚!C 語言字串處理有很多坑?

          【CSDN 編者按】毋庸置疑,在使用 C 字串時必須小心,否則你就會因為各種的未定義行為而感到頭疼。 原文連結:https://www.de...

          為什麼要避免使用 libc

          為什麼要避免使用 libc

          【CSDN 編者按】libc 是 Linux 下的標準 C 庫,也是初學者寫 hello world 包時含有的頭檔案 #include <...

          不要再用 C/C++ 的這種說法了!

          不要再用 C/C++ 的這種說法了!

          我們對「C/C++」這種寫法或說法似乎在無形之中早已習以為常,然而,這種做法真的是對的嗎? 在今天這篇文章中,有開發者呼籲應該立即停止使用「...

          Python 真的很糟糕嗎?

          Python 真的很糟糕嗎?

          隨著 AI 的發展,憑藉易學易用的語法、豐富的庫和框架,Python 在機器學習、深度學習、自然語言處理和資料科學等領域有著廣泛的應用。然而...