Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

64bit run_pe load 64bit payload not working on windows 11 24H2 #59

Open
idigger opened this issue Oct 13, 2024 · 6 comments
Open

64bit run_pe load 64bit payload not working on windows 11 24H2 #59

idigger opened this issue Oct 13, 2024 · 6 comments

Comments

@idigger
Copy link

idigger commented Oct 13, 2024

errrr111111

Load 32bit payload ok.
errrrr22222

64bit and 32bit are ok on before windows 11 24H2

@NotCapengeR
Copy link
Contributor

NotCapengeR commented Nov 15, 2024

Starting from Windows 11 24H2, Microsoft has implemented a new Control Flow Guard or CFG system, which should limit the places where the application can execute code. You can read more info on MSDN.

Hackers from the UnknownCheats forum analyzed the PE loader in Ntdll and found a function that initializes this mechanism for the PE image — RtlpInsertOrRemoveScpCfgFunctionTable. It seems like there is even a working code that patches RtlpInsertOrRemoveScpCfgFunctionTable function according to a hard-coded offset.

const auto NtdllBase = reinterpret_cast<PBYTE>(GetModuleHandleW(L"ntdll.dll"));
const BYTE Patch[4] =
{
    0x48, 0x31, 0xC0, // xor rax, rax
    0xC3 // ret
};
// Patching RtlpInsertOrRemoveScpCfgFunctionTable function of Ntdll using hard-coded offset
WriteProcessMemory(pi.hProcess, NtdllBase + 0x7BE0, Patch, sizeof(Patch), nullptr);

Original post — https://www.unknowncheats.me/forum/4239032-post15.html

Perhaps zeroing IMAGE_LOAD_CONFIG_DIRECTORY of the payload image before copying it to another process can also help.

@NotCapengeR
Copy link
Contributor

NotCapengeR commented Dec 15, 2024

So, I did get an Ntdll sample from Windows 11 24H2. The RtlpInsertOrRemoveScpCfgFunctionTable function is not exported, so patching it by offset is not a good idea, but I found something interesting in IDA...

NTSTATUS __stdcall RtlpInsertOrRemoveScpCfgFunctionTable(PVOID ImageBase, __int64 Reserved, bool insertOrRemove)
{
  NTSTATUS result; // eax
  char *pageBase; // rax
  __int64 offsetToRtlTable; // rcx
  ULONG_PTR ImageExtensionLength; // [rsp+30h] [rbp-38h] BYREF
  PVOID DynamicTable; // [rsp+38h] [rbp-30h] BYREF
  MEMORY_IMAGE_EXTENSION_INFORMATION info; // [rsp+40h] [rbp-28h] BYREF

  ImageExtensionLength = 0i64;
  memset(&info, 0, sizeof(info));
  result = ZwQueryVirtualMemory(
             (HANDLE)0xFFFFFFFFFFFFFFFFi64,
             ImageBase,
             MemoryImageExtensionInformation,
             &info,
             0x18ui64,
             &ImageExtensionLength);
  if ( result == -1073741637 )
    return 279;
  if ( result >= 0 )
  {
    if ( !info.PageSize )
      return 279;
    pageBase = (char *)ImageBase + *(_QWORD *)&info.PageOffset;
    offsetToRtlTable = *(unsigned int *)((char *)ImageBase + *(_QWORD *)&info.PageOffset + 20);
    if ( !(_DWORD)offsetToRtlTable )
      return 279;
    if ( insertOrRemove )
    {
      result = RtlAddGrowableFunctionTable(
                 &DynamicTable,
                 (PRUNTIME_FUNCTION)&pageBase[offsetToRtlTable],
                 1u,
                 1u,
                 (ULONG_PTR)ImageBase + *(_QWORD *)&info.PageOffset,
                 (ULONG_PTR)&pageBase[info.PageSize]);
      if ( result >= 0 )
        return 0;
    }
    else
    {
      RtlDeleteFunctionTable((PRUNTIME_FUNCTION)&pageBase[offsetToRtlTable]);
      return 0;
    }
  }
  return result;
}

RtlpInsertOrRemoveScpCfgFunctionTable is just a wrapper around RtlDeleteFunctionTable and RtlAddGrowableFunctionTable. We are lucky — both of these functions are exported and not used anywhere else, so we can safely patch them. Oh, and by the way: it turned out that another function requires a patch — NtManageHotPatch, it is also exported, so there are no problems for us.
Final code should be something like this:

#ifdef _WIN64 // Dynamic function tables is only for 64-bits images
	auto hNtdll = GetModuleHandleW(L"ntdll.dll"); // Found Ntdll base address
	if (hNtdll) {
		// Stub for functions
		const BYTE patch[4] =
		{
			0x48, 0x31, 0xC0, // xor rax, rax
			0xC3 // ret
		};

		FARPROC NtManageHotPatch = GetProcAddress(hNtdll, "NtManageHotPatch");
		FARPROC RtlAddGrowableFunctionTable = GetProcAddress(hNtdll, "RtlAddGrowableFunctionTable");
		FARPROC RtlDeleteFunctionTable = GetProcAddress(hNtdll, "RtlDeleteFunctionTable");
		
		if (NtManageHotPatch && RtlAddGrowableFunctionTable && RtlDeleteFunctionTable) {
			// Wanna some patches?
			WriteProcessMemory(pi.hProcess, NtManageHotPatch, patch, sizeof(patch), nullptr);
			WriteProcessMemory(pi.hProcess, RtlAddGrowableFunctionTable, patch, sizeof(patch), nullptr);
			WriteProcessMemory(pi.hProcess, RtlDeleteFunctionTable, patch, sizeof(patch), nullptr);
			
			// Apply changes to cache
			FlushInstructionCache(pi.hProcess, NtManageHotPatch, sizeof(patch));
			FlushInstructionCache(pi.hProcess, RtlAddGrowableFunctionTable, sizeof(patch));
			FlushInstructionCache(pi.hProcess, RtlDeleteFunctionTable, sizeof(patch));
		}
	}
#endif

I could do a pull request with this, but I don't have Windows 11 24H2 to test this code.

@hasherezade
Copy link
Owner

Thank you for finding all those details @NotCapengeR ! It is definitely very useful! I will try to get Windows 11 24H2 and test it whenever I get some free time.

@bytecode77
Copy link

@NotCapengeR I have, too, realized, that Windows 24H2 broke RunPE, so I stumbled upon your suggestion. I tried it (with VirtualProtectEx before WriteProcessMemory), but without success, yet. I will do my own research next, but may I ask, if any of you solved this rather new issue?

@NotCapengeR
Copy link
Contributor

NotCapengeR commented Jan 5, 2025

@NotCapengeR I have, too, realized, that Windows 24H2 broke RunPE, so I stumbled upon your suggestion. I tried it (with VirtualProtectEx before WriteProcessMemory), but without success, yet. I will do my own research next, but may I ask, if any of you solved this rather new issue?

As I said, I don't have Windows 11 24H2, so I cannot test my code. If it doesn't working, you need to debug and find a function that is causing the error (btw, what is this error?). But it seems to me that a patch of these functions is enough for everything to work, perhaps a few more functions need a patch too

@harunkocacaliskan
Copy link

Any updates for this issue, did anybody tried patch ntdll method? I have tried some other PE Loaders, some of them has same error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants