原理介紹¶
首先,對格式化字符串漏洞的原理進行簡單介紹。
格式化字符串函數介紹¶
格式化字符串函數可以接受可變數量的參數,並將第一個參數作爲格式化字符串,根據其來解析之後的參數。通俗來說,格式化字符串函數就是將計算機內存中表示的數據轉化爲我們人類可讀的字符串格式。幾乎所有的C/C++程序都會利用格式化字符串函數來輸出信息,調試程序,或者處理字符串。一般來說,格式化字符串在利用的時候主要分爲三個部分
- 格式化字符串函數
- 格式化字符串
- 後續參數,可選
這裏我們給出一個簡單的例子,其實相信大多數人都接觸過printf函數之類的。之後我們再一個一個進行介紹。
格式化字符串函數¶
常見的有格式化字符串函數有
- 輸入
- scanf
- 輸出
函數 | 基本介紹 |
---|---|
printf | 輸出到stdout |
fprintf | 輸出到指定FILE流 |
vprintf | 根據參數列表格式化輸出到 stdout |
vfprintf | 根據參數列表格式化輸出到指定FILE流 |
sprintf | 輸出到字符串 |
snprintf | 輸出指定字節數到字符串 |
vsprintf | 根據參數列表格式化輸出到字符串 |
vsnprintf | 根據參數列表格式化輸出指定字節到字符串 |
setproctitle | 設置argv |
syslog | 輸出日誌 |
err, verr, warn, vwarn等 | 。。。 |
格式化字符串¶
這裏我們瞭解一下格式化字符串的格式,其基本格式如下
%[parameter][flags][field width][.precision][length]type
- parameter
- n$,獲取格式化字符串中的指定參數
- flag
- field width
- 輸出的最小寬度
- precision
- 輸出的最大長度
- length,輸出的長度
- hh,輸出一個字節
- h,輸出一個雙字節
- type
- d/i,有符號整數
- u,無符號整數
- x/X,16進制unsigned int 。x使用小寫字母;X使用大寫字母。如果指定了精度,則輸出的數字不足時在左側補0。默認精度爲1。精度爲0且值爲0,則輸出爲空。
- o,8進制unsigned int 。如果指定了精度,則輸出的數字不足時在左側補0。默認精度爲1。精度爲0且值爲0,則輸出爲空。
- s,如果沒有用l標誌,輸出null結尾字符串直到精度規定的上限;如果沒有指定精度,則輸出所有字節。如果用了l標誌,則對應函數參數指向wchar_t型的數組,輸出時把每個寬字符轉化爲多字節字符,相當於調用wcrtomb 函數。
- c,如果沒有用l標誌,把int參數轉爲unsigned char型輸出;如果用了l標誌,把wint_t參數轉爲包含兩個元素的wchart_t數組,其中第一個元素包含要輸出的字符,第二個元素爲null寬字符。
- p, void *型,輸出對應變量的值。printf("%p",a)用地址的格式打印變量a的值,printf("%p", &a)打印變量a所在的地址。
- n,不輸出字符,但是把已經成功輸出的字符個數寫入對應的整型指針參數所指的變量。
- %, '
%
'字面值,不接受任何flags, width。
參數¶
就是相應的要輸出的變量。
格式化字符串漏洞原理¶
在一開始,我們就給出格式化字符串的基本介紹,這裏再說一些比較細緻的內容。我們上面說,格式化字符串函數是根據格式化字符串來進行解析的 。那麼相應的要被解析的參數的個數也自然是由這個格式化字符串所控制。比如說'%s'表明我們會輸出一個字符串參數。
我們再繼續以上面的爲例子進行介紹
對於這樣的例子,在進入printf函數的之前(即還沒有調用printf),棧上的佈局由高地址到低地址依次如下
some value
3.14
123456
addr of "red"
addr of format string: Color %s...
注:這裏我們假設3.14上面的值爲某個未知的值。
在進入printf之後,函數首先獲取第一個參數,一個一個讀取其字符會遇到兩種情況
- 當前字符不是%,直接輸出到相應標準輸出。
- 當前字符是%, 繼續讀取下一個字符
- 如果沒有字符,報錯
- 如果下一個字符是%,輸出%
- 否則根據相應的字符,獲取相應的參數,對其進行解析並輸出
那麼假設,此時我們在編寫程序時候,寫成了下面的樣子
printf("Color %s, Number %d, Float %4.2f");
此時我們可以發現我們並沒有提供參數,那麼程序會如何運行呢?程序照樣會運行,會將棧上存儲格式化字符串地址上面的三個變量分別解析爲
- 解析其地址對應的字符串
- 解析其內容對應的整形值
- 解析其內容對應的浮點值
對於2,3來說倒還無妨,但是對於對於1來說,如果提供了一個不可訪問地址,比如0,那麼程序就會因此而崩潰。
這基本就是格式化字符串漏洞的基本原理了。