1"""
2Windows specific utility functions, this module should be imported in a try,
3except block because it is only applicable on Windows platforms.
4
5
6Much of what is here was adapted from the following:
7
8    https://stackoverflow.com/a/43233332
9    http://stackoverflow.com/questions/29566330
10"""
11
12import collections
13import ctypes
14import logging
15import os
16from ctypes import wintypes
17
18import ntsecuritycon
19import psutil
20import win32api
21import win32con
22import win32process
23import win32security
24import win32service
25
26# Set up logging
27log = logging.getLogger(__name__)
28
29ntdll = ctypes.WinDLL("ntdll")
30secur32 = ctypes.WinDLL("secur32")
31kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
32advapi32 = ctypes.WinDLL("advapi32", use_last_error=True)
33userenv = ctypes.WinDLL("userenv", use_last_error=True)
34
35SYSTEM_SID = "S-1-5-18"
36LOCAL_SRV_SID = "S-1-5-19"
37NETWORK_SRV_SID = "S-1-5-19"
38
39LOGON_WITH_PROFILE = 0x00000001
40
41WINSTA_ALL = (
42    win32con.WINSTA_ACCESSCLIPBOARD
43    | win32con.WINSTA_ACCESSGLOBALATOMS
44    | win32con.WINSTA_CREATEDESKTOP
45    | win32con.WINSTA_ENUMDESKTOPS
46    | win32con.WINSTA_ENUMERATE
47    | win32con.WINSTA_EXITWINDOWS
48    | win32con.WINSTA_READATTRIBUTES
49    | win32con.WINSTA_READSCREEN
50    | win32con.WINSTA_WRITEATTRIBUTES
51    | win32con.DELETE
52    | win32con.READ_CONTROL
53    | win32con.WRITE_DAC
54    | win32con.WRITE_OWNER
55)
56
57DESKTOP_ALL = (
58    win32con.DESKTOP_CREATEMENU
59    | win32con.DESKTOP_CREATEWINDOW
60    | win32con.DESKTOP_ENUMERATE
61    | win32con.DESKTOP_HOOKCONTROL
62    | win32con.DESKTOP_JOURNALPLAYBACK
63    | win32con.DESKTOP_JOURNALRECORD
64    | win32con.DESKTOP_READOBJECTS
65    | win32con.DESKTOP_SWITCHDESKTOP
66    | win32con.DESKTOP_WRITEOBJECTS
67    | win32con.DELETE
68    | win32con.READ_CONTROL
69    | win32con.WRITE_DAC
70    | win32con.WRITE_OWNER
71)
72
73MAX_COMPUTER_NAME_LENGTH = 15
74
75SECURITY_LOGON_TYPE = wintypes.ULONG
76Interactive = 2
77Network = 3
78Batch = 4
79Service = 5
80
81LOGON_SUBMIT_TYPE = wintypes.ULONG
82PROFILE_BUFFER_TYPE = wintypes.ULONG
83
84MsV1_0InteractiveLogon = 2
85MsV1_0Lm20Logon = 3
86MsV1_0NetworkLogon = 4
87MsV1_0WorkstationUnlockLogon = 7
88MsV1_0S4ULogon = 12
89MsV1_0NoElevationLogon = 82
90
91KerbInteractiveLogon = 2
92KerbWorkstationUnlockLogon = 7
93KerbS4ULogon = 12
94
95MSV1_0_S4U_LOGON_FLAG_CHECK_LOGONHOURS = 0x2
96
97KERB_S4U_LOGON_FLAG_CHECK_LOGONHOURS = 0x2
98KERB_S4U_LOGON_FLAG_IDENTITY = 0x8
99
100TOKEN_SOURCE_LENGTH = 8
101
102NEGOTIATE_PACKAGE_NAME = b"Negotiate"
103MICROSOFT_KERBEROS_NAME = b"Kerberos"
104MSV1_0_PACKAGE_NAME = b"MICROSOFT_AUTHENTICATION_PACKAGE_V1_0"
105
106DELETE = 0x00010000
107READ_CONTROL = 0x00020000
108WRITE_DAC = 0x00040000
109WRITE_OWNER = 0x00080000
110
111STANDARD_RIGHTS_REQUIRED = DELETE | READ_CONTROL | WRITE_DAC | WRITE_OWNER
112
113TOKEN_ASSIGN_PRIMARY = 0x0001
114TOKEN_DUPLICATE = 0x0002
115TOKEN_IMPERSONATE = 0x0004
116TOKEN_QUERY = 0x0008
117TOKEN_QUERY_SOURCE = 0x0010
118TOKEN_ADJUST_PRIVILEGES = 0x0020
119TOKEN_ADJUST_GROUPS = 0x0040
120TOKEN_ADJUST_DEFAULT = 0x0080
121TOKEN_ADJUST_SESSIONID = 0x0100
122
123TOKEN_ALL_ACCESS = (
124    STANDARD_RIGHTS_REQUIRED
125    | TOKEN_ASSIGN_PRIMARY
126    | TOKEN_DUPLICATE
127    | TOKEN_IMPERSONATE
128    | TOKEN_QUERY
129    | TOKEN_QUERY_SOURCE
130    | TOKEN_ADJUST_PRIVILEGES
131    | TOKEN_ADJUST_GROUPS
132    | TOKEN_ADJUST_DEFAULT
133    | TOKEN_ADJUST_SESSIONID
134)
135
136DUPLICATE_CLOSE_SOURCE = 0x00000001
137DUPLICATE_SAME_ACCESS = 0x00000002
138
139TOKEN_TYPE = wintypes.ULONG
140TokenPrimary = 1
141TokenImpersonation = 2
142
143SECURITY_IMPERSONATION_LEVEL = wintypes.ULONG
144SecurityAnonymous = 0
145SecurityIdentification = 1
146SecurityImpersonation = 2
147SecurityDelegation = 3
148
149
150class NTSTATUS(wintypes.LONG):
151    def to_error(self):
152        return ntdll.RtlNtStatusToDosError(self)
153
154    def __repr__(self):
155        name = self.__class__.__name__
156        status = wintypes.ULONG.from_buffer(self)
157        return "{}({})".format(name, status.value)
158
159
160PNTSTATUS = ctypes.POINTER(NTSTATUS)
161
162
163class BOOL(wintypes.BOOL):
164    def __repr__(self):
165        name = self.__class__.__name__
166        return "{}({})".format(name, bool(self))
167
168
169class HANDLE(wintypes.HANDLE):
170    __slots__ = ("closed",)
171
172    def __int__(self):
173        return self.value or 0
174
175    def Detach(self):
176        if not getattr(self, "closed", False):
177            self.closed = True
178            value = int(self)
179            self.value = None
180            return value
181        raise ValueError("already closed")
182
183    def Close(self, CloseHandle=kernel32.CloseHandle):
184        if self and not getattr(self, "closed", False):
185            CloseHandle(self.Detach())
186
187    __del__ = Close
188
189    def __repr__(self):
190        return "{}({})".format(self.__class__.__name__, int(self))
191
192
193class LARGE_INTEGER(wintypes.LARGE_INTEGER):
194    # https://msdn.microsoft.com/en-us/library/ff553204
195    ntdll.RtlSecondsSince1970ToTime.restype = None
196    _unix_epoch = wintypes.LARGE_INTEGER()
197    ntdll.RtlSecondsSince1970ToTime(0, ctypes.byref(_unix_epoch))
198    _unix_epoch = _unix_epoch.value
199
200    def __int__(self):
201        return self.value
202
203    def __repr__(self):
204        name = self.__class__.__name__
205        return "{}({})".format(name, self.value)
206
207    def as_time(self):
208        time100ns = self.value - self._unix_epoch
209        if time100ns >= 0:
210            return time100ns / 1e7
211        raise ValueError("value predates the Unix epoch")
212
213    @classmethod
214    def from_time(cls, t):
215        time100ns = int(t * 10 ** 7)
216        return cls(time100ns + cls._unix_epoch)
217
218
219CHAR = ctypes.c_char
220WCHAR = ctypes.c_wchar
221PCHAR = ctypes.POINTER(CHAR)
222PWCHAR = ctypes.POINTER(WCHAR)
223
224
225class STRING(ctypes.Structure):
226    _fields_ = (
227        ("Length", wintypes.USHORT),
228        ("MaximumLength", wintypes.USHORT),
229        ("Buffer", PCHAR),
230    )
231
232
233LPSTRING = ctypes.POINTER(STRING)
234
235
236class UNICODE_STRING(ctypes.Structure):
237    _fields_ = (
238        ("Length", wintypes.USHORT),
239        ("MaximumLength", wintypes.USHORT),
240        ("Buffer", PWCHAR),
241    )
242
243
244LPUNICODE_STRING = ctypes.POINTER(UNICODE_STRING)
245
246
247class LUID(ctypes.Structure):
248    _fields_ = (
249        ("LowPart", wintypes.DWORD),
250        ("HighPart", wintypes.LONG),
251    )
252
253    def __new__(cls, value=0):
254        return cls.from_buffer_copy(ctypes.c_ulonglong(value))
255
256    def __int__(self):
257        return ctypes.c_ulonglong.from_buffer(self).value
258
259    def __repr__(self):
260        name = self.__class__.__name__
261        return "{}({})".format(name, int(self))
262
263
264LPLUID = ctypes.POINTER(LUID)
265PSID = wintypes.LPVOID
266
267
268class SID_AND_ATTRIBUTES(ctypes.Structure):
269    _fields_ = (
270        ("Sid", PSID),
271        ("Attributes", wintypes.DWORD),
272    )
273
274
275LPSID_AND_ATTRIBUTES = ctypes.POINTER(SID_AND_ATTRIBUTES)
276
277
278class TOKEN_GROUPS(ctypes.Structure):
279    _fields_ = (
280        ("GroupCount", wintypes.DWORD),
281        ("Groups", SID_AND_ATTRIBUTES * 1),
282    )
283
284
285LPTOKEN_GROUPS = ctypes.POINTER(TOKEN_GROUPS)
286
287
288class TOKEN_SOURCE(ctypes.Structure):
289    _fields_ = (
290        ("SourceName", CHAR * TOKEN_SOURCE_LENGTH),
291        ("SourceIdentifier", LUID),
292    )
293
294    def __init__(self, SourceName=None, SourceIdentifier=None):
295        super().__init__()
296        if SourceName is not None:
297            if not isinstance(SourceName, bytes):
298                SourceName = SourceName.encode("mbcs")
299            self.SourceName = SourceName
300        if SourceIdentifier is None:
301            # pylint: disable=access-member-before-definition
302            luid = self.SourceIdentifier
303            # pylint: enable=access-member-before-definition
304            ntdll.NtAllocateLocallyUniqueId(ctypes.byref(luid))
305        else:
306            self.SourceIdentifier = SourceIdentifier
307
308
309LPTOKEN_SOURCE = ctypes.POINTER(TOKEN_SOURCE)
310py_source_context = TOKEN_SOURCE(b"PYTHON  ")
311py_origin_name = __name__.encode()
312py_logon_process_name = "{}-{}".format(py_origin_name, os.getpid())
313SIZE_T = ctypes.c_size_t
314
315
316class QUOTA_LIMITS(ctypes.Structure):
317    _fields_ = (
318        ("PagedPoolLimit", SIZE_T),
319        ("NonPagedPoolLimit", SIZE_T),
320        ("MinimumWorkingSetSize", SIZE_T),
321        ("MaximumWorkingSetSize", SIZE_T),
322        ("PagefileLimit", SIZE_T),
323        ("TimeLimit", wintypes.LARGE_INTEGER),
324    )
325
326
327LPQUOTA_LIMITS = ctypes.POINTER(QUOTA_LIMITS)
328LPULONG = ctypes.POINTER(wintypes.ULONG)
329LSA_OPERATIONAL_MODE = wintypes.ULONG
330LPLSA_OPERATIONAL_MODE = LPULONG
331LPHANDLE = ctypes.POINTER(wintypes.HANDLE)
332LPLPVOID = ctypes.POINTER(wintypes.LPVOID)
333LPDWORD = ctypes.POINTER(wintypes.DWORD)
334
335
336class ContiguousUnicode(ctypes.Structure):
337    # _string_names_: sequence matched to underscore-prefixed fields
338    def __init__(self, *args, **kwargs):  # pylint: disable=useless-super-delegation
339        super().__init__(*args, **kwargs)
340
341    def _get_unicode_string(self, name):
342        wchar_size = ctypes.sizeof(WCHAR)
343        s = getattr(self, "_{}".format(name))
344        length = s.Length // wchar_size
345        buf = s.Buffer
346        if buf:
347            return buf[:length]
348        return None
349
350    def _set_unicode_buffer(self, values):
351        cls = type(self)
352        wchar_size = ctypes.sizeof(WCHAR)
353        bufsize = (len("\x00".join(values)) + 1) * wchar_size
354        ctypes.resize(self, ctypes.sizeof(cls) + bufsize)
355        addr = ctypes.addressof(self) + ctypes.sizeof(cls)
356        for value in values:
357            bufsize = (len(value) + 1) * wchar_size
358            ctypes.memmove(addr, value, bufsize)
359            addr += bufsize
360
361    def _set_unicode_string(self, name, value):
362        values = []
363        for n in self._string_names_:
364            if n == name:
365                values.append(value or "")
366            else:
367                values.append(getattr(self, n) or "")
368        self._set_unicode_buffer(values)
369
370        cls = type(self)
371        wchar_size = ctypes.sizeof(WCHAR)
372        addr = ctypes.addressof(self) + ctypes.sizeof(cls)
373        for n, v in zip(self._string_names_, values):
374            ptr = ctypes.cast(addr, PWCHAR)
375            ustr = getattr(self, "_{}".format(n))
376            length = ustr.Length = len(v) * wchar_size
377            full_length = length + wchar_size
378            if (n == name and value is None) or (
379                n != name and not (length or ustr.Buffer)
380            ):
381                ustr.Buffer = None
382                ustr.MaximumLength = 0
383            else:
384                ustr.Buffer = ptr
385                ustr.MaximumLength = full_length
386            addr += full_length
387
388    def __getattr__(self, name):
389        if name not in self._string_names_:
390            raise AttributeError
391        return self._get_unicode_string(name)
392
393    def __setattr__(self, name, value):
394        if name in self._string_names_:
395            self._set_unicode_string(name, value)
396        else:
397            super().__setattr__(name, value)
398
399    @classmethod
400    def from_address_copy(cls, address, size=None):
401        x = ctypes.Structure.__new__(cls)
402        if size is not None:
403            ctypes.resize(x, size)
404        ctypes.memmove(ctypes.byref(x), address, ctypes.sizeof(x))
405        delta = ctypes.addressof(x) - address
406        for n in cls._string_names_:
407            ustr = getattr(x, "_{}".format(n))
408            addr = ctypes.c_void_p.from_buffer(ustr.Buffer)
409            if addr:
410                addr.value += delta
411        return x
412
413
414class AuthInfo(ContiguousUnicode):
415    # _message_type_: from a logon-submit-type enumeration
416    def __init__(self):
417        super().__init__()
418        self.MessageType = self._message_type_
419
420
421class MSV1_0_INTERACTIVE_LOGON(AuthInfo):
422    _message_type_ = MsV1_0InteractiveLogon
423    _string_names_ = "LogonDomainName", "UserName", "Password"
424
425    _fields_ = (
426        ("MessageType", LOGON_SUBMIT_TYPE),
427        ("_LogonDomainName", UNICODE_STRING),
428        ("_UserName", UNICODE_STRING),
429        ("_Password", UNICODE_STRING),
430    )
431
432    def __init__(self, UserName=None, Password=None, LogonDomainName=None):
433        super().__init__()
434        if LogonDomainName is not None:
435            self.LogonDomainName = LogonDomainName
436        if UserName is not None:
437            self.UserName = UserName
438        if Password is not None:
439            self.Password = Password
440
441
442class S4ULogon(AuthInfo):
443    _string_names_ = "UserPrincipalName", "DomainName"
444
445    _fields_ = (
446        ("MessageType", LOGON_SUBMIT_TYPE),
447        ("Flags", wintypes.ULONG),
448        ("_UserPrincipalName", UNICODE_STRING),
449        ("_DomainName", UNICODE_STRING),
450    )
451
452    def __init__(self, UserPrincipalName=None, DomainName=None, Flags=0):
453        super().__init__()
454        self.Flags = Flags
455        if UserPrincipalName is not None:
456            self.UserPrincipalName = UserPrincipalName
457        if DomainName is not None:
458            self.DomainName = DomainName
459
460
461class MSV1_0_S4U_LOGON(S4ULogon):
462    _message_type_ = MsV1_0S4ULogon
463
464
465class KERB_S4U_LOGON(S4ULogon):
466    _message_type_ = KerbS4ULogon
467
468
469PMSV1_0_S4U_LOGON = ctypes.POINTER(MSV1_0_S4U_LOGON)
470PKERB_S4U_LOGON = ctypes.POINTER(KERB_S4U_LOGON)
471
472
473class ProfileBuffer(ContiguousUnicode):
474    # _message_type_
475    def __init__(self):
476        super().__init__()
477        self.MessageType = self._message_type_
478
479
480class MSV1_0_INTERACTIVE_PROFILE(ProfileBuffer):
481    _message_type_ = MsV1_0InteractiveLogon
482    _string_names_ = (
483        "LogonScript",
484        "HomeDirectory",
485        "FullName",
486        "ProfilePath",
487        "HomeDirectoryDrive",
488        "LogonServer",
489    )
490    _fields_ = (
491        ("MessageType", PROFILE_BUFFER_TYPE),
492        ("LogonCount", wintypes.USHORT),
493        ("BadPasswordCount", wintypes.USHORT),
494        ("LogonTime", LARGE_INTEGER),
495        ("LogoffTime", LARGE_INTEGER),
496        ("KickOffTime", LARGE_INTEGER),
497        ("PasswordLastSet", LARGE_INTEGER),
498        ("PasswordCanChange", LARGE_INTEGER),
499        ("PasswordMustChange", LARGE_INTEGER),
500        ("_LogonScript", UNICODE_STRING),
501        ("_HomeDirectory", UNICODE_STRING),
502        ("_FullName", UNICODE_STRING),
503        ("_ProfilePath", UNICODE_STRING),
504        ("_HomeDirectoryDrive", UNICODE_STRING),
505        ("_LogonServer", UNICODE_STRING),
506        ("UserFlags", wintypes.ULONG),
507    )
508
509
510def _check_status(result, func, args):
511    if result.value < 0:
512        raise ctypes.WinError(result.to_error())
513    return args
514
515
516def _check_bool(result, func, args):
517    if not result:
518        raise ctypes.WinError(ctypes.get_last_error())
519    return args
520
521
522INVALID_HANDLE_VALUE = wintypes.HANDLE(-1).value
523INVALID_DWORD_VALUE = wintypes.DWORD(-1).value  # ~WinAPI
524INFINITE = INVALID_DWORD_VALUE
525STD_INPUT_HANDLE = wintypes.DWORD(-10).value
526STD_OUTPUT_HANDLE = wintypes.DWORD(-11).value
527STD_ERROR_HANDLE = wintypes.DWORD(-12).value
528
529
530class SECURITY_ATTRIBUTES(ctypes.Structure):
531    _fields_ = (
532        ("nLength", wintypes.DWORD),
533        ("lpSecurityDescriptor", wintypes.LPVOID),
534        ("bInheritHandle", wintypes.BOOL),
535    )
536
537    def __init__(self, **kwds):
538        self.nLength = ctypes.sizeof(self)
539        super().__init__(**kwds)
540
541
542LPSECURITY_ATTRIBUTES = ctypes.POINTER(SECURITY_ATTRIBUTES)
543LPBYTE = ctypes.POINTER(wintypes.BYTE)
544LPHANDLE = PHANDLE = ctypes.POINTER(ctypes.c_void_p)
545LPDWORD = ctypes.POINTER(ctypes.c_ulong)
546
547
548class STARTUPINFO(ctypes.Structure):
549    """https://msdn.microsoft.com/en-us/library/ms686331"""
550
551    _fields_ = (
552        ("cb", wintypes.DWORD),
553        ("lpReserved", wintypes.LPWSTR),
554        ("lpDesktop", wintypes.LPWSTR),
555        ("lpTitle", wintypes.LPWSTR),
556        ("dwX", wintypes.DWORD),
557        ("dwY", wintypes.DWORD),
558        ("dwXSize", wintypes.DWORD),
559        ("dwYSize", wintypes.DWORD),
560        ("dwXCountChars", wintypes.DWORD),
561        ("dwYCountChars", wintypes.DWORD),
562        ("dwFillAttribute", wintypes.DWORD),
563        ("dwFlags", wintypes.DWORD),
564        ("wShowWindow", wintypes.WORD),
565        ("cbReserved2", wintypes.WORD),
566        ("lpReserved2", LPBYTE),
567        ("hStdInput", wintypes.HANDLE),
568        ("hStdOutput", wintypes.HANDLE),
569        ("hStdError", wintypes.HANDLE),
570    )
571
572    def __init__(self, **kwds):
573        self.cb = ctypes.sizeof(self)
574        super().__init__(**kwds)
575
576
577LPSTARTUPINFO = ctypes.POINTER(STARTUPINFO)
578
579
580class PROC_THREAD_ATTRIBUTE_LIST(ctypes.Structure):
581    pass
582
583
584PPROC_THREAD_ATTRIBUTE_LIST = ctypes.POINTER(PROC_THREAD_ATTRIBUTE_LIST)
585
586
587class STARTUPINFOEX(STARTUPINFO):
588    _fields_ = (("lpAttributeList", PPROC_THREAD_ATTRIBUTE_LIST),)
589
590
591LPSTARTUPINFOEX = ctypes.POINTER(STARTUPINFOEX)
592
593
594class PROCESS_INFORMATION(ctypes.Structure):
595    """https://msdn.microsoft.com/en-us/library/ms684873"""
596
597    _fields_ = (
598        ("hProcess", wintypes.HANDLE),
599        ("hThread", wintypes.HANDLE),
600        ("dwProcessId", wintypes.DWORD),
601        ("dwThreadId", wintypes.DWORD),
602    )
603
604
605LPPROCESS_INFORMATION = ctypes.POINTER(PROCESS_INFORMATION)
606
607
608class HANDLE_IHV(wintypes.HANDLE):
609    pass
610
611
612def errcheck_ihv(result, func, args):
613    if result.value == INVALID_HANDLE_VALUE:
614        raise ctypes.WinError(ctypes.get_last_error())
615    return result.value
616
617
618class DWORD_IDV(wintypes.DWORD):
619    pass
620
621
622def errcheck_idv(result, func, args):
623    if result.value == INVALID_DWORD_VALUE:
624        raise ctypes.WinError(ctypes.get_last_error())
625    return result.value
626
627
628def errcheck_bool(result, func, args):
629    if not result:
630        raise ctypes.WinError(ctypes.get_last_error())
631    return args
632
633
634def _win(func, restype, *argtypes):
635    func.restype = restype
636    func.argtypes = argtypes
637    if issubclass(restype, NTSTATUS):
638        func.errcheck = _check_status
639    elif issubclass(restype, BOOL):
640        func.errcheck = _check_bool
641    elif issubclass(restype, HANDLE_IHV):
642        func.errcheck = errcheck_ihv
643    elif issubclass(restype, DWORD_IDV):
644        func.errcheck = errcheck_idv
645    else:
646        func.errcheck = errcheck_bool
647
648
649# https://msdn.microsoft.com/en-us/library/ms683231
650_win(kernel32.GetStdHandle, HANDLE_IHV, wintypes.DWORD)  # _In_ nStdHandle
651
652
653# https://msdn.microsoft.com/en-us/library/ms724211
654_win(kernel32.CloseHandle, wintypes.BOOL, wintypes.HANDLE)  # _In_ hObject
655
656
657# https://msdn.microsoft.com/en-us/library/ms724935
658_win(
659    kernel32.SetHandleInformation,
660    wintypes.BOOL,
661    wintypes.HANDLE,  # _In_ hObject
662    wintypes.DWORD,  # _In_ dwMask
663    wintypes.DWORD,
664)  # _In_ dwFlags
665
666
667# https://msdn.microsoft.com/en-us/library/ms724251
668_win(
669    kernel32.DuplicateHandle,
670    wintypes.BOOL,
671    wintypes.HANDLE,  # _In_  hSourceProcessHandle,
672    wintypes.HANDLE,  # _In_  hSourceHandle,
673    wintypes.HANDLE,  # _In_  hTargetProcessHandle,
674    LPHANDLE,  # _Out_ lpTargetHandle,
675    wintypes.DWORD,  # _In_  dwDesiredAccess,
676    wintypes.BOOL,  # _In_  bInheritHandle,
677    wintypes.DWORD,
678)  # _In_  dwOptions
679
680
681# https://msdn.microsoft.com/en-us/library/ms683179
682_win(kernel32.GetCurrentProcess, wintypes.HANDLE)
683
684
685# https://msdn.microsoft.com/en-us/library/ms683189
686_win(
687    kernel32.GetExitCodeProcess,
688    wintypes.BOOL,
689    wintypes.HANDLE,  # _In_  hProcess,
690    LPDWORD,
691)  # _Out_ lpExitCode
692
693
694# https://msdn.microsoft.com/en-us/library/aa365152
695_win(
696    kernel32.CreatePipe,
697    wintypes.BOOL,
698    PHANDLE,  # _Out_    hReadPipe,
699    PHANDLE,  # _Out_    hWritePipe,
700    LPSECURITY_ATTRIBUTES,  # _In_opt_ lpPipeAttributes,
701    wintypes.DWORD,
702)  # _In_     nSize
703
704
705# https://msdn.microsoft.com/en-us/library/ms682431
706# _win(advapi32.CreateProcessWithTokenW, wintypes.BOOL,
707#    PHANDLE,       # _In_        lpUsername
708#    wintypes.DWORD,         # _In_        dwLogonFlags
709#    wintypes.LPCWSTR,       # _In_opt_    lpApplicationName
710#    wintypes.LPWSTR,        # _Inout_opt_ lpCommandLine
711#    wintypes.DWORD,         # _In_        dwCreationFlags
712#    wintypes.LPVOID,        # _In_opt_     lpEnvironment
713#    wintypes.LPCWSTR,       # _In_opt_    lpCurrentDirectory
714#    LPSTARTUPINFO,          # _In_        lpStartupInfo
715#    LPPROCESS_INFORMATION)  # _Out_       lpProcessInformation
716
717
718# https://msdn.microsoft.com/en-us/library/ms682431
719_win(
720    advapi32.CreateProcessWithLogonW,
721    wintypes.BOOL,
722    wintypes.LPCWSTR,  # _In_        lpUsername
723    wintypes.LPCWSTR,  # _In_opt_    lpDomain
724    wintypes.LPCWSTR,  # _In_        lpPassword
725    wintypes.DWORD,  # _In_        dwLogonFlags
726    wintypes.LPCWSTR,  # _In_opt_    lpApplicationName
727    wintypes.LPWSTR,  # _Inout_opt_ lpCommandLine
728    wintypes.DWORD,  # _In_        dwCreationFlags
729    wintypes.LPCWSTR,  # _In_opt_    lpEnvironment
730    wintypes.LPCWSTR,  # _In_opt_    lpCurrentDirectory
731    LPSTARTUPINFO,  # _In_        lpStartupInfo
732    LPPROCESS_INFORMATION,
733)  # _Out_       lpProcessInformation
734
735
736# https://msdn.microsoft.com/en-us/library/ms683179
737_win(kernel32.GetCurrentProcess, wintypes.HANDLE)
738
739
740# https://msdn.microsoft.com/en-us/library/ms724251
741_win(
742    kernel32.DuplicateHandle,
743    BOOL,
744    wintypes.HANDLE,  # _In_  hSourceProcessHandle
745    wintypes.HANDLE,  # _In_  hSourceHandle
746    wintypes.HANDLE,  # _In_  hTargetProcessHandle
747    LPHANDLE,  # _Out_ lpTargetHandle
748    wintypes.DWORD,  # _In_  dwDesiredAccess
749    wintypes.BOOL,  # _In_  bInheritHandle
750    wintypes.DWORD,
751)  # _In_  dwOptions
752
753
754# https://msdn.microsoft.com/en-us/library/ms724295
755_win(
756    kernel32.GetComputerNameW, BOOL, wintypes.LPWSTR, LPDWORD  # _Out_   lpBuffer
757)  # _Inout_ lpnSize
758
759
760# https://msdn.microsoft.com/en-us/library/aa379295
761_win(
762    advapi32.OpenProcessToken,
763    BOOL,
764    wintypes.HANDLE,  # _In_  ProcessHandle
765    wintypes.DWORD,  # _In_  DesiredAccess
766    LPHANDLE,
767)  # _Out_ TokenHandle
768
769
770# https://msdn.microsoft.com/en-us/library/aa446617
771_win(
772    advapi32.DuplicateTokenEx,
773    BOOL,
774    wintypes.HANDLE,  # _In_     hExistingToken
775    wintypes.DWORD,  # _In_     dwDesiredAccess
776    LPSECURITY_ATTRIBUTES,  # _In_opt_ lpTokenAttributes
777    SECURITY_IMPERSONATION_LEVEL,  # _In_     ImpersonationLevel
778    TOKEN_TYPE,  # _In_     TokenType
779    LPHANDLE,
780)  # _Out_    phNewToken
781
782
783# https://msdn.microsoft.com/en-us/library/ff566415
784_win(ntdll.NtAllocateLocallyUniqueId, NTSTATUS, LPLUID)  # _Out_ LUID
785
786
787# https://msdn.microsoft.com/en-us/library/aa378279
788_win(
789    secur32.LsaFreeReturnBuffer,
790    NTSTATUS,
791    wintypes.LPVOID,
792)  # _In_ Buffer
793
794
795# https://msdn.microsoft.com/en-us/library/aa378265
796_win(
797    secur32.LsaConnectUntrusted,
798    NTSTATUS,
799    LPHANDLE,
800)  # _Out_ LsaHandle
801
802
803# https://msdn.microsoft.com/en-us/library/aa378318
804_win(
805    secur32.LsaRegisterLogonProcess,
806    NTSTATUS,
807    LPSTRING,  # _In_  LogonProcessName
808    LPHANDLE,  # _Out_ LsaHandle
809    LPLSA_OPERATIONAL_MODE,
810)  # _Out_ SecurityMode
811
812
813# https://msdn.microsoft.com/en-us/library/aa378269
814_win(secur32.LsaDeregisterLogonProcess, NTSTATUS, wintypes.HANDLE)  # _In_ LsaHandle
815
816
817# https://msdn.microsoft.com/en-us/library/aa378297
818_win(
819    secur32.LsaLookupAuthenticationPackage,
820    NTSTATUS,
821    wintypes.HANDLE,  # _In_  LsaHandle
822    LPSTRING,  # _In_  PackageName
823    LPULONG,
824)  # _Out_ AuthenticationPackage
825
826
827# https://msdn.microsoft.com/en-us/library/aa378292
828_win(
829    secur32.LsaLogonUser,
830    NTSTATUS,
831    wintypes.HANDLE,  # _In_     LsaHandle
832    LPSTRING,  # _In_     OriginName
833    SECURITY_LOGON_TYPE,  # _In_     LogonType
834    wintypes.ULONG,  # _In_     AuthenticationPackage
835    wintypes.LPVOID,  # _In_     AuthenticationInformation
836    wintypes.ULONG,  # _In_     AuthenticationInformationLength
837    LPTOKEN_GROUPS,  # _In_opt_ LocalGroups
838    LPTOKEN_SOURCE,  # _In_     SourceContext
839    LPLPVOID,  # _Out_    ProfileBuffer
840    LPULONG,  # _Out_    ProfileBufferLength
841    LPLUID,  # _Out_    LogonId
842    LPHANDLE,  # _Out_    Token
843    LPQUOTA_LIMITS,  # _Out_    Quotas
844    PNTSTATUS,
845)  # _Out_    SubStatus
846
847
848def duplicate_token(
849    source_token=None,
850    access=TOKEN_ALL_ACCESS,
851    impersonation_level=SecurityImpersonation,
852    token_type=TokenPrimary,
853    attributes=None,
854):
855    close_source = False
856    if source_token is None:
857        close_source = True
858        source_token = HANDLE()
859        advapi32.OpenProcessToken(
860            kernel32.GetCurrentProcess(), TOKEN_ALL_ACCESS, ctypes.byref(source_token)
861        )
862    token = HANDLE()
863    try:
864        advapi32.DuplicateTokenEx(
865            source_token,
866            access,
867            attributes,
868            impersonation_level,
869            token_type,
870            ctypes.byref(token),
871        )
872    finally:
873        if close_source:
874            source_token.Close()
875    return token
876
877
878def lsa_connect_untrusted():
879    handle = wintypes.HANDLE()
880    secur32.LsaConnectUntrusted(ctypes.byref(handle))
881    return handle.value
882
883
884def lsa_register_logon_process(logon_process_name):
885    if not isinstance(logon_process_name, bytes):
886        logon_process_name = logon_process_name.encode("mbcs")
887    logon_process_name = logon_process_name[:127]
888    buf = ctypes.create_string_buffer(logon_process_name, 128)
889    name = STRING(len(logon_process_name), len(buf), buf)
890    handle = wintypes.HANDLE()
891    mode = LSA_OPERATIONAL_MODE()
892    secur32.LsaRegisterLogonProcess(
893        ctypes.byref(name), ctypes.byref(handle), ctypes.byref(mode)
894    )
895    return handle.value
896
897
898def lsa_lookup_authentication_package(lsa_handle, package_name):
899    if not isinstance(package_name, bytes):
900        package_name = package_name.encode("mbcs")
901    package_name = package_name[:127]
902    buf = ctypes.create_string_buffer(package_name)
903    name = STRING(len(package_name), len(buf), buf)
904    package = wintypes.ULONG()
905    secur32.LsaLookupAuthenticationPackage(
906        lsa_handle, ctypes.byref(name), ctypes.byref(package)
907    )
908    return package.value
909
910
911LOGONINFO = collections.namedtuple(
912    "LOGONINFO", ("Token", "LogonId", "Profile", "Quotas")
913)
914
915
916def lsa_logon_user(
917    auth_info,
918    local_groups=None,
919    origin_name=py_origin_name,
920    source_context=None,
921    auth_package=None,
922    logon_type=None,
923    lsa_handle=None,
924):
925    if local_groups is None:
926        plocal_groups = LPTOKEN_GROUPS()
927    else:
928        plocal_groups = ctypes.byref(local_groups)
929    if source_context is None:
930        source_context = py_source_context
931    if not isinstance(origin_name, bytes):
932        origin_name = origin_name.encode("mbcs")
933    buf = ctypes.create_string_buffer(origin_name)
934    origin_name = STRING(len(origin_name), len(buf), buf)
935    if auth_package is None:
936        if isinstance(auth_info, MSV1_0_S4U_LOGON):
937            auth_package = NEGOTIATE_PACKAGE_NAME
938        elif isinstance(auth_info, KERB_S4U_LOGON):
939            auth_package = MICROSOFT_KERBEROS_NAME
940        else:
941            auth_package = MSV1_0_PACKAGE_NAME
942    if logon_type is None:
943        if isinstance(auth_info, S4ULogon):
944            logon_type = win32con.LOGON32_LOGON_NETWORK
945        else:
946            logon_type = Interactive
947    profile_buffer = wintypes.LPVOID()
948    profile_buffer_length = wintypes.ULONG()
949    profile = None
950    logonid = LUID()
951    htoken = HANDLE()
952    quotas = QUOTA_LIMITS()
953    substatus = NTSTATUS()
954    deregister = False
955    if lsa_handle is None:
956        lsa_handle = lsa_connect_untrusted()
957        deregister = True
958    try:
959        if isinstance(auth_package, (str, bytes)):
960            auth_package = lsa_lookup_authentication_package(lsa_handle, auth_package)
961        try:
962            secur32.LsaLogonUser(
963                lsa_handle,
964                ctypes.byref(origin_name),
965                logon_type,
966                auth_package,
967                ctypes.byref(auth_info),
968                ctypes.sizeof(auth_info),
969                plocal_groups,
970                ctypes.byref(source_context),
971                ctypes.byref(profile_buffer),
972                ctypes.byref(profile_buffer_length),
973                ctypes.byref(logonid),
974                ctypes.byref(htoken),
975                ctypes.byref(quotas),
976                ctypes.byref(substatus),
977            )
978        except OSError:
979            if substatus.value:
980                raise ctypes.WinError(substatus.to_error())
981            raise
982        finally:
983            if profile_buffer:
984                address = profile_buffer.value
985                buftype = PROFILE_BUFFER_TYPE.from_address(address).value
986                if buftype == MsV1_0InteractiveLogon:
987                    profile = MSV1_0_INTERACTIVE_PROFILE.from_address_copy(
988                        address, profile_buffer_length.value
989                    )
990                secur32.LsaFreeReturnBuffer(address)
991    finally:
992        if deregister:
993            secur32.LsaDeregisterLogonProcess(lsa_handle)
994    return LOGONINFO(htoken, logonid, profile, quotas)
995
996
997def logon_msv1(
998    name,
999    password,
1000    domain=None,
1001    local_groups=None,
1002    origin_name=py_origin_name,
1003    source_context=None,
1004):
1005    return lsa_logon_user(
1006        MSV1_0_INTERACTIVE_LOGON(name, password, domain),
1007        local_groups,
1008        origin_name,
1009        source_context,
1010    )
1011
1012
1013def logon_msv1_s4u(
1014    name, local_groups=None, origin_name=py_origin_name, source_context=None
1015):
1016    domain = ctypes.create_unicode_buffer(MAX_COMPUTER_NAME_LENGTH + 1)
1017    length = wintypes.DWORD(len(domain))
1018    kernel32.GetComputerNameW(domain, ctypes.byref(length))
1019    return lsa_logon_user(
1020        MSV1_0_S4U_LOGON(name, domain.value), local_groups, origin_name, source_context
1021    )
1022
1023
1024def logon_kerb_s4u(
1025    name,
1026    realm=None,
1027    local_groups=None,
1028    origin_name=py_origin_name,
1029    source_context=None,
1030    logon_process_name=py_logon_process_name,
1031):
1032    lsa_handle = lsa_register_logon_process(logon_process_name)
1033    try:
1034        return lsa_logon_user(
1035            KERB_S4U_LOGON(name, realm),
1036            local_groups,
1037            origin_name,
1038            source_context,
1039            lsa_handle=lsa_handle,
1040        )
1041    finally:
1042        secur32.LsaDeregisterLogonProcess(lsa_handle)
1043
1044
1045def DuplicateHandle(
1046    hsrc=kernel32.GetCurrentProcess(),
1047    srchandle=kernel32.GetCurrentProcess(),
1048    htgt=kernel32.GetCurrentProcess(),
1049    access=0,
1050    inherit=False,
1051    options=win32con.DUPLICATE_SAME_ACCESS,
1052):
1053    tgthandle = wintypes.HANDLE()
1054    kernel32.DuplicateHandle(
1055        hsrc, srchandle, htgt, ctypes.byref(tgthandle), access, inherit, options
1056    )
1057    return tgthandle.value
1058
1059
1060def CreatePipe(inherit_read=False, inherit_write=False):
1061    read, write = wintypes.HANDLE(), wintypes.HANDLE()
1062    kernel32.CreatePipe(ctypes.byref(read), ctypes.byref(write), None, 0)
1063    if inherit_read:
1064        kernel32.SetHandleInformation(
1065            read, win32con.HANDLE_FLAG_INHERIT, win32con.HANDLE_FLAG_INHERIT
1066        )
1067    if inherit_write:
1068        kernel32.SetHandleInformation(
1069            write, win32con.HANDLE_FLAG_INHERIT, win32con.HANDLE_FLAG_INHERIT
1070        )
1071    return read.value, write.value
1072
1073
1074def set_user_perm(obj, perm, sid):
1075    """
1076    Set an object permission for the given user sid
1077    """
1078    info = (
1079        win32security.OWNER_SECURITY_INFORMATION
1080        | win32security.GROUP_SECURITY_INFORMATION
1081        | win32security.DACL_SECURITY_INFORMATION
1082    )
1083    sd = win32security.GetUserObjectSecurity(obj, info)
1084    dacl = sd.GetSecurityDescriptorDacl()
1085    ace_cnt = dacl.GetAceCount()
1086    found = False
1087    for idx in range(0, ace_cnt):
1088        (aceType, aceFlags), ace_mask, ace_sid = dacl.GetAce(idx)
1089        ace_exists = (
1090            aceType == ntsecuritycon.ACCESS_ALLOWED_ACE_TYPE
1091            and ace_mask == perm
1092            and ace_sid == sid
1093        )
1094        if ace_exists:
1095            # If the ace already exists, do nothing
1096            break
1097    else:
1098        dacl.AddAccessAllowedAce(dacl.GetAclRevision(), perm, sid)
1099        sd.SetSecurityDescriptorDacl(1, dacl, 0)
1100        win32security.SetUserObjectSecurity(obj, info, sd)
1101
1102
1103def grant_winsta_and_desktop(th):
1104    """
1105    Grant the token's user access to the current process's window station and
1106    desktop.
1107    """
1108    current_sid = win32security.GetTokenInformation(th, win32security.TokenUser)[0]
1109    # Add permissions for the sid to the current windows station and thread id.
1110    # This prevents windows error 0xC0000142.
1111    winsta = win32process.GetProcessWindowStation()
1112    set_user_perm(winsta, WINSTA_ALL, current_sid)
1113    desktop = win32service.GetThreadDesktop(win32api.GetCurrentThreadId())
1114    set_user_perm(desktop, DESKTOP_ALL, current_sid)
1115
1116
1117def environment_string(env):
1118    senv = ""
1119    for k, v in env.items():
1120        senv += k + "=" + v + "\0"
1121    senv += "\0"
1122    return ctypes.create_unicode_buffer(senv)
1123
1124
1125def CreateProcessWithTokenW(
1126    token,
1127    logonflags=0,
1128    applicationname=None,
1129    commandline=None,
1130    creationflags=0,
1131    environment=None,
1132    currentdirectory=None,
1133    startupinfo=None,
1134):
1135    creationflags |= win32con.CREATE_UNICODE_ENVIRONMENT
1136    if commandline is not None:
1137        commandline = ctypes.create_unicode_buffer(commandline)
1138    if startupinfo is None:
1139        startupinfo = STARTUPINFO()
1140    if currentdirectory is not None:
1141        currentdirectory = ctypes.create_unicode_buffer(currentdirectory)
1142    if environment is not None:
1143        environment = ctypes.pointer(environment_string(environment))
1144    process_info = PROCESS_INFORMATION()
1145    ret = advapi32.CreateProcessWithTokenW(
1146        token,
1147        logonflags,
1148        applicationname,
1149        commandline,
1150        creationflags,
1151        environment,
1152        currentdirectory,
1153        ctypes.byref(startupinfo),
1154        ctypes.byref(process_info),
1155    )
1156    if ret == 0:
1157        winerr = win32api.GetLastError()
1158        exc = OSError(win32api.FormatMessage(winerr))
1159        exc.winerror = winerr
1160        raise exc
1161    return process_info
1162
1163
1164def enumerate_tokens(sid=None, session_id=None, privs=None):
1165    """
1166    Enumerate tokens from any existing processes that can be accessed.
1167    Optionally filter by sid.
1168    """
1169    for p in psutil.process_iter():
1170        if p.pid == 0:
1171            continue
1172        try:
1173            ph = win32api.OpenProcess(win32con.PROCESS_ALL_ACCESS, 0, p.pid)
1174        except win32api.error as exc:
1175            if exc.winerror == 5:
1176                log.debug("Unable to OpenProcess pid=%d name=%s", p.pid, p.name())
1177                continue
1178            raise exc
1179        try:
1180            access = (
1181                win32security.TOKEN_DUPLICATE
1182                | win32security.TOKEN_QUERY
1183                | win32security.TOKEN_IMPERSONATE
1184                | win32security.TOKEN_ASSIGN_PRIMARY
1185            )
1186            th = win32security.OpenProcessToken(ph, access)
1187        except Exception as exc:  # pylint: disable=broad-except
1188            log.debug(
1189                "OpenProcessToken failed pid=%d name=%s user%s",
1190                p.pid,
1191                p.name(),
1192                p.username(),
1193            )
1194            continue
1195        try:
1196            process_sid = win32security.GetTokenInformation(
1197                th, win32security.TokenUser
1198            )[0]
1199        except Exception as exc:  # pylint: disable=broad-except
1200            log.exception(
1201                "GetTokenInformation pid=%d name=%s user%s",
1202                p.pid,
1203                p.name(),
1204                p.username(),
1205            )
1206            continue
1207
1208        proc_sid = win32security.ConvertSidToStringSid(process_sid)
1209        if sid and sid != proc_sid:
1210            log.debug("Token for pid does not match user sid: %s", sid)
1211            continue
1212
1213        if (
1214            session_id
1215            and win32security.GetTokenInformation(th, win32security.TokenSessionId)
1216            != session_id
1217        ):
1218            continue
1219
1220        def has_priv(tok, priv):
1221            luid = win32security.LookupPrivilegeValue(None, priv)
1222            for priv_luid, flags in win32security.GetTokenInformation(
1223                tok, win32security.TokenPrivileges
1224            ):
1225                if priv_luid == luid:
1226                    return True
1227            return False
1228
1229        if privs:
1230            has_all = True
1231            for name in privs:
1232                if not has_priv(th, name):
1233                    has_all = False
1234            if not has_all:
1235                continue
1236        yield dup_token(th)
1237
1238
1239def impersonate_sid(sid, session_id=None, privs=None):
1240    """
1241    Find an existing process token for the given sid and impersonate the token.
1242    """
1243    for tok in enumerate_tokens(sid, session_id, privs):
1244        tok = dup_token(tok)
1245        elevate_token(tok)
1246        if win32security.ImpersonateLoggedOnUser(tok) == 0:
1247            raise OSError("Impersonation failure")
1248        return tok
1249    raise OSError("Impersonation failure")
1250
1251
1252def dup_token(th):
1253    """
1254    duplicate the access token
1255    """
1256    # TODO: is `duplicate_token` the same?
1257    sec_attr = win32security.SECURITY_ATTRIBUTES()
1258    sec_attr.bInheritHandle = True
1259    return win32security.DuplicateTokenEx(
1260        th,
1261        win32security.SecurityImpersonation,
1262        win32con.MAXIMUM_ALLOWED,
1263        win32security.TokenPrimary,
1264        sec_attr,
1265    )
1266
1267
1268def elevate_token(th):
1269    """
1270    Set all token privileges to enabled
1271    """
1272    # Get list of privileges this token contains
1273    privileges = win32security.GetTokenInformation(th, win32security.TokenPrivileges)
1274
1275    # Create a set of all privileges to be enabled
1276    enable_privs = set()
1277    for luid, flags in privileges:
1278        enable_privs.add((luid, win32con.SE_PRIVILEGE_ENABLED))
1279
1280    # Enable the privileges
1281    if win32security.AdjustTokenPrivileges(th, 0, enable_privs) == 0:
1282        raise OSError(win32api.FormatMessage(win32api.GetLastError()))
1283
1284
1285def make_inheritable(token):
1286    """Create an inheritable handle"""
1287    return win32api.DuplicateHandle(
1288        win32api.GetCurrentProcess(),
1289        token,
1290        win32api.GetCurrentProcess(),
1291        0,
1292        1,
1293        win32con.DUPLICATE_SAME_ACCESS,
1294    )
1295
1296
1297def CreateProcessWithLogonW(
1298    username=None,
1299    domain=None,
1300    password=None,
1301    logonflags=0,
1302    applicationname=None,
1303    commandline=None,
1304    creationflags=0,
1305    environment=None,
1306    currentdirectory=None,
1307    startupinfo=None,
1308):
1309    creationflags |= win32con.CREATE_UNICODE_ENVIRONMENT
1310    if commandline is not None:
1311        commandline = ctypes.create_unicode_buffer(commandline)
1312    if startupinfo is None:
1313        startupinfo = STARTUPINFO()
1314    if environment is not None:
1315        environment = ctypes.pointer(environment_string(environment))
1316    process_info = PROCESS_INFORMATION()
1317    advapi32.CreateProcessWithLogonW(
1318        username,
1319        domain,
1320        password,
1321        logonflags,
1322        applicationname,
1323        commandline,
1324        creationflags,
1325        environment,
1326        currentdirectory,
1327        ctypes.byref(startupinfo),
1328        ctypes.byref(process_info),
1329    )
1330    return process_info
1331