-
Notifications
You must be signed in to change notification settings - Fork 33
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
Windows 11 24H2 #1
Comments
Yes |
ahh, ok, I wonder what I'm doing wrong with my library. It will refuse to work. I based from the libs you reference in the readme. It's a Delphi adaptation. |
Ah, interesting. I know the concepts themselves work for 24H2, but I just tested the library for this project and reproduced the issue you experienced. I'll troubleshoot this and work on a fix. |
Still need to troubleshoot why the normal manual mapping approach is not currently working in this library on 24H2. The concept still works so it must be an implementation issue. I did test and find that Module Doppelgänging does still work fine on 24H2 if that'd help your project. I'll update this issue once I have more information. |
I see. I used the redirecting LoadLibrary approach, being the easier one to understand. I've been trying many things to get it working, but I just don't understand what is going wrong in 24H2. I guess I need to try and understand Module Doppelganging. Also is it possible to have a C interface to your lib? and will it work from a DLL? |
If it helps, I believe the approach can be simplified down to this:
I'd be happy to add this. I need to fix some things with the library (ex. making the manual mapping approach work on 24H2) so I can add this in with that work. Would this definition meet your needs?
It should, yes. |
thanks, that would be great. I tried doing something similar, but it will just crash for me: #ifdef __cplusplus
extern "C" {
#endif
// Define the C wrapper function
__declspec(dllexport) HMODULE LoadLibraryWrapper(
const wchar_t* fileName,
const uint8_t* bytes,
size_t bytesSize,
DWORD flags,
const wchar_t* modListName,
DWORD nativeFlags) {
// Forward declare the original function
HMODULE LoadLibrary(const std::wstring& fileName,
const std::vector<std::byte>& bytes,
DWORD flags,
const std::wstring& modListName,
DWORD nativeFlags);
// Convert C types to C++ types
std::wstring cppFileName(fileName);
std::vector<std::byte> cppBytes(reinterpret_cast<const std::byte*>(bytes), reinterpret_cast<const std::byte*>(bytes) + bytesSize);
std::wstring cppModListName(modListName);
// Call the original function and return the result
return Pl::LoadLibrary(cppFileName, cppBytes, flags, cppModListName, nativeFlags);
}
#ifdef __cplusplus
}
#endif
it's probably that code, I'm, not a c/c++ programmer. I know enough to try and adapt for Dephi. And I will look over those steps you outlined, many thanks. |
Hey @jarroddavis68, I added a C API for the project which is exposed by the #include "perfect_loader.h"
int wmain(int argc, wchar_t** argv) {
// snippet ...
HMODULE library = LoadDllFromMemory(dllBytes, dllSize, 0, fileName, PL_LOAD_FLAGS_USETXF, L"");
// snippet ...
} The The library's other technique for reflectively loading a library (manually mapping a DLL in memory then redirecting I hope this helps your work, and thank you for noticing the failure on 24H2 and filling the issue! 🙂 |
Many Thanks!
The concept continued to work for me too, the hooks would fire, but when LoadLibrary returns, it will always be NULL.
I will check this out, again, thank you! |
Sure thing! For the issue regarding manual mapping, at some point Microsoft introduced logic in the loader such that the loader will reattempt mapping the section one additional time when our library returns |
here is my hooked NtMapViewOfSection code, how can I adapt it to what you described above. It never seems to get called again. function HookNtMapViewOfSection(
ASectionHandle: THandle;
AProcessHandle: THandle;
ABaseAddress: PPVOID;
AZeroBits: ULONG_PTR;
ACommitSize: SIZE_T;
ASectionOffset: PLARGEINTEGER;
AViewSize: PSIZE_T;
AInheritDisposition: ULONG;
AAllocationType: ULONG;
AWin32Protect: ULONG
): NTSTATUS; stdcall;
var
LOldNtMapViewOfSection: TNtMapViewOfSection; // Original NtMapViewOfSection function
begin
// Stop the current hook to prevent recursion
NtMapViewOfSectionHook.Stop();
if DllData = nil then
begin
// Retrieve the original NtMapViewOfSection function pointer
LOldNtMapViewOfSection := TNtMapViewOfSection(NtMapViewOfSectionHook.GetOldFunction());
// Call the original NtMapViewOfSection function with provided parameters
Result := LOldNtMapViewOfSection(
ASectionHandle,
AProcessHandle,
ABaseAddress^,
AZeroBits,
ACommitSize,
ASectionOffset,
AViewSize^,
AInheritDisposition,
AAllocationType,
AWin32Protect
);
Exit;
end;
// Map the DLL into memory
HookMapDll(DllData, ABaseAddress^, AViewSize^);
DllData := nil; // Clear DLL data after mapping
// Return a custom NTSTATUS code indicating successful mapping
Result := $40000003;
end;
function HookNtMapViewOfSection(
ASectionHandle: THandle;
AProcessHandle: THandle;
ABaseAddress: PPVOID;
AZeroBits: ULONG_PTR;
ACommitSize: SIZE_T;
ASectionOffset: PLARGEINTEGER;
AViewSize: PSIZE_T;
AInheritDisposition: ULONG;
AAllocationType: ULONG;
AWin32Protect: ULONG
): NTSTATUS; stdcall;
var
LOldNtMapViewOfSection: TNtMapViewOfSection;
begin
// Stop the current hook to prevent recursion
NtMapViewOfSectionHook.Stop();
try
// Retrieve the original NtMapViewOfSection function pointer
LOldNtMapViewOfSection := TNtMapViewOfSection(NtMapViewOfSectionHook.GetOldFunction());
// Perform manual mapping if DllData is set
if DllData <> nil then
begin
HookMapDll(DllData, ABaseAddress^, AViewSize^);
DllData := nil; // Clear DLL data after mapping
Result := $40000003; // Indicate successful mapping
Exit;
end;
// Call the original NtMapViewOfSection function
Result := LOldNtMapViewOfSection(
ASectionHandle,
AProcessHandle,
ABaseAddress^,
AZeroBits,
ACommitSize,
ASectionOffset,
AViewSize^,
AInheritDisposition,
AAllocationType,
AWin32Protect
);
// Handle retry for STATUS_IMAGE_NOT_AT_BASE
if Result = STATUS_IMAGE_NOT_AT_BASE then
begin
// Retry mapping logic for Windows 11 24H2 compatibility
NtMapViewOfSectionHook.Start('ntdll.dll', 'NtMapViewOfSection', FARPROC(@HookNtMapViewOfSection));
Result := LOldNtMapViewOfSection(
ASectionHandle,
AProcessHandle,
ABaseAddress^,
AZeroBits,
ACommitSize,
ASectionOffset,
AViewSize^,
AInheritDisposition,
AAllocationType,
AWin32Protect
);
end;
finally
// Restart the hook to ensure future calls are handled
NtMapViewOfSectionHook.Start('ntdll.dll', 'NtMapViewOfSection', FARPROC(@HookNtMapViewOfSection));
end;
end;
function HookNtOpenFile(
AFileHandle: PHANDLE;
ADesiredAccess: ACCESS_MASK;
AObjectAttributes: POBJECT_ATTRIBUTES;
AIoStatusBlock: PIO_STATUS_BLOCK;
AShareAccess: ULONG;
AOpenOptions: ULONG
): NTSTATUS; stdcall;
var
LResultStatus: NTSTATUS; // Result status from the original NtOpenFile
LOldNtOpenFile: TNtOpenFile; // Original NtOpenFile function
begin
// Temporarily suspend the NtOpenFile hook to avoid recursion
NtOpenFileHook.Suspended();
// Retrieve the original NtOpenFile function pointer
LOldNtOpenFile := TNtOpenFile(NtOpenFileHook.GetOldFunction());
// Call the original NtOpenFile function with provided parameters
LResultStatus := LOldNtOpenFile(
AFileHandle,
ADesiredAccess,
AObjectAttributes,
AIoStatusBlock,
AShareAccess,
AOpenOptions
);
// Check if the ObjectName contains the hook reference DLL name
if wcsstr(AObjectAttributes^.ObjectName^.Buffer, HOOK_REFERENCE_DLL) <> nil then
begin
// Stop the NtOpenFile hook and start the NtMapViewOfSection hook
NtOpenFileHook.Stop();
NtMapViewOfSectionHook.Start('ntdll.dll', 'NtMapViewOfSection', FARPROC(@HookNtMapViewOfSection));
Exit(LResultStatus); // Return the original result status
end;
// Restore the NtOpenFile hook before returning
NtOpenFileHook.Restore;
Result := LResultStatus;
end;
function MemoryLoadLibrary(const AData: Pointer): THandle;
begin
// Enter critical section to ensure thread-safe operations
CriticalSection.Enter();
try
// Start hooking the NtOpenFile function in ntdll.dll with HookNtOpenFile as the replacement
NtOpenFileHook.Start('ntdll.dll', 'NtOpenFile', @HookNtOpenFile);
DllData := AData; // Store the DLL data pointer
Result := LoadLibraryW(HOOK_REFERENCE_DLL); // Load the reference DLL using wide-character API
finally
// Leave the critical section after operations are complete
CriticalSection.Leave();
end;
end; |
Hey @jarroddavis68, I have never written Pascal code before and as such I'm not equipped to help troubleshoot your code. I wish I could provide better help with that and I'm sorry I can't 😕 By chance, did you attempt using the C interface that was added for your request to see if it'd solve your need? |
No worries, understand.
Yes, while it does work, the library itself is c++ as such I'm unable to used it from Delphi without using it as a DLL, which defeats the purpose of not being able to use DLLs, heh. Delphi can statically link to a C library however using |
Ah dang, that makes sense and its unfortunate it wouldn't work in for your use case in its current form. I appreciate the extra insight! |
Ok, I think I may have a working solution for Delphi that can use perfect-loader. Seems to be working. Let me do a few more tests and I will report back on how I got it working. 🫰 |
Everything seems to be working on par with OS
I will be reactivating my Many thanks my friend! 👏🏿 // Pprovides functionality for dynamically loading a DLL from memory,
// managing its lifecycle, and cleaning up resources upon application shutdown.
var
// Handle to the loaded DLL. Initially nil.
DllHandle: Pointer = nil;
// Temporary file name used for storing data related to the DLL loading process.
TempFilename: string = '';
// Loads a DLL from memory.
// @param AData Pointer to the memory block containing the DLL data.
// @param ASize Size of the memory block in bytes.
// @return Handle to the loaded DLL on success; 0 on failure.
function LoadMemoryDLL(const AData: Pointer; const ASize: NativeUInt): THandle;
begin
// Calls an external function to load the DLL from memory.
Result := LoadDllFromMemory(AData, ASize, 0, PChar(TempFilename), PL_LOAD_FLAGS_USETXF, nil);
end;
// Loads the custom DLL and initializes its exported function.
// @param AError Output parameter that stores the error message, if any.
// @return True if the DLL is loaded successfully; False otherwise.
function LoadDLL(var AError: string): Boolean;
begin
Result := False;
// Check if the DLL is already loaded.
if Assigned(DllHandle) then
begin
Result := True;
Exit;
end;
// Load the DLL into memory using a custom loader function.
DllHandle := MemoryLoadLibrary(@PERFECT_LOADER[0]);
if not Assigned(DllHandle) then
begin
AError := 'Unable to load perfect-loader dll';
Exit;
end;
// Retrieve the address of the `LoadDllFromMemory` function from the loaded DLL.
LoadDllFromMemory := MemoryGetProcAddress(DllHandle, 'LoadDllFromMemory');
if not Assigned(LoadDllFromMemory) then
begin
AError := 'Unable to get perfect-loader dll exports';
Exit;
end;
// Generate a temporary file name for auxiliary operations.
TempFilename := TPath.Combine(TPath.GetTempPath, TPath.GetGUIDFileName + '.txt');
// Write a dummy text to the temporary file to verify file system access.
TFile.WriteAllText(TempFilename, 'MemoryDLL');
// Verify that the temporary file exists.
Result := TFile.Exists(TempFilename);
end;
// Unloads the DLL and releases allocated resources.
procedure UnloadDLL();
begin
// Check if the DLL handle is valid.
if not Assigned(DllHandle) then Exit;
// Free the loaded DLL from memory.
MemoryFreeLibrary(DllHandle);
DllHandle := nil;
// Delete the temporary file, if it exists.
if TFile.Exists(TempFilename) then
TFile.Delete(TempFilename);
end;
// Initialization block to load the DLL during application startup.
initialization
var
LError: string;
begin
// Enable memory leak reporting for debugging purposes.
ReportMemoryLeaksOnShutdown := True;
try
// Attempt to load the DLL. Terminate the application on failure.
if not LoadDLL(LError) then
begin
MessageBox(0, PChar(LError), 'Critical Initialization Error', MB_ICONERROR);
Halt(1);
end;
except
on E: Exception do
begin
// Display any exceptions encountered during initialization.
MessageBox(0, PChar(E.Message), 'Critical Initialization Error', MB_ICONERROR);
end;
end;
end;
// Finalization block to clean up resources during application shutdown.
finalization
begin
try
// Unload the DLL and delete temporary files.
UnloadDLL();
except
on E: Exception do
begin
// Display any exceptions encountered during finalization.
MessageBox(0, PChar(E.Message), 'Critical Shutdown Error', MB_ICONERROR);
end;
end;
end; |
That's fantastic news! I'm so happy it's able to provide a solution for your project. Awesome work! 😄 |
Ok, repo updated and now using |
Ok, I've been playing around trying to get this to work, I noticed it will attempt 3 times, then succeed, but when I GetProcAddress to a routine and try to call it, I will get a DEP error. Any ideas? |
Oh odd. iirc, I only saw it reattempt once in my testing but I could definitely be misremembering 🤔 What path did you provide to LoadLibray to kickoff the loading process? I feel like it may be due to a different characteristic of the DLL causing LoadLibray to do different processing. I chatted with @rbmm previously who developed the same method for a different project (link) and he mentioned he needed to provide the path for a DLL "without CFG that is larger than the in-memory library you intend to load." @rbmm, do you know if the DEP error @jarroddavis68 experienced is related to this maybe due to something different? |
memdorydll.mp4function HookNtMapViewOfSection(
ASectionHandle: THandle;
AProcessHandle: THandle;
ABaseAddress: PPVOID;
AZeroBits: ULONG_PTR;
ACommitSize: SIZE_T;
ASectionOffset: PLARGEINTEGER;
AViewSize: PSIZE_T;
AInheritDisposition: ULONG;
AAllocationType: ULONG;
AWin32Protect: ULONG
): NTSTATUS; stdcall;
begin
inc(count);
writeln('HookNtMapViewOfSection Attempt #: ', count);
// Only do our mapping if we have DLL data
HookMapDll(DllData, ABaseAddress^, AViewSize^);
BaseAddress := THandle(ABaseAddress^);
// Always return STATUS_IMAGE_NOT_AT_BASE to let loader retry
Result := STATUS_IMAGE_NOT_AT_BASE;
end; I'm using |
Interesting, A-Normal-User, said failed to work also on 24H2, but after disabling security, it worked. Hmmm. I've been working on this for the past few days, and I don't know what else to try at this point. We obviously cannot ask a user to disable security, so what can be done I wonder? |
What do you mean by "disable security?" I ask to ensure I have not missed a test case, but I have gotten the manual mapping approach to work on 24H2 without disabling anything. I just need time to provide an update to the library.
The alternative Module Doppelgänging approach works without any needed updates. Do you have a specific need for the manual mapping approach instead of this one? |
I'm just sharing what he told me: no, what you have is working fine for me. I was just playing around trying to see if I could get my original 23H2 code to work on 24H2. It's pure pascal and worked perfect on 23H2. It would just be cool if I could also get it working. I usually have two projects going on, a main one and a side project. When I get tired, bored, stumped on one, I jump on the order just to keep the creative juices flowing, heh. |
if you post the binary file(s) - I can look at it under the debugger and tell you what actually happened, where the error is you can also look/test link : load.exe (static linked code) or load2.exe ( shell code used for load dll) or load3.exe ( compressed shell code) (the same for x86 too ) with dll.dll or any your dll and yes, if ZwMapViewOfSection return STATUS_IMAGE_NOT_AT_BASE loader by some reason unload dll and then reload it again. i dont understand sense of this. in general secuense is next:
|
@rbmm, thank you for the response! It's helpful to see that you are experiencing
That makes since, and I totally understand! I'm the same way regarding the main and side projects 👍 Got an update that I believe will help you @jarroddavis68. Admittedly, I thought I was testing on 24H2 but was only testing on 24H1 🤦♂️. My apologies for the bad testing on my end! I have been troubleshooting since Monday and have manual mapping finally (and actually this time) working again for 24H2. The first fix that needed to occur is to maintain the hook on As a side note, after much more testing, I still have only encountered Now: Here is the relevant change to Windows that caused the other issues for the project on 24H2. On 24H2, Microsoft introduced a new technology called
Here is why that caused an issue for the manual mapping approach. In a nutshell, when a module has This specifically happens in two locations and both need to be accounted for. The first is in void RtlpInsertOrRemoveScpCfgFunctionTable(PVOID DllBase, bool Reserved, char Insert) {
MEMORY_IMAGE_EXTENSION_INFORMATION memoryInfo = { 0 };
SIZE_T returnLength = 0;
NTSTATUS status = NtQueryVirtualMemory(-1, DllBase, MemoryImageExtensionInformation, &memoryInfo, sizeof(memoryInfo), &returnLength);
if (status != STATUS_NOT_SUPPORTED && NT_SUCCESS(status) && memoryInfo.ExtensionSize != 0) {
PRTL_SCP_CFG_HEADER scpCfg = (PRTL_SCP_CFG_HEADER)((size_t)DllBase + MemoryInfo.ExtensionImageBaseRva);
ULONG fnTableRva = (scpCfg->Common).FnTableRva;
if (fnTableRva) {
PRUNTIME_FUNCTION FunctionTable = (PRUNTIME_FUNCTION)((size_t)scpCfg + fnTableRva);
if (Insert) {
PVOID DynamicTable;
status = RtlAddGrowableFunctionTable(&DynamicTable, FunctionTable, 1, 1, RangeBase, RangeBase + memoryInfo.ExtensionSize);
}
else {
status = RtlDeleteFunctionTable(FunctionTable);
}
}
}
return status;
} Here is how to account for this code:
The second location is in // The type name is based off of the operation names defined in
// chc's hot patch testing repo:
// https://github.com/chc/NtManageHotpatchTests
//
// The structure of the type was identified through manual auditing.
typedef struct _QUERY_SINGLE_LOADED_PATCH {
ULONG Version; // Valid versions are 1 or above
ULONG Unknown1;
LPVOID Unknown2;
LPVOID DllBase;
GUID Unknown3; // Type was assumed based on its length and may be wrong
LPVOID Unknown4;
} QUERY_SINGLE_LOADED_PATCH, *PQUERY_SINGLE_LOADED_PATCH;
NTSTATUS NTAPI NtManageHotPatch(ULONG Operation, PVOID SubmitBuffer, ULONG SubmitBufferLength, NTSTATUS* OperationStatus);
NTSTATUS LdrpQueryCurrentPatch(LPVOID DllBase, PQUERY_SINGLE_LOADED_PATCH Buffer) {
Buffer.Version = 1;
Buffer.Unknown1 = 0;
Buffer.Unknown2 = -1;
Buffer.DllBase = DllBase;
Buffer.Unknown3 = { 0 };
Buffer.Unknown4 = (LPVOID)0x0;
NTSTATUS operationStatus = 0;
// The operation name for argument 1 was also taken from chc's repo.
NTSTATUS status = NtManageHotPatch(OPERATION_QUERY_SINGLE_LOADED_PATCH, &buffer, sizeof(buffer), &operationStatus);
if (status == STATUS_BUFFER_TOO_SMALL) {
return STATUS_PATCH_CONFLICT;
}
return status;
} There are multiple ways to account for this code. The code is only called if an
The perfect loader library now implements the last method to account for Again, I apologize for my bad testing earlier which made me think the project was working! I appreciate you reporting the issue and helping work through a solution for Windows 11 24H2! 🙏 I am going to close the issue now that this project's implementation of the manual mapping approach is working again on 24H2, but please follow up if you get it working in your Pascal project as well. It would be cool to see that working again too. |
hm.. i only just now install 24H2 (never look it before and yet not read about new cfg here). after fast test look like my binary work ok, without any fix. and if set LdrpIsHotPatchingEnabled to a non-zero value - any loadlibrary (normal, not from memory) fail with "A system patch could not be applied due to conflicting accesses to the system image. " error. interesting are you can test https://github.com/rbmm/ARL/tree/main/x64/Release on self system (24h2) (load[n].exe - load dll in current process and unload it after message box closed. possible use dll.dll for test or any system or your dll. exe.exe - load dll to explorer and then to fontdrvhost.exe. |
funny - after very fast test own debugger and tools, i not found any breaks, but what i view unusual just - almost all dlls in all processes now have LDRP_IMAGE_NOT_AT_BASE (in Loader Data Table Entry Flags) even ntdll.dll strange. but probably sense of this undocumented flag changed |
I believe I may have the answer for this. After debugging That approach allows the code for The manual mapping approach that's implemented in I may be wrong, but I believe that is why ARL has continued to work on 24H2 but
That is interesting! I agree that this behavior is likely related to Microsoft's implementation of hot patching, but definitely want to investigate that behavior further 🙂 |
yes, correct. i select dll without cfg (otherwise will be crash if cfg enabled in process) and map it as image section. we need no cfg for any address inside this section will be valid. for virtual memory allocation this is true. about LDRP_IMAGE_NOT_AT_BASE - i sure sense of this flag is changed. now it mean something else. almost all dlls have it on (despite it at base) but some not have |
I doubt it, at least because I don't display the entire section (not the entire system dll), but the minimum required size so that my (smaller) dll fits. So everything that goes further doesn't apply here. Nevertheless, in my tests, loading works. |
Hi, will this work in windows 24H2?
The text was updated successfully, but these errors were encountered: