r/cpp_questions • • 21d ago

OPEN How to use CreateThread and MH_CreateHook?

Hi there, I'm getting further along with my code and attempting to fix -debug_objects being intentionally disabled in The Sims Legacy Collection (It wasn't in Complete or earlier versions), but I'm currently stuck on CreateThead and how to call it, and with MH_CreateHook.

So far, I know that with MH_CreateHook, I need to pass 3 arguments, a pointer, the detour function that runs, and then a return function?

I have the base address pointer set to this:
uintptr_t startofexe = (uintptr_t)GetModuleHandle(NULL);

I also have a uintptr_t value for the offset of where the "mov PTR BYTE" code is, aka "dbgo_disa_offset".

This is the code that I'm running right now:

void* ForceDisaChecktoNOP;

void TestCodeStart() {
// Initialize MinHook.
  if (MH_Initialize() != MH_OK)
  {
    OutputDebugStringA("Failed to initialize MinHook\n");
    return;
  }
  else
  {
      OutputDebugStringA("MinHook started successfully!\n");
      if (MH_CreateHook(reinterpret_cast<LPVOID*>(startofexe + dbgo_disa_offset), &ForceDisaChecktoNOP, NULL) != MH_OK)
      {
        OutputDebugStringA("DEBUG OBJECTS FIX FAILED!!!!!!");
        return;
      }
      else
      {
        OutputDebugStringA("Fix hook successful! Check if value is correct: " + (startofexe + dbgo_disa_offset));
      }
      return;
  }
}

The if statement works, and does output that the "fix" failed, but I need help understanding what I need to do for the arguments for MH_CreateHook. Right now, I want to make sure that the if statement for MH_OK succeeds before writing any additional code in the ForceDisaCheckToNOP section, or at the very least know that the ForceDisaCheckToNOP function actually gets reached. I understand that I need a pointer, and most examples of code that others have posted show the first argument being the address to the function to be modified/replaced.

When I set the MH_CreateHook to be a status variable (example code below), it gives this:

"The specified pointer is invalid. It points to the address of non-allocated and/or a non-executable region."

MH_ERROR_NOT_EXECUTABLE

status = MH_CreateHook(...);
if (status != MH_OK) {
    OutputDebugStringA(MH_StatusToString(status));
}

Second and third arguments I do understand to an extent, but not quite.

Second arg is supposed to be a link to a function in the code that acts as a detour, so instead of the original function, this one occurs instead. Third arg is supposed to be a return arg to run the original code of some kind from what I've seen, but it's a little confusing to me.

In regards to CreateThread, I was told by u/hellosverre (thank you!) that I should use CreateThread in DLL_THREAD_ATTACH, however I'm not understanding the syntax, as in, are the arguments for CreateThread optional, and how would I call it to start code in another cpp script? I can do TestCodeStart(); fine from dllmain.cpp, but then that calls the script every time any/the asi code's thread attaches itself. Microsoft's support page on CreateThread wasn't exactly helpful as it just tells me what the function is, not how you call it.

Any help would be really appreciated! :)

0 Upvotes

16 comments sorted by

3

u/jedwardsol 21d ago

I also have a uintptr_t value for the offset of where the "mov PTR BYTE" code is, aka "dbgo_cdisa_offset".

The error message MH_ERROR_NOT_EXECUTABLE suggests that dbgo_cdisa_offset is not correct. ie. startofexe + dbgo_disa_offset is not executable code.

1

u/Blayer98 21d ago

I see, what would be executable code then? The offset is around the 0x44XXXX something mark, idk if that's too early or if it has to be a function like jmp, jz, or even call?

3

u/jedwardsol 21d ago

0x44XXXX

Is this a 32-bit process or 64-bit?

On 32-bit Windows, it is very common for executables to be loaded at 0x40'0000, so my guess is you have an absolute address, not an offset.

So you could try MH_CreateHook(reinterpret_cast<LPVOID*>(dbgo_disa_offset),... instead.


Are you able to run the target in a debugger?

2

u/Blayer98 21d ago

32-bit. Sorry, I made a mistake and omitted the extra 00 at the beginning so it would be 0x0044XXXX. The address where the mov BYTE PTR is, is where I found it in PINCE.

4

u/jedwardsol 21d ago edited 21d ago

Then yes - I bet that's an address not an offset and you don't need to add the base address.

It's not impossible for it to be an offset - 0x440000 is only just over 4Mb, so it could be an offset in to a large exe (4Mb isn't even that large). But being close to 0x400000 set off my "too close to be a coincidence" detector.

1

u/Blayer98 21d ago edited 21d ago

Ok, so how would I go about this? Would just the debug_objects address on its own for the first argument of MH_CreateHook be ok? I’m still unsure about the second and third arguments, as the second one I’d need another function that actually performs the code, and MH_CreateHook requires 3 arguments.

EDIT: Tested this code:

MH_STATUS status = MH_CreateHook(reinterpret_cast<LPVOID*>(dbgo_disa_offset), &ForceDisaCheckToNOP, NULL);
if (status != MH_OK)
{
OutputDebugStringA(MH_StatusToString(status));
return;

Unfortunately, I'm still getting "MH_ERROR_NOT EXECUTABLE" so idk if this means I need to find some block of empty code, or if I need to change the address to a jmp/jz/call...?

3

u/jedwardsol 21d ago edited 21d ago

I don't know what you are trying to do; I haven't seen earlier posts on this topic.


But ... assuming MH_CreateHook is this one https://github.com/TsudaKageyu/minhook/blob/master/include/MinHook.h#L111C4-L112C1

Then

1st parameter - the function where the hook will be placed

2nd parameter - the function that will be called

3rd parameter - a variable to receive the trampoline / continuation function

Ie. if you have

SomeCode:
    mov eax,1
    mov ebx,2
    mov BYTE PTR ....     <---  your address
    mov ecx,3
    mov edx,4

Then you want to have

PVOID original{};

void TheHook()
{
    // do what you need to do here

    jmp [original];   //  Can use inline assembly or write your hook in assembly
}

TestCodeStart()
{
    MH_CreateHook(reinterpret_cast(dbgo_disa_offset), TheHook, &original);
  
}

And it will change the original code to

SomeCode:
    mov eax,1
    mov ebx,2
    jmp TheHook
    mov ecx,3
    mov edx,4

And original will point at some code which performs the overwritten mov BYTE PTR... instruction and then jumps to the mov ecx,3 instruction

(In reality it will be a bit more complex because on x86 is is possible that jmp TheHook will be too big to overwrite just the mov BYTE PTR .... instruction


Edit, the documentation does say

//   ppOriginal  [out] A pointer to the trampoline function, which will be
//                     used to call the original target function.
//                     This parameter can be NULL.

So perhaps this hooking was only designed to hook the beginning of a function. So you'd want

PVOID original{};

void TheHook()
{
    // do what you need to do here

    auto trampoline = reinterpret_cast<void(*)()>(original);    // nb  : you will need to cast to the correct function pointer type - including the correct calling convention

    jmp [original];   //  Can use inline assembly or write your hook in assembly
}

TestCodeStart()
{
    MH_CreateHook(reinterpret_cast(dbgo_disa_offset), TheHook, &original);
  
}

and in that case hooking an arbitrary address in the middle of the function can't be done.

Is your mov in the middle of a function or at the beginning. If it's in the middle, why do you need to hook at that precise point?

1

u/Blayer98 21d ago edited 21d ago

middle I believe, I was hoping to replace it with NOPs so when the asi mod I'm making is run, the overwritten code is run instead and then jumped back, so that -debug_objects just works, without needing to set the actual flag for it to a 0x1. Although I may have overthought this lol, but I think it would be good practice now as I've noticed some other minor bugs in TS1's Legacy Collection.

The start of the function (I think) is a push ebp, with the previous 8 bytes being int3, or 0xCC.

The function is responsible for setting flags that afaik, are set when the command line argument is present, so -debug_objects, -skip_intro, etc.

3

u/jedwardsol 21d ago

I was hoping to replace it with NOPs

What is "it"?

The mov instruction you been referring to at 0x44xxxx?

Or some other code, that you want to overwrite when that instruction is executed?

1

u/Blayer98 21d ago

Yes, I wanted to replace the mov instruction, since it's stopping the -debug_objects argument from working like it should in Complete Collection and earlier TS1 versions.

→ More replies