Search code examples
c++visual-studiox86inline-assemblydetours

Recovering Detoured Library Functions


The question is fairly straight forward, what I'm trying to do is restore my process' detoured functions.

When I say detoured I mean the usual jmp instruction to an unknown location.

For example, when the ntdll.dll export NtOpenProcess() is not detoured, the first 5 bytes of the instruction of the function are along the lines of mov eax, *.

(The * offset depending on the OS version.)

When it gets detoured, that mov eax, * turns into a jmp.

What I'm trying to do is restore their bytes to what they were originally before any memory modifications.

My idea was to try and read the information I need from the disk, not from memory, however I do not know how to do that as I'm just a beginner.

Any help or explanation is greatly welcomed, if I did not explain my problem correctly please tell me!


Solution

  • I ended up figuring it out.

    Example on NtOpenProcess. Instead of restoring the bytes I decided to jump over them instead.

    First we have to define the base of ntdll.

    /* locate ntdll */
    #define NTDLL _GetModuleHandleA("ntdll.dll")
    

    Once we've done that, we're good to go. GetOffsetFromRva will calculate the offset of the file based on the address and module header passed to it.

    DWORD GetOffsetFromRva(IMAGE_NT_HEADERS * nth, DWORD RVA)
    {
        PIMAGE_SECTION_HEADER sectionHeader = IMAGE_FIRST_SECTION(nth);
    
        for (unsigned i = 0, sections = nth->FileHeader.NumberOfSections; i < sections; i++, sectionHeader++)
        {
            if (sectionHeader->VirtualAddress <= RVA)
            {
                if ((sectionHeader->VirtualAddress + sectionHeader->Misc.VirtualSize) > RVA)
                {
                    RVA -= sectionHeader->VirtualAddress;
                    RVA += sectionHeader->PointerToRawData;
                    return RVA;
                }
            }
        }
        return 0;
    }
    

    We call this to get us the file offset that we need in order to find the original bytes of the function.

    DWORD GetExportPhysicalAddress(HMODULE hmModule, char* szExportName)
    {
        if (!hmModule)
        {
            return 0;
        }
    
        DWORD dwModuleBaseAddress = (DWORD)hmModule;
    
        IMAGE_DOS_HEADER* pHeaderDOS = (IMAGE_DOS_HEADER *)hmModule;
        if (pHeaderDOS->e_magic != IMAGE_DOS_SIGNATURE)
        {
            return 0;
        }
    
        IMAGE_NT_HEADERS * pHeaderNT = (IMAGE_NT_HEADERS *)(dwModuleBaseAddress + pHeaderDOS->e_lfanew);
        if (pHeaderNT->Signature != IMAGE_NT_SIGNATURE)
        {
            return 0;
        }
    
        /* get the export virtual address through a custom GetProcAddress function. */
        void* pExportRVA = GetProcedureAddress(hmModule, szExportName);
    
        if (pExportRVA)
        {
            /* convert the VA to RVA... */
            DWORD dwExportRVA = (DWORD)pExportRVA - dwModuleBaseAddress;
    
            /* get the file offset and return */
            return GetOffsetFromRva(pHeaderNT, dwExportRVA);
        }
    
        return 0;
    }
    

    Using the function that gets us the file offset, we can now read the original export bytes.

    size_t ReadExportFunctionBytes(HMODULE hmModule, char* szExportName, BYTE* lpBuffer, size_t t_Count)
    {
        /* get the offset */
        DWORD dwFileOffset = GetExportPhysicalAddress(hmModule, szExportName);
        if (!dwFileOffset)
        {
            return 0;
        }
    
        /* get the path of the targetted module */
        char szModuleFilePath[MAX_PATH];
        GetModuleFileNameA(hmModule, szModuleFilePath, MAX_PATH);
        if (strnull(szModuleFilePath))
        {
            return 0;
        }
    
        /* try to open the file off the disk */
        FILE *fModule = fopen(szModuleFilePath, "rb");
        if (!fModule)
        {
            /* we couldn't open the file */
            return 0;
        }
    
        /* go to the offset and read it */
        fseek(fModule, dwFileOffset, SEEK_SET);
        size_t t_Read = 0;
    
        if ((t_Read = fread(lpBuffer, t_Count, 1, fModule)) == 0)
        {
            /* we didn't read anything */
            return 0;
        }
    
        /* close file and return */
        fclose(fModule);
    
        return t_Read;
    }
    

    And we can retrieve the syscall index from the mov instruction originally placed in the first 5 bytes of the export on x86.

    DWORD GetSyscallIndex(char* szFunctionName)
    {
        BYTE buffer[5];
        ReadExportFunctionBytes(NTDLL, szFunctionName, buffer, 5);
        if (!buffer)
        {
            return 0;
        }
    
        return BytesToDword(buffer + 1);
    }
    

    Get the NtOpenProcess address and add 5 to trampoline over it.

    DWORD _ptrNtOpenProcess = (DWORD) GetProcAddress(NTDLL, "NtOpenProcess") + 5;
    DWORD _oNtOpenProcess = GetSyscallIndex("NtOpenProcess");
    

    The recovered/reconstructed NtOpenProcess.

    __declspec(naked) NTSTATUS NTAPI _NtOpenProcess
    (
        _Out_    PHANDLE            ProcessHandle,
        _In_     ACCESS_MASK        DesiredAccess,
        _In_     POBJECT_ATTRIBUTES ObjectAttributes,
        _In_opt_ PCLIENT_ID         ClientId
    ) {
        __asm
        {
            mov eax, [_oNtOpenProcess]
            jmp    dword ptr ds : [_ptrNtOpenProcess]
        }
    }
    

    Let's call it.

    int main()
    {
        printf("NtOpenProcess %x index: %x\n", _ptrNtOpenProcess, _oNtOpenProcess);
    
        uint32_t pId = 0;
        do 
        {
            pId = GetProcessByName("notepad.exe");
            Sleep(200);
    
        } while (pId == 0);
    
        OBJECT_ATTRIBUTES oa;
        CLIENT_ID cid;
        cid.UniqueProcess = (HANDLE)pId;
        cid.UniqueThread = 0;
        InitializeObjectAttributes(&oa, NULL, 0, NULL, NULL);
    
        HANDLE hProcess;
        NTSTATUS ntStat;
    
        ntStat = _NtOpenProcess(&hProcess, PROCESS_ALL_ACCESS, &oa, &cid);
    
        if (!NT_SUCCESS(ntStat))
        {
            printf("Couldn't open the process. NTSTATUS: %d", ntStat);
            return 0;
        }
    
        printf("Successfully opened the process.");
    
        /* clean up. */
        NtClose(hProcess);
    
        getchar();
        return 0;
    }