PPLGuardDll/dllexploit.cpp (437 lines of code) (raw):
#include "dllexploit.h"
void DoStuff()
{
LPWSTR pwszDllName = NULL;
BOOL bSuccess = FALSE;
WCHAR wszEventName[MAX_PATH] = { 0 };
HANDLE hEvent = NULL;
//
// 1. Parse the command line
//
ParseCommandLine();
if (g_bDebug)
LogToConsole(L"DEBUG mode enabled\n");
//
// Signal first Event (DLL loaded)
//
StringCchPrintf(wszEventName, MAX_PATH, L"Global\\%ws_DLL_LOADED", g_pwszGuid);
if (hEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, wszEventName))
{
if (!SetEvent(hEvent))
LogLastError(L"SetEvent");
CloseHandle(hEvent);
}
else
LogLastError(L"OpenEvent");
if (g_bVerbose)
LogToConsole(L"[*] DLL loaded.\n");
//
// 2. Do some cleanup
//
// First things first, we need to delete the symbolic link that was created in \KnownDlls.
// As this code is executed as SYSTEM inside a PPL with the WindowsTCB protection level, it
// should not be a problem.
//
if (!GetCurrentDllFileName(&pwszDllName))
goto end;
if (!DeleteKnownDllEntry(pwszDllName))
LogToConsole(L"[-] Failed to delete KnownDll entry '%ws'\n", pwszDllName);
else
{
if (g_bVerbose)
LogToConsole(L"[*] KnownDll entry '%ws' removed.\n", pwszDllName);
}
if (g_bHardenAMPPLOnly)
{
bSuccess = HardenAntiMalwareServices();
}
else
{
//
// 3. Deny SYSTEM write access to \KnownDlls to block future exploits
//
if (!MakeKnownDllsReadOnly(L"\\KnownDlls"))
{
LogToConsole(L"[-] Failed to lock down KnownDlls entry\n");
}
else
{
bSuccess = TRUE;
if (g_bVerbose)
LogToConsole(L"[*] Successfully locked down KnownDlls.\n");
}
//
// 4. Deny SYSTEM write access to \KnownDlls32 to block future exploits
//
if (!MakeKnownDllsReadOnly(L"\\KnownDlls32"))
{
LogToConsole(L"[-] Failed to lock down KnownDlls32 entry\n");
}
else
{
bSuccess = TRUE;
if (g_bVerbose)
LogToConsole(L"[*] Successfully locked down KnownDlls32.\n");
}
}
if (bSuccess)
{
//
// Signal second Event (success)
//
StringCchPrintf(wszEventName, MAX_PATH, L"Global\\%ws_DLL_SUCCESS", g_pwszGuid);
if (hEvent = OpenEvent(EVENT_MODIFY_STATE, FALSE, wszEventName))
{
if (!SetEvent(hEvent))
LogLastError(L"SetEvent");
CloseHandle(hEvent);
}
else
LogLastError(L"OpenEvent");
}
end:
if (pwszDllName)
LocalFree(pwszDllName);
}
void LogToConsole(LPCWSTR pwszFormat, ...)
{
//
// The process in which we load this DLL does not have a console so we need to attach to the
// parent process' console. To do so, we can call AttachConsole with the special value
// ATTACH_PARENT_PROCESS. Then, we can get the STDOUT handle. This handle is stored will be
// stored as a global variable so we need to initialize it only once.
//
if (g_hConsoleOutput == NULL)
{
AttachConsole(ATTACH_PARENT_PROCESS);
if (!(g_hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE)))
return;
}
//
// Prepare otuput string and use WriteConsole instead of wprintf. This way, we can directly use
// the STDOUT handle we got previously.
//
DWORD dwOutputStringSize = 0;
LPWSTR pwszOutputString = NULL;
va_list va;
size_t offset = 0;
va_start(va, pwszFormat);
if (g_bDebug)
dwOutputStringSize += (DWORD)wcslen(L"[DEBUG] (DLL) ") * sizeof(WCHAR);
else
dwOutputStringSize += (DWORD)wcslen(L"(DLL) ") * sizeof(WCHAR);
dwOutputStringSize += _vscwprintf(pwszFormat, va) * sizeof(WCHAR) + 2; // \0
pwszOutputString = (LPWSTR)LocalAlloc(LPTR, dwOutputStringSize);
if (pwszOutputString)
{
if (g_bDebug)
StringCchPrintf(pwszOutputString, dwOutputStringSize, L"[DEBUG] (DLL) ");
else
StringCchPrintf(pwszOutputString, dwOutputStringSize, L"(DLL) ");
if (SUCCEEDED(StringCbLength(pwszOutputString, dwOutputStringSize, &offset)))
{
StringCbVPrintf(&pwszOutputString[offset / sizeof(WCHAR)], dwOutputStringSize - offset, pwszFormat, va);
WriteConsole(g_hConsoleOutput, pwszOutputString, (DWORD)wcslen(pwszOutputString), NULL, NULL);
}
LocalFree(pwszOutputString);
}
va_end(va);
}
void LogLastError(LPCWSTR pwszFunctionName)
{
DWORD dwLastError = GetLastError();
if (dwLastError != ERROR_SUCCESS)
LogToConsole(L"Function '%ws' returned error code %d - %ws\n", pwszFunctionName, dwLastError, _com_error(HRESULT_FROM_WIN32(dwLastError)).ErrorMessage());
}
BOOL GetCurrentDllFileName(LPWSTR* ppwszDllName)
{
WCHAR wszDllPath[MAX_PATH];
LPWSTR pwszDllName = NULL;
GetModuleFileName(g_hInstance, wszDllPath, MAX_PATH);
if (GetLastError() == ERROR_SUCCESS)
{
pwszDllName = PathFindFileName(wszDllPath);
*ppwszDllName = (LPWSTR)LocalAlloc(LPTR, 64 * sizeof(WCHAR));
if (*ppwszDllName)
{
StringCchPrintf(*ppwszDllName, 64, L"%ws", pwszDllName);
return TRUE;
}
}
return FALSE;
}
BOOL DeleteKnownDllEntry(LPCWSTR pwszDllName)
{
BOOL bReturnValue = FALSE;
NTSTATUS status = 0;
HANDLE hLink = NULL;
LPWSTR pwszLinkPath = NULL;
UNICODE_STRING name = { 0 };
OBJECT_ATTRIBUTES oa = { 0 };
SECURITY_DESCRIPTOR sd = { 0 };
SECURITY_ATTRIBUTES sa = { 0 };
//
// Build the path of the symbolic link object to delete. The name of the DLL can be determined
// at runtime by invoking 'GetCurrentDllFileName'. The final path will be something such as
// '\KnownDlls\DPAPI.dll'.
//
pwszLinkPath = (LPWSTR)LocalAlloc(LPTR, (MAX_PATH + 1) * sizeof(WCHAR));
if (!pwszLinkPath)
goto end;
StringCchPrintf(pwszLinkPath, MAX_PATH, L"\\KnownDlls\\%ws", pwszDllName);
if (g_bDebug)
LogToConsole(L"Object to delete: %ws\n", pwszLinkPath);
RtlInitUnicodeString(&name, pwszLinkPath);
InitializeObjectAttributes(&oa, &name, OBJ_CASE_INSENSITIVE, nullptr, nullptr);
//
// Here we want to call NtOpenSymbolicLinkObject with DELETE access because we want to delete
// the link. Unfortunately, the inherited ACL does not grant us this right and we will thus
// get an "Access denied" error. What we can do though is open the symbolic link object with
// WRITE_DAC access in order to change the ACL of the object.
//
status = NtOpenSymbolicLinkObject(&hLink, WRITE_DAC, &oa);
SetLastError(RtlNtStatusToDosError(status));
if (status != 0)
{
LogLastError(L"NtOpenSymbolicLinkObject");
goto end;
}
if (g_bDebug)
LogToConsole(L"NtOpenSymbolicLinkObject('%ws', WRITE_DAC) OK\n", pwszLinkPath);
//
// Prepare the Security Descriptor. Here we will just use a NULL DACL. This will give everyone
// access to the object but that's not really an issue because we'll delete it right after.
//
InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
#pragma warning( suppress : 6248 ) // Disable warning as setting a NULL DACL is intentional here
if (!SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE))
{
LogLastError(L"SetSecurityDescriptorDacl");
}
sa.nLength = sizeof(sa);
sa.bInheritHandle = FALSE;
sa.lpSecurityDescriptor = &sd;
//
// Apply the new Security Descriptor.
//
if (!SetKernelObjectSecurity(hLink, DACL_SECURITY_INFORMATION, &sd))
{
LogLastError(L"SetKernelObjectSecurity");
goto end;
}
if (g_bDebug)
LogToConsole(L"SetKernelObjectSecurity OK\n");
//
// At this point we can close the object handle because only the WRITE_DAC right is associated
// to it. This handle will not allow us to delete the object.
//
status = NtClose(hLink);
SetLastError(RtlNtStatusToDosError(status));
if (status != 0)
{
LogLastError(L"NtClose");
goto end;
}
if (g_bDebug)
LogToConsole(L"NtClose OK\n");
//
// This time, we should be able to open the link object with DELETE access.
//
status = NtOpenSymbolicLinkObject(&hLink, DELETE, &oa);
SetLastError(RtlNtStatusToDosError(status));
if (status != 0)
{
LogLastError(L"NtOpenSymbolicLinkObject");
goto end;
}
if (g_bDebug)
LogToConsole(L"NtOpenSymbolicLinkObject('%ws', DELETE) OK\n", pwszLinkPath);
//
// Now, we can invoke NtMakeTemporaryObject to disable the "Permanent" flag of the object. When
// an object does not have the "Permanent" flag enabled, it is automatically deleted when all
// its handles are closed.
//
status = NtMakeTemporaryObject(hLink);
SetLastError(RtlNtStatusToDosError(status));
if (status != 0)
{
LogLastError(L"NtMakeTemporaryObject");
goto end;
}
if (g_bDebug)
LogToConsole(L"NtMakeTemporaryObject OK\n");
bReturnValue = status == STATUS_SUCCESS;
//
// We should be the only process to have an opened handle on this object. So, if we close it,
// the link should be automatically deleted.
//
end:
if (hLink)
NtClose(hLink);
if (pwszLinkPath)
LocalFree(pwszLinkPath);
return bReturnValue;
}
BOOL MakeKnownDllsReadOnly(LPCWSTR pKnownDlls)
{
BOOL bReturnValue = FALSE;
NTSTATUS status = 0;
HANDLE hKnownDlls = NULL;
UNICODE_STRING name = { 0 };
OBJECT_ATTRIBUTES oa = { 0 };
PSECURITY_DESCRIPTOR pOriginalSd = NULL;
DWORD dwBytesNeeded = 0;
LPWSTR pOriginalSdString = NULL;
std::wstring newSdString;
PSECURITY_DESCRIPTOR pNewSd = NULL;
DWORD newSdLength = 0;
RtlInitUnicodeString(&name, pKnownDlls);
InitializeObjectAttributes(&oa, &name, OBJ_CASE_INSENSITIVE, nullptr, nullptr);
//
// Open \KnownDlls with the ability to RW the DACL
//
status = NtOpenDirectoryObject(&hKnownDlls, READ_CONTROL | WRITE_DAC, &oa);
SetLastError(RtlNtStatusToDosError(status));
if (status != 0)
{
LogLastError(L"NtOpenSymbolicLinkObject");
goto end;
}
if (g_bDebug)
LogToConsole(L"NtOpenSymbolicLinkObject('%ws', WRITE_DAC) OK\n", pKnownDlls);
//
// Retrieve DACL
//
(void)GetKernelObjectSecurity(hKnownDlls, DACL_SECURITY_INFORMATION, NULL, 0, &dwBytesNeeded);
if (0 == dwBytesNeeded)
{
LogLastError(L"GetKernelObjectSecurity");
goto end;
}
pOriginalSd = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, dwBytesNeeded);
if (!pOriginalSd)
{
LogLastError(L"GetKernelObjectSecurity allocate SD");
goto end;
}
if (!GetKernelObjectSecurity(hKnownDlls, DACL_SECURITY_INFORMATION, pOriginalSd, dwBytesNeeded, &dwBytesNeeded))
{
LogLastError(L"GetKernelObjectSecurity");
goto end;
}
//
// Structure -> string to avoid ugly APIs
//
if (!ConvertSecurityDescriptorToStringSecurityDescriptorW(pOriginalSd, SDDL_REVISION_1, DACL_SECURITY_INFORMATION, &pOriginalSdString, NULL))
{
LogLastError(L"ConvertSecurityDescriptorToStringSecurityDescriptorW");
goto end;
}
//
// Deny GENERIC_WRITE access to EVERYONE
//
newSdString = L"D:(D;OICI;GW;;;WD)";
newSdString += (pOriginalSdString + 2);
if (g_bDebug)
{
LogToConsole(L"Original SD: %s\n", pOriginalSdString);
LogToConsole(L"New SD: %s\n", newSdString.c_str());
}
//
// Convert back to structure
//
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(newSdString.c_str(), SDDL_REVISION_1, &pNewSd, &newSdLength))
{
LogLastError(L"ConvertStringSecurityDescriptorToSecurityDescriptorW");
goto end;
}
//
// Apply the new Security Descriptor.
//
if (!SetKernelObjectSecurity(hKnownDlls, DACL_SECURITY_INFORMATION, pNewSd))
{
LogLastError(L"SetKernelObjectSecurity");
goto end;
}
if (g_bDebug)
LogToConsole(L"SetKernelObjectSecurity OK\n");
bReturnValue = TRUE;
end:
if (hKnownDlls)
NtClose(hKnownDlls);
if (pOriginalSd)
LocalFree(pOriginalSd);
if (pOriginalSdString)
LocalFree(pOriginalSdString);
if (pNewSd)
LocalFree(pNewSd);
return bReturnValue;
}
typedef NTSTATUS(NTAPI* RtlAddProcessTrustLabelAce_t)(PACL pACL,
ULONG aceRevision,
ULONG aceFlags,
PSID pSID,
ULONG aceType,
ACCESS_MASK mask);
static RtlAddProcessTrustLabelAce_t RtlAddProcessTrustLabelAce =
(RtlAddProcessTrustLabelAce_t)GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "RtlAddProcessTrustLabelAce");
BOOL ProcessIsAMPPL(HANDLE hProcess)
{
BOOL bResult = FALSE;
PS_PROTECTION protection = { 0, };
NTSTATUS status = STATUS_SUCCESS;
ULONG resultLength = 0;
status = NtQueryInformationProcess(hProcess, ProcessProtectionInformation, &protection, sizeof(protection), &resultLength);
if (!NT_SUCCESS(status))
{
goto end;
}
bResult = (PsProtectedSignerAntimalware == protection.Signer) &&
(PsProtectedTypeProtectedLight == protection.Type);
end:
return bResult;
}
BOOL HardenAntiMalwareServiceToken(HANDLE hProcess)
{
BOOL bResult = FALSE;
HANDLE hToken = NULL;
SID_IDENTIFIER_AUTHORITY sidAuth = SECURITY_PROCESS_TRUST_AUTHORITY;
PSID pPPLSID = NULL;
byte saclBuf[4096] = { 0, };
const size_t saclSize = sizeof(saclBuf);
PACL pNewSACL = (PACL)&saclBuf;
DWORD dwStatus = ERROR_SUCCESS;
if (!ProcessIsAMPPL(hProcess))
{
goto end;
}
if (!OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, &hToken))
{
LogLastError(L"OpenProcessToken");
goto end;
}
if (!AllocateAndInitializeSid(&sidAuth, SECURITY_PROCESS_TRUST_AUTHORITY_RID_COUNT,
SECURITY_PROCESS_PROTECTION_TYPE_LITE_RID,
SECURITY_PROCESS_PROTECTION_LEVEL_ANTIMALWARE_RID, 0, 0, 0, 0, 0, 0, &pPPLSID))
{
LogLastError(L"AllocateAndInitializeSid");
goto end;
}
if (!InitializeAcl(pNewSACL, saclSize, ACL_REVISION))
{
LogLastError(L"InitializeAcl");
goto end;
}
dwStatus = RtlNtStatusToDosError(RtlAddProcessTrustLabelAce(pNewSACL, ACL_REVISION,
OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE, pPPLSID,
SYSTEM_PROCESS_TRUST_LABEL_ACE_TYPE, TOKEN_READ));
if (ERROR_SUCCESS != dwStatus)
{
SetLastError(dwStatus);
LogLastError(L"RtlAddProcessTrustLabelAce");
goto end;
}
dwStatus = SetSecurityInfo(hToken, SE_KERNEL_OBJECT, PROCESS_TRUST_LABEL_SECURITY_INFORMATION, NULL, NULL, NULL, pNewSACL);
if (ERROR_SUCCESS != dwStatus)
{
SetLastError(dwStatus);
LogLastError(L"SetSecurityInfo");
goto end;
}
bResult = TRUE;
end:
if (hToken)
{
CloseHandle(hToken);
}
if (pPPLSID)
{
LocalFree(pPPLSID);
}
return bResult;
}
BOOL HardenAntiMalwareServices()
{
BOOL bResult = FALSE;
HANDLE hSnapshot = NULL;
HANDLE hProcess = NULL;
PROCESSENTRY32 processEntry = { 0, };
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE == hSnapshot)
{
goto end;
}
processEntry.dwSize = sizeof(processEntry);
if (!Process32FirstW(hSnapshot, &processEntry))
{
goto end;
}
do
{
if (hProcess)
{
CloseHandle(hProcess);
}
hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, processEntry.th32ProcessID);
if (!hProcess)
{
continue;
}
if (!ProcessIsAMPPL(hProcess))
{
continue;
}
LogToConsole(L"Hardening token of AM-PPL service: %ws (PID %u)\n", processEntry.szExeFile, processEntry.th32ProcessID);
bResult = HardenAntiMalwareServiceToken(hProcess);
} while (Process32Next(hSnapshot, &processEntry));
end:
if (INVALID_HANDLE_VALUE != hSnapshot)
{
CloseHandle(hSnapshot);
}
return bResult;
}
BOOL ParseCommandLine()
{
LPWSTR pwszCommandLine = GetCommandLine();
LPWSTR* argv = NULL;
int argc = 0;
int i = 0;
argv = CommandLineToArgvW(pwszCommandLine, &argc);
if (!argv)
return FALSE;
if (argc < 2)
return FALSE;
g_pwszGuid = argv[1];
for (size_t i = 2; i < argc; i++)
{
if (_wcsicmp(argv[i], L"-v") == 0)
g_bVerbose = TRUE;
else if (_wcsicmp(argv[i], L"-d") == 0)
{
g_bVerbose = TRUE;
g_bDebug = TRUE;
}
else if (_wcsicmp(argv[i], L"-a") == 0)
{
g_bHardenAMPPLOnly = TRUE;
}
}
return TRUE;
}