api: don't auto-elevate
There's no longer a need to do this for every API call. This only exists now for the pnp guid reuse workaround hack. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
This commit is contained in:
		
							parent
							
								
									d33732ab4b
								
							
						
					
					
						commit
						ed2f5cc225
					
				@ -668,18 +668,13 @@ WintunFreeAdapter(_In_ WINTUN_ADAPTER *Adapter)
 | 
				
			|||||||
_Return_type_success_(return != NULL) WINTUN_ADAPTER *WINAPI
 | 
					_Return_type_success_(return != NULL) WINTUN_ADAPTER *WINAPI
 | 
				
			||||||
    WintunOpenAdapter(_In_z_ const WCHAR *Pool, _In_z_ const WCHAR *Name)
 | 
					    WintunOpenAdapter(_In_z_ const WCHAR *Pool, _In_z_ const WCHAR *Name)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    if (!ElevateToSystem())
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        LOG(WINTUN_LOG_ERR, L"Failed to impersonate SYSTEM user");
 | 
					 | 
				
			||||||
        return NULL;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    DWORD LastError;
 | 
					    DWORD LastError;
 | 
				
			||||||
    WINTUN_ADAPTER *Adapter = NULL;
 | 
					    WINTUN_ADAPTER *Adapter = NULL;
 | 
				
			||||||
    HANDLE Mutex = NamespaceTakePoolMutex(Pool);
 | 
					    HANDLE Mutex = NamespaceTakePoolMutex(Pool);
 | 
				
			||||||
    if (!Mutex)
 | 
					    if (!Mutex)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        LastError = LOG(WINTUN_LOG_ERR, L"Failed to take %s pool mutex", Pool);
 | 
					        LastError = LOG(WINTUN_LOG_ERR, L"Failed to take %s pool mutex", Pool);
 | 
				
			||||||
        goto cleanupToken;
 | 
					        goto cleanup;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    HDEVINFO DevInfo = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, NULL, NULL, DIGCF_PRESENT, NULL, NULL, NULL);
 | 
					    HDEVINFO DevInfo = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, NULL, NULL, DIGCF_PRESENT, NULL, NULL, NULL);
 | 
				
			||||||
@ -759,8 +754,7 @@ cleanupDevInfo:
 | 
				
			|||||||
    SetupDiDestroyDeviceInfoList(DevInfo);
 | 
					    SetupDiDestroyDeviceInfoList(DevInfo);
 | 
				
			||||||
cleanupMutex:
 | 
					cleanupMutex:
 | 
				
			||||||
    NamespaceReleaseMutex(Mutex);
 | 
					    NamespaceReleaseMutex(Mutex);
 | 
				
			||||||
cleanupToken:
 | 
					cleanup:
 | 
				
			||||||
    RevertToSelf();
 | 
					 | 
				
			||||||
    SetLastError(LastError);
 | 
					    SetLastError(LastError);
 | 
				
			||||||
    return Adapter;
 | 
					    return Adapter;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1840,11 +1834,6 @@ _Return_type_success_(return != NULL) WINTUN_ADAPTER *WINAPI WintunCreateAdapter
 | 
				
			|||||||
    _In_opt_ const GUID *RequestedGUID,
 | 
					    _In_opt_ const GUID *RequestedGUID,
 | 
				
			||||||
    _Out_opt_ BOOL *RebootRequired)
 | 
					    _Out_opt_ BOOL *RebootRequired)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    if (!ElevateToSystem())
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        LOG(WINTUN_LOG_ERR, L"Failed to impersonate SYSTEM user");
 | 
					 | 
				
			||||||
        return NULL;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    BOOL DummyRebootRequired;
 | 
					    BOOL DummyRebootRequired;
 | 
				
			||||||
    if (!RebootRequired)
 | 
					    if (!RebootRequired)
 | 
				
			||||||
        RebootRequired = &DummyRebootRequired;
 | 
					        RebootRequired = &DummyRebootRequired;
 | 
				
			||||||
@ -1855,12 +1844,11 @@ _Return_type_success_(return != NULL) WINTUN_ADAPTER *WINAPI WintunCreateAdapter
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        Adapter = CreateAdapterViaRundll32(Pool, Name, RequestedGUID, RebootRequired);
 | 
					        Adapter = CreateAdapterViaRundll32(Pool, Name, RequestedGUID, RebootRequired);
 | 
				
			||||||
        LastError = Adapter ? ERROR_SUCCESS : GetLastError();
 | 
					        LastError = Adapter ? ERROR_SUCCESS : GetLastError();
 | 
				
			||||||
        goto cleanupToken;
 | 
					        goto cleanup;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    Adapter = CreateAdapter(Pool, Name, RequestedGUID, RebootRequired);
 | 
					    Adapter = CreateAdapter(Pool, Name, RequestedGUID, RebootRequired);
 | 
				
			||||||
    LastError = Adapter ? ERROR_SUCCESS : GetLastError();
 | 
					    LastError = Adapter ? ERROR_SUCCESS : GetLastError();
 | 
				
			||||||
cleanupToken:
 | 
					cleanup:
 | 
				
			||||||
    RevertToSelf();
 | 
					 | 
				
			||||||
    return RET_ERROR(Adapter, LastError);
 | 
					    return RET_ERROR(Adapter, LastError);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1869,11 +1857,6 @@ _Return_type_success_(return != FALSE) BOOL WINAPI WintunDeleteAdapter(
 | 
				
			|||||||
    _In_ BOOL ForceCloseSessions,
 | 
					    _In_ BOOL ForceCloseSessions,
 | 
				
			||||||
    _Out_opt_ BOOL *RebootRequired)
 | 
					    _Out_opt_ BOOL *RebootRequired)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    if (!ElevateToSystem())
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        LOG(WINTUN_LOG_ERR, L"Failed to impersonate SYSTEM user");
 | 
					 | 
				
			||||||
        return FALSE;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    BOOL DummyRebootRequired;
 | 
					    BOOL DummyRebootRequired;
 | 
				
			||||||
    if (!RebootRequired)
 | 
					    if (!RebootRequired)
 | 
				
			||||||
        RebootRequired = &DummyRebootRequired;
 | 
					        RebootRequired = &DummyRebootRequired;
 | 
				
			||||||
@ -1883,14 +1866,14 @@ _Return_type_success_(return != FALSE) BOOL WINAPI WintunDeleteAdapter(
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        LastError =
 | 
					        LastError =
 | 
				
			||||||
            DeleteAdapterViaRundll32(Adapter, ForceCloseSessions, RebootRequired) ? ERROR_SUCCESS : GetLastError();
 | 
					            DeleteAdapterViaRundll32(Adapter, ForceCloseSessions, RebootRequired) ? ERROR_SUCCESS : GetLastError();
 | 
				
			||||||
        goto cleanupToken;
 | 
					        goto cleanup;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    HANDLE Mutex = NamespaceTakePoolMutex(Adapter->Pool);
 | 
					    HANDLE Mutex = NamespaceTakePoolMutex(Adapter->Pool);
 | 
				
			||||||
    if (!Mutex)
 | 
					    if (!Mutex)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        LastError = LOG(WINTUN_LOG_ERR, L"Failed to take %s pool mutex", Adapter->Pool);
 | 
					        LastError = LOG(WINTUN_LOG_ERR, L"Failed to take %s pool mutex", Adapter->Pool);
 | 
				
			||||||
        goto cleanupToken;
 | 
					        goto cleanup;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    HDEVINFO DevInfo;
 | 
					    HDEVINFO DevInfo;
 | 
				
			||||||
@ -1930,8 +1913,7 @@ cleanupDevInfo:
 | 
				
			|||||||
    SetupDiDestroyDeviceInfoList(DevInfo);
 | 
					    SetupDiDestroyDeviceInfoList(DevInfo);
 | 
				
			||||||
cleanupMutex:
 | 
					cleanupMutex:
 | 
				
			||||||
    NamespaceReleaseMutex(Mutex);
 | 
					    NamespaceReleaseMutex(Mutex);
 | 
				
			||||||
cleanupToken:
 | 
					cleanup:
 | 
				
			||||||
    RevertToSelf();
 | 
					 | 
				
			||||||
    return RET_ERROR(TRUE, LastError);
 | 
					    return RET_ERROR(TRUE, LastError);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1990,12 +1972,6 @@ cleanupMutex:
 | 
				
			|||||||
_Return_type_success_(return != FALSE) BOOL WINAPI
 | 
					_Return_type_success_(return != FALSE) BOOL WINAPI
 | 
				
			||||||
    WintunDeletePoolDriver(_In_z_ const WCHAR *Pool, _Out_opt_ BOOL *RebootRequired)
 | 
					    WintunDeletePoolDriver(_In_z_ const WCHAR *Pool, _Out_opt_ BOOL *RebootRequired)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    if (!ElevateToSystem())
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        LOG(WINTUN_LOG_ERR, L"Failed to impersonate SYSTEM user");
 | 
					 | 
				
			||||||
        return FALSE;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    BOOL DummyRebootRequired;
 | 
					    BOOL DummyRebootRequired;
 | 
				
			||||||
    if (!RebootRequired)
 | 
					    if (!RebootRequired)
 | 
				
			||||||
        RebootRequired = &DummyRebootRequired;
 | 
					        RebootRequired = &DummyRebootRequired;
 | 
				
			||||||
@ -2005,20 +1981,20 @@ _Return_type_success_(return != FALSE) BOOL WINAPI
 | 
				
			|||||||
    if (MAYBE_WOW64 && NativeMachine != IMAGE_FILE_PROCESS)
 | 
					    if (MAYBE_WOW64 && NativeMachine != IMAGE_FILE_PROCESS)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        LastError = DeletePoolDriverViaRundll32(Pool, RebootRequired) ? ERROR_SUCCESS : GetLastError();
 | 
					        LastError = DeletePoolDriverViaRundll32(Pool, RebootRequired) ? ERROR_SUCCESS : GetLastError();
 | 
				
			||||||
        goto cleanupToken;
 | 
					        goto cleanup;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!DeleteAllOurAdapters(Pool, RebootRequired))
 | 
					    if (!DeleteAllOurAdapters(Pool, RebootRequired))
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        LastError = GetLastError();
 | 
					        LastError = GetLastError();
 | 
				
			||||||
        goto cleanupToken;
 | 
					        goto cleanup;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    HANDLE DriverInstallationLock = NamespaceTakeDriverInstallationMutex();
 | 
					    HANDLE DriverInstallationLock = NamespaceTakeDriverInstallationMutex();
 | 
				
			||||||
    if (!DriverInstallationLock)
 | 
					    if (!DriverInstallationLock)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        LastError = LOG(WINTUN_LOG_ERR, L"Failed to take driver installation mutex");
 | 
					        LastError = LOG(WINTUN_LOG_ERR, L"Failed to take driver installation mutex");
 | 
				
			||||||
        goto cleanupToken;
 | 
					        goto cleanup;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    HDEVINFO DeviceInfoSet = SetupDiGetClassDevsW(&GUID_DEVCLASS_NET, NULL, NULL, 0);
 | 
					    HDEVINFO DeviceInfoSet = SetupDiGetClassDevsW(&GUID_DEVCLASS_NET, NULL, NULL, 0);
 | 
				
			||||||
    if (!DeviceInfoSet)
 | 
					    if (!DeviceInfoSet)
 | 
				
			||||||
@ -2060,25 +2036,19 @@ cleanupDeviceInfoSet:
 | 
				
			|||||||
    SetupDiDestroyDeviceInfoList(DeviceInfoSet);
 | 
					    SetupDiDestroyDeviceInfoList(DeviceInfoSet);
 | 
				
			||||||
cleanupDriverInstallationLock:
 | 
					cleanupDriverInstallationLock:
 | 
				
			||||||
    NamespaceReleaseMutex(DriverInstallationLock);
 | 
					    NamespaceReleaseMutex(DriverInstallationLock);
 | 
				
			||||||
cleanupToken:
 | 
					cleanup:
 | 
				
			||||||
    RevertToSelf();
 | 
					 | 
				
			||||||
    return RET_ERROR(TRUE, LastError);
 | 
					    return RET_ERROR(TRUE, LastError);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_Return_type_success_(return != FALSE) BOOL WINAPI
 | 
					_Return_type_success_(return != FALSE) BOOL WINAPI
 | 
				
			||||||
    WintunEnumAdapters(_In_z_ const WCHAR *Pool, _In_ WINTUN_ENUM_CALLBACK Func, _In_ LPARAM Param)
 | 
					    WintunEnumAdapters(_In_z_ const WCHAR *Pool, _In_ WINTUN_ENUM_CALLBACK Func, _In_ LPARAM Param)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    if (!ElevateToSystem())
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        LOG(WINTUN_LOG_ERR, L"Failed to impersonate SYSTEM user");
 | 
					 | 
				
			||||||
        return FALSE;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    DWORD LastError = ERROR_SUCCESS;
 | 
					    DWORD LastError = ERROR_SUCCESS;
 | 
				
			||||||
    HANDLE Mutex = NamespaceTakePoolMutex(Pool);
 | 
					    HANDLE Mutex = NamespaceTakePoolMutex(Pool);
 | 
				
			||||||
    if (!Mutex)
 | 
					    if (!Mutex)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        LastError = LOG(WINTUN_LOG_ERR, L"Failed to take %s pool mutex", Pool);
 | 
					        LastError = LOG(WINTUN_LOG_ERR, L"Failed to take %s pool mutex", Pool);
 | 
				
			||||||
        goto cleanupToken;
 | 
					        goto cleanup;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    HDEVINFO DevInfo = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, NULL, NULL, DIGCF_PRESENT, NULL, NULL, NULL);
 | 
					    HDEVINFO DevInfo = SetupDiGetClassDevsExW(&GUID_DEVCLASS_NET, NULL, NULL, DIGCF_PRESENT, NULL, NULL, NULL);
 | 
				
			||||||
    if (DevInfo == INVALID_HANDLE_VALUE)
 | 
					    if (DevInfo == INVALID_HANDLE_VALUE)
 | 
				
			||||||
@ -2112,7 +2082,6 @@ _Return_type_success_(return != FALSE) BOOL WINAPI
 | 
				
			|||||||
    SetupDiDestroyDeviceInfoList(DevInfo);
 | 
					    SetupDiDestroyDeviceInfoList(DevInfo);
 | 
				
			||||||
cleanupMutex:
 | 
					cleanupMutex:
 | 
				
			||||||
    NamespaceReleaseMutex(Mutex);
 | 
					    NamespaceReleaseMutex(Mutex);
 | 
				
			||||||
cleanupToken:
 | 
					cleanup:
 | 
				
			||||||
    RevertToSelf();
 | 
					 | 
				
			||||||
    return RET_ERROR(TRUE, LastError);
 | 
					    return RET_ERROR(TRUE, LastError);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -9,19 +9,19 @@
 | 
				
			|||||||
#include <Windows.h>
 | 
					#include <Windows.h>
 | 
				
			||||||
#include <TlHelp32.h>
 | 
					#include <TlHelp32.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_Return_type_success_(return != FALSE) BOOL ElevateToSystem(void)
 | 
					static _Return_type_success_(return != FALSE) BOOL ElevateToSystem(void)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    HANDLE CurrentProcessToken, ThreadToken, ProcessSnapshot, WinlogonProcess, WinlogonToken, DuplicatedToken;
 | 
					    HANDLE CurrentProcessToken, ThreadToken, ProcessSnapshot, WinlogonProcess, WinlogonToken, DuplicatedToken;
 | 
				
			||||||
    PROCESSENTRY32W ProcessEntry = { .dwSize = sizeof(PROCESSENTRY32W) };
 | 
					    PROCESSENTRY32W ProcessEntry = { .dwSize = sizeof(PROCESSENTRY32W) };
 | 
				
			||||||
    BOOL Ret;
 | 
					    BOOL Ret;
 | 
				
			||||||
    DWORD LastError = ERROR_SUCCESS;
 | 
					    DWORD LastError = ERROR_SUCCESS;
 | 
				
			||||||
    TOKEN_PRIVILEGES Privileges = { .PrivilegeCount = 1, .Privileges = { { .Attributes = SE_PRIVILEGE_ENABLED } } };
 | 
					    TOKEN_PRIVILEGES Privileges = { .PrivilegeCount = 1, .Privileges = { { .Attributes = SE_PRIVILEGE_ENABLED } } };
 | 
				
			||||||
    CHAR LocalSystemSid[0x400];
 | 
					    CHAR LocalSystemSid[MAX_SID_SIZE];
 | 
				
			||||||
    DWORD RequiredBytes = sizeof(LocalSystemSid);
 | 
					    DWORD RequiredBytes = sizeof(LocalSystemSid);
 | 
				
			||||||
    struct
 | 
					    struct
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        TOKEN_USER MaybeLocalSystem;
 | 
					        TOKEN_USER MaybeLocalSystem;
 | 
				
			||||||
        CHAR LargeEnoughForLocalSystem[0x400];
 | 
					        CHAR LargeEnoughForLocalSystem[MAX_SID_SIZE];
 | 
				
			||||||
    } TokenUserBuffer;
 | 
					    } TokenUserBuffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ret = CreateWellKnownSid(WinLocalSystemSid, NULL, &LocalSystemSid, &RequiredBytes);
 | 
					    Ret = CreateWellKnownSid(WinLocalSystemSid, NULL, &LocalSystemSid, &RequiredBytes);
 | 
				
			||||||
@ -46,7 +46,7 @@ _Return_type_success_(return != FALSE) BOOL ElevateToSystem(void)
 | 
				
			|||||||
        goto cleanup;
 | 
					        goto cleanup;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (EqualSid(TokenUserBuffer.MaybeLocalSystem.User.Sid, LocalSystemSid))
 | 
					    if (EqualSid(TokenUserBuffer.MaybeLocalSystem.User.Sid, LocalSystemSid))
 | 
				
			||||||
        return TRUE;
 | 
					        return ImpersonateSelf(SecurityImpersonation);
 | 
				
			||||||
    Ret = LookupPrivilegeValueW(NULL, SE_DEBUG_NAME, &Privileges.Privileges[0].Luid);
 | 
					    Ret = LookupPrivilegeValueW(NULL, SE_DEBUG_NAME, &Privileges.Privileges[0].Luid);
 | 
				
			||||||
    if (!Ret)
 | 
					    if (!Ret)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
@ -122,81 +122,6 @@ cleanup:
 | 
				
			|||||||
    return FALSE;
 | 
					    return FALSE;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_Return_type_success_(return != NULL) HANDLE GetPrimarySystemTokenFromThread(void)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    HANDLE CurrentToken, DuplicatedToken;
 | 
					 | 
				
			||||||
    BOOL Ret;
 | 
					 | 
				
			||||||
    DWORD LastError;
 | 
					 | 
				
			||||||
    TOKEN_PRIVILEGES Privileges = { .PrivilegeCount = 1, .Privileges = { { .Attributes = SE_PRIVILEGE_ENABLED } } };
 | 
					 | 
				
			||||||
    CHAR LocalSystemSid[0x400];
 | 
					 | 
				
			||||||
    DWORD RequiredBytes = sizeof(LocalSystemSid);
 | 
					 | 
				
			||||||
    struct
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        TOKEN_USER MaybeLocalSystem;
 | 
					 | 
				
			||||||
        CHAR LargeEnoughForLocalSystem[0x400];
 | 
					 | 
				
			||||||
    } TokenUserBuffer;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Ret = CreateWellKnownSid(WinLocalSystemSid, NULL, &LocalSystemSid, &RequiredBytes);
 | 
					 | 
				
			||||||
    if (!Ret)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        LastError = LOG_LAST_ERROR(L"Failed to create SID");
 | 
					 | 
				
			||||||
        return NULL;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    Ret = OpenThreadToken(
 | 
					 | 
				
			||||||
        GetCurrentThread(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES | TOKEN_DUPLICATE, FALSE, &CurrentToken);
 | 
					 | 
				
			||||||
    if (!Ret && GetLastError() == ERROR_NO_TOKEN)
 | 
					 | 
				
			||||||
        Ret = OpenProcessToken(
 | 
					 | 
				
			||||||
            GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES | TOKEN_DUPLICATE, &CurrentToken);
 | 
					 | 
				
			||||||
    if (!Ret)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        LastError = LOG_LAST_ERROR(L"Failed to open token");
 | 
					 | 
				
			||||||
        return NULL;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    Ret = GetTokenInformation(CurrentToken, TokenUser, &TokenUserBuffer, sizeof(TokenUserBuffer), &RequiredBytes);
 | 
					 | 
				
			||||||
    if (!Ret)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        LastError = LOG_LAST_ERROR(L"Failed to get token information");
 | 
					 | 
				
			||||||
        goto cleanup;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!EqualSid(TokenUserBuffer.MaybeLocalSystem.User.Sid, LocalSystemSid))
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        LOG(WINTUN_LOG_ERR, L"Not SYSTEM");
 | 
					 | 
				
			||||||
        LastError = ERROR_ACCESS_DENIED;
 | 
					 | 
				
			||||||
        goto cleanup;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    Ret = LookupPrivilegeValueW(NULL, SE_ASSIGNPRIMARYTOKEN_NAME, &Privileges.Privileges[0].Luid);
 | 
					 | 
				
			||||||
    if (!Ret)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        LastError = LOG_LAST_ERROR(L"Failed to lookup privilege value");
 | 
					 | 
				
			||||||
        goto cleanup;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    Ret = AdjustTokenPrivileges(CurrentToken, FALSE, &Privileges, 0, NULL, NULL);
 | 
					 | 
				
			||||||
    if (!Ret)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        LastError = LOG_LAST_ERROR(L"Failed to adjust token privileges");
 | 
					 | 
				
			||||||
        goto cleanup;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    Ret = DuplicateTokenEx(
 | 
					 | 
				
			||||||
        CurrentToken,
 | 
					 | 
				
			||||||
        TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY,
 | 
					 | 
				
			||||||
        NULL,
 | 
					 | 
				
			||||||
        SecurityImpersonation,
 | 
					 | 
				
			||||||
        TokenPrimary,
 | 
					 | 
				
			||||||
        &DuplicatedToken);
 | 
					 | 
				
			||||||
    if (!Ret)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        LastError = LOG_LAST_ERROR(L"Failed to duplicate token");
 | 
					 | 
				
			||||||
        goto cleanup;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    CloseHandle(CurrentToken);
 | 
					 | 
				
			||||||
    return DuplicatedToken;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
cleanup:
 | 
					 | 
				
			||||||
    CloseHandle(CurrentToken);
 | 
					 | 
				
			||||||
    SetLastError(LastError);
 | 
					 | 
				
			||||||
    return NULL;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_Return_type_success_(return != FALSE) BOOL ImpersonateService(_In_z_ WCHAR *ServiceName, _In_ HANDLE *OriginalToken)
 | 
					_Return_type_success_(return != FALSE) BOOL ImpersonateService(_In_z_ WCHAR *ServiceName, _In_ HANDLE *OriginalToken)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    HANDLE ThreadToken, ServiceProcess, ServiceToken, DuplicatedToken;
 | 
					    HANDLE ThreadToken, ServiceProcess, ServiceToken, DuplicatedToken;
 | 
				
			||||||
@ -212,20 +137,14 @@ _Return_type_success_(return != FALSE) BOOL ImpersonateService(_In_z_ WCHAR *Ser
 | 
				
			|||||||
        GetLastError() != ERROR_NO_TOKEN)
 | 
					        GetLastError() != ERROR_NO_TOKEN)
 | 
				
			||||||
        return FALSE;
 | 
					        return FALSE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!ElevateToSystem())
 | 
				
			||||||
 | 
					        goto cleanup;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!LookupPrivilegeValueW(NULL, SE_DEBUG_NAME, &Privileges.Privileges[0].Luid))
 | 
					    if (!LookupPrivilegeValueW(NULL, SE_DEBUG_NAME, &Privileges.Privileges[0].Luid))
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        LastError = LOG_LAST_ERROR(L"Failed to lookup privilege value");
 | 
					        LastError = LOG_LAST_ERROR(L"Failed to lookup privilege value");
 | 
				
			||||||
        goto cleanup;
 | 
					        goto cleanup;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (!*OriginalToken)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        RevertToSelf();
 | 
					 | 
				
			||||||
        if (!ImpersonateSelf(SecurityImpersonation))
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            LastError = LOG_LAST_ERROR(L"Failed to impersonate self");
 | 
					 | 
				
			||||||
            goto cleanup;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, FALSE, &ThreadToken))
 | 
					    if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, FALSE, &ThreadToken))
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        LastError = LOG_LAST_ERROR(L"Failed to open thread token");
 | 
					        LastError = LOG_LAST_ERROR(L"Failed to open thread token");
 | 
				
			||||||
 | 
				
			|||||||
@ -7,10 +7,6 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include <Windows.h>
 | 
					#include <Windows.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_Return_type_success_(return != FALSE) BOOL ElevateToSystem(void);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_Return_type_success_(return != NULL) HANDLE GetPrimarySystemTokenFromThread(void);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_Return_type_success_(return != FALSE) BOOL ImpersonateService(_In_z_ WCHAR *ServiceName, _In_ HANDLE *OriginalToken);
 | 
					_Return_type_success_(return != FALSE) BOOL ImpersonateService(_In_z_ WCHAR *ServiceName, _In_ HANDLE *OriginalToken);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
_Return_type_success_(return != FALSE) BOOL RestoreToken(_In_ HANDLE OriginalToken);
 | 
					_Return_type_success_(return != FALSE) BOOL RestoreToken(_In_ HANDLE OriginalToken);
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										43
									
								
								api/entry.c
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								api/entry.c
									
									
									
									
									
								
							@ -21,6 +21,7 @@
 | 
				
			|||||||
HINSTANCE ResourceModule;
 | 
					HINSTANCE ResourceModule;
 | 
				
			||||||
HANDLE ModuleHeap;
 | 
					HANDLE ModuleHeap;
 | 
				
			||||||
SECURITY_ATTRIBUTES SecurityAttributes = { .nLength = sizeof(SECURITY_ATTRIBUTES) };
 | 
					SECURITY_ATTRIBUTES SecurityAttributes = { .nLength = sizeof(SECURITY_ATTRIBUTES) };
 | 
				
			||||||
 | 
					BOOL IsLocalSystem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static FARPROC WINAPI
 | 
					static FARPROC WINAPI
 | 
				
			||||||
DelayedLoadLibraryHook(unsigned dliNotify, PDelayLoadInfo pdli)
 | 
					DelayedLoadLibraryHook(unsigned dliNotify, PDelayLoadInfo pdli)
 | 
				
			||||||
@ -35,6 +36,41 @@ DelayedLoadLibraryHook(unsigned dliNotify, PDelayLoadInfo pdli)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const PfnDliHook __pfnDliNotifyHook2 = DelayedLoadLibraryHook;
 | 
					const PfnDliHook __pfnDliNotifyHook2 = DelayedLoadLibraryHook;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static BOOL
 | 
				
			||||||
 | 
					InitializeSecurityObjects(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    BYTE LocalSystemSid[MAX_SID_SIZE];
 | 
				
			||||||
 | 
					    DWORD RequiredBytes = sizeof(LocalSystemSid);
 | 
				
			||||||
 | 
					    HANDLE CurrentProcessToken;
 | 
				
			||||||
 | 
					    struct
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        TOKEN_USER MaybeLocalSystem;
 | 
				
			||||||
 | 
					        CHAR LargeEnoughForLocalSystem[MAX_SID_SIZE];
 | 
				
			||||||
 | 
					    } TokenUserBuffer;
 | 
				
			||||||
 | 
					    BOOL Ret = FALSE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!CreateWellKnownSid(WinLocalSystemSid, NULL, LocalSystemSid, &RequiredBytes))
 | 
				
			||||||
 | 
					        return FALSE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &CurrentProcessToken))
 | 
				
			||||||
 | 
					        return FALSE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!GetTokenInformation(CurrentProcessToken, TokenUser, &TokenUserBuffer, sizeof(TokenUserBuffer), &RequiredBytes))
 | 
				
			||||||
 | 
					        goto cleanupProcessToken;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    IsLocalSystem = EqualSid(TokenUserBuffer.MaybeLocalSystem.User.Sid, LocalSystemSid);
 | 
				
			||||||
 | 
					    Ret = ConvertStringSecurityDescriptorToSecurityDescriptorW(
 | 
				
			||||||
 | 
					        IsLocalSystem ? L"O:SYD:P(A;;GA;;;SY)(A;;GA;;;BA)S:(ML;;NWNRNX;;;HI)"
 | 
				
			||||||
 | 
					                      : L"O:BAD:P(A;;GA;;;SY)(A;;GA;;;BA)S:(ML;;NWNRNX;;;HI)",
 | 
				
			||||||
 | 
					        SDDL_REVISION_1,
 | 
				
			||||||
 | 
					        &SecurityAttributes.lpSecurityDescriptor,
 | 
				
			||||||
 | 
					        NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cleanupProcessToken:
 | 
				
			||||||
 | 
					    CloseHandle(CurrentProcessToken);
 | 
				
			||||||
 | 
					    return Ret;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
BOOL APIENTRY
 | 
					BOOL APIENTRY
 | 
				
			||||||
DllMain(_In_ HINSTANCE hinstDLL, _In_ DWORD fdwReason, _In_ LPVOID lpvReserved)
 | 
					DllMain(_In_ HINSTANCE hinstDLL, _In_ DWORD fdwReason, _In_ LPVOID lpvReserved)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@ -47,8 +83,11 @@ DllMain(_In_ HINSTANCE hinstDLL, _In_ DWORD fdwReason, _In_ LPVOID lpvReserved)
 | 
				
			|||||||
        ModuleHeap = HeapCreate(0, 0, 0);
 | 
					        ModuleHeap = HeapCreate(0, 0, 0);
 | 
				
			||||||
        if (!ModuleHeap)
 | 
					        if (!ModuleHeap)
 | 
				
			||||||
            return FALSE;
 | 
					            return FALSE;
 | 
				
			||||||
        ConvertStringSecurityDescriptorToSecurityDescriptorW(
 | 
					        if (!InitializeSecurityObjects())
 | 
				
			||||||
            L"O:SYD:P(A;;GA;;;SY)", SDDL_REVISION_1, &SecurityAttributes.lpSecurityDescriptor, NULL);
 | 
					        {
 | 
				
			||||||
 | 
					            HeapDestroy(ModuleHeap);
 | 
				
			||||||
 | 
					            return FALSE;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        AdapterInit();
 | 
					        AdapterInit();
 | 
				
			||||||
        NamespaceInit();
 | 
					        NamespaceInit();
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
 | 
				
			|||||||
@ -29,3 +29,4 @@
 | 
				
			|||||||
extern HINSTANCE ResourceModule;
 | 
					extern HINSTANCE ResourceModule;
 | 
				
			||||||
extern HANDLE ModuleHeap;
 | 
					extern HANDLE ModuleHeap;
 | 
				
			||||||
extern SECURITY_ATTRIBUTES SecurityAttributes;
 | 
					extern SECURITY_ATTRIBUTES SecurityAttributes;
 | 
				
			||||||
 | 
					extern BOOL IsLocalSystem;
 | 
				
			||||||
 | 
				
			|||||||
@ -59,8 +59,8 @@ static _Return_type_success_(return != FALSE) BOOL NamespaceRuntimeInit(void)
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    BYTE Sid[MAX_SID_SIZE];
 | 
					    BYTE Sid[MAX_SID_SIZE];
 | 
				
			||||||
    DWORD SidSize = MAX_SID_SIZE;
 | 
					    DWORD SidSize = sizeof(Sid);
 | 
				
			||||||
    if (!CreateWellKnownSid(WinLocalSystemSid, NULL, Sid, &SidSize))
 | 
					    if (!CreateWellKnownSid(IsLocalSystem ? WinLocalSystemSid : WinBuiltinAdministratorsSid, NULL, Sid, &SidSize))
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        LastError = LOG_LAST_ERROR(L"Failed to create SID");
 | 
					        LastError = LOG_LAST_ERROR(L"Failed to create SID");
 | 
				
			||||||
        goto cleanupBCryptCloseAlgorithmProvider;
 | 
					        goto cleanupBCryptCloseAlgorithmProvider;
 | 
				
			||||||
 | 
				
			|||||||
@ -176,23 +176,15 @@ static _Return_type_success_(return != FALSE) BOOL ExecuteRunDll32(
 | 
				
			|||||||
                        .hStdOutput = StreamWStdout,
 | 
					                        .hStdOutput = StreamWStdout,
 | 
				
			||||||
                        .hStdError = StreamWStderr };
 | 
					                        .hStdError = StreamWStderr };
 | 
				
			||||||
    PROCESS_INFORMATION pi;
 | 
					    PROCESS_INFORMATION pi;
 | 
				
			||||||
    HANDLE ProcessToken = GetPrimarySystemTokenFromThread();
 | 
					    if (!CreateProcessW(RunDll32Path, CommandLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
 | 
				
			||||||
    if (!ProcessToken)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        LastError = LOG(WINTUN_LOG_ERR, L"Failed to get primary system token from thread");
 | 
					 | 
				
			||||||
        goto cleanupThreads;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!CreateProcessAsUserW(ProcessToken, RunDll32Path, CommandLine, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi))
 | 
					 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        LastError = LOG_LAST_ERROR(L"Failed to create process: %s", CommandLine);
 | 
					        LastError = LOG_LAST_ERROR(L"Failed to create process: %s", CommandLine);
 | 
				
			||||||
        goto cleanupToken;
 | 
					        goto cleanupThreads;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    LastError = ERROR_SUCCESS;
 | 
					    LastError = ERROR_SUCCESS;
 | 
				
			||||||
    WaitForSingleObject(pi.hProcess, INFINITE);
 | 
					    WaitForSingleObject(pi.hProcess, INFINITE);
 | 
				
			||||||
    CloseHandle(pi.hProcess);
 | 
					    CloseHandle(pi.hProcess);
 | 
				
			||||||
    CloseHandle(pi.hThread);
 | 
					    CloseHandle(pi.hThread);
 | 
				
			||||||
cleanupToken:
 | 
					 | 
				
			||||||
    CloseHandle(ProcessToken);
 | 
					 | 
				
			||||||
cleanupThreads:
 | 
					cleanupThreads:
 | 
				
			||||||
    if (ThreadStderr)
 | 
					    if (ThreadStderr)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
				
			|||||||
@ -77,7 +77,7 @@ _Return_type_success_(return != NULL) TUN_SESSION *WINAPI
 | 
				
			|||||||
    if (!Session)
 | 
					    if (!Session)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        LastError = GetLastError();
 | 
					        LastError = GetLastError();
 | 
				
			||||||
        goto out;
 | 
					        goto cleanup;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const ULONG RingSize = TUN_RING_SIZE(Capacity);
 | 
					    const ULONG RingSize = TUN_RING_SIZE(Capacity);
 | 
				
			||||||
    BYTE *AllocatedRegion = VirtualAlloc(0, (size_t)RingSize * 2, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
 | 
					    BYTE *AllocatedRegion = VirtualAlloc(0, (size_t)RingSize * 2, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
 | 
				
			||||||
@ -86,18 +86,13 @@ _Return_type_success_(return != NULL) TUN_SESSION *WINAPI
 | 
				
			|||||||
        LastError = LOG_LAST_ERROR(L"Failed to allocate ring memory (requested size: 0x%zx)", (size_t)RingSize * 2);
 | 
					        LastError = LOG_LAST_ERROR(L"Failed to allocate ring memory (requested size: 0x%zx)", (size_t)RingSize * 2);
 | 
				
			||||||
        goto cleanupRings;
 | 
					        goto cleanupRings;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (!ElevateToSystem())
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        LastError = LOG(WINTUN_LOG_ERR, L"Failed to impersonate SYSTEM user");
 | 
					 | 
				
			||||||
        goto cleanupAllocatedRegion;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    Session->Descriptor.Send.RingSize = RingSize;
 | 
					    Session->Descriptor.Send.RingSize = RingSize;
 | 
				
			||||||
    Session->Descriptor.Send.Ring = (TUN_RING *)AllocatedRegion;
 | 
					    Session->Descriptor.Send.Ring = (TUN_RING *)AllocatedRegion;
 | 
				
			||||||
    Session->Descriptor.Send.TailMoved = CreateEventW(&SecurityAttributes, FALSE, FALSE, NULL);
 | 
					    Session->Descriptor.Send.TailMoved = CreateEventW(&SecurityAttributes, FALSE, FALSE, NULL);
 | 
				
			||||||
    if (!Session->Descriptor.Send.TailMoved)
 | 
					    if (!Session->Descriptor.Send.TailMoved)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        LastError = LOG_LAST_ERROR(L"Failed to create send event");
 | 
					        LastError = LOG_LAST_ERROR(L"Failed to create send event");
 | 
				
			||||||
        goto cleanupToken;
 | 
					        goto cleanupAllocatedRegion;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Session->Descriptor.Receive.RingSize = RingSize;
 | 
					    Session->Descriptor.Receive.RingSize = RingSize;
 | 
				
			||||||
@ -129,7 +124,6 @@ _Return_type_success_(return != NULL) TUN_SESSION *WINAPI
 | 
				
			|||||||
        LastError = LOG_LAST_ERROR(L"Failed to register rings");
 | 
					        LastError = LOG_LAST_ERROR(L"Failed to register rings");
 | 
				
			||||||
        goto cleanupHandle;
 | 
					        goto cleanupHandle;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    RevertToSelf();
 | 
					 | 
				
			||||||
    Session->Capacity = Capacity;
 | 
					    Session->Capacity = Capacity;
 | 
				
			||||||
    (void)InitializeCriticalSectionAndSpinCount(&Session->Receive.Lock, LOCK_SPIN_COUNT);
 | 
					    (void)InitializeCriticalSectionAndSpinCount(&Session->Receive.Lock, LOCK_SPIN_COUNT);
 | 
				
			||||||
    (void)InitializeCriticalSectionAndSpinCount(&Session->Send.Lock, LOCK_SPIN_COUNT);
 | 
					    (void)InitializeCriticalSectionAndSpinCount(&Session->Send.Lock, LOCK_SPIN_COUNT);
 | 
				
			||||||
@ -140,13 +134,11 @@ cleanupReceiveTailMoved:
 | 
				
			|||||||
    CloseHandle(Session->Descriptor.Receive.TailMoved);
 | 
					    CloseHandle(Session->Descriptor.Receive.TailMoved);
 | 
				
			||||||
cleanupSendTailMoved:
 | 
					cleanupSendTailMoved:
 | 
				
			||||||
    CloseHandle(Session->Descriptor.Send.TailMoved);
 | 
					    CloseHandle(Session->Descriptor.Send.TailMoved);
 | 
				
			||||||
cleanupToken:
 | 
					 | 
				
			||||||
    RevertToSelf();
 | 
					 | 
				
			||||||
cleanupAllocatedRegion:
 | 
					cleanupAllocatedRegion:
 | 
				
			||||||
    VirtualFree(AllocatedRegion, 0, MEM_RELEASE);
 | 
					    VirtualFree(AllocatedRegion, 0, MEM_RELEASE);
 | 
				
			||||||
cleanupRings:
 | 
					cleanupRings:
 | 
				
			||||||
    Free(Session);
 | 
					    Free(Session);
 | 
				
			||||||
out:
 | 
					cleanup:
 | 
				
			||||||
    SetLastError(LastError);
 | 
					    SetLastError(LastError);
 | 
				
			||||||
    return NULL;
 | 
					    return NULL;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user