天天看點

雪城大學資訊安全講義 七、格式化字元串漏洞七、格式化字元串漏洞

七、格式化字元串漏洞

原文: Format String Vulnerability 譯者: 飛龍
printf ( user_input );           

上面的代碼在 C 程式中十分常見。這一章中,我們會發現如果程式使用權限運作(例如 Set-UID 程式),可能造成什麼問題。

1 格式化字元串

  • 什麼是格式化字元串?
    printf ("The magic number is: %d\n", 1911);           
    被列印的文本是

    The magic number is:

    ,後面是格式化參數

    %d

    。它在輸出中由參數 1911 替換。是以輸出是這樣:

    The magic number is: 1911

    。除了

    %d

    ,還有幾種其它的格式化參數,每種都有不同的含義。下面的表格總結了這些格式化參數:
    參數 含義 傳遞方式
    ------------------------------------------
    
    %d 十進制 (int) 傳值 
    %u 無符号十進制 (unsigned int) 傳值 
    %x 十六集進制 (unsigned int) 傳值 
    %s 字元串 ((const) (unsigned) char *) 傳址 
    %n 目前為止寫入的字元數 (* int) 傳址           
  • 棧和它在格式化字元串中的作用

    格式化函數的行為格式化字元串控制。函數從棧上擷取由格式化字元串請求的參數。

    printf ("a has value %d, b has value %d, c is at address: %08x\n", a, b, &c);           
  • 如果格式化字元串和實際參數之間不比對,會如何?
    printf ("a has value %d, b has value %d, c is at address: %08x\n", a, b);           
    • 在上面的例子中,格式化字元串請求三個參數,但是程式實際上提供了兩個(也就是

      a

      b

      )。
    • 這個可以通過編譯器嘛?
      • 函數

        printf

        定義為參數長度可變的函數。是以,通過檢視參數數量,一切都正常。
      • 為了尋找不比對,編譯器需要了解

        printf

        如何工作,以及格式化字元串是什麼意思。但是,編譯器不會做這種分析。
      • 有時,格式化字元串不是個字元串常量。它在程式執行期間生成。是以,這裡編譯器沒有辦法發現不比對。
    • printf

      可能檢測不比對嗎?
      • printf

        從棧上擷取參數。如果格式化字元串需要三個參數,它會從棧上擷取三個參數。除非棧上存在标記,

        printf

        不知道它超出了提供給它的參數範圍。
      • 由于不存在标記,

        printf

        會繼續從棧上抓取資料。在不比對的情況下,它會抓取一些不屬于這個函數調用的資料。

2 格式化字元串漏洞攻擊

  • 使程式崩潰
    printf ("%s%s%s%s%s%s%s%s%s%s%s%s");           
    • 對于每一個

      %s

      printf

      會從棧上抓取一個數值,将其看做位址,并将由該位址指向的記憶體内容列印為字元串,直到遇到了空字元(數值 0 而不是字元 0)。
    • 由于

      printf

      抓取的數值可能不是有效位址,由該數值指向的記憶體可能不存在(也就是沒有實體記憶體賦給這個位址),程式就會崩潰。
    • 也可能數值碰巧是有效位址,但是位址空間被保護了(也就是為核心空間預留)。這樣的話,程式也會崩潰。
  • 檢視棧
    printf ("%08x %08x %08x %08x %08x\n");           
    • 這讓

      printf

      函數從棧上擷取五個參數,并将其展示為填充長度為 8 的十六進制數值。是以輸出可能為:
      40012980 080628c4 bffff7a4 00000005 08059c04           
  • 檢視任何位址的記憶體
    • 我們需要提供記憶體位址。但是我們不能修改代碼,我們隻能提供格式化字元串。
    • 如果我們使用

      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 放入字元串,就會儲存 ASCII 值 1 和 0。它們的 ASCII 值是 49 和 48。
    • %x

      讓棧指針沿着格式化字元串移動。
    • 這裡是攻擊方式,如果

      user_input

      包含下面的格式化字元串:
      "\x10\x01\x48\x08 %x %x %x %x %s"           
    • 本質上,我們使用四個

      %x

      來使

      printf

      的指針,向我們儲存在格式化字元串中的位址移動。一旦到達了目标,我們就會像

      printf

      提供

      %s

      ,使其列印出位址

      0x10014808

      的内容。函數

      printf

      會将記憶體看做字元串,并列印出來,知道到達了字元串尾部(空字元)。
    • user_input

      和傳給

      printf

      函數的位址之間的棧空間并不是

      printf

      的。但是,由于程式中的格式化字元串漏洞。

      printf

      将它們看做比對格式化字元串中

      %x

      的參數。
    • 這個攻擊的關鍵就是弄清楚

      user_input

      printf

      的位址的距離。這個距離決定了在提供

      %s

      之前,你需要向格式化字元串插入多少個

      %x

  • 在程序的記憶體中向任何位址寫入整數
    • %n

      :目前為止寫入的字元數量,儲存在一個整數中,它由相應參數表示。
      int i; 
      printf ("12345%n", &i);           
    • 它使

      printf

      将 5 寫入變量

      i

    • 使用檢視任意位址記憶體的相同方式,我們可以使

      printf

      将整數寫入任意位址。隻需要将上面例子中的

      %s

      替換為

      %n

      ,就會覆寫

      0x10014808

      位址處的内容。
    • 使用這個攻擊,攻擊者可以做這些事情:
      • 覆寫控制通路權限的重要程式标志位
      • 覆寫棧上的傳回位址,函數指針,以及其他
    • 但是,寫入的值由

      %n

      之前已列印的字元數量決定。是否真的可以寫入任意整數呢?
      • 使用僞造的輸出字元。為了寫入值 1000,應該事先列印 1000 個僞造字元的間隔。
      • 為了避免過長的格式化字元串,我們可以使用格式化标志的寬度限定。
  • 預防措施
    • 位址空間随機化:就像用于保護緩沖區溢出攻擊的預防措施那樣,位址空間随機化攻擊者難以找到他們想要讀取或寫入什麼位址。(譯者注:但是仍然有一些區域無法随機化,比如 PLT)。

繼續閱讀