2020年7月27日 星期一

Format string attack

Format string (格式化字串)是程式的API (printf、snprintf 、puts等等)指定輸出參數的格式與相對位置的字串參數,例如%d、%x、%f和%s等等,都是代表在輸出參數上顯示什麼樣的數值格式。

一、Format string漏洞程式碼

此漏洞程式,主要是將輸入參數(argv[1])複製(strcpy)到區域變數的buf,然後透過printf直接將buf裡的字串印出到螢幕上,因此我們在輸入參數時,使用%x將堆疊上的資訊印出來,達成資訊洩漏的弱點,另外也可以使用%n來寫入資料到記憶體,這樣就能將數值隨意寫入記憶體。

#include <stdio.h>

void main(int argc, char **argv)
{
   char buf[1024] = {0};

   strcpy(buf, argv[1]);

   printf(buf);

   system("exit");
}

二、檢視堆疊上的資訊

我們使用AAAA來檢測它是在堆疊上的哪個位置,由於參數使用%x,它會從堆疊上獲取4 Bytes的數值將它印出到螢幕上,我們發現在堆疊的第4個位置有顯示A的Ascii(0x41),因此我們可以寫入某個記憶體位址到堆疊上,然後透過%n將數值寫入此記憶體位址。



三、查看GOT (Global Offset Table)

Linux在調用API時,會從GOT裡面獲取該API的調用位址,因此我們可以覆寫目標API的GOT,就能控制調用的API。

system的在GOT的位址為0x080496ec。



四、覆寫system的GOT

進入gdb後,在0x0804846a下中斷點,我們可以觀察在調用printf後,是否有成功將數值覆寫system的GOT。



如果直接輸入參數,它會在堆疊上寫入Ascii的數值,那麼我們要寫入16進制的數值到堆疊上就必須使用shell指令來讓它轉換,因此我們透過shell指令間接調用python,在參數的前綴使用\x,代表它是16進制的數值,然後我們寫入0x080496ec到堆疊上,由於x86記憶體是Little endian,因此寫入順序是反過來的。

我們在第二步驟已知參數前4 Bytes的數值,會寫在堆疊第4個位置上,我們會在參數設置%4$n,%n會寫入數值到記憶體位址,其中4$是指定堆疊的第4個位置,我們可以看到0x80496ec有被寫入4的數值,因為%n要寫入多少數值會由%n前面的字串占有多少Bytes,從我們的例子來看,%n前面的字串有\xec\x96\x04\x08,種共占4 Bytes。



接下來我們預計將0x12345678寫到system的GOT。

%n要寫入多少的數值是由前面占的字串Bytes總數,我們在%x的x前面加上數值,就能增加它的Bytes數,不過它並不能一次寫入4 Bytes數值的長度,因此我們僅能2個2 Bytes來填入4 Bytes,那麼我們會透過%hn來寫入2 Bytes。



我們要如何計算將0x5678寫入堆疊的第4個位置,並且將0x1234寫入堆疊的第5個位置 ?

0x5678減去前面占據的Bytes數。(0x5678 – 8 = 0x5670,將0x5670轉成十進制為22128)



0x1234減去前面占據的Bytes數後,會變成負數,因此在0x1234前面加1就會是正數。(0x11234 – 0x5678 = 0xBBBC,將0xBBBC轉成十進制為48060)



因為我們的參數有%22128x和%48060x,它會在執行printf後,在螢幕上印出0x12345678個空白。

最後成功將0x12345678覆寫到system的GOT,