Skip to content

Commit

Permalink
[FEATURE] Add NTDLL patch to support run_pe on Windows 11 24H2 (Issue #…
Browse files Browse the repository at this point in the history
  • Loading branch information
hasherezade committed Jan 26, 2025
1 parent fc70bd4 commit 972ddd1
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 8 deletions.
2 changes: 2 additions & 0 deletions run_pe/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ include_directories ( ${PECONV_DIR}/include )
set (srcs
main.cpp
run_pe.cpp
patch_ntdll.cpp
)

set (hdrs
run_pe.h
patch_ntdll.h
)

add_executable ( ${PROJECT_NAME} ${hdrs} ${srcs} )
Expand Down
43 changes: 39 additions & 4 deletions run_pe/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,43 @@
#include "run_pe.h"

LPCTSTR version = TEXT("0.1.7");
bool g_PatchRequired = false;

bool isWindows1124H2OrLater()
{
NTSYSAPI NTSTATUS RtlGetVersion( PRTL_OSVERSIONINFOW lpVersionInformation );

RTL_OSVERSIONINFOW osVersionInfo = { 0 };
osVersionInfo.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOW);

HMODULE hNtdll = GetModuleHandleA("ntdll");
if (!hNtdll) return false; // should never happen

auto _RtlGetVersion = reinterpret_cast<decltype(&RtlGetVersion)>(GetProcAddress(hNtdll, "RtlGetVersion"));
NTSTATUS status = _RtlGetVersion(
&osVersionInfo
);
if (status != S_OK) {
std::cerr << "Failed to retrieve OS version information." << std::endl;
return false;
}
// Check major version and build number for Windows 11
if (osVersionInfo.dwMajorVersion > 10 ||
(osVersionInfo.dwMajorVersion == 10 && osVersionInfo.dwBuildNumber >= 26100)) {
return true;
}
return false;
}

int _tmain(int argc, LPTSTR argv[])
{
LPTSTR payload_path = NULL;
LPTSTR target_path = NULL;

if (isWindows1124H2OrLater()) {
std::cout << "WARNING: Executing RunPE on Windows11 24H2 or above requires patching NTDLL.ZwQueryVirtualMemory\n";
g_PatchRequired = true;
}
if (argc < 3) {
std::tcout << TEXT("[ run_pe v") << version << TEXT(" ]\n")
<< TEXT("Args: <payload_path> <target_path>\n");
Expand All @@ -29,8 +60,12 @@ int _tmain(int argc, LPTSTR argv[])
std::tcout << TEXT("Payload: ") << payload_path << TEXT("\n");
std::tcout << TEXT("Target: ") << target_path << TEXT("\n");

run_pe(payload_path, target_path, trimmedCmdLine.c_str());

system("pause");
return 0;
bool isOk = run_pe(payload_path, target_path, trimmedCmdLine.c_str());
if (!isOk) {
std::cerr << "Failed!\n";
}
else {
std::cout << "Done!\n";
}
return isOk ? 0 : (-1);
}
80 changes: 80 additions & 0 deletions run_pe/patch_ntdll.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#include "patch_ntdll.h"
#include <peconv.h>

bool apply_ntdll_patch(HANDLE hProcess, LPVOID module_ptr)
{
HMODULE hNtdll = GetModuleHandleA("ntdll");
if (!hNtdll) return false; // should never happen

ULONGLONG pos = 8;
const SIZE_T stub_size = 0x20;

ULONG_PTR _ZwQueryVirtualMemory = (ULONG_PTR)GetProcAddress(hNtdll, "ZwQueryVirtualMemory");
if (!_ZwQueryVirtualMemory || _ZwQueryVirtualMemory < pos) {
return false;
}
ULONG_PTR stub_ptr = (ULONG_PTR)_ZwQueryVirtualMemory - pos;
DWORD oldProtect = 0;
if (!VirtualProtectEx(hProcess, (LPVOID)stub_ptr, stub_size, PAGE_READWRITE, &oldProtect)) {
return false;
}
LPVOID patch_space = VirtualAllocEx(hProcess, 0, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!patch_space) {
return false;
}
BYTE stub_buffer_orig[stub_size] = { 0 };
SIZE_T out_bytes = 0;
if (!ReadProcessMemory(hProcess, (LPVOID)stub_ptr, stub_buffer_orig, stub_size, &out_bytes) || out_bytes != stub_size) {
return false;
}
const BYTE nop_pattern[sizeof(LPVOID)] = {0x0F, 0x1F, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00};
if (::memcmp(stub_buffer_orig, nop_pattern, sizeof(nop_pattern)) != 0) {
return false;
}

// prepare the patched stub:
const size_t syscall_pattern_full = 8;
const size_t syscall_pattern_start = 4;
const BYTE syscall_fill_pattern[] = {
0x4C, 0x8B, 0xD1, //mov r10,rcx
0xB8, 0xFF, 0x00, 0x00, 0x00 // mov eax,[syscall ID]
};
if (::memcmp(stub_buffer_orig + pos, syscall_fill_pattern, syscall_pattern_start) != 0) {
return false;
}

BYTE stub_buffer_patched[stub_size] = { 0 };
::memcpy(stub_buffer_patched, stub_buffer_orig, stub_size);

const BYTE jump_to_contnue[] = { 0xFF, 0x25, 0xEA, 0xFF, 0xFF, 0xFF };
ULONG_PTR _ZwQueryVirtualMemory_continue = (ULONG_PTR)_ZwQueryVirtualMemory + syscall_pattern_full;
BYTE stub_buffer_trampoline[stub_size * 2] = { 0 };
::memset(stub_buffer_trampoline, 0x90, sizeof(stub_buffer_trampoline));
::memcpy(stub_buffer_trampoline + stub_size, stub_buffer_orig, stub_size);
::memcpy(stub_buffer_trampoline + stub_size - sizeof(LPVOID), &module_ptr, sizeof(LPVOID));
::memcpy(stub_buffer_trampoline + stub_size, &_ZwQueryVirtualMemory_continue, sizeof(LPVOID));
::memcpy(stub_buffer_trampoline + stub_size + pos + syscall_pattern_full, jump_to_contnue, sizeof(jump_to_contnue));

BYTE mini_patch[] = { 0x49, 0x83, 0xF8, 0x0E, 0x75, 0x22, 0x48, 0x3B, 0x15, 0x0B, 0x00, 0x00, 0x00, 0x75, 0x19, 0xB8, 0xBB, 0x00, 0x00, 0xC0, 0xC3 };
::memcpy(stub_buffer_trampoline, mini_patch, sizeof(mini_patch));

const BYTE jump_back[] = { 0xFF, 0x25, 0xF2, 0xFF, 0xFF, 0xFF };
::memcpy(stub_buffer_patched, &patch_space, sizeof(LPVOID));
::memset(stub_buffer_patched + pos, 0x90, syscall_pattern_full);
::memcpy(stub_buffer_patched + pos, jump_back, sizeof(jump_back));


if (!WriteProcessMemory(hProcess, (LPVOID)stub_ptr, stub_buffer_patched, stub_size, &out_bytes) || out_bytes != stub_size) {
return false;
}
if (!VirtualProtectEx(hProcess, (LPVOID)stub_ptr, stub_size, oldProtect, &oldProtect)) {
return false;
}
if (!WriteProcessMemory(hProcess, (LPVOID)patch_space, stub_buffer_trampoline, sizeof(stub_buffer_trampoline), &out_bytes) || out_bytes != sizeof(stub_buffer_trampoline)) {
return false;
}
if (!VirtualProtectEx(hProcess, (LPVOID)patch_space, stub_size, PAGE_EXECUTE_READ, &oldProtect)) {
return false;
}
return true;
}
5 changes: 5 additions & 0 deletions run_pe/patch_ntdll.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#pragma once

#include <windows.h>

bool apply_ntdll_patch(HANDLE hProcess, LPVOID module_ptr);
11 changes: 7 additions & 4 deletions run_pe/run_pe.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

#include <peconv.h>
#include <iostream>
#include "patch_ntdll.h"

using namespace peconv;
extern bool g_PatchRequired;

bool create_suspended_process(IN LPCTSTR path, IN LPCTSTR cmdLine, OUT PROCESS_INFORMATION &pi)
{
Expand Down Expand Up @@ -221,14 +223,17 @@ bool _run_pe(BYTE *loaded_pe, size_t payloadImageSize, PROCESS_INFORMATION &pi,
std::cout << "Writing to the remote process failed!\n";
return false;
}
#ifdef _DEBUG

printf("Loaded at: %p\n", loaded_pe);
#endif

//5. Redirect the remote structures to the injected payload (EntryPoint and ImageBase must be changed):
if (!redirect_to_payload(loaded_pe, remoteBase, pi, is32bit)) {
std::cerr << "Redirecting failed!\n";
return false;
}
if (!is32bit && g_PatchRequired && !apply_ntdll_patch(pi.hProcess, remoteBase)) {
std::cout << "ERROR: failed to apply the required patch on NTDLL\n";
}
//6. Resume the thread and let the payload run:
ResumeThread(pi.hThread);
return true;
Expand Down Expand Up @@ -273,7 +278,6 @@ bool run_pe(IN LPCTSTR payloadPath, IN LPCTSTR targetPath, IN LPCTSTR cmdLine)
std::cerr << "Loading failed!\n";
return false;
}

// Get the payload's architecture and check if it is compatibile with the loader:
const WORD payload_arch = get_nt_hdr_architecture(loaded_pe);
if (payload_arch != IMAGE_NT_OPTIONAL_HDR32_MAGIC && payload_arch != IMAGE_NT_OPTIONAL_HDR64_MAGIC) {
Expand Down Expand Up @@ -308,7 +312,6 @@ bool run_pe(IN LPCTSTR payloadPath, IN LPCTSTR targetPath, IN LPCTSTR cmdLine)

//3. Perform the actual RunPE:
bool isOk = _run_pe(loaded_pe, payloadImageSize, pi, is32bit_payload);

//4. Cleanup:
if (!isOk) { //if injection failed, kill the process
terminate_process(pi.dwProcessId);
Expand Down

0 comments on commit 972ddd1

Please sign in to comment.