Click here to Skip to main content
16,022,205 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
See more:
The hook works fine but the trampoline is not work at all, So the trampoline is designed to return after the Hook Bytes and continue the execution and then filter the results.
C++
#include "pch.h"
// Define the function pointer type for the original function
typedef NTSTATUS(NTAPI* NtQuerySystemInformation_t)(
    SYSTEM_INFORMATION_CLASS SystemInformationClass,
    PVOID SystemInformation,
    ULONG SystemInformationLength,
    PULONG ReturnLength
    );
// Global pointer to the original function
NtQuerySystemInformation_t OriginalNtQuerySystemInformation = nullptr;
// Hooked function
NTSTATUS __stdcall HookedNtQuerySystemInformation(
    SYSTEM_INFORMATION_CLASS SystemInformationClass,
    PVOID SystemInformation,
    ULONG SystemInformationLength,
    PULONG ReturnLength
) {
    // Ensure stack alignment for x64 before any function call
    NTSTATUS status = OriginalNtQuerySystemInformation(SystemInformationClass, SystemInformation, SystemInformationLength, ReturnLength);

    if (SystemInformationClass == SystemProcessInformation && NT_SUCCESS(status)) {
        auto* current = reinterpret_cast<SYSTEM_PROCESS_INFORMATION*>(SystemInformation);
        SYSTEM_PROCESS_INFORMATION* previous = nullptr;
        while (current) {
            // Ensure that ImageName.Buffer is not NULL
            if (current->ImageName.Buffer != NULL) {
                // Check if the process name matches "$root"
                if (wcscmp(current->ImageName.Buffer, L"$root.exe") == 0) {
                    // Remove this process from the list by linking the previous entry to the next one
                    if (previous) {
                        if (current->NextEntryOffset == 0) {
                            previous->NextEntryOffset = 0;
                        }
                        else {
                            previous->NextEntryOffset += current->NextEntryOffset;
                        }
                    }
                    else {
                        if (current->NextEntryOffset == 0) {
                            // This is the only process in the list
                            ZeroMemory(current, sizeof(SYSTEM_PROCESS_INFORMATION));
                        }
                        else {
                            // Shift the list to remove the current process
                            auto nextEntry = reinterpret_cast<SYSTEM_PROCESS_INFORMATION*>(
                                reinterpret_cast<BYTE*>(current) + current->NextEntryOffset
                                );
                            memmove(current, nextEntry, SystemInformationLength - ((BYTE*)nextEntry - (BYTE*)SystemInformation));
                        }
                    }
                }
                else {
                    // Advance to the next process
                    previous = current;
                }
            }
            if (current->NextEntryOffset == 0) break;
            current = reinterpret_cast<SYSTEM_PROCESS_INFORMATION*>(
                reinterpret_cast<BYTE*>(current) + current->NextEntryOffset
                );
        }
    }
    return status;
}
void* HookFunction(void* AddressToHook, void* AddressToHookTo, int HookSize) {
    // Instruction sequence for jumping to an arbitrary address using RAX
    BYTE jmp_rax[12] = {
        0x48, 0xB8,             // MOV RAX, AddressToHookTo
        0x00, 0x00, 0x00, 0x00, // Placeholder for address (AddressToHookTo)
        0x00, 0x00, 0x00, 0x00,
        0xFF, 0xE0              // JMP RAX
    };
    // Allocate memory for the trampoline
    char* tramp = (char*)VirtualAlloc(nullptr, HookSize + sizeof(jmp_rax), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (!tramp) {
        return nullptr;
    }
    // Copy the original instructions to the trampoline
    memcpy(tramp, AddressToHook, HookSize);

    // Calculate the return address for the trampoline
    void* jmp_ret_address = (void*)((char*)AddressToHook + HookSize);
    // Set the jump back to the original function after the copied bytes
    *(void**)&jmp_rax[2] = jmp_ret_address;
    memcpy(tramp + HookSize, jmp_rax, sizeof(jmp_rax));
    // Now set up the hook to jump to the replacement function
    *(void**)&jmp_rax[2] = AddressToHookTo;
    // Change memory protection to allow writing the hook
    DWORD oldProtect = 0;
    if (VirtualProtect(AddressToHook, HookSize, PAGE_EXECUTE_READWRITE, &oldProtect)) {
        // Write the jump to the replacement function at the start of the original function
        memcpy(AddressToHook, jmp_rax, sizeof(jmp_rax));
        // Fill the remaining space with NOPs if necessary
        for (int i = sizeof(jmp_rax); i < HookSize; i++) {
            *((BYTE*)AddressToHook + i) = 0x90; // NOP
        }
        // Restore original protection
        VirtualProtect(AddressToHook, HookSize, oldProtect, &oldProtect);
    }
    return tramp; // Return the trampoline address
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    switch (ul_reason_for_call) {
    case DLL_PROCESS_ATTACH:
        if (DisableThreadLibraryCalls(hModule)) {
            HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
            if (hNtdll) {
                OriginalNtQuerySystemInformation = (NtQuerySystemInformation_t)GetProcAddress(hNtdll, "NtQuerySystemInformation");
                if (OriginalNtQuerySystemInformation) {
                    OriginalNtQuerySystemInformation = (NtQuerySystemInformation_t)HookFunction(OriginalNtQuerySystemInformation, HookedNtQuerySystemInformation, 16);
                }
            }
        }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

I have looked at the instructions from the function and the size is 16 as we don't want to leave a instruction corrupted
NtQuerySystemInfomation ASM x64
00007FF8CF990650 | 4C:8BD1                      | mov r10,rcx                             | rcx:NtQueryInformationThread+14
00007FF8CF990653 | B8 36000000                  | mov eax,36                              | 36:'6'
00007FF8CF990658 | F60425 0803FE7F 01           | test byte ptr ds:[7FFE0308],1           |
00007FF8CF990660 | 75 03                        | jne ntdll.7FF8CF990665                  |
00007FF8CF990662 | 0F05                         | syscall                                 |
00007FF8CF990664 | C3                           | ret                                     |
00007FF8CF990665 | CD 2E                        | int 2E                                  |
00007FF8CF990667 | C3                           | ret                                     |
00007FF8CF990668 | 0F1F8400 00000000            | nop dword ptr ds:[rax+rax],eax          |

And I overwrite the 3 instructions and so the size is 16
and this is the app im using to test the DLL
C++
#include <Windows.h>
#include <winternl.h>
#include <stdio.h>

#pragma comment(lib, "ntdll.lib")

#define _HOOK
void RunCheck() {
    ULONG dwSize = 0;
    NTSTATUS status;
    // First call to get the required buffer size
    status = NtQuerySystemInformation(SystemProcessInformation, NULL, 0, &dwSize);
    if (NT_ERROR(status)) {
        fprintf(stderr, "Failed to get buffer size. Status: 0x%08x\n", status);
    }
    dwSize = dwSize * 2;
    // Allocate the buffer with the required size
    void* buffer = malloc(dwSize);
    if (!buffer) {
        fprintf(stderr, "Failed to allocate memory.\n");
        return;
    }
    // Second call to get the actual process information
    status = NtQuerySystemInformation(SystemProcessInformation, buffer, dwSize, &dwSize);
    if (!NT_SUCCESS(status)) {
        fprintf(stderr, "Failed to query system information. Status: 0x%08x\n", status);
        free(buffer);
        return;
    }
    // Iterate through the process list
    auto* current = reinterpret_cast<SYSTEM_PROCESS_INFORMATION*>(buffer);
    while (current) {
        // Safely print the process image name
        wprintf(L"Image Name: %ws | PID: %u\n", current->ImageName.Buffer ? current->ImageName.Buffer : L"(null)", (ULONG)(ULONG_PTR)current->UniqueProcessId);
        if (current->NextEntryOffset == 0) break;
        current = reinterpret_cast<SYSTEM_PROCESS_INFORMATION*>(reinterpret_cast<BYTE*>(current) + current->NextEntryOffset);
    }
    // Free the allocated buffer
    free(buffer);
}
int main() {
    // Load the DLL, ensure the path is correct
#ifdef _HOOK
    if (!LoadLibraryA("Hook.dll")) {
        fprintf(stderr, "Failed to load the DLL.\n");
        return 1;
    }
#endif // _HOOK
    // Run the check
    RunCheck();
    return 0;
}

Without the Hook this code works fine and the NTSTATUS error i get when the Hook is installed is 0xC000001C (STATUS_ACCESS_VIOLATION)

What I have tried:

I can't think of anything to try as far as i can see the code is correct. I thought it might be the register RAX as its used in the storage of the syscall code in the EAX register. But I'm not sure i will give that a try tho

Now i Know they are lots of hooking libs but i kind of wanted to learn to hook functions without them. As it helped me learn ASM and stuff.
Posted

I don't have an answer for you but what I can state is I wrote a DLL for the purpose of hooking to keyboard and mouse messages so I could implement my own screen saver and my apps could be aware of its operations. This worked great for me and no assembly code was needed. I recommend that you try to do something simple like that to start with and then add additional functionality as desired. I called SetWindowsHookEx, CallNextHookEx, and UnhookWindowsHookEx to do this and I would start there if I were you.
 
Share this answer
 
Comments
CPallini 7-Aug-24 2:04am    
5.
WOLF 2018 9-Aug-24 13:00pm    
I'm trying to do using ASM as its a little low level
// Instruction sequence for jumping to an arbitrary address using R11
BYTE jmp_r11[14] = {
    0x49, 0xBB,             // MOV R11, AddressToHookTo
    0x00, 0x00, 0x00, 0x00, // Placeholder for address (AddressToHookTo)
    0x00, 0x00, 0x00, 0x00,
    0x41, 0xFF, 0xE3        // JMP R11
};
I managed to fix the problem by using the R11 instead of the RAX, This is because of an argument was being over written when EAX is accessed and meant that the syscode was wrong. But with the use of a register less used like R11, R12, R13, R14 and R15, This meant that nothing was over written.
 
Share this answer
 
In principle, I would also share Rick's view that you shouldn't programme something like this yourself. It takes time, may be faulty, not performant or architecture-dependent. A hook code should actually adapt to the architecture, or at least recognise under which architecture the code is running under so that it can react to it. I would suggest MinHook or Detours for this.

With the hook used here, only the minimum part of the original code required for the hook is saved and then overwritten with your own hook code. As the original partial code is not complete, the original function can only be used after the original code has been restored. This is often not intended.

Nevertheless, it would be relatively easy to extend the example code presented so that it can be compiled with both 32-bit and 64-bit. Here is the approach:
C
// Define the size for hook routines
const int HOOK_SIZE_32 = 6;    // 32-bit JMP is 5 bytes plus padding (NOPs)
const int HOOK_SIZE_64 = 14;   // 64-bit MOV + JMP (R11) 13 bytes plus padding (NOPs)

// Hook function for setting the hook
void* HookFunction(void* addressToHook, void* addressToHookTo) 
{
    bool is64 = is64Bit();
    int hookSize = is64 ? HOOK_SIZE_64 : HOOK_SIZE_32;

    char* tramp = (char*)VirtualAlloc(nullptr, hookSize,
                   MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (!tramp)
        return nullptr;

    // Copy the original instructions to tramp
    memcpy(tramp, addressToHook, hookSize);

    // Create hook code
    BYTE* hookCode = new BYTE[hookSize];
    if(!hookCode)
        return nullptr;

    memset(hookCode, 0x90, hookSize); // Fill all with NOPs (padding)
    
    if (is64) {
        // 64-Bit Hook-Code (MOV R11, Hook-Address + JMP R11; 13 Byte) padding to 16 bytes
        hookCode[0] = 0x49;    // REX.W Prefix
        hookCode[1] = 0xBB;    // MOV R11, addressToHookTo
        *(void**)&hookCode[2] = addressToHookTo;
        hookCode[10] = 0x41;   // REX Prefix 32-Bit Register
        hookCode[11] = 0xFF;   // JMP R11
        hookCode[12] = 0xE3;
    }
    else {
        // 32-Bit Hook-Code (JMP relative; 5 Byte) padding to 8 bytes
        hookCode[0] = 0xE9;    // JMP relative
        *(int*)&hookCode[1] = static_cast<int>((char*)addressToHookTo - (char*)addressToHook - 5);
    }
   // ... 
 
Share this answer
 
v3
Comments
WOLF 2018 12-Aug-24 4:20am    
You are correct but I kinda planned to make two DLL for this project.

Hook64.dll and Hook32.dll. then the main program chooses what one to load depending on the process architecture. This kinda makes things easier to maintain. But this is mostly seeing what I can do. As I'm learning I wanted to explore hooking and next I will be learning about IAT hooking. So it all part of a process to become more knowledgeable in ASM and C++ as well as understanding hooking.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900