格式化字串漏洞簡介

2021-07-09 02:25:29 字數 3787 閱讀 4598

格式化字串,也是一種比較常見的漏洞型別。會觸發該漏洞的函式很有限。主要就是printf還有sprintf,fprintf等等c庫中print家族的函式。 我們先來看看printf的函式宣告

int printf(const char* format,...)
這個是每個學過c語言的人一定會知道、會使用的函式。先是乙個字串指標,它指向的乙個format字串。後面是個數可變的引數。

一般人可能會這麼用它

char

str[100];

scanf("%s",str);

printf("%s",str);

這個程式沒有問題。然後會有一些人為了偷懶會寫成這種樣子

char

str[100];

scanf("%s",str);

printf(str)

這個程式在printf處用了一種偷懶的寫法。這看起來是沒有什麼問題。但是卻產生了乙個非常嚴重的漏洞。

千萬不要將printf中的format字串的操縱權交給使用者。保證printf函式的第乙個引數是不可變的,在程式設計師的掌握中的。

這裡我們就要詳細的講述一下printf的執行原理了。因為64位上printf函式的行為發生了許多變化。這裡暫時不進行說明。不過如果清楚了漏洞的產生原因,依然可以使用此漏洞。

先看看正常的情況

#include 

int main(void)

首先,看看彙編的原始碼,額暫時搞不到,還是手寫吧

.data

str db "test",0

format db "%d

%d%d

%d%s",0

.code

push str

push

21hpush

8push

6push

5push

format

call printf

差不多就這樣。這個時候的棧就會是這個樣子的。

-00000003                 db ? ;

-00000002 db ? ;

-00000001 db ? ;

+00000000 s db 4 dup(?)

+00000004 r db 4 dup(?)

+00000008 format db 4 ;"%d%d%d%c"

+0000000c %d db 4 ; 4

+00000010 %d db 4 ; 6

+00000014 %d db 4 ; 8

+00000018 %x db 4 ; 0x21

+0000001c %s db 4 ; "test"

+0000001c ; end of stack variables

根據cdecl的函式呼叫規定,函式的從最右邊的引數開始,逐個壓棧。如果要傳入的是乙個字串,那麼就將字串的指標壓棧。這一切都井井有條的進行著。如果是一般的函式,函式的呼叫者和被呼叫者都應該知道函式的引數個數以及每個引數的型別。對於不相同的型別,編譯器還會自動的進行型別的轉換,或者是發生編譯錯誤,提醒程式的編寫者。

但是,到了printf函式,一切就不一樣了。因為printf是c語言中少有的支援可變引數的庫函式。對於可變引數的函式,一切就變得模糊了起來。函式的呼叫者可以自由的指定函式引數的數量和型別,被呼叫者無法知道在函式呼叫之前到底有多少引數被壓入棧幀當中。所以printf函式要求傳入乙個format引數用以指定到底有多少,怎麼樣的引數被傳入其中。然後它就會忠實的按照函式的呼叫者傳入的格式乙個乙個的列印出資料。

當然這會產生乙個嚴重的問題。如果我們無意或者有意,在format中,或者說我們要求printf列印的資料數量大於我們所給的數量會怎樣?printf函式不可能知道棧幀中哪一些資料是傳入它引數,哪些是屬於函式呼叫者的資料。看下面段**

#include 

int main(void)

這裡我們只給了printf乙個引數,卻讓其列印出12個int型別的資料,我們編譯執行看看會有什麼結果。

這裡可以看到,printf忠實的按照我們意願列印出了12個數值。這些數值不是我們輸入的引數,而是儲存在棧中的其他的數值。通過這個特性,黑客們就創造出了格式化字串的漏洞。

剛才也看到了printf可以列印出呼叫者棧幀中的資訊。在0day攻擊當中,如何獲得對方記憶體中的資料是非常重要的乙個技巧,而格式化字串漏洞的其中乙個利用方法便是能夠獲得記憶體中那些本不應該被我們知道的資料。這個過程我們稱之為leak記憶體。0day攻擊中一種重要的方法ret to libc就是以leak基位址為前提的。

只要我們在format中填入足夠的引數,那麼printf就可以打出儲存在棧中的,那些本不能被知道的資訊。只要計算好format在棧中的位址與需要leak的資訊位址之差。就可以得到想要的資料

比如format在0x20處而dest資料在0x00處。他們一共相差32個位元組,那麼我們就可以構造」%f%f%f%d,%x」這樣的字串。逗號前面會的」%f%f%f%d」可以列印出比foramt更高位的28個位元組的資料,當然這不是我們想要的。然後最後的乙個%x便可以以16進製制的形式列印出我們想要的資料了。

然後,更進一步,我們知道格式化字串還有%s引數。那麼,如果在棧中儲存有指向我們感興趣資料的指標,我們就可以在列印指標的時候使用乙個%s來列印別的地方的內容。而且一般的程式都會將使用者輸入的資料儲存在棧上。這就給了我們乙個構造指標的機會,再結合格式化字串漏洞,幾乎可以得到所有記憶體資料。

#include 

int main(void)

這是一段有點神奇的**,讓我們看看它的執行結果。

可以發現a的值被printf函式修改為了7。這就是%n的功效了。這是乙個不常用到的引數。它的功能是將%n之前printf已經列印的字元個數賦值給傳入的指標。通過%n我們就可以修改記憶體中的值了。和%sleak記憶體一樣,只要棧中有我們需要修改的記憶體的位址就可以使用格式化字串的漏洞修改它。

當然,如果需要修改的資料是相當大的數值時,我們可以使用%02333d這種形式。在列印數值右側用0補齊不足位數的方式來補齊足。

可以看出,格式化字串可以修改的記憶體範圍更加廣。只要構造出指標,就可以改寫記憶體中的任何數值。和棧溢位的地毯轟炸不同。這種一次只能改寫乙個dword大小的記憶體的攻擊方式更加精而致命

最好的學習方法就是實踐,現在我們就來實驗一下格式字串漏洞的功效。 首先,**

#include 

int main(void)

return

0;}

我們可以需要修改的變數是flag,而指標p便是指向flag的指標。所以可以通過p來修改flag的值為2000,從而達到我們列印出good!!的目標

%010x

%010x

%010x

%01970x

%n

這個便是我構造出的poc,很短,但是很強悍(→_→)。 那麼我們來看看效果吧

格式化字串漏洞

在編寫程式時由於編寫的不規範有可能產生這個漏洞。下面乙個例子 includeint a 2 int main 編譯時使用 gcc test.c m32表示編譯成32位的程式上面這個例子便是乙個很簡單的格式化字串漏洞,產生格式化字串漏洞需要兩個條件 下面講解printf輸出的原理 eg name su...

格式化字串漏洞歸納

主要是構建框架,沒有詳細闡述內容 分為兩部分,漏洞原理與利用方式 通常來說,利用格式化字串漏洞使得程式崩潰是最為簡單的利用方式,因為我們只需要輸入若干個 s即可。這是因為棧上不可能每個值都對應了合法的位址,所以總是會有某個位址可以使得程式崩潰。這一利用,雖然攻擊者本身似乎並不能控制程式,但是這樣卻可...

pwn 格式化字串漏洞

原理 因為沒有正確使用printf 函式 正確使用 printf s str 不正規使用 printf str 控制字串str可以爆出stack內內容從而實現任意位址讀或者任意位址寫 入門題01 18行存在格式化字串漏洞 只需輸入在hello之後輸入password所在位址,接收password值再...