kernel32.CreateProcessW:访问PROCESS_INFORMATION结构时Python崩溃

0 投票
1 回答
889 浏览
提问于 2025-04-18 09:41

经过很长一段时间,我正在尝试运行一些代码,来解释调试是怎么回事。这本书(《灰帽Python》)相当老旧,是为32位系统和Python 2.7写的。

我在64位的Windows 8上用Python 3.4来试这个代码。程序启动得很成功,但一旦我尝试访问PROCESS_INFORMATION这个结构,Python就崩溃了。我在64位的Windows 7上也试过,同样崩溃。

在Eclipse里,我没有收到错误信息(在我的Windows 7机器上也没有,Python.exe只是停止工作),但在Windows 8上我收到了一个错误信息(通过Powershell):

[*] Field 0: ('hProcess', <class 'ctypes.c_void_p'>)
[*] Field 1: ('hThread', <class 'ctypes.c_void_p'>)
[*] Field 2: ('dwProcessId', <class 'ctypes.c_ulong'>)
[*] Field 3: ('dwThreadId', <class 'ctypes.c_ulong'>)
Traceback (most recent call last):
  File "my_test.py", line 11, in <module>
    debugger.load("C:\\Windows\\System32\\calc.exe")
  File "C:\Workspace\my_debugger\my_debugger.py", line 57, in lo
    byref(process_information)):
OSError: exception: access violation reading 0xFFFFFFFFFFFFFFFF

看起来指针指向了错误的地方。它在调用CreateProcessW()的时候就停止了!

在Eclipse里:

[*] Field 0: ('hProcess', <class 'ctypes.c_void_p'>)
[*] Field 1: ('hThread', <class 'ctypes.c_void_p'>)
[*] Field 2: ('dwProcessId', <class 'ctypes.c_ulong'>)
[*] Field 3: ('dwThreadId', <class 'ctypes.c_ulong'>)
[*] We have successfully launched the process!
[*] PROCESS_INFORMATION object: <my_debugger_defines.PROCESS_INFORMATION object at  0x00000000031623C8>

它在调用之后停止了!

我已经根据这个问题对下面的代码进行了修改,但没有效果。

这是我的定义:

from ctypes import *
from ctypes.wintypes import *

# Let's map the Microsoft types to ctypes for clarity
LPBYTE  = POINTER(BYTE)

# Constants
DEBUG_PROCESS = 0x00000001
CREATE_NEW_CONSOLE = 0x00000010

# Structures for CreateProcessA() function
class STARTUPINFOW(Structure):
    _fields = [
               ("cb",               DWORD),
               ("lpReserved",       LPWSTR),
               ("lpDesktop",        LPWSTR),
               ("lpTitle",          LPWSTR),
               ("dwX",              DWORD),
               ("dwY",              DWORD),
               ("dwXSize",          DWORD),
               ("dwYSize",          DWORD),
               ("dwXCountChars",    DWORD),
               ("dwYCountChars",    DWORD),
               ("dwFillAtrribute",  DWORD),
               ("dwFlags",          DWORD),
               ("wShowWindow",      WORD),
               ("cbReserved2",      WORD),
               ("lpReserved2",      LPBYTE),
               ("hStdInput",        HANDLE),
               ("hStdOutput",       HANDLE),
               ("hStdError",        HANDLE),
              ]
LPSTARTUPINFOW = POINTER(STARTUPINFOW)


class PROCESS_INFORMATION(Structure):
    _fields = [
               ("hProcess",         HANDLE),
               ("hThread",          HANDLE),
               ("dwProcessId",      DWORD),
               ("dwThreadId",       DWORD),
              ]
LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION)

这是主要的代码:

kernel32 = windll.kernel32

class Debugger():

    def __init__(self):
        '''
        Constructor
        '''
        pass

    def load(self, path_to_exe):

        # dwCreation flag determines how to create the process
        # set creation_flags = CREATE_NEW_CONSOLE if you want
        # to see the calculator GUI
        creation_flags = DEBUG_PROCESS

        # instantiate the structs
        startupinfo         = STARTUPINFOW()
        process_information = PROCESS_INFORMATION()

        # The following two optiions allow the started process
        # to be shown as a seperate window. This also illustrates
        # how different settings in the STARTUPINFO struct can affect
        # the debuggee.
        startupinfo.dwFlags     = 0x1
        startupinfo.wShowWindow = 0x0

        # We then initialize the cb variable in the STARTUPINFO struct
        # which is just the size of the struct itself
        startupinfo.cb = sizeof(startupinfo)

        print("[*] PROCESS_INFORMATION object: %s" % process_information)
        for count, field in enumerate(process_information._fields):
            print("[*] Field %d: %s" % (count, field))
        if kernel32.CreateProcessW(path_to_exe,
                                   None,
                                   None,
                                   None,
                                   None,
                                   creation_flags,
                                   None,
                                   None,
                                   byref(startupinfo),
                                   byref(process_information)):
            print("[*] We have successfully launched the process!")
            print("[*] PROCESS_INFORMATION object: %s" % process_information)
            for count, field in enumerate(process_information._fields):
                print("[*] Field %d: %s" % (count, field))
        else:
            print("[*] Error: 0x%08x." % kernel32.GetLastError())

        print("[*] Debugger finished.")

它是通过以下方式调用的:

import my_debugger

debugger = my_debugger.Debugger()

debugger.load("C:\\Windows\\System32\\calc.exe")

我承认我在这方面有点无能为力,但总得从某个地方开始。正如你从输出中看到的,在调用CreateProcessW()之前,我可以正常访问这个结构,但在成功启动进程后,这个结构似乎就坏掉了。

为什么我的process_information结构会坏掉呢?

我担心我看了几个小时,还是没能发现一个小错误。

非常感谢你的支持!

1 个回答

1

你的结构体定义中使用了 _fields,而不是正确的属性名 _fields_。为了帮助你发现这样的拼写错误,可以定义 __slots__ = '__weakref__'。这样做可以防止实例生成一个 __dict__,但仍然可以创建弱引用。当然,如果你在 __slots__ 的定义中也拼错了,那还是个问题,所以在较大的项目中,应该使用工厂函数来减少由于拼写错误而导致的隐性错误,这些错误可能在程序神秘崩溃之前不会被发现。

元类 _ctypes.PyCStructType 在创建一个 Structure 子类时,会为 _fields_ 中的名字添加描述符,所以一般情况下,实例不需要一个 dict。如果你不小心使用了 _fields 或其他拼写错误的属性名,那么就不会添加描述符。在这种情况下,访问某个字段时会引发 AttributeError。使用 __slots__ 也可以防止字段名的拼写错误错误地创建实例属性。

from ctypes import *

class Good(Structure):
    __slots__ = '__weakref__'
    _fields_ = [('a', c_int), 
                ('b', c_int)]

class Bad(Structure):
    __slots__ = '__weakref__'
    _fields = [('a', c_int), 
               ('b', c_int)]

>>> g = Good()
>>> g.a = 1
>>> g.c = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Good' object has no attribute 'c'

>>> b = Bad()
>>> b.a = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Bad' object has no attribute 'a'

注意:

  • bInheritHandles 应该是 0 而不是 None。如果你定义了 CreateProcessW.argtypes,那么传递 None 会导致 ArgumentError,因为 BOOL 不是一个指针。
  • CreationFlags 应该包含 CREATE_UNICODE_ENVIRONMENT。你传递了 NULLlpEnvironment,所以新进程会继承 Python 的 Unicode 环境。
  • 与其打印错误代码,不如使用 raise WinError()。这样会引发一个 OSError,并附上来自 FormatError(GetLastError()) 的格式化错误信息。

撰写回答