查看原文
其他

PE头分析详解和VC++代码实现

菜鸟m号 看雪学院 2021-03-07
本文为看雪论坛优秀文章

看雪论坛作者ID:菜鸟m号



软件安全这课快到期末了,我把之前写的山寨一个PE解析器代码整理复习一下,奈何菜鸟我不太会MFC编程,所以下面的程序主要是在命令行完成。
代码最后全部打包压缩上传。

PE结构百度图一堆一堆。

首先打开文件函数,设置完参数可以让它选择DLL或者EXE文件:

//取得文件路径部分
    TCHAR szFilePath[MAX_PATH];
    OPENFILENAME ofn = { 0 };
    memset(szFilePath, 0, MAX_PATH);
    memset(&ofn, 0, sizeof(ofn));
    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = NULL;
    ofn.hInstance = GetModuleHandle(NULL);
    ofn.nMaxFile = MAX_PATH;
    ofn.lpstrInitialDir = L".";
    ofn.lpstrFile = szFilePath;
    ofn.lpstrTitle = L"选择PE文件";
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
    ofn.lpstrFilter = L"(*.*)\0*.exe;*.dll\0";
    GetOpenFileName(&ofn);


运行之后可以选择文件并得到文件绝对路径地址如图:

     



然后用内存映射文件打开文件(这里不能用openfile函数),然后得到映射基地址,那打开文件之后获得的地址是文件地址:

if (szFilePath == NULL)
    {
        MessageBox(NULL, L"打开文件错误", NULL, NULL);
        return 0;
    }
    //创建文件句柄
 
    HANDLE hFile = CreateFile(szFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (INVALID_HANDLE_VALUE == hFile)
    {
        MessageBox(NULL, L"创建PE内核失败", NULL, NULL);
        return 0;
    }
    //创建文件映射内核对象
    HANDLE hMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
    if (NULL == hMapping)
    {
        CloseHandle(hFile);
        hFile = NULL;
        return 0;
    }
    //获得映射基地址
    LPVOID ImageBase = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
    if (NULL == ImageBase)
    {
        CloseHandle(hMapping);
        CloseHandle(hFile);
 
        hMapping = NULL;
        hFile = NULL;
        return FALSE;
    }


之后根据对应的指针对各个头赋值:

//下面是DOS头分析/////////////////////////////////////////////////
    PIMAGE_DOS_HEADER pDosHeader = NULL;
    pDosHeader = (PIMAGE_DOS_HEADER)ImageBase;
    if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
    {
        MessageBox(NULL, TEXT("PE文件错误"), NULL, MB_OK);
    }
 
    //下面是获得NT头/////////////////////////////////////////////////////
    PIMAGE_NT_HEADERS pNtHeader = NULL;
    pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)(pDosHeader->e_lfanew) + (DWORD)ImageBase);
    if (pNtHeader->Signature != IMAGE_NT_SIGNATURE)
    {
        MessageBox(NULL, TEXT("PE文件错误"), NULL, MB_OK);
    }
    PIMAGE_FILE_HEADER pFileHeader = NULL;
    pFileHeader = &pNtHeader->FileHeader;
 
    PIMAGE_OPTIONAL_HEADER pOptionHeader = NULL;
    pOptionHeader = &pNtHeader->OptionalHeader;
 
    PIMAGE_SECTION_HEADER pSectionHeader = NULL;
    pSectionHeader = IMAGE_FIRST_SECTION(pNtHeader);


得到各个头文件针位置之后就可以打印出各个参数的值了:

/***************** 显示需要的数据部分 *******************/
    cout << "所有需要的关键信息如下:" << endl;
    cout << "_IMAGE_DOS_HEADER RVA地址:" << hex << pDosHeader << endl;
    cout << "e_magic值:" << hex << pDosHeader->e_magic << endl;
    cout << "e_lfarlc值:" << hex << pDosHeader->e_lfarlc << endl << endl;
 
    cout << "_IMAGE_NT_HEADERS RVA地址:" << hex << pNtHeader << endl;
    cout << "Signature 值:" << hex << pNtHeader->Signature << endl << endl;
 
    cout << "_IMAGE_FILE_HEADERS RVA地址:" << hex << pFileHeader << endl;
    cout << "Machine 值:" << hex << pFileHeader->Machine << endl;
    cout << "NumberOfSections 值:" << hex << pFileHeader->NumberOfSections << endl;
    cout << "Characteristics 值:" << hex << pFileHeader->Characteristics << endl << endl;
 
    cout << "_IMAGE_OPTION_HEADERS RVA地址:" << hex << pOptionHeader << endl;
    cout << "Magic 值:" << hex << pOptionHeader->Magic << endl;
    cout << "SizeOfCode 值:" << hex << pOptionHeader->SizeOfCode << endl;
    cout << "AddressOfEntryPoint 值" << hex << pOptionHeader->AddressOfEntryPoint << endl;
    cout << "ImageBase 值:" << hex << pOptionHeader->ImageBase << endl;
    cout << "SectionAlignment 值" << hex << pOptionHeader->SectionAlignment << endl;
    cout << "FileAlignment 值" << hex << pOptionHeader->FileAlignment << endl;
    cout << "SizeOfImage 值:" << hex << pOptionHeader->SizeOfImage << endl << endl;
 
    cout << "_IMAGE_SECTION_HEADERS RVA地址:" << pSectionHeader << endl;
    for (int i = 0; i < pFileHeader->NumberOfSections; i++) //循环打印各个区段的值
    {
        cout << " Section Name:" << pSectionHeader->Name << endl;
        cout << " VirtualAddress:" << hex << pSectionHeader->VirtualAddress << endl;
        cout << " SizeOfRawData:" << hex << pSectionHeader->SizeOfRawData << endl;
        cout << " PointerToRelocations:" << hex << pSectionHeader->PointerToRelocations << endl;
        cout << " NumberOfLinenumbers:" << hex << pSectionHeader->NumberOfLinenumbers << endl;
        cout << " Characteristics:" << hex << pSectionHeader->Characteristics << endl;
 
        ++pSectionHeader;
        cout << endl;
    }


打印输入表和输出表,这里一定不要忘记这个头文件:

#include <imagehlp.h>
#pragma comment ( lib, "imagehlp.lib" ) //这里是关键,我是谷歌找到的,不然编译不了imagehlp.h
#include<iostream>
#include<CommCtrl.h>


打印导入表,我用的是IAT表,这个比较保险,就算函数没有名字也有VA地址:

int showIAT(PIMAGE_IMPORT_DESCRIPTOR pImport, LPVOID ImageBase, PIMAGE_NT_HEADERS pNtHeader, PIMAGE_OPTIONAL_HEADER pOptionHeader)
{
    char* szFuncName;
    DWORD * pdwThunkRVA = NULL;
    PIMAGE_IMPORT_BY_NAME pByName = NULL;
    pdwThunkRVA = (DWORD*)ImageRvaToVa(pNtHeader, ImageBase, pImport->FirstThunk, NULL); //取出o指向的IAT地址,并转换成DWORD指针
     
    if (!pdwThunkRVA)
    {
        return 0;
    }
    while (*pdwThunkRVA)
    {
        if (HIWORD(*pdwThunkRVA) == 0x8000)
        {
            cout << "函数序号:" << hex << *pdwThunkRVA << endl;
        }
        else 
        {
        pByName = (PIMAGE_IMPORT_BY_NAME)ImageRvaToVa(pNtHeader, ImageBase, (DWORD)(*pdwThunkRVA), NULL);
        if (pByName)
        cout << "funName:" << (char*)pByName->Name << endl;
        }
        ++pdwThunkRVA;
    }
    return 0;
}
 
 
void showIIDandIED( LPVOID ImageBase, PIMAGE_NT_HEADERS pNtHeader, PIMAGE_OPTIONAL_HEADER pOptionHeader)
{
    PIMAGE_IMPORT_DESCRIPTOR pImport = NULL;
    DWORD IIDRVA;
    IIDRVA = pOptionHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress; //记住是数组的第二项开始的,保存的是RVA值
    pImport = (PIMAGE_IMPORT_DESCRIPTOR)ImageRvaToVa(pNtHeader, ImageBase, IIDRVA,NULL);//调用 imagehlp中的ImageRvaToVA
    string name = "";
     
 
    if (!pImport)
    {
        int error = GetLastError();
        if (error == 0)
            MessageBox(NULL, TEXT("文件没有输入表"), NULL, MB_OK);
        else
            MessageBox(NULL, TEXT("无法获得Import Directory"), NULL, MB_OK);
        return;
    }
    while (pImport->FirstThunk)
    {
        cout << " DLLName: " << (char*)ImageRvaToVa(pNtHeader, ImageBase, pImport->Name, NULL) << endl; //四个参数
        cout << " Characteristics: " << hex << pImport->Characteristics << endl;
        cout << " ForwarderChain: " << hex << pImport->ForwarderChain << endl;
        cout << " TimeDateStamp: " << hex << pImport->TimeDateStamp << endl;
        cout << " OriginalFirstThunk(INT): " << hex << pImport->OriginalFirstThunk << endl;
 
        cout << " FirstThunk(IAT):" << hex << pImport->FirstThunk << endl;
        showIAT(pImport, ImageBase, pNtHeader, pOptionHeader);
        cout << endl;
        ++pImport;
    }

输出表部分IED:

////////////////////////////////////////IED/////////////////////////////////////////////////////
    cout << "//////////////////////////////文件导出表部分/////////////////////////////////////////" << endl;
    PIMAGE_EXPORT_DIRECTORY pExportDir = NULL; //指向IED指针
    pExportDir = (PIMAGE_EXPORT_DIRECTORY)ImageRvaToVa(pNtHeader, ImageBase, pOptionHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress,NULL);
    if (!pExportDir)
    {
        int error = GetLastError();
        if (error == 0)
            MessageBox(NULL, TEXT("文件没有输出表"), NULL, MB_OK);
        else
            MessageBox(NULL, TEXT("无法获得Export Directory"), NULL, MB_OK);
        return;
    }
    cout << " AddressOfFunctions: " << hex << pExportDir->AddressOfFunctions << endl;
    cout << " AddressOfNameOrdinals: " << hex << pExportDir->AddressOfNameOrdinals << endl;
    cout << " AddressOfNames: " << hex << pExportDir->AddressOfNames << endl;
    cout << " Base: " << hex << pExportDir->Base << endl;
    cout << " MajorVersion: " << hex << pExportDir->MajorVersion << endl;
    cout << " MinorVersion: " << hex << pExportDir->MinorVersion << endl;
    cout << " Characteristics: " << hex << pExportDir->Characteristics << endl;
    cout << " Name: " << hex << pExportDir->Name << endl;
    cout << " NumberOfFunctions: " << hex << pExportDir->NumberOfFunctions << endl;
    cout << " NumberOfNames: " << hex << pExportDir->NumberOfNames << endl;
    cout << " TimeDateStamp: " << hex << pExportDir->TimeDateStamp << endl;
 
 
    //cout << " pExportDir->Name: " << (char*)ImageRvaToVa(pNTH, LocalImageBase, pExportDir->Name, NULL) << endl;
 
    char* szFuncName;
 
    UINT NumOfName = 0;
    PDWORD pEAT, pENT;
    PWORD pEOT;
    UINT i = 0, j = 0, k = 0;
    pEOT = (PWORD)ImageRvaToVa(pNtHeader, ImageBase, pExportDir->AddressOfNameOrdinals,NULL);
    pEAT = (PDWORD)ImageRvaToVa(pNtHeader, ImageBase, pExportDir->AddressOfFunctions,NULL);
    pENT = (PDWORD)ImageRvaToVa(pNtHeader, ImageBase, pExportDir->AddressOfNames,NULL);
    NumOfName = pExportDir->NumberOfNames; //函数名字数量
    if (!pEAT)
        return;
    for (i = 0; i < pExportDir->NumberOfFunctions; i++)
    {
        if (*pEAT)
        {
            for (j = 0; j < NumOfName; j++)
            {
                if (i == pEOT[j])
                {
                    szFuncName = (char*)ImageRvaToVa(pNtHeader, ImageBase, pENT[j],NULL);
                    cout <<"FunctioRVA:"<< pEAT[i] <<'\t'<<"FunctionName:"<< szFuncName<<endl;
                }
            }
        }
        ++pEAT;
    }
}


之后老师要求写一个程序,可以找出加载一个程序起来之后调用的DLL  VA地址,代码也放进来,(备注应该比较详细)。里面写了一些函数处理,比如之前读取文件之后选择的是绝对路径,要把那些前缀什么的去掉。

然后就是Tchar 转 STRING函数,char转tchar函数(当时搞这些编码转换头都疼)。这个程序的原理和一种反调试技术的实现差不多,就是遍历进程空间,找到对应的模块名,然后关掉它。代码中我也用了。

#include <iostream>
#include <iomanip>
#include <string>
#include <tchar.h>
#include <windows.h>
#include <TlHelp32.h>
#include<tlhelp32.h>
#include<wchar.h>
#include <cstring>
#include <imagehlp.h>
#pragma comment ( lib, "imagehlp.lib" ) //这里是关键,我是谷歌找到的,不然编译不了imagehlp.h
#include<CommCtrl.h>
#pragma comment (lib, "imagehlp.lib")
#pragma comment (lib, "advapi32.lib")
using namespace std;
string TCHAR2STRING(TCHAR* STR)  //tchar转string
{
    int iLen = WideCharToMultiByte(CP_ACP, 0, STR, -1, NULL, 0, NULL, NULL);
    char* chRtn = new char[iLen * sizeof(char)];
    WideCharToMultiByte(CP_ACP, 0, STR, -1, chRtn, iLen, NULL, NULL);
    std::string str(chRtn);
    return str;
}
HMODULE GetProcessModuleHandle(DWORD pid, CONST TCHAR* moduleName) { // 根据 PID 、模块名(需要写后缀,如:".dll"),获取模块入口地址。
    MODULEENTRY32 moduleEntry;
    HANDLE handle = NULL;
    handle = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid); // 获取进程快照中包含在th32ProcessID中指定的进程的所有的模块。
    if (!handle) {
        CloseHandle(handle);
        return NULL;
    }
    ZeroMemory(&moduleEntry, sizeof(MODULEENTRY32));
    moduleEntry.dwSize = sizeof(MODULEENTRY32);
    if (!Module32First(handle, &moduleEntry)) {
        CloseHandle(handle);
        return NULL;
    }
    do {
        if (_tcscmp(moduleEntry.szModule, moduleName) == 0) { return moduleEntry.hModule; }
    } while (Module32Next(handle, &moduleEntry));
    CloseHandle(handle);
    return 0;
}
string NameOfexe(string name)  //自定义一个取出文件地址绝对路径剔除多余参数函数
{
    string exeName;
    int k=0;
    for (int i = 0; i< name.length(); i++)
    {
        if (name[i] == '\\'){
            k = i;
            for (int j = i; j < name.length() - i; j++)
            {
                if (name[j] == '\\')
                    break;
        }
        }
    }
    int k2 = 0;
    int i = k+1;
    for(i; i < name.length(); i++)
    {
        exeName+= name[i];
        k2++;
    }
    return exeName;
}
TCHAR* char2TCAHR(const char* str)   //char*转tchar*
 
{
 
    int size = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);
 
    TCHAR* retStr = new TCHAR[size * sizeof(TCHAR)];
 
    MultiByteToWideChar(CP_ACP, 0, str, -1, retStr, size);
 
    return retStr;
 
}
void showAllImageBase(TCHAR *name, LPVOID ImageBase, PIMAGE_NT_HEADERS pNtHeader, PIMAGE_OPTIONAL_HEADER pOptionHeader){
    HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); // 进程快照句柄
    PROCESSENTRY32 process = { sizeof(PROCESSENTRY32) }; // 存放进程快照的结构体
 
    /*下面这部分是取出导入表DLL名字,保存起来用来,和之前实验的一样。*/
    PIMAGE_IMPORT_DESCRIPTOR pImport = NULL;
    DWORD IIDRVA;
    IIDRVA = pOptionHeader->DataDirectory[1].VirtualAddress; //记住是数组的第二项开始的,保存的是RVA值
    pImport = (PIMAGE_IMPORT_DESCRIPTOR)ImageRvaToVa(pNtHeader, ImageBase, IIDRVA, NULL);//调用 imagehlp中的ImageRvaToVA
 
    // 遍历进程
    char* a;
    int k = 0;
    while (Process32Next(hProcessSnap, &process)) {
        // .exe 进程
        string s_szExeFile = TCHAR2STRING(process.szExeFile); // char* 转 string
        if (s_szExeFile == NameOfexe(TCHAR2STRING(name)))
        {
            cout << NameOfexe(TCHAR2STRING(name)) <<'\t'<<"基地址:"<< ImageBase << endl;
            HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, process.th32ProcessID); // 进程句柄
            while (pImport->FirstThunk)
            {
                a= (char*)ImageRvaToVa(pNtHeader, ImageBase, pImport->Name,NULL) ;
                cout << a;
                cout <<'\t'<< "模块VA地址:" << GetProcessModuleHandle(process.th32ProcessID, char2TCAHR(a)) << endl;
                ++pImport;
            }
            k = 1;
        }
    }
    if (k == 0) //程序没有运行
    {
        STARTUPINFO si; //一些必备参数设置
        memset(&si, 0, sizeof(STARTUPINFO));
        si.cb = sizeof(STARTUPINFO);
        si.dwFlags = STARTF_USESHOWWINDOW;
        si.wShowWindow = SW_SHOW;
        PROCESS_INFORMATION pi; //必备参数设置结束
        if (!CreateProcess(NULL, name, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) //
        {
            cout << "Create Fail!" << endl;
        }
        Sleep(1000);
        HANDLE hProcessSnap1 = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); // 进程快照句柄
        PROCESSENTRY32 process1 = { sizeof(PROCESSENTRY32) }; // 存放进程快照的结构体
        char* a;
         
        while (Process32Next(hProcessSnap1, &process1)) {
            // .exe 进程
            string s_szExeFile = TCHAR2STRING(process1.szExeFile); // char* 转 string
            if (s_szExeFile == NameOfexe(TCHAR2STRING(name)))
            {
                cout << NameOfexe(TCHAR2STRING(name)) << ImageBase << endl;
                HANDLE hProcess1 = OpenProcess(PROCESS_ALL_ACCESS, FALSE, process1.th32ProcessID); // 进程句柄
                while (pImport->FirstThunk)
                {
                    a = (char*)ImageRvaToVa(pNtHeader, ImageBase, pImport->Name, NULL);
                    cout << a;
                    cout <<'\t'<< "模块VA地址:" << GetProcessModuleHandle(process1.th32ProcessID, char2TCAHR(a)) << endl;
                    ++pImport;
                }
                k = 1;
            }
        }
    }
 
}

为了验证自己的代码正不正确,我找了一个内存映射文件读取程序,然后再代码中调用它:(这有一个弊端,就是程序一定要运行起来。

int FindPID(string name)
{
    PROCESSENTRY32 pe32;
    pe32.dwSize = sizeof(pe32);
    HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (hProcessSnap == INVALID_HANDLE_VALUE) {
        cout << "CreateToolhelp32Snapshot Error!" << endl;;
        return false;
    }
    BOOL bResult = Process32First(hProcessSnap, &pe32);
    int num(0);
    while (bResult)
    {
        if (TCHAR2STRING(pe32.szExeFile) == NameOfexe(name))
        {
            return pe32.th32ProcessID;
        }
        bResult = Process32Next(hProcessSnap, &pe32);
    }
    CloseHandle(hProcessSnap);
    return -1;
}
/*int string2tchar(std::string& src, TCHAR* buf)
{
#ifdef UNICODE
    _stprintf_s(buf, MAX_PATH, _T("%S"), src.c_str());//%S宽字符
#else
    _stprintf_s(buf, MAX_PATH, _T("%s"), src.c_str());//%s单字符
#endif
    return 0;
}*/

void showAllImageBase2(TCHAR* name, LPVOID ImageBase, PIMAGE_NT_HEADERS pNtHeader, PIMAGE_OPTIONAL_HEADER pOptionHeader)
{
    //std::cout << FindPID(TCHAR2STRING(name));
    STARTUPINFO si; //一些必备参数设置
    memset(&si, 0, sizeof(STARTUPINFO));
    si.cb = sizeof(STARTUPINFO);
    si.dwFlags = STARTF_USESHOWWINDOW;
    si.wShowWindow = SW_SHOW;
    PROCESS_INFORMATION pi; //必备参数设置结束
    string vmmapName = "S:\\14-VMMap.exe";
    vmmapName += " ";
    vmmapName += to_string(FindPID(TCHAR2STRING(name)));
    //cout << vmmapName << endl;
    TCHAR wc[MAX_PATH];
#ifdef UNICODE
    _stprintf_s(wc, MAX_PATH, _T("%S"), vmmapName.c_str());//%S宽字符
#else
    _stprintf_s(wc, MAX_PATH, _T("%s"), vmmapName.c_str());//%s单字符
#endif
 
     
    if (!CreateProcess(NULL,wc , NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) //
    {
        cout << "Create Fail!" << endl;
    }
}


程序是VMmap.exe,把它放到D盘下,压缩包里面有。

运行示意图:

选择刚刚的exe,一直否下去(因为我把各个模块放在不同头文件里面,比较乱不好意思),就可以得到头文件的相关信息:






然后命令行得到相关信息:




选择调试程序就是得到导入表的DLL的VA地址:



实验2就是那个导入表和输出表信息:一般DLL都没有输出表。



代码打包上传了,三个头文件和一个cpp,代码顺序比较乱还在学习软件保护。




- End -






看雪ID:菜鸟m号

https://bbs.pediy.com/user-856851.htm 


*本文由看雪论坛  菜鸟m号  原创,转载请注明来自看雪社区




推荐文章++++

GandCrab v5.2 分析

恶意代码分析中一些常见的非PE样本分析

定制Xposed框架

通过一道pwn题详细分析retdlresolve技术

打造属于自己的渗透神器——WiFi-ducky










进阶安全圈,不得不读的一本书






公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



“阅读原文”一起来充电吧!

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存