Jammerx2
Jammerx2

Reputation: 864

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.

Upvotes: 0

Views: 879

Answers (1)

Eryk Sun
Eryk Sun

Reputation: 34260

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)

Upvotes: 4

Related Questions