接著幾天應該會陸續介紹六大系中比較簡單且經典的題目,讓大家可以對 CTF 有比較直觀的了解,接下來才會開始提升難度,預計可能 Day 10 之後就不太適合初學者閱讀了,也是我可能會開始斷更的日子...Orz
CWE-120: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')
Buffer Overflow (後簡稱 BOF) 根據 CWE 的定義是:
The program copies an input buffer to an output buffer without verifying that the size of the input buffer is less than the size of the output buffer, leading to a buffer overflow.
對於會寫 C 語言的人,應該看上面的敘述就可以了解何謂 BOF,C 語言在處理資料流時,必須事先宣告 buffer 來處理資料,需要精確了解要處理資料的大小避免發生 bug,但比較現代的程式語言在 buffer 不夠時會自動 extend buffer (e.g. 像是 C++ 的 string),即使不會自動 extend 時也會在 runtime 偵測到並拋出 exception,也因為這樣,近代的程式語言比較不容易發生 BOF 的問題
底下是一個很典型的 BOF 範例:
void foo(int size)
{
char buf[0x20];
read(0, buf, size);
printf("buf: %s\n", buf);
}
int main(int argc, char *argv[])
{
foo(atoi(argv[1]));
return 0;
}
將上面的程式碼編譯成執行檔之後正常執行會得到這樣的結果:
dada@DADA-LAPTOP:/tmp$ echo -ne 'ggwp' | ./a.out 100
buf: ggwp
輸了了 buf: ggwp
,跟我們的預期相符,但如果給比較長的 input 就會發生奇怪的現象,下面一共給了 41 個 a
:
dada@DADA-LAPTOP:/tmp$ echo -ne 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' | ./a.out 123
buf: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaau*
我們預期的 output 應該是 buf: aaaa....
,但可以看到上面被我特別標成粗體的 u*
,接在 41 個 a 後面,並不是我們預期的 output,這就是發生 BOF 症狀的一種,程式將 buf 後面不應該被存取的資料給印出來了,這是因為 printf()
和 %s
的功能是將後面參數視為字串印出,C 語言預設對字串的結尾是 '\0'
(null byte),但由於 read()
得到的 input 已經超過 buf
宣告的大小,並且中間沒有任何 null byte,因此會將後面原本不應該存取的資料視為字串的一部分而印出來
BOF 根據 buffer 在記憶體中儲存位置的不同,大致可以分為三個種類:
三者的 root cause 基本上相同,都是因為在進行 buffer copy 時沒有檢查 input size,導致資料可能會超出 output buffer 而改到其他資料,但因為 stack, heap 和 bss 等的用途不同、存放資料也不同,導致發生 BOF 的利用方式和危害程度也會有差異
以前 stack overflow 是最好利用的,其次是 heap overflow,而發生在 bss 或 data 上的 overflow 要視能覆蓋掉什麼資料而定,不一定有辦法利用,但現今 stack 預設 compiler 有 stack canary 對 stack overflow 進行保護,heap 在 ptmalloc 的機制中也加了很多檢查,並且經常需要 leak 一些記憶體位置之後才方便利用,因此發生在 bss 或 data 段的 BOF 反而是最容易被忽略的
由於本次挑戰主旨是介紹 CTF 題目,因此這邊就不細講如何利用了,有興趣的人可以自行 google 相關教學,個人推薦 angelboy 的投影片
前面提到 stack overflow 在現今的有 stack canary 的保護之下,在現實環境下已經難以利用,除非像 CTF 題目刻意設計過,否則只有在 fork 出來的 process 下才有利用機會,但我當時在這題學到一招,可以在有 canary 的保護下,將 stack overflow 的漏洞變成可以 leak 任意 program data 的用法,解題的詳細經過可以參考我的 blog: https://ddaa.tw/32c3ctf_2015_pwn_200_readme.html
char flag[] = "NOT_THE_REAL_FLAG";
int main()
{
char buf[0x100];
puts("Hello!");
printf("What's your name?");
scanf("%s", buf);
printf("Nice to meet you, %s.\n", buf);
printf("Please overwrite the flag: ")
scanf("%s", buf);
memcpy(flag, buf, sizeof flag);
puts("Thank you, bye!")
return 0;
}
這題的原始碼猜測大約是長這樣,問題在使用了 scanf("%s", buf)
的寫法,由於 scanf()
只有讀到換行 '\n'
、空白 ' '
等特殊字元才會停止,因此會發生 BOF
但由於這題有 stack canary 的保護,即使一眼就看到漏洞也沒辦法簡單的疊 ROP 或跳 shellcode 拿 shell,加上這題很反常的直接把 flag 寫在 source code 裡面,當時就開始把方向想到要如何 leak data,經過一陣 google 之後搜尋到了一篇韓國 conference incognito 的投影片,裡面有用韓文描述如何透過偵測的 stack smash 的錯誤訊息來 leak 資料,具體作法如下:
flag
變成 LIBC_FATAL_STDERR=1
作為後面偽造環境變數的字串flag
的 address 0x600d20
argv[0]
的位置寫上真正 flag 的位置 0x400d20
__stack_chk_fail()
進行錯誤處理
LIBC_FATAL_STDERR=1
,因此會將原本噴到 terminal 上的錯誤訊息改送到 stderr
argv[0]
已經被改成 flag 的位置,因此在印 stack smash 的錯誤訊息時,原本的程式名稱就會變成 flag 內容
__libc_message(2, "*** %s ***: %s terminated\n",msg, __libc_argv[0] ?: "<unknown>");
Hello!
What's your name? Nice to meet you, .
Please overwrite the flag: Thank you, bye!
*** stack smashing detected ***:32C3_ELF_caN_b3_pre7ty_we!rd
... terminated