Python ขัดข้องหลังจากการเรียกไปยัง CreateProcessWithLogonW

การใช้โค้ดที่พบที่นี่ จะทำให้สามารถเปิดแอปพลิเคชันได้สำเร็จ ในฐานะผู้ใช้สำรอง อย่างไรก็ตาม หลังจากเปิดตัวแอปพลิเคชัน Python ก็ขัดข้อง และ Windows แจ้งว่า "python.exe หยุดทำงาน" ดูเหมือนว่าจะเกิดขึ้นหลังจากที่เรียกใช้ฟังก์ชันเสร็จแล้วเท่านั้น แต่ดูเหมือนว่าจะไม่มีสาเหตุจากสิ่งใดภายในฟังก์ชัน

import ctypes, sys
from ctypes import Structure, sizeof

NULL  = 0
TRUE  = 1
FALSE = 0

INVALID_HANDLE_VALUE = -1

WORD   = ctypes.c_ushort
DWORD  = ctypes.c_uint
LPSTR  = ctypes.c_char_p
LPBYTE = LPSTR
HANDLE = DWORD

# typedef struct _PROCESS_INFORMATION {
#     HANDLE hProcess;
#     HANDLE hThread;
#     DWORD dwProcessId;
#     DWORD dwThreadId;
# } PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;
class PROCESS_INFORMATION(Structure):
   _pack_   = 1
   _fields_ = [
       ('hProcess',    HANDLE),
       ('hThread',     HANDLE),
       ('dwProcessId', DWORD),
       ('dwThreadId',  DWORD),
   ]

# typedef struct _STARTUPINFO {
#     DWORD   cb;
#     LPSTR   lpReserved;
#     LPSTR   lpDesktop;
#     LPSTR   lpTitle;
#     DWORD   dwX;
#     DWORD   dwY;
#     DWORD   dwXSize;
#     DWORD   dwYSize;
#     DWORD   dwXCountChars;
#     DWORD   dwYCountChars;
#     DWORD   dwFillAttribute;
#     DWORD   dwFlags;
#     WORD    wShowWindow;
#     WORD    cbReserved2;
#     LPBYTE  lpReserved2;
#     HANDLE  hStdInput;
#     HANDLE  hStdOutput;
#     HANDLE  hStdError;
# } STARTUPINFO, *LPSTARTUPINFO;
class STARTUPINFO(Structure):
   _pack_   = 1
   _fields_ = [
       ('cb',              DWORD),
       ('lpReserved',      DWORD),     # LPSTR
       ('lpDesktop',       LPSTR),
       ('lpTitle',         LPSTR),
       ('dwX',             DWORD),
       ('dwY',             DWORD),
       ('dwXSize',         DWORD),
       ('dwYSize',         DWORD),
       ('dwXCountChars',   DWORD),
       ('dwYCountChars',   DWORD),
       ('dwFillAttribute', DWORD),
       ('dwFlags',         DWORD),
       ('wShowWindow',     WORD),
       ('cbReserved2',     WORD),
       ('lpReserved2',     DWORD),     # LPBYTE
       ('hStdInput',       DWORD),
       ('hStdOutput',      DWORD),
       ('hStdError',       DWORD),
   ]

# BOOL WINAPI CreateProcessWithLogonW(
#   __in         LPCWSTR lpUsername,
#   __in_opt     LPCWSTR lpDomain,
#   __in         LPCWSTR lpPassword,
#   __in         DWORD dwLogonFlags,
#   __in_opt     LPCWSTR lpApplicationName,
#   __inout_opt  LPWSTR lpCommandLine,
#   __in         DWORD dwCreationFlags,
#   __in_opt     LPVOID lpEnvironment,
#   __in_opt     LPCWSTR lpCurrentDirectory,
#   __in         LPSTARTUPINFOW lpStartupInfo,
#   __out        LPPROCESS_INFORMATION lpProcessInfo
# );
def CreateProcessWithLogonW(lpUsername = None, lpDomain = None, lpPassword =
None, dwLogonFlags = 0, lpApplicationName = None, lpCommandLine = None,
dwCreationFlags = 0, lpEnvironment = None, lpCurrentDirectory = None,
lpStartupInfo = None):
   if not lpUsername:
       lpUsername          = NULL
   else:
       lpUsername          = ctypes.c_wchar_p(lpUsername)
   if not lpDomain:
       lpDomain            = NULL
   else:
       lpDomain            = ctypes.c_wchar_p(lpDomain)
   if not lpPassword:
       lpPassword          = NULL
   else:
       lpPassword          = ctypes.c_wchar_p(lpPassword)
   if not lpApplicationName:
       lpApplicationName   = NULL
   else:
       lpApplicationName   = ctypes.c_wchar_p(lpApplicationName)
   if not lpCommandLine:
       lpCommandLine       = NULL
   else:
       lpCommandLine       = ctypes.create_unicode_buffer(lpCommandLine)
   if not lpEnvironment:
       lpEnvironment       = NULL
   else:
       lpEnvironment       = ctypes.c_wchar_p(lpEnvironment)
   if not lpCurrentDirectory:
       lpCurrentDirectory  = NULL
   else:
       lpCurrentDirectory  = ctypes.c_wchar_p(lpCurrentDirectory)
   if not lpStartupInfo:
       lpStartupInfo              = STARTUPINFO()
       lpStartupInfo.cb           = sizeof(STARTUPINFO)
       lpStartupInfo.lpReserved   = 0
       lpStartupInfo.lpDesktop    = 0
       lpStartupInfo.lpTitle      = 0
       lpStartupInfo.dwFlags      = 0
       lpStartupInfo.cbReserved2  = 0
       lpStartupInfo.lpReserved2  = 0
   lpProcessInformation              = PROCESS_INFORMATION()
   lpProcessInformation.hProcess     = INVALID_HANDLE_VALUE
   lpProcessInformation.hThread      = INVALID_HANDLE_VALUE
   lpProcessInformation.dwProcessId  = 0
   lpProcessInformation.dwThreadId   = 0
   success = ctypes.windll.advapi32.CreateProcessWithLogonW(lpUsername,
lpDomain, lpPassword, dwLogonFlags, lpApplicationName,
ctypes.byref(lpCommandLine), dwCreationFlags, lpEnvironment,
lpCurrentDirectory, ctypes.byref(lpStartupInfo),
ctypes.byref(lpProcessInformation))
   if success == FALSE:
       raise ctypes.WinError()
   #A raw_input or other blocking function here will prevent python from crashing until continuing
   return lpProcessInformation #Happens whether or not this is returned

CreateProcessWithLogonW("User", "Domain", "Password", 0, None, "C:\\Windows\\notepad.exe")
print("Test") #This will never be reached

ตามที่ฉันแสดงความคิดเห็นในโค้ด หากคุณป้องกันไม่ให้ถึงจุดสิ้นสุดของฟังก์ชัน ความผิดพลาดจะไม่เกิดขึ้น สิ่งใดก็ตามหลังจากที่ขอบเขตกลับสู่ภายนอกฟังก์ชันจะไม่สามารถเข้าถึงได้และ python.exe จะเสียหาย

วิธีแก้ปัญหาที่ฉันลองคือใช้ Taskkill ที่ส่วนท้ายของฟังก์ชันเพื่อฆ่ากระบวนการ python.exe ด้วย PID สิ่งนี้ป้องกันไม่ให้ข้อความแสดงข้อผิดพลาดเกิดขึ้นตามที่คาดไว้ แต่ก็น้อยกว่าอุดมคติเนื่องจากเป็นการฆ่ากระบวนการลูกใด ๆ (รวมถึงกระบวนการที่เปิดใช้งานได้สำเร็จ) ฉันไม่สามารถหาเหตุผลว่าทำไมการเรียกใช้ฟังก์ชันจนเสร็จสิ้นจะทำให้ Python หยุดทำงาน สิ่งนี้เกิดขึ้นทั้งใน Python 2.7 และ 3.x ข้อเสนอแนะใด ๆ ที่ชื่นชมอย่างมาก


person Jammerx2    schedule 21.09.2013    source แหล่งที่มา


คำตอบ (1)


การใช้ DWORD 32 บิตสำหรับ HANDLE หรือตัวชี้ประเภทอื่นๆ ไม่ถูกต้องใน Windows 64 บิต โมดูล ctypes.wintypes กำหนดประเภทที่ทำงานบน Windows ทั้งแบบ 32 บิตและ 64 บิต หากไม่มีประเภทใดประเภทหนึ่ง คุณอาจพบคำจำกัดความได้ใน ประเภทข้อมูล Windows.

การตั้งค่า _pack_ = 1 ใช้การจัดตำแหน่ง 1 ไบต์อย่างไม่ถูกต้องแทนการเติมด้วยการจัดตำแหน่งดั้งเดิม นอกจากนี้ STARTUPINFOW ควรใช้ LPWSTR แทน LPSTR

ลองเขียนใหม่นี้:

import ctypes
from ctypes import wintypes

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

CREATE_NEW_CONSOLE         = 0x00000010
CREATE_NO_WINDOW           = 0x08000000
DETACHED_PROCESS           = 0x00000008
CREATE_NEW_PROCESS_GROUP   = 0x00000200
CREATE_UNICODE_ENVIRONMENT = 0x00000400

if not hasattr(wintypes, 'LPBYTE'):
    wintypes.LPBYTE = ctypes.POINTER(wintypes.BYTE)

class HANDLE(wintypes.HANDLE):

    def detach(self):
        handle, self.value = self.value, None
        return wintypes.HANDLE(handle)

    def close(self, CloseHandle=kernel32.CloseHandle):
        if self:
            CloseHandle(self.detach())

    def __del__(self):
        self.close()

class PROCESS_INFORMATION(ctypes.Structure):
    """http://msdn.microsoft.com/en-us/library/ms684873"""
    _fields_ = (('hProcess',    HANDLE),
                ('hThread',     HANDLE),
                ('dwProcessId', wintypes.DWORD),
                ('dwThreadId',  wintypes.DWORD))

LPPROCESS_INFORMATION = ctypes.POINTER(PROCESS_INFORMATION)

class STARTUPINFOW(ctypes.Structure):
    """http://msdn.microsoft.com/en-us/library/ms686331"""
    _fields_ = (('cb',              wintypes.DWORD),
                ('lpReserved',      wintypes.LPWSTR),
                ('lpDesktop',       wintypes.LPWSTR),
                ('lpTitle',         wintypes.LPWSTR),
                ('dwX',             wintypes.DWORD),
                ('dwY',             wintypes.DWORD),
                ('dwXSize',         wintypes.DWORD),
                ('dwYSize',         wintypes.DWORD),
                ('dwXCountChars',   wintypes.DWORD),
                ('dwYCountChars',   wintypes.DWORD),
                ('dwFillAttribute', wintypes.DWORD),
                ('dwFlags',         wintypes.DWORD),
                ('wShowWindow',     wintypes.WORD),
                ('cbReserved2',     wintypes.WORD),
                ('lpReserved2',     wintypes.LPBYTE),
                ('hStdInput',       wintypes.HANDLE),
                ('hStdOutput',      wintypes.HANDLE),
                ('hStdError',       wintypes.HANDLE))

    def __init__(self, *args, **kwds):
        self.cb = ctypes.sizeof(self)
        super(STARTUPINFOW, self).__init__(*args, **kwds)

LPSTARTUPINFOW = ctypes.POINTER(STARTUPINFOW)

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

# http://msdn.microsoft.com/en-us/library/ms682431
advapi32.CreateProcessWithLogonW.errcheck = _check_bool
advapi32.CreateProcessWithLogonW.argtypes = (
    wintypes.LPCWSTR,      # lpUsername
    wintypes.LPCWSTR,      # lpDomain
    wintypes.LPCWSTR,      # lpPassword
    wintypes.DWORD,        # dwLogonFlags
    wintypes.LPCWSTR,      # lpApplicationName
    wintypes.LPWSTR,       # lpCommandLine (inout)
    wintypes.DWORD,        # dwCreationFlags
    wintypes.LPCWSTR,      # lpEnvironment  (force Unicode)
    wintypes.LPCWSTR,      # lpCurrentDirectory
    LPSTARTUPINFOW,        # lpStartupInfo
    LPPROCESS_INFORMATION) # lpProcessInfo (out)

def CreateProcessWithLogonW(username, password, domain=None, logonflags=0,
                            executable=None, commandline=None, creationflags=0,
                            env=None, cwd=None, startupinfo=None):
    if commandline is not None:
        commandline = ctypes.create_unicode_buffer(commandline)
    creationflags |= CREATE_UNICODE_ENVIRONMENT
    if startupinfo is None:
        startupinfo = STARTUPINFOW()
    pi = PROCESS_INFORMATION()
    advapi32.CreateProcessWithLogonW(username, domain, password, logonflags,
                                     executable, commandline, creationflags,
                                     env, cwd, ctypes.byref(startupinfo),
                                     ctypes.byref(pi))
    return pi.hProcess, pi.hThread, pi.dwProcessId, pi.dwThreadId

if __name__ == '__main__':
    import os
    import getpass
    username = input('username: ')
    password = getpass.getpass('password: ')
    exe = os.environ['ComSpec']
    cflags = CREATE_NEW_CONSOLE
    hProcess, hThread, pid, tid = CreateProcessWithLogonW(
            username, password, executable=exe, creationflags=cflags)
    print('PID: %d' % pid)
person Eryk Sun    schedule 21.09.2013
comment
คุณอาจต้องการแทนที่ return wintypes.HANDLE(handle) ด้วย return handle ดูการใช้งานประเภท HANDLE แบบอื่นได้ที่นี่: stackoverflow.com/a/43233332 - person newlog; 20.12.2018
comment
@newlog ฉันจะกระทบยอดทั้งสองเวอร์ชัน คำตอบอื่น ๆ ของฉัน (ใช่แล้วยาวมาก) เป็นคำตอบที่ใหม่กว่า IIRC ข้อกังวลของฉันในกรณีนี้คือภายในกรอบงาน ctypes ตัวจัดการจะต้องเป็นประเภทตัวชี้ เว้นแต่เราจะรับประกันว่ามีการระบุฟังก์ชัน argtypes ทั้งหมดเพื่อแปลง Python int เป็น C void * การแยกออกในกรณีนี้มีไว้เพื่อป้องกันการสรุปที่เรียก CloseHandle เท่านั้น แต่นั่นเป็นเพียงข้อกังวลเล็กน้อย ดังนั้นฉันจะเปลี่ยนให้ส่งคืน int แทน - person Eryk Sun; 20.12.2018