美文网首页
作业 : 内存加载的PE文件 & 解析导入表 & IATHook

作业 : 内存加载的PE文件 & 解析导入表 & IATHook

作者: 温柔倾怀 | 来源:发表于2023-06-01 09:41 被阅读0次

    要读取已经执行的进程的 PE 结构,可以使用以下步骤:

    1. 获取目标进程的句柄(使用 OpenProcess 函数)。
    2. 获取目标进程的基址(即模块句柄)(使用 GetModuleHandleExEnumProcessModules 函数)。
    3. 读取目标进程内存中的 DOS 头(使用 ReadProcessMemory 函数)。
    4. 从 DOS 头中获取 NT 头的偏移位置(即 DOS 头中的 e_lfanew 字段)。
    5. 读取目标进程内存中的 NT 头(使用 ReadProcessMemory 函数)。
    6. 从 NT 头中获取可选头的偏移位置。
    7. 读取目标进程内存中的可选头(使用 ReadProcessMemory 函数)。
    8. 根据可选头中的数据目录表中的导入表项,获取导入表的位置。
    9. 读取目标进程内存中的导入表(使用 ReadProcessMemory 函数)。
    void OpenProcessAnalyzeImportTable(DWORD dwProcessId)
    {
        // 13056
        HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, dwProcessId);
        if (hProcess != NULL)
        {
            // 获取目标进程模块句柄
            HMODULE hModules[1];
            DWORD cbNeeded;
            if (!EnumProcessModules(hProcess, hModules, sizeof(hModules), &cbNeeded))
            {
                printf("获取模块句柄失败\n");
                CloseHandle(hProcess);
                return;
            }
            //解析DOS头
            IMAGE_DOS_HEADER DosHeader;
            ReadProcessMemory(hProcess, (LPVOID)hModules[0], &DosHeader, sizeof(IMAGE_DOS_HEADER), NULL);
            printf("Dos e_magic : %08X\n", DosHeader.e_magic);
            printf("Dos e_lfanew : %08X\n", DosHeader.e_lfanew);
    
            // 获取 NT 头的偏移位置
            DWORD ntHeaderOffset = DosHeader.e_lfanew;
            // e_lfanew 字段中的偏移是相对于可执行文件的起始位置的偏移
            // 读取目标进程的 NT 头
            IMAGE_NT_HEADERS ntHeader;
            if (!ReadProcessMemory(hProcess, (LPBYTE)hModules[0] + ntHeaderOffset, &ntHeader, sizeof(ntHeader), NULL))
            {
                printf("读取 NT 头失败\n");
                CloseHandle(hProcess);
                return;
            }
            printf("NT Signature : %08X\n", ntHeader.Signature);
            
            //标准文件头
            IMAGE_FILE_HEADER fileHeader;
            DWORD fileHeaderOffset = DosHeader.e_lfanew + sizeof(DWORD);
            if (!ReadProcessMemory(hProcess, (LPBYTE)hModules[0] + fileHeaderOffset, &fileHeader, sizeof(fileHeader), NULL))
            {
                printf("读取标准文件头失败\n");
                CloseHandle(hProcess);
                return;
            }
            printf("Section number : %08X\n", fileHeader.NumberOfSections);
    
            //扩展文件头
            IMAGE_OPTIONAL_HEADER optionHeader;
            DWORD optionHeaderOffset = DosHeader.e_lfanew + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER);
            if (!ReadProcessMemory(hProcess,(LPBYTE)hModules[0] + optionHeaderOffset, &optionHeader, sizeof(optionHeader), NULL))
            {
                printf("读取扩展头失败\n");
                CloseHandle(hProcess);
                return;
            }
            printf("IMAGE_OPTIONAL_HEADER Magic %08X\n", optionHeader.Magic);
    
            for (size_t i = 0; i < IMAGE_NUMBEROF_DIRECTORY_ENTRIES; i++)
            {
                printf("%s\n", DataDirName[i]);
                printf("\t%08X\n", optionHeader.DataDirectory[i].VirtualAddress);
            }
    
             // 获取导入表的位置
            DWORD importTableRva = optionHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
            // 读取目标进程的导入表
            IMAGE_IMPORT_DESCRIPTOR importTable;
            if (!ReadProcessMemory(hProcess, (LPBYTE)hModules[0] + importTableRva, &importTable, sizeof(IMAGE_IMPORT_DESCRIPTOR), NULL))
            {
                printf("读取导入表失败\n");
                CloseHandle(hProcess);
                return;
            }
            DWORD dwIndex = 1;
            
            while (importTable.OriginalFirstThunk != 0 || importTable.FirstThunk != 0)
            {
                // 读取模块名 !!! 
                // importTable.Name RVA 
                char szModuleName[MAX_PATH];
                LPVOID moduleNameAddress = (LPBYTE)hModules[0] + importTable.Name;
                if (!ReadProcessMemory(hProcess, moduleNameAddress, szModuleName, sizeof(szModuleName), NULL))
                {
                    printf("读取模块名失败\n");
                    CloseHandle(hProcess);
                    return;
                }
                printf("模块名称 : %s\n", szModuleName);
                printf("\t日期时间标志 : %08X\n", importTable.TimeDateStamp);
                printf("\tForwarderChain : %08X\n", importTable.ForwarderChain);
                printf("\t名称Offset : %08X\n", importTable.Name);
                printf("\tFirstThunk : %08X\n", importTable.FirstThunk);
                printf("\tOriginalFirstThunk : %08X\n", importTable.OriginalFirstThunk);
                ReadProcessMemory(hProcess, (LPBYTE)hModules[0] + importTableRva + sizeof(IMAGE_IMPORT_DESCRIPTOR) * dwIndex, &importTable, sizeof(IMAGE_IMPORT_DESCRIPTOR), NULL);
                dwIndex++;
            }
        }
        else
        {
            DWORD dwError = GetLastError();
            printf("进程打开失败,错误代码 %d\n", dwError);
        }
    }
    
    

    OpenProcess 函数

    OpenProcess 是一个 Windows API 函数,用于打开一个已存在的进程并返回其句柄。

    语法

    HANDLE OpenProcess(
      DWORD dwDesiredAccess, // 指定对进程对象的访问权限
      BOOL bInheritHandle, // 指定新句柄是否可被子进程继承
      DWORD dwProcessId // 要打开的进程的标识符。可以使用进程标识符(PID)来指定目标进程
    );
    

    示例

    DWORD dwProcessId = 1234;
    
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
    if (hProcess != NULL) {
      printf("进程句柄获取成功!\n");
    
      // 对进程进行操作...
    
      CloseHandle(hProcess);
    } else {
      DWORD dwError = GetLastError();
      printf("进程句柄获取失败,错误代码:%d\n", dwError);
    }
    

    GetModuleHandleEx 函数

    GetModuleHandleEx 函数用于获取指定进程中模块的句柄(基址)。

    函数原型

    BOOL GetModuleHandleEx(
      DWORD   dwFlags, // 标志位,用于指定搜索模块的方式
      LPCTSTR lpModuleName, // 要获取句柄的模块名称。可以为 NULL,表示获取当前进程中的模块句柄
      HMODULE *phModule // 用于接收获取到的模块句柄的指针
    );
    

    GetModuleHandleEx 函数获取当前进程的模块句柄

    HMODULE hModule = NULL;
    GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, NULL, &hModule)
    

    GetModuleHandleEx 函数获取指定进程的模块句柄

    // 获取指定进程的句柄
    HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
    // 获取模块的基址
    GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,NULL, &hModule)
    

    进程句柄 & 内存基址 & 模块句柄 & 模块基址

    • 进程的句柄与加载到内存的基址是相关的。
    • 进程句柄只是一个用于标识和引用进程的标识符,它本身并不直接提供进程的内存位置信息。
    • 但是,通过使用进程句柄可以获取进程加载到内存中的模块句柄(Module Handle)或模块基址(Module Base Address)
    • 模块句柄或模块基址是指表示进程中加载的某个模块(如动态链接库或可执行文件)在内存中的起始地址。通过获取进程的模块句柄或模块基址,可以进一步定位和操作模块内的数据、函数和资源。
    • 对于一个已加载的模块,模块句柄和模块基址通常是相同的,它们都指向模块在内存中的起始位置。因此,在大多数情况下,可以将模块句柄和模块基址视为相同的概念,用于引用和访问模块。

    EnumProcessModules 函数

    EnumProcessModules 函数是Windows API中的一个函数,用于获取指定进程中加载的所有模块(DLL)的句柄。

    BOOL EnumProcessModules(
      HANDLE  hProcess, // 要枚举模块的进程的句柄
      HMODULE *lphModule, // 指向模块句柄数组的指针,用于接收模块句柄
      DWORD   cb, // 指定模块句柄数组的大小(以字节为单位)
      LPDWORD lpcbNeeded // 指向接收实际返回的模块句柄数的变量的指针
    );
    

    该函数的作用是枚举指定进程中加载的模块,并将每个模块的句柄存储在提供的数组中。通过指定的句柄数组大小,可以控制要获取的模块句柄的数量。

    示例代码 获取指定进程中加载的模块(DLL)的文件名

    tips

    • 获取所有模块,定义接收模块数组 HMODULE hModules[1024];,数组中的第一个元素(hModules[0])对应的是主模块(也就是进程自身的模块)的句柄
    DWORD processId = 1234; // 替换为目标进程的 ID
    // 打开进程获取句柄
    HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
    
    HMODULE hModules[1024];
    DWORD cbNeeded;
    
    // 获取进程中加载的模块句柄
    if (EnumProcessModules(hProcess, hModules, sizeof(hModules), &cbNeeded))
    {
        int moduleCount = cbNeeded / sizeof(HMODULE);
    
        for (int i = 0; i < moduleCount; i++)
        {
            TCHAR szModuleName[MAX_PATH];
            // 获取模块文件名
            if (GetModuleFileNameEx(hProcess, hModules[i], szModuleName, sizeof(szModuleName) / sizeof(TCHAR)))
            {
                std::cout << "Module " << i + 1 << ": " << szModuleName << std::endl;
            }
        }
    }
    else
    {
        std::cout << "Failed to enumerate modules" << std::endl;
    }
    

    ReadProcessMemory 函数

    ReadProcessMemory 是一个 Windows API 函数,用于从一个指定进程的内存中读取数据。

    语法

    BOOL ReadProcessMemory(
      HANDLE  hProcess, // 要读取内存的目标进程的句柄
      LPCVOID lpBaseAddress, // 要读取的内存地址在目标进程中的起始位置
      LPVOID  lpBuffer, // 用于存储读取的数据的缓冲区
      SIZE_T  nSize, // 要读取的字节数
      SIZE_T  *lpNumberOfBytesRead  // 指向存储实际读取字节数的变量的指针
    );
    

    示例

    DWORD dwProcessId = 1234;
    HANDLE hProcess = OpenProcess(PROCESS_VM_READ, FALSE, dwProcessId);
    
    if (hProcess != NULL) {
        LPVOID lpBaseAddress = (LPVOID)0x12345678;
        BYTE buffer[1024];
        SIZE_T nSize = sizeof(buffer);
        SIZE_T nBytesRead = 0;
    
        BOOL bSuccess = ReadProcessMemory(hProcess, lpBaseAddress, buffer, nSize, &nBytesRead);
    
        if (bSuccess) {
            printf("读取成功!读取到的字节数:%d\n", nBytesRead);
    
            // 处理读取到的数据...
        } else {
            DWORD dwError = GetLastError();
            printf("读取失败,错误代码:%d\n", dwError);
        }
    
        CloseHandle(hProcess);
    } else {
        DWORD dwError = GetLastError();
        printf("进程句柄获取失败,错误代码:%d\n", dwError);
    }
    

    相关文章

      网友评论

          本文标题:作业 : 内存加载的PE文件 & 解析导入表 & IATHook

          本文链接:https://www.haomeiwen.com/subject/abheedtx.html