\x66\xbf\x04\x00\xb0\x3c\x0f\x05
Shell Code 開發流程:
- 準備好要產生 shell code 的範例 C 程式碼
- Compile binary,其中 CFLAGS 需要帶有 -static
- 使用工具 (e.g. objdump) 反組譯 binary 得到 assembly code
- 從 ASM 中得到所需要的核心程式碼:ShellCode.s
- Compile ASM (e.g. as),並且測試程式是否符合需求
- 反組譯 binary 得到 machine code
當你的 shell code 完成之後,一定要測試是否可以執行。 可以用一個簡單的 C 程式,測試你的 shell code 是否滿足設計上的需求。
const char shellcode[] = "Your shell code";
int main(int argc, char *argv) {
(*(void (*)()) shellcode)();
return 0;
}
第一個 Shell Code
第一個 Shell Code 絕對不是 Hello World!。對我來說,第一個程式一定是可以 compile 並且正確執行的程式。所以想像中第一個程式應該完全沒有作用的空函數:
/* Compile by - gcc -Wall -O0 -g -static -o test test.c */
int main() {
return 0;
}
接下來使用工具 (objdump -S test) 來得到反組譯後的 assembly code, 就可以看到一大串的程式碼,其中我們只關心如何正確的離開這個程式。 經由一連串的搜尋之後我們找到 _exit 是最後離開的邏輯。
在 x86-based 64-bit 的環境下,呼叫 system call 的 asm code 為 syscall, 他會根據當時的 EAX 的值來決定使用哪一個 system call。這個值也可以看 kernel 的 syscall table 來得知。
0000000000430970 <_exit>:
430970: 48 63 f7 movslq %edi,%rsi
430973: 49 c7 c2 d0 ff ff ff mov $0xffffffffffffffd0,%r10
43097a: 41 b9 e7 00 00 00 mov $0xe7,%r9d
430980: 41 b8 3c 00 00 00 mov $0x3c,%r8d
430986: eb 19 jmp 4309a1 <_exit+0x31>
430988: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
43098f: 00
430990: 48 89 f7 mov %rsi,%rdi
430993: 44 89 c0 mov %r8d,%eax
430996: 0f 05 syscall
# ... Skip ...
看ㄧ下產生出來的 ASM code,我們知道在 syscall 執行前需要填入 eax 跟 rdi 兩個 register 當中。移除掉不需要的程式碼後,我們只需要將預計使用的回傳值, 塞入 rdi 當中,跟 _exit 所使用的 syscall number 填入 eax當中即可 。精簡後 assembly code 可以改為:
mov $0, %di
mov $60, %al
syscall
最後 compile 精減過後的 ASM:
as -o test.o test.s
ld -o test test.o
我們執行 test 這個程式發現他會馬上結束,看回傳值 (echo $?) 也符合預期。 最後可執行的 Shell Code 如下:
const char shellcode[] = \
"\x66\xbf\x00\x00" /* mov $0x0,%di */ \
"\xb0\x3c" /* mov $0x3c,%al */ \
"\x0f\x05"; /* syscall */
int main(void)
{
(*(void (*)()) shellcode)();
return 1;
}