如何使用python ctypes访问另一个进程的PEB

时间:2016-01-30 20:04:58

标签: python-2.7 winapi ctypes

到现在为止,我有这种代码的和平(我知道它的丑陋,但这不是现在的重点)

我无法弄清楚如何发布以下系统并构建正确的结构来访问另一个进程的PEB。

我想做以下事情:

  1. HANDLE pHandle = OpenProcess
  2. NTSTATUS status = NtQueryInfornationProcess(pHandle, 0, peb, peb_len, 0)
  3. 走PEB_LDR_DATA
  4. 代码:

    from ctypes import *
    from ctypes.wintypes import *
    from _multiprocessing import win32
    import argparse
    
    
    class UNICODE_STRING(Structure):
        _fields_ = [
            ("Length", USHORT),
            ("MaximumLength", USHORT),
            ("Buffer", c_wchar_p)
        ]
    
    class RTL_USER_PROCESS_PARAMETERS(Structure):
        _fields_ = [
            ("Reserved1", BYTE*16),
            ("Reserved2", BYTE*10),
            ("ImagePathName", UNICODE_STRING),
            ("CommandLine", UNICODE_STRING)
        ]
    
    class PEB(Structure):
        _fields_ = [
            ("Reserved1", BYTE*2),
            ("BeingDebugged", BYTE),
            ("Reserved2", BYTE),
            ("Rserved3", LPVOID),
            ("Ldr", LPVOID),
            ("ProcessParameters", POINTER(RTL_USER_PROCESS_PARAMETERS)),
            ("Reserved4", BYTE*104),
            ("Reserved5", LPVOID*52),
            ("PostProcessInitRoutine", LPVOID),
            ("Reserved6", BYTE*128),
            ("Reserved7", LPVOID),
            ("SessionId", ULONG)
        ]
    
    class PROCESS_BASIC_INFORMATION(Structure):
        _fields_ = [
            ("Reserved1", LPVOID),
            ("PebBaseAddress", POINTER(PEB)),
            ("Reserved2", LPVOID*2),
            ("UniqueProcessId", POINTER(ULONG)),
            ("Reserved3", LPVOID)
        ]
    
    
    def main():
        # Command Line Arguments Parsing
        parser = argparse.ArgumentParser()
        parser.add_argument('pid', metavar='<process id>', type=int, help='shows basic info about the process')
        parser.add_argument('-dS', metavar='dump strings', help='dump all used strings to txt file')
        parser.add_argument('-dD', metavar='dump dll', help='dump all used strings to txt file')
        args = parser.parse_args()
    
        var_pid = args.pid
    
        # WinAPi Calls
    
        # Variables Definition
        pHandle = HANDLE()
        NTSTATUS = ULONG()
        pbi = PROCESS_BASIC_INFORMATION()
        pPEB = PEB()
        pRTL = RTL_USER_PROCESS_PARAMETERS()
        pCMD = UNICODE_STRING()
        ReturnValue = BOOL()
        bytesRead = ULONG()
    
        # OpenProcess
        pHandle = windll.kernel32.OpenProcess(win32.PROCESS_ALL_ACCESS, 0, var_pid)
    
        # NtQueryInformationProcess
        NTSTATUS = windll.ntdll.NtQueryInformationProcess(pHandle, 0, byref(pbi), sizeof(pbi), None)
    
        # ReadProcessMemory
        ReturnValue = windll.kernel32.ReadProcessMemory(pHandle, pbi.PebBaseAddress, byref(pPEB), sizeof(PEB), byref(bytesRead))
        ReturnValue = windll.kernel32.ReadProcessMemory(pHandle, pPEB.ProcessParameters, byref(pRTL), sizeof(RTL_USER_PROCESS_PARAMETERS), byref(bytesRead))
    
        #msvcrt = cdll.msvcrt
        #msvcrt.printf("%s", pRTL.CommandLine.Buffer)
    
        temp = pRTL.ImagePathName.Buffer # cant read the unicode from this buffer
    
        ReturnValue = windll.kernel32.CloseHandle(pHandle)
        exit(0)
    
    if __name__ == '__main__':
        main()
    

    **编辑: 我设法得到PEB并在其中移动结构但是我无法从缓冲区中读取UNICODE字符串。 例如,我想阅读命令行参数

2 个答案:

答案 0 :(得分:3)

重申NtQueryInformationProcess是本机系统调用,在Windows编程中不鼓励这样做。 Microsoft没有为ntdll.dll提供导入库,因此调用其导出函数的唯一方法是通过GetProcAddress动态调用。这当然是ctypes如何工作的,因此调用本机NTAPI函数对Python来说并不困难。问题是缺乏官方支持和文档,NT数据结构,API和可用信息类都可能发生变化。

另请注意,在从64位进程调用时,查询ProcessBasicInformation将检索64位PEB的地址。因此,当从64位进程查询WOW64 32位进程时,您将只看到本机64位模块ntdll.dll,wow64.dll,wow64win.dll和wow64cpu.dll。这是一个answer,它提供了一种技术,通过使用从64位TEB到32位TEB的魔术偏移来查找32位PEB的地址,该TEB具有指向32位PEB的指针。但是,当然这个实现细节可以随时改变,打破依赖它的代码。

以下示例具有查询和使用ProcessBasicInformation所需的ctypes定义,用于具有相同体系结构的给定进程(即本机64位或WOW64 32位)。它包含一个演示用法的类,并提供进程ID,会话ID,映像路径,命令行和已加载模块的路径的属性。

该示例使用RemotePointer的{​​{1}}子类以及ctypes._Pointer工厂函数。此类重写RPOINTER以便于在另一个进程的地址空间中取消引用指针值。索引键是__getitem__形式的元组。可选的index, handle[, size]参数(以字节为单位)对于大小字符串(例如NTAPI size)非常有用,例如UNICODE_STRING。不支持以空值终止的字符串,因为ustr.Buffer[0, hProcess, usrt.Length]需要一个大小的缓冲区。

遍历加载程序数据的逻辑在私有ReadProcessMemory方法中,该方法使用内存中顺序链接列表遍历加载的模块。请注意,_modules_iter链接到InMemoryOrderModuleList结构的InMemoryOrderLinks字段,依此类推,列表中的每个链接。模块迭代器必须通过此字段的偏移量调整每个条目的基址。在C API中,这将使用LDR_DATA_TABLE_ENTRY宏。

如果没有提供进程ID或句柄,CONTAINING_RECORD构造函数默认查询当前进程。如果通话状态为错误或警告(即否定ProcessInformation),则会在{3.3}之前调用NTSTATUS以获取NtErrorOSError的实例。

我有,但没有包含更详细的WindowsError版本,调用NtError来获取格式化的错误消息,使用ntdll.dll作为源模块。我可以根据要求更新答案以包含此版本。

该示例在Windows 7和10中使用32位和64位版本的Python 2.7和3.5进行了测试。对于远程进程测试,子进程模块用于启动第二个Python实例。将事件句柄传递给子进程以进行同步。如果父进程没有等待子进程完成加载并设置事件,则读取时子进程的加载器数据可能无法完全初始化。

FormatMessage

示例:

import ctypes
from ctypes import wintypes

ntdll = ctypes.WinDLL('ntdll')
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)

# WINAPI Definitions

PROCESS_VM_READ           = 0x0010
PROCESS_QUERY_INFORMATION = 0x0400

ERROR_INVALID_HANDLE = 0x0006
ERROR_PARTIAL_COPY   = 0x012B

PULONG = ctypes.POINTER(wintypes.ULONG)
ULONG_PTR = wintypes.LPVOID
SIZE_T = ctypes.c_size_t

def _check_bool(result, func, args):
    if not result:
        raise ctypes.WinError(ctypes.get_last_error())
    return args

kernel32.ReadProcessMemory.errcheck = _check_bool
kernel32.ReadProcessMemory.argtypes = (
    wintypes.HANDLE,  # _In_  hProcess
    wintypes.LPCVOID, # _In_  lpBaseAddress
    wintypes.LPVOID,  # _Out_ lpBuffer
    SIZE_T,           # _In_  nSize
    ctypes.POINTER(SIZE_T))  # _Out_ lpNumberOfBytesRead

kernel32.CloseHandle.errcheck = _check_bool
kernel32.CloseHandle.argtypes = (wintypes.HANDLE,)

kernel32.GetCurrentProcess.restype = wintypes.HANDLE
kernel32.GetCurrentProcess.argtypes = ()

kernel32.OpenProcess.errcheck = _check_bool
kernel32.OpenProcess.restype = wintypes.HANDLE
kernel32.OpenProcess.argtypes = (
   wintypes.DWORD, # _In_ dwDesiredAccess
   wintypes.BOOL,  # _In_ bInheritHandle
   wintypes.DWORD) # _In_ dwProcessId

class RemotePointer(ctypes._Pointer):
    def __getitem__(self, key):
        # TODO: slicing
        size = None
        if not isinstance(key, tuple):
            raise KeyError('must be (index, handle[, size])')
        if len(key) > 2:
            index, handle, size = key
        else:
            index, handle = key
        if isinstance(index, slice):
            raise TypeError('slicing is not supported')
        dtype = self._type_
        offset = ctypes.sizeof(dtype) * index
        address = PVOID.from_buffer(self).value + offset
        simple = issubclass(dtype, ctypes._SimpleCData)
        if simple and size is not None:
            if dtype._type_ == wintypes.WCHAR._type_:
                buf = (wintypes.WCHAR * (size // 2))()
            else:
                buf = (ctypes.c_char * size)()
        else:
            buf = dtype()
        nread = SIZE_T()
        kernel32.ReadProcessMemory(handle,
                                   address,
                                   ctypes.byref(buf),
                                   ctypes.sizeof(buf),
                                   ctypes.byref(nread))
        if simple:
            return buf.value
        return buf

    def __setitem__(self, key, value):
        # TODO: kernel32.WriteProcessMemory
        raise TypeError('remote pointers are read only')

    @property
    def contents(self):
        # a handle is required
        raise NotImplementedError

_remote_pointer_cache = {}
def RPOINTER(dtype):
    if dtype in _remote_pointer_cache:
        return _remote_pointer_cache[dtype]
    name = 'RP_%s' % dtype.__name__
    ptype = type(name, (RemotePointer,), {'_type_': dtype})
    _remote_pointer_cache[dtype] = ptype
    return ptype

# NTAPI Definitions

NTSTATUS = wintypes.LONG
PVOID = wintypes.LPVOID
RPWSTR = RPOINTER(wintypes.WCHAR)
PROCESSINFOCLASS = wintypes.ULONG

ProcessBasicInformation   = 0
ProcessDebugPort          = 7
ProcessWow64Information   = 26
ProcessImageFileName      = 27
ProcessBreakOnTermination = 29

STATUS_UNSUCCESSFUL         = NTSTATUS(0xC0000001)
STATUS_INFO_LENGTH_MISMATCH = NTSTATUS(0xC0000004).value
STATUS_INVALID_HANDLE       = NTSTATUS(0xC0000008).value
STATUS_OBJECT_TYPE_MISMATCH = NTSTATUS(0xC0000024).value

class UNICODE_STRING(ctypes.Structure):
    _fields_ = (('Length',        wintypes.USHORT),
                ('MaximumLength', wintypes.USHORT),
                ('Buffer',        RPWSTR))

class LIST_ENTRY(ctypes.Structure):
    pass

RPLIST_ENTRY = RPOINTER(LIST_ENTRY)

LIST_ENTRY._fields_ = (('Flink', RPLIST_ENTRY),
                       ('Blink', RPLIST_ENTRY))

class LDR_DATA_TABLE_ENTRY(ctypes.Structure):
    _fields_ = (('Reserved1',          PVOID * 2),
                ('InMemoryOrderLinks', LIST_ENTRY),
                ('Reserved2',          PVOID * 2),
                ('DllBase',            PVOID),
                ('EntryPoint',         PVOID),
                ('Reserved3',          PVOID),
                ('FullDllName',        UNICODE_STRING),
                ('Reserved4',          wintypes.BYTE * 8),
                ('Reserved5',          PVOID * 3),
                ('CheckSum',           PVOID),
                ('TimeDateStamp',      wintypes.ULONG))

RPLDR_DATA_TABLE_ENTRY = RPOINTER(LDR_DATA_TABLE_ENTRY)

class PEB_LDR_DATA(ctypes.Structure):
    _fields_ = (('Reserved1',               wintypes.BYTE * 8),
                ('Reserved2',               PVOID * 3),
                ('InMemoryOrderModuleList', LIST_ENTRY))

RPPEB_LDR_DATA = RPOINTER(PEB_LDR_DATA)

class RTL_USER_PROCESS_PARAMETERS(ctypes.Structure):
    _fields_ = (('Reserved1',     wintypes.BYTE * 16),
                ('Reserved2',     PVOID * 10),
                ('ImagePathName', UNICODE_STRING),
                ('CommandLine',   UNICODE_STRING))

RPRTL_USER_PROCESS_PARAMETERS = RPOINTER(RTL_USER_PROCESS_PARAMETERS)
PPS_POST_PROCESS_INIT_ROUTINE = PVOID

class PEB(ctypes.Structure):
    _fields_ = (('Reserved1',              wintypes.BYTE * 2),
                ('BeingDebugged',          wintypes.BYTE),
                ('Reserved2',              wintypes.BYTE * 1),
                ('Reserved3',              PVOID * 2),
                ('Ldr',                    RPPEB_LDR_DATA),
                ('ProcessParameters',      RPRTL_USER_PROCESS_PARAMETERS),
                ('Reserved4',              wintypes.BYTE * 104),
                ('Reserved5',              PVOID * 52),
                ('PostProcessInitRoutine', PPS_POST_PROCESS_INIT_ROUTINE),
                ('Reserved6',              wintypes.BYTE * 128),
                ('Reserved7',              PVOID * 1),
                ('SessionId',              wintypes.ULONG))

RPPEB = RPOINTER(PEB)

class PROCESS_BASIC_INFORMATION(ctypes.Structure):
    _fields_ = (('Reserved1',       PVOID),
                ('PebBaseAddress',  RPPEB),
                ('Reserved2',       PVOID * 2),
                ('UniqueProcessId', ULONG_PTR),
                ('Reserved3',       PVOID))

def NtError(status):
    import sys
    descr = 'NTSTATUS(%#08x) ' % (status % 2**32,)
    if status & 0xC0000000 == 0xC0000000:
        descr += '[Error]'
    elif status & 0x80000000 == 0x80000000:
        descr += '[Warning]'
    elif status & 0x40000000 == 0x40000000:
        descr += '[Information]'
    else:
        descr += '[Success]'
    if sys.version_info[:2] < (3, 3):
        return WindowsError(status, descr)
    return OSError(None, descr, None, status)

NtQueryInformationProcess = ntdll.NtQueryInformationProcess
NtQueryInformationProcess.restype = NTSTATUS
NtQueryInformationProcess.argtypes = (
    wintypes.HANDLE,  # _In_      ProcessHandle
    PROCESSINFOCLASS, # _In_      ProcessInformationClass
    PVOID,            # _Out_     ProcessInformation
    wintypes.ULONG,   # _In_      ProcessInformationLength
    PULONG)           # _Out_opt_ ReturnLength

class ProcessInformation(object):
    _close_handle = False
    _closed = False
    _module_names = None

    def __init__(self, process_id=None, handle=None):
        if process_id is None and handle is None:
            handle = kernel32.GetCurrentProcess()
        elif handle is None:
            handle = kernel32.OpenProcess(PROCESS_VM_READ |
                                          PROCESS_QUERY_INFORMATION,
                                          False, process_id)
            self._close_handle = True
        self._handle = handle
        self._query_info()
        if process_id is not None and self._process_id != process_id:
            raise NtError(STATUS_UNSUCCESSFUL)

    def __del__(self, CloseHandle=kernel32.CloseHandle):
        if self._close_handle and not self._closed:
            try:
                CloseHandle(self._handle)
            except WindowsError as e:
                if e.winerror != ERROR_INVALID_HANDLE:
                    raise
            self._closed = True

    def _query_info(self):
        info = PROCESS_BASIC_INFORMATION()
        handle = self._handle
        status = NtQueryInformationProcess(handle,
                                           ProcessBasicInformation,
                                           ctypes.byref(info),
                                           ctypes.sizeof(info),
                                           None)
        if status < 0:
            raise NtError(status)
        self._process_id = info.UniqueProcessId
        self._peb = peb = info.PebBaseAddress[0, handle]
        self._params = peb.ProcessParameters[0, handle]
        self._ldr = peb.Ldr[0, handle]

    def _modules_iter(self):
        headaddr = (PVOID.from_buffer(self._peb.Ldr).value +
                    PEB_LDR_DATA.InMemoryOrderModuleList.offset)
        offset = LDR_DATA_TABLE_ENTRY.InMemoryOrderLinks.offset
        pentry = self._ldr.InMemoryOrderModuleList.Flink
        while pentry:
            pentry_void = PVOID.from_buffer_copy(pentry)
            if pentry_void.value == headaddr:
                break
            pentry_void.value -= offset
            pmod = RPLDR_DATA_TABLE_ENTRY.from_buffer(pentry_void)
            mod = pmod[0, self._handle]
            yield mod
            pentry = LIST_ENTRY.from_buffer(mod, offset).Flink

    def update_module_names(self):
        names = []
        for m in self._modules_iter():
            ustr = m.FullDllName
            name = ustr.Buffer[0, self._handle, ustr.Length]
            names.append(name)
        self._module_names = names

    @property
    def module_names(self):
        if self._module_names is None:
            self.update_module_names()
        return self._module_names

    @property
    def process_id(self):
        return self._process_id

    @property
    def session_id(self):
        return self._peb.SessionId

    @property
    def image_path(self):
        ustr = self._params.ImagePathName
        return ustr.Buffer[0, self._handle, ustr.Length]

    @property
    def command_line(self):
        ustr = self._params.CommandLine
        buf = ustr.Buffer[0, self._handle, ustr.Length]
        return buf

在Windows 10中使用64位Python 3.5输出

if __name__ == '__main__':
    import os
    import sys
    import subprocess
    import textwrap

    class SECURITY_ATTRIBUTES(ctypes.Structure):
        _fields_ = (('nLength',              wintypes.DWORD),
                    ('lpSecurityDescriptor', wintypes.LPVOID),
                    ('bInheritHandle',       wintypes.BOOL))
        def __init__(self, *args, **kwds):
            super(SECURITY_ATTRIBUTES, self).__init__(*args, **kwds)
            self.nLength = ctypes.sizeof(self)

    def test_remote(use_pid=True, show_modules=False):
        sa = SECURITY_ATTRIBUTES(bInheritHandle=True)
        hEvent = kernel32.CreateEventW(ctypes.byref(sa), 0, 0, None)
        try:
            script = textwrap.dedent(r"""
            import sys
            import ctypes
            kernel32 = ctypes.WinDLL('kernel32')
            kernel32.SetEvent(%d)
            sys.stdin.read()""").strip() % hEvent
            cmd = '"%s" -c "%s"' % (sys.executable, script)
            proc = subprocess.Popen(cmd, stdin=subprocess.PIPE,
                                    close_fds=False)
            try:
                kernel32.WaitForSingleObject(hEvent, 5000)
                if use_pid:
                    pi = ProcessInformation(proc.pid)
                else:
                    pi = ProcessInformation(handle=int(proc._handle))
                assert pi.process_id == proc.pid
                assert pi.image_path == sys.executable
                assert pi.command_line == cmd
                assert pi.module_names[0] == sys.executable
                if show_modules:
                    print('\n'.join(pi.module_names))
            finally:
                proc.terminate()
        finally:
            kernel32.CloseHandle(hEvent)

    print('Test 1: current process')
    pi = ProcessInformation()
    assert os.getpid() == pi.process_id
    assert pi.image_path == pi.module_names[0]
    print('Test 2: remote process (Handle)')
    test_remote(use_pid=False)
    print('Test 3: remote process (PID)')
    test_remote(show_modules=True)

答案 1 :(得分:1)

我只是将其作为解决方案发布,因为评论太小而且无法轻松格式化。

首先,要强调[MS.Docs]: NtQueryInformationProcess function上的内容:

  在未来的Windows版本中,

NtQueryInformationProcess 可能会被更改或不可用。应用程序应使用本主题中列出的备用功能。

     

...

     

NtQueryInformationProcess 函数及其返回的结构是操作系统的内部结构,可能会从一个Windows版本更改为另一个版本。为了保持应用程序的兼容性,最好使用 ProcessInformationClass 参数描述中提到的公共函数。

     

如果您使用 NtQueryInformationProcess ,请通过run-time dynamic linking访问该功能。如果函数已从操作系统更改或删除,则可以使代码有机会正常响应。但是,可能无法检测到签名更改。

     

此功能没有关联的导入库。您必须使用LoadLibraryGetProcAddress函数动态链接到Ntdll.dll。

现在,如果您仍想使用此func,则必须(递归地)定义它使用的所有结构。其中一些可以在[MS.Docs]: _PEB structure[MS.Docs]: _PEB_LDR_DATA structure找到,但您可以在 VStudio winternl.h中找到它们再次:在未来的Windows版本中可能会更改

另外:在处理 WinAPI 时,请务必检查返回码。

我在 Python 中定义了一些(不是全部)结构;我还必须更改一些现有的(注意,如果一个成员从结构开始有一个不正确的偏移,它将被搞砸):

import ctypes
from ctypes import wintypes


class LIST_ENTRY(ctypes.Structure):
    pass

LIST_ENTRY._fields = [
    ("Flink", ctypes.POINTER(LIST_ENTRY)),
    ("Blink", ctypes.POINTER(LIST_ENTRY)),
]


class UNICODE_STRING(ctypes.Structure):
    _fields_ = [
        ("Length", wintypes.USHORT),
        ("MaximumLength", wintypes.USHORT),
        ("Buffer", ctypes.POINTER(ctypes.c_wchar)),
    ]


class LDR_DATA_TABLE_ENTRY(ctypes.Structure):
    _fields_ = [
        ("Reserved1", ctypes.c_void_p * 2),
        ("InMemoryOrderLinks", LIST_ENTRY),
        ("Reserved2", ctypes.c_void_p * 2),
        ("DllBase", ctypes.c_void_p),
        ("EntryPoint", ctypes.c_void_p),
        ("Reserved3", ctypes.c_void_p),
        ("FullDllName", UNICODE_STRING),
        ("Reserved4", wintypes.BYTE * 8),
        ("Reserved5", ctypes.c_void_p * 3),
        ("Reserved6", ctypes.c_void_p),
        ("TimeDateStamp", wintypes.ULONG),
    ]


class PEB_LDR_DATA(ctypes.Structure):
    _fields_ = [
        ("Reserved1", wintypes.BYTE * 8),
        ("Reserved2", ctypes.c_void_p * 3),
        ("InMemoryOrderModuleList", LIST_ENTRY),
    ]


class PEB(ctypes.Structure):
    _fields_ = [
        ("Reserved1", wintypes.BYTE * 2),
        ("BeingDebugged", wintypes.BYTE),
        ("Reserved2", wintypes.BYTE),
        ("Reserved3", ctypes.c_void_p * 2),
        ("Ldr", ctypes.POINTER(PEB_LDR_DATA)),
        ("ProcessParameters", ctypes.POINTER(ctypes.c_int)),
        ("Reserved4", wintypes.BYTE * 104),
        ("Reserved5", ctypes.c_void_p * 52),
        ("PPS_POST_PROCESS_INIT_ROUTINE", ctypes.POINTER(ctypes.c_int)),
        ("Reserved6", wintypes.BYTE * 128),
        ("Reserved7", ctypes.c_void_p),
        ("SessionId", ctypes.c_ulong),
    ]


class PROCESS_BASIC_INFORMATION(ctypes.Structure):
    _fields_ = [
        ("Reserved1", ctypes.c_void_p),
        ("PebBaseAddress", ctypes.POINTER(PEB)),
        ("Reserved2", ctypes.c_void_p * 2),
        ("UniqueProcessId", ctypes.POINTER(ctypes.c_ulong)),
        ("Reserved3", ctypes.c_void_p),
    ]

最后,我想补充一点,即使他们可能在内部调用此函数,也很可能使用其他(支持的)方法(属于公共 API 的函数)获取任何数据。