要實際討論漏洞利用對策與繞過方法,首先需要一個實際的利用目標——一個接受連入連線的伺服器程式。在 Unix 中,這類程式通常是系統 daemon。
什麼是 daemon#
daemon 是在背景執行、並以特定方式脫離控制終端的程式。
daemon 一詞由 1960 年代的 MIT 駭客首創,指的是物理學家 James Maxwell 1867 年思想實驗中的分子分揀「惡魔」——一個能毫不費力執行困難任務、看似違反熱力學第二定律的存在。類似地,Linux 的系統 daemon 不知疲倦地執行提供 SSH 服務、維護系統日誌等任務。daemon 程式名稱通常以
d結尾(如sshd、syslogd)。
daemon() 函式會生成一個新的背景程序:
int daemon(int nochdir, int noclose);- 除非
nochdir非零,否則把當前工作目錄改成根目錄/。 - 除非
noclose非零,否則把標準輸入/輸出/錯誤重導到/dev/null。 - 這個函式 fork,若成功則父程序
_exit(0),後續錯誤只有子程序看得到。
訊號速成#
系統 daemon 脫離控制終端執行,因此新的 tinyweb daemon 寫入日誌檔,並用**訊號(signal)**控制。訊號是 Unix 的一種行程間通訊方式。
程序收到訊號時,其執行流程被作業系統中斷以呼叫訊號處理器(signal handler)。訊號由編號識別,各有預設處理器。例如在程式的控制終端按 CTRL-C 會送出中斷訊號,其預設處理器結束程式——即使程式卡在無窮迴圈也能被中斷。
用 signal() 可註冊自訂處理器:
signal(SIGTERM, handle_shutdown); // 被 kill 時呼叫 handle_shutdown
signal(SIGINT, handle_shutdown); // 被中斷時呼叫
常見訊號:SIGINT(2, Ctrl-C)、SIGQUIT(3, Ctrl-)、SIGKILL(9)、SIGSEGV(11, 段錯誤)、SIGTERM(15, kill 預設)、SIGTSTP(20, Ctrl-Z)。
用
kill命令可送特定訊號給程序(預設送SIGTERM)。但SIGKILL(kill -9)的處理器無法更改,所以kill -9永遠能終止程序。
Tinyweb Daemon#
把 tinyweb.c 加上幾處修改就成了更實際的系統 daemon:fork 到背景、寫帶時間戳的日誌檔、監聽 SIGTERM 以便被 kill 時乾淨關閉。
void handle_shutdown(int signal) { // 被 kill 時呼叫
timestamp(logfd);
write(logfd, "Shutting down.\n", 16);
close(logfd);
close(sockfd);
exit(0);
}日誌檔描述元與接收連線的 socket 宣告為全域,好讓
handle_shutdown()能乾淨地關閉它們。日誌檔含時間戳,也含程式捕捉到終止訊號、呼叫handle_shutdown()優雅退出時寫下的關閉訊息:
07/22/2007 17:55:45> Starting up.
07/22/2007 17:57:00> From 127.0.0.1:38127 "HEAD / HTTP/1.0" 200 OK
07/22/2007 17:57:21> Shutting down.