Jammerx2 Jammerx2 - 8 days ago 7
Python Question

Python Crashes After Call to CreateProcessWithLogonW

Using the code found here one can successfully launch an application as an alternate user. However, after the application is launched Python crashes, and Windows displays "python.exe has stopped working". It seems to only happen after the function has been finished calling, but does not seem to be caused by anything within the function.

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


As I commented in the code, if you prevent the end of the function from being reached the crash does not occur. Anything after the scope returns to outside of the function will not be reached and python.exe will crash.

A workaround that I have tried is to use taskkill at the end of the function to kill the python.exe process by its PID. This did prevent the error message from occurring as expected, but less than ideal as it also kills any child processes (including the one that successfully launches). I cannot figure out any reason why completing the function call would cause Python to crash. This occurs both in Python 2.7 and 3.x. Any suggestions are greatly appreciated.

Answer

Using a 32-bit DWORD for a HANDLE, or any other pointer type, is incorrect on 64-bit Windows. The ctypes.wintypes module defines types that work on both 32-bit and 64-bit Windows. If it lacks a particular type, you can probably find the definition in Windows Data Types.

Setting _pack_ = 1 incorrectly uses 1-byte alignment instead of padding with native alignment. Also, STARTUPINFOW should use LPWSTR instead of LPSTR.

Try this rewrite:

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)