格式化字串小實驗

2021-07-07 01:18:56 字數 3033 閱讀 6696

在**裡看到格式化字串攻擊的說明,不是很理解,決定實踐一下,結合tim newsham的format string attacks(

進行實驗。這裡只測試了一下%x和%n引數,更多種類的格式化字串攻擊請查閱其他資料。至於測試的環境,應當是c編譯環境都可以,但是在實驗過程中,我發現dev c++預設不對%n進行預期操作,而是像遇到停止符一樣的效果,查了一下發現由於printf函式在使用%n存在安全問題,所以vs2008在預設的情況下禁止這樣使用(

可能dev c++也是如此,但目前沒有找到開啟的開關,轉向gcc。

環境:ubuntu 12.04 32位,核心3.8.0。

為了演示簡單,這裡關閉了位址隨機化。

格式化函式:用於將程式語言的原型變數轉換為使用者可讀的字串形式,如fprintf,printf,sprintf,snprintf,vfprintf,vprintf,vsprintf,vsnprintf,格式化字串引數包括%%,%p,%d,%c,%u,%x,%s,%n等,其中%x從棧中讀取資料,%s讀取字串,%n將當前列印出的字元數目寫入引數指定的位址。(詳見:

(1)先檢視一下缺少部分引數的情況,不難得知,這裡操作符與引數對應的情況是從左到右進行匹配,如果從右到左第乙個列印的應該是123的%x格式。

#include int main()

%d列印出123的值,暫且不知後面兩個%x列印了不知什麼資料,實際上是讀取了

棧中123引數位址後面(高位址)的兩個整數。

(2)無實參的情況

#include int main()

通過gcc簡單編譯執行,得到以下的結果。

這裡發現第二個printf列印出了a,b,c變數的位址,中間隔了乙個數之後,列印出了a,b,c的值,為什麼會這樣,不妨通過insight或gdb除錯檢視一下程式執行時的記憶體布局。這裡使用視覺化的insight進行檢視,儘管關閉了位址隨機化,但是insight除錯中的程式位址和程式實際執行的位址是不一樣的,會有乙個偏移,如下:

為了更具體地了解一下程式的運**況,檢視暫存器,得知esp為0xbffff650,檢視esp位址起始的記憶體布局。在第二個printf處設了斷點,結合彙編**一起看,可知0xbffff664~0xbffff66c依次是a,b,c變數,esp+4~+c分別是第乙個printf的引數:&a,&b,&c,esp處的0x8048510是格式化字串的位置,常量字串位址應該在資料段。第二個printf只是設定了0x8048520為格式化字串位址,main函式沒有為它傳入其他引數,而printf不知道,當printf解析到4個%x格式符時,自動讀取了比esp高的4個棧記憶體單元當作引數,因此列印出了我們看到的六個值,包括a,b,c的值。

(3)%n測試

在知道程式棧布局之後,我們嘗試改寫變數a的值,這裡把"hello world"的字元數即11寫入a。這裡printf("hello world%n\n");第乙個引數為格式化字串"hello world%n\n"位址,因為遇到了%n,將後乙個引數作為寫入位址,因為第乙個printf中將&a當作了第二個引數依然留在棧中,因此%n寫入了&a,改變了a的值。這個實驗說明了%n可能導致一些誤操作。

(4)模擬format string attack中的實驗

上乙個實驗中因為第乙個printf呼叫使用了&a作為位址,預設為第二個引數,很容易就改寫了a的值。但是一般的攻擊程式中可能不會在此前列印變數位址,更多的只是呼叫printf(字串),因此需要特殊構造的格式來進行攻擊。format string attack中利用snprintf,將argv[1]寫入buf,這裡的str直接靜態初始化了,通過printf來攻擊,原理都是類似的。這裡的a位址0xbffff6a4和%x是通過除錯和測驗事先知道的,如果填成隨意乙個位址執行程式可能會導致core dump。

#include int main()

(注意這裡的第乙個%x列印出的是d,為了使str的位址對齊4,緩衝區大小須是4的倍數,大小為20時空格分配有點不夠用了,和後面的值連在了一起。)這裡的%n對應printf認為的的第7個引數,剛好是str的低4個位元組,存放的是0xbffff6a4即a的位址,因此%n向a的位址寫入已輸出的位元組數31。

為了知道第多少個引數對應的是str和a,可以先通過多個%x列印棧布局得知。對於除錯得知a位址的方法是隨便填充乙個數作為前4位元組(中間不要出現0x00位元組,否則會被當作終止符),如\x12\x34\x56\x78,然後導致core dump,通過gdb 可執行程式 core檢視程式崩潰時的環境。

進入gdb之後看到程式在vfprintf(printf內部呼叫的)處終止了,具體哪一步出錯這裡沒有進一步**。

因為不是main的棧幀了,所以esp並不是main的esp,ebp儲存上乙個棧幀esp+4的位置,按道理即是printf的棧幀,不過我們還是可以在此附近檢視尋找是否有main棧幀的內容。\x12\x34\x56\x78%x%x %x %x %x%n的16進製制格式還是比較好辨認的,找到0x78563412,發現0xbffff6a8是str位址,這也與按順序推導的第乙個引數0xbffff690處的值一致,因為printf將str當作了格式化字串,因此str位址是第乙個引數,0xbffff6a4即為a的位址。這裡發現另乙個有意思的東西是這段**裡加入了canary的檢查,開始從gs載入值存在棧中,而最後通過異或操作進行比較,如果不同則呼叫__stack_chk_fail異常處理函式,而前面的測試**是沒有的。

字串格式化

sprintf snprintf snprintf std stringstream std strstream boost lexical cast boost format cstring format 1 sprintf 使用 sprintf 不安全,輕則破壞資料的準確性,重則程式崩潰。請看下...

格式化字串

通常在使用字串的時候,會對字串進行格式化,然後輸出或呼叫 一般我們使用替換標記對字串進行格式化 string str1 string.format add is 1,2,3 而且在c 中的替換標記可以以任意順序和次數出現在格式化字串中,但替換值是按順序排的,而且替換標記不能超出索引範圍 string...

字串格式化

例如 string s hello map.put target world string res format s,map 有什麼用呢?比如在some.properties中配置模板字串,但是如果用 這種方式,在配置了spring讀取properties注入變數的時候,這個變數就找不到會報錯。這個...