恢复绕行的库函数

Posted

技术标签:

【中文标题】恢复绕行的库函数【英文标题】:Recovering Detoured Library Functions 【发布时间】:2016-06-20 15:39:46 【问题描述】:

这个问题很简单,我想做的是恢复我的流程的迂回功能。

当我说绕道时,我指的是通常的 jmp 指令到一个未知的位置。

例如ntdll.dll导出NtOpenProcess()不绕行时,函数指令的前5个字节沿mov eax, *行。

(* 偏移量取决于操作系统版本。)

当它绕道而行时,mov eax, * 变成了jmp.

我正在尝试将它们的字节恢复到任何内存修改之前的原始状态。

我的想法是尝试从磁盘而不是内存中读取我需要的信息,但是我不知道如何做到这一点,因为我只是一个初学者。

非常欢迎任何帮助或解释,如果我没有正确解释我的问题,请告诉我!

【问题讨论】:

【参考方案1】:

我终于弄明白了。

关于 NtOpenProcess 的示例。 我决定跳过它们而不是恢复字节。

首先我们要定义ntdll的基础。

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

一旦我们完成了,我们就可以开始了。 GetOffsetFromRva 将根据传递给它的地址和模块头计算文件的偏移量。

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;

我们调用它来获取我们需要的文件偏移量,以便找到函数的原始字节。

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;

使用获取文件偏移量的函数,我们现在可以读取原始导出字节。

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;

我们可以从 mov 指令中检索系统调用索引,该指令最初放置在 x86 上导出的前 5 个字节中。

DWORD GetSyscallIndex(char* szFunctionName)

    BYTE buffer[5];
    ReadExportFunctionBytes(NTDLL, szFunctionName, buffer, 5);
    if (!buffer)
    
        return 0;
    

    return BytesToDword(buffer + 1);

获取 NtOpenProcess 地址并在其上添加 5 到蹦床。

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

恢复/重建的 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]
    

让我们称之为吧。

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;

【讨论】:

函数ReadExportFunctionBytes中存在资源泄漏:需要在倒数第二个return之前调用fclose(fModule)。

以上是关于恢复绕行的库函数的主要内容,如果未能解决你的问题,请参考以下文章

绕行 DrawText

整型转换字符串的库函数 C语言

C语言中的库函数定义在啥地方啊?

Linux Ubuntu下的绕行功能

调用C语言提供的对字符进行处理的库函数时,在#include 命令行应包括的头文件是▁▁▁。

c语言常用库函数都有哪些