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

Crash on call DllMain #1

Open
rohybnol opened this issue Dec 21, 2021 · 22 comments
Open

Crash on call DllMain #1

rohybnol opened this issue Dec 21, 2021 · 22 comments

Comments

@rohybnol
Copy link

rohybnol commented Dec 21, 2021

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);
}
@rohybnol rohybnol changed the title Nothing seems to happen Crash on call DllMain Dec 21, 2021
@kkent030315
Copy link
Owner

this doesn't have an implementation of calling entry point

@rohybnol
Copy link
Author

Correct hence I added one as seen above.
Its failing when calling it on deployment_va.Value which should be correct as far I understand your POC.
Thanks very much.

@rohybnol
Copy link
Author

@kkent030315 do you have an idea what could be the reason for the crash after calling DllMain on deployment_va.Value?
Technically it should be fine when call DllMain on deployment_va.Value but maybe this is not possible with your POC?
Thanks again.

@kkent030315
Copy link
Owner

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

@kkent030315
Copy link
Owner

kkent030315 commented Dec 22, 2021

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)

@rohybnol
Copy link
Author

rohybnol commented Dec 22, 2021

Firstly thanks for your suggestions, I adopted it and tried it out now using the following:

#include "lazy_imports.hpp"
#include <stdio.h>

BOOL WINAPI DllMain(
	_In_ HINSTANCE hinstDLL,
	_In_ DWORD     fdwReason,
	_In_ LPVOID    lpvReserved
)
{
	if (fdwReason == DLL_PROCESS_ATTACH)
	{
		char Buffer[512];
		sprintf_s(Buffer, "Calling MessageBoxA from DllMain @ %016llx!\n", &DllMain);
                LI_FN(MessageBoxA)(GetDesktopWindow(), Buffer, "Hello", MB_SYSTEMMODAL);
	}
	return TRUE;
}

Also added CreateRemoteThread to execute the EP as the following:

    CreateRemoteThread(target_process_handle, nullptr, 0, (LPTHREAD_START_ROUTINE)remote_image_base, nullptr, 0, nullptr);

Where CreateRemoteThread has been called on deployment virtual address on the target remote process.
This sadly results in an instant Memory Management BSOD once executed without showing the actual MessageBoxA.

@kkent030315
Copy link
Owner

kkent030315 commented Dec 22, 2021

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.

@kkent030315
Copy link
Owner

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

@kkent030315
Copy link
Owner

Also sprintf is a CRT import.

@kkent030315
Copy link
Owner

kkent030315 commented Dec 22, 2021

  • 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

@kkent030315
Copy link
Owner

kkent030315 commented Dec 22, 2021

LI_FN(MessageBoxA)(GetDesktopWindow(), Buffer, "Hello", MB_SYSTEMMODAL);

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

MessageBoxA(nullptr, "A", "B", MB_OK);

@rohybnol
Copy link
Author

rohybnol commented Dec 22, 2021

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!

@rohybnol
Copy link
Author

rohybnol commented Dec 22, 2021

I have no crashes anymore but the sample MessageBox (removed the sprintf as well to be sure) does still not pop up, the code I used:

#include "lazy_imports.hpp"
#include <stdio.h>

BOOL WINAPI DllMain(
	_In_ HINSTANCE hinstDLL,
	_In_ DWORD     fdwReason,
	_In_ LPVOID    lpvReserved
)
{
	if (fdwReason == DLL_PROCESS_ATTACH)
	{
           LI_FN(MessageBoxA)(nullptr, "Hello", "Hello", MB_SYSTEMMODAL);
	}
	return TRUE;
}

The output is correct too:
grafik

@kkent030315
Copy link
Owner

kkent030315 commented Dec 23, 2021

See function type in CreateRemoteProcess

https://stackoverflow.com/questions/19472837/what-is-a-lpthread-start-routine

@rohybnol
Copy link
Author

rohybnol commented Dec 23, 2021

I see so the above implementation is not correct? How would I go about the correct way? As far I understand LPTHREAD_START_ROUTINE is a function pointer so when I set the dllmain funcptr to the EP it should execute?
Thanks again ✌️

@kkent030315
Copy link
Owner

kkent030315 commented Dec 23, 2021

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");

@kkent030315
Copy link
Owner

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

@rohybnol
Copy link
Author

So I cannot pass a function like I did as it will exceed 1 argument, I see and understand.
Would there be a way to call it with more arguments to it? Or what would be a better way?
I see the sample EP you've posted with just 1 argument, I will give it a try now.
I am very thankful for your help!

@kkent030315
Copy link
Owner

kkent030315 commented Dec 23, 2021

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;
}

@rohybnol
Copy link
Author

Firstly, marry xmas!
Secondly, thanks again for that detailed writeup!
I implemented it as you have suggested, however the sample MessageBox does still not popup, also the allocated RWX memory in the target process seems to be empty for whatever reason.

Implanter:

    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;
	
    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(target_process_handle, nullptr, sizeof(my_entry_point_context_t), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    WPM(target_process_handle, alloc_base, my_entry_context);
	CreateRemoteThread(target_process_handle, NULL, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(implant_ep), alloc_base, 0, 0);

    VirtualFree(mapped_image, 0, MEM_RELEASE);

Payload Dll:

#include "pch.h"
#include "lazy_imports.hpp"
#include <stdio.h>

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

void MyEntryPoint(my_entry_point_context_t* context) 
{
	/* Called by CreateRemoteThread with passing dynamically allocated context */
	context->aaa;
	context->bbb;
	context->ccc;

	LI_FN(MessageBoxA)(nullptr, "HELLO", "Hello", MB_SYSTEMMODAL);
}

@kkent030315
Copy link
Owner

try_read_deployed_image

Debug it by yourself.

@evo15
Copy link

evo15 commented Jun 8, 2024

Im having the same issue, although im using a different driver. Can i contact you on telegram or discord to help me fix that problem. The payload susscedfully gets injected to the target process, just when I call t he dllmain i get issues (crash) Tried debugging without being able to solve it.

Im down to pay you for sure.

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

3 participants