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; }