PageTableInjection: Crash on call DllMain

Hello! Thanks for the POC and that you share it with us! I tried to adopt your POC and do some tryings altough whenever I try to call the actual DLLMain Entrypoint it just seems to crash, any idea? Here is how I did it:

print_info("Payload is now mapped to the target process VA: 0x%llX\n", deployment_va.Value);
try_read_deployed_image((u64)deployment_va.Value, target_process_id);
DWORD ep_rva = get_entry_point_rva(file_buffer.data());
printf("%llu \r\n", ep_rva);
int ret = run_implant(deployment_va.Value, ep_rva);
printf("return: %i \r\n", ret);

int run_implant(PVOID mapped, DWORD ep_rva)
{
	ULONG_PTR implant_ep = (ULONG_PTR)mapped + ep_rva;
	BOOL(*dll_main)(HINSTANCE, DWORD, LPVOID) = (BOOL(*)(HINSTANCE, DWORD, LPVOID))(implant_ep);
	return dll_main((HINSTANCE)mapped, DLL_PROCESS_ATTACH, 0);
}

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Comments: 21 (13 by maintainers)

Most upvoted comments

2nd param won’t be available in CreateRemoteThread so your if statement won’t evaluated

using MyEntryPointType = void (*)(void* context);
/* DLL Payload: No CRT, No static links */
void MyEntryPoint(void* context) { LI_FN(MessageBoxA)(nullptr, "A", "B", MB_OK); }
static_assert(
	std::is_same_v<decltype(&MyEntryPoint), MyEntryPointType>,
	"entry point function type mismatch");

Yes it should crash because you are referencing 2nd (and 3rd) parameters in your DllMain which did not provided. CreateRemoteThread can only pass one context parameter.

You are right, I should have spot more on it this is pretty much my first time working with CreateRemoteThread for injecting code, seems pretty interesting and powerful. I came to this solution, does this seem correct?

    // The virtual memory on the deployment location of the target process 
    VIRTUAL_ADDRESS deployment_va = { remote_image_base };
    deployment_va.Pml4Index = payload.PML4Index;

    print_info("Payload is now mapped to the target process VA: 0x%llX\n", deployment_va.Value);

    try_read_deployed_image((u64)deployment_va.Value, target_process_id);

    HANDLE target_process_handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, target_process_id);
    DWORD ep_rva = get_entry_point_rva(file_buffer.data());
    ULONG_PTR implant_ep = (ULONG_PTR)deployment_va.Value + ep_rva;
    BOOL(*dll_main)(HINSTANCE, DWORD, LPVOID) = (BOOL(*)(HINSTANCE, DWORD, LPVOID))(implant_ep);

    CreateRemoteThread(target_process_handle, NULL, 0, (PTHREAD_START_ROUTINE)dll_main, NULL, 0, NULL);

    VirtualFree(mapped_image, 0, MEM_RELEASE);
    return true;

See function pointer type in CreateRemoteThread and stop defining entry point as a normal DllMain template style

I agree, since this is just a test project I decided to define it as normal DllMain, custom entry point is always better.

Also sprintf is a CRT import.

That is correct as well, I am working with your suggestions currently to ditch all CRT stuff in order to complete my tests.

  • disable security check in C++ option

    • disable control flow guard in C++ option

    • override entry point in linker option

    • disable all default libraries in linker option These should make compiled image cleaner and minimum imports / without CRT support

The first 2 were already disabled, I am working on the minimum imports and without CRT support right now, hoping this solves my issues.

LI_FN(MessageBoxA)(GetDesktopWindow(), Buffer, “Hello”, MB_SYSTEMMODAL);

You don’t have to pass HWND, could be null.

MessageBoxA(nullptr, “A”, “B”, MB_OK);

That is correct as well, I basically put in there as I had some compiler issues, altough to be sure I had tested the Dll with an normal injection before to be sure the Dll works.

Thanks for all your suggestions and the help!

there aren’t any way you can do but you can pass a pointer to the dynamically allocated context

struct my_entry_point_context_t {
	unsigned long aaa;
	unsigned long bbb;
	unsigned long ccc;
};

/* On the implanter */
my_entry_point_context_t my_entry_context{};
my_entry_context.aaa = 1;
my_entry_context.bbb = 2;
my_entry_context.ccc = 3;

const auto alloc_base = VirtualAllocEx(
	deployment_target,
	nullptr,
	sizeof(my_entry_point_context_t),
	MEM_RESERVE | MEM_COMMIT,
	PAGE_EXECUTE_READWRITE);

template<typename T>
bool write(const void* addr, const T&& value) {
	return !!WriteProcessMemory(deployment_target, addr, &value, sizeof(T), ...);
}

write(alloc_base, my_entry_context);
CreateRemoteThread(
	deployment_target,
	...,
	reinterpret_cast<LPTHREAD_START_ROUTINE>(deployment.Value+MyEntryPointRva),
	alloc_base);

/* On the deployed dll payload */
void MyEntryPoint(my_entry_point_context_t* context) {
	/* Called by CreateRemoteThread with passing dynamically allocated context */
	context->aaa;
	context->bbb;
	context->ccc;
}

indeed, 2nd parameter is not passed by createremotethread, the value will be random because it points to the incorrect stack memory

LI_FN(MessageBoxA)(GetDesktopWindow(), Buffer, “Hello”, MB_SYSTEMMODAL);

You don’t have to pass HWND, could be null.

MessageBoxA(nullptr, “A”, “B”, MB_OK);

  • disable security check in C++ option
  • disable control flow guard in C++ option
  • override entry point in linker option
  • disable all default libraries in linker option These should make compiled image cleaner and minimum imports / without CRT support

Also sprintf is a CRT import.

See function pointer type in CreateRemoteThread and stop defining entry point as a normal DllMain template style

Yes it should crash because you are referencing 2nd (and 3rd) parameters in your DllMain which did not provided. CreateRemoteThread can only pass one context parameter.

Additionally the example code of PE manual map does not support/resolve imports You have to implement it by yourself (or use lazy importer which does not left any imports)

Wrong context. You are executing entry point on the local process which is not existent, deployment virtual address is only exists on the target remote process Use CreateRemoteThread or any hooks Also this is manual map, you do not have to define entry point as normal DLLs DllMain

this doesn’t have an implementation of calling entry point