Last active
February 14, 2020 12:21
-
-
Save lambdalisue/ce420370e7e24619db8e32e826fb1f86 to your computer and use it in GitHub Desktop.
Spoof process parent in Windows via Win32 APIs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ctypes | |
from contextlib import contextmanager, ExitStack | |
from ctypes import byref, sizeof, Structure, POINTER | |
from ctypes import wintypes | |
# Alias | |
windll = ctypes.windll # type: ignore | |
WinError = ctypes.WinError # type: ignore | |
SIZE_T = ctypes.c_size_t | |
TRUE = 1 | |
FALSE = 0 | |
# https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfow | |
class STARTUPINFOW(Structure): | |
_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.LPVOID), # LPBYTE | |
('hStdInput', wintypes.HANDLE), | |
('hStdOutput', wintypes.HANDLE), | |
('hStdError', wintypes.HANDLE), | |
] | |
# https://docs.microsoft.com/ja-jp/windows/win32/api/winbase/ns-winbase-startupinfoexw | |
class STARTUPINFOEXW(Structure): | |
_fields_ = [ | |
('StartupInfo', STARTUPINFOW), | |
('lpAttributeList', wintypes.LPVOID), # LPPROC_THREAD_ATTRIBUTE_LIST | |
] | |
# https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-process_information | |
class PROCESS_INFORMATION(Structure): | |
_fields_ = [ | |
('hProcess', wintypes.HANDLE), | |
('hThread', wintypes.HANDLE), | |
('dwProcessId', wintypes.DWORD), | |
('dwThreadId', wintypes.DWORD), | |
] | |
def errcheck(result, func, args): | |
if not result: | |
raise WinError() | |
return args | |
# https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getshellwindow | |
GetShellWindow = windll.user32.GetShellWindow | |
GetShellWindow.argtypes = () | |
GetShellWindow.restype = wintypes.HWND | |
# https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowthreadprocessid | |
GetWindowThreadProcessId = windll.user32.GetWindowThreadProcessId | |
GetWindowThreadProcessId.argtypes = (wintypes.HWND, wintypes.LPDWORD) | |
GetWindowThreadProcessId.restype = wintypes.DWORD | |
# https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess | |
OpenProcess = windll.kernel32.OpenProcess | |
OpenProcess.argtypes = (wintypes.DWORD, wintypes.BOOL, wintypes.DWORD) | |
OpenProcess.restype = wintypes.HANDLE | |
# https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-initializeprocthreadattributelist?redirectedfrom=MSDN | |
InitializeProcThreadAttributeList = windll.kernel32.InitializeProcThreadAttributeList | |
InitializeProcThreadAttributeList.argtypes = ( | |
wintypes.LPVOID, # LPPROC_THREAD_ATTRIBUTE_LIST | |
wintypes.DWORD, | |
wintypes.DWORD, | |
POINTER(SIZE_T), | |
) | |
InitializeProcThreadAttributeList.restype = wintypes.BOOL | |
# https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute?redirectedfrom=MSDN | |
UpdateProcThreadAttribute = windll.kernel32.UpdateProcThreadAttribute | |
UpdateProcThreadAttribute.argtypes = ( | |
wintypes.LPVOID, # LPPROC_THREAD_ATTRIBUTE_LIST | |
wintypes.DWORD, | |
SIZE_T, # DWORD_PTR | |
wintypes.LPVOID, | |
SIZE_T, | |
wintypes.LPVOID, | |
POINTER(SIZE_T), | |
) | |
UpdateProcThreadAttribute.restype = wintypes.BOOL | |
UpdateProcThreadAttribute.errcheck = errcheck | |
# https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-deleteprocthreadattributelist | |
DeleteProcThreadAttributeList = windll.kernel32.DeleteProcThreadAttributeList | |
DeleteProcThreadAttributeList.argtypes = (wintypes.LPVOID,) | |
DeleteProcThreadAttributeList.restype = None | |
# https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw | |
CreateProcessW = windll.kernel32.CreateProcessW | |
CreateProcessW.argtypes = ( | |
wintypes.LPCWSTR, | |
wintypes.LPWSTR, | |
wintypes.LPVOID, # LPSECURITY_ATTRIBUTES | |
wintypes.LPVOID, # LPSECURITY_ATTRIBUTES | |
wintypes.BOOL, | |
wintypes.DWORD, | |
wintypes.LPVOID, | |
wintypes.LPCWSTR, | |
wintypes.LPVOID, | |
POINTER(PROCESS_INFORMATION), | |
) | |
CreateProcessW.restype = wintypes.BOOL | |
CreateProcessW.errcheck = errcheck | |
# https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject | |
WaitForSingleObject = windll.kernel32.WaitForSingleObject | |
WaitForSingleObject.argtypes = (wintypes.HANDLE, wintypes.DWORD) | |
WaitForSingleObject.restype = wintypes.DWORD | |
# https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle | |
CloseHandle = windll.kernel32.CloseHandle | |
CloseHandle.argtypes = (wintypes.HANDLE,) | |
CloseHandle.restype = wintypes.BOOL | |
CloseHandle.errcheck = errcheck | |
@contextmanager | |
def get_shell_window_process(): | |
hWnd = GetShellWindow() | |
if hWnd is None: | |
raise RuntimeError("No shell process exist") | |
lpdwProcessId = wintypes.DWORD() | |
GetWindowThreadProcessId(hWnd, byref(lpdwProcessId)) | |
# NOTE: | |
# PROCESS_CREATE_PROCESS is required by PROC_THREAD_ATTRIBUTE_PARENT_PROCESS | |
PROCESS_CREATE_PROCESS = 0x0080 | |
hProcess = OpenProcess( | |
PROCESS_CREATE_PROCESS, | |
FALSE, | |
lpdwProcessId.value, | |
) | |
if not hProcess: | |
raise WinError() | |
try: | |
yield hProcess | |
finally: | |
CloseHandle(hProcess) | |
@contextmanager | |
def create_attribute_list(parent: int): | |
lpSize = SIZE_T() | |
InitializeProcThreadAttributeList(None, 1, 0, byref(lpSize)) | |
AttributeList = (wintypes.BYTE * lpSize.value)() | |
ret = InitializeProcThreadAttributeList( | |
byref(AttributeList), 1, 0, byref(lpSize) | |
) | |
if not ret: | |
raise WinError() | |
hProcess = wintypes.HANDLE(parent) | |
ProcThreadAttributeParentProcess = 0 | |
PROC_THREAD_ATTRIBUTE_INPUT = 0x00020000 | |
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = ProcThreadAttributeParentProcess | PROC_THREAD_ATTRIBUTE_INPUT | |
UpdateProcThreadAttribute( | |
byref(AttributeList), | |
0, | |
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, | |
byref(hProcess), | |
sizeof(wintypes.HANDLE), | |
None, | |
None, | |
) | |
try: | |
yield AttributeList | |
finally: | |
DeleteProcThreadAttributeList(byref(AttributeList)) | |
@contextmanager | |
def create_process(cmd: str, attribute_list: ctypes.Array): | |
StartupInfo = STARTUPINFOEXW() | |
StartupInfo.lpAttributeList = ctypes.cast( | |
byref(attribute_list), | |
wintypes.LPVOID, | |
) | |
StartupInfo.StartupInfo.cb = sizeof(StartupInfo) | |
DETACHED_PROCESS = 0x00000008 | |
CREATE_NEW_PROCESS_GROUP = 0x00000200 | |
EXTENDED_STARTUPINFO_PRESENT = 0x00080000 | |
lpCommandLine = wintypes.LPWSTR(cmd) | |
ProcessInformation = PROCESS_INFORMATION() | |
CreateProcessW( | |
None, | |
lpCommandLine, | |
None, | |
None, | |
TRUE, | |
DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | EXTENDED_STARTUPINFO_PRESENT, | |
None, | |
None, | |
byref(StartupInfo), | |
byref(ProcessInformation), | |
) | |
# we don't need thread handle | |
CloseHandle(ProcessInformation.hThread) | |
# yield process handle | |
try: | |
yield ProcessInformation.hProcess | |
finally: | |
CloseHandle(ProcessInformation.hProcess) | |
def main(): | |
import sys | |
with ExitStack() as stack: | |
parent = stack.enter_context(get_shell_window_process()) | |
attribute_list = stack.enter_context(create_attribute_list(parent)) | |
process_handle = stack.enter_context(create_process( | |
f"{sys.executable} -c \"import time; time.sleep(10)\"", | |
attribute_list, | |
)) | |
INFINITE = -1 | |
ret = WaitForSingleObject(process_handle, INFINITE) | |
if ret: | |
raise WinError() | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
http://winprogger.com/launching-a-non-child-process/
https://devblogs.microsoft.com/oldnewthing/20190425-00/?p=102443