• 十分钟教你轻松掌握「移动PE导出表」,快来学习!


    今天的文章分享是 i 春秋论坛作者flag0原创的文章,浅析移动PE导出表的相关内容,文章未经许可禁止转载!

    十分钟教你轻松掌握「移动PE导出表」,快来学习!

     

    注:i 春秋公众号旨在为大家提供更多的学习方法与技能技巧,文章仅供学习参考。

    十分钟教你轻松掌握「移动PE导出表」,快来学习!

     

    导出表概述

    导出表一般多见于DLL和SYS文件,EXE在少部分情况下拥有导出表,导出表中存储了当前DLL可供调用的函数的地址、函数名称,导出表的地址存储在数据目录项的第一个结构中。

    IMAGE_SECTION_HEADER[0]

    typedef struct _IMAGE_DATA_DIRECTORY {DWORD   VirtualAddress;//导出表开始的地址 RVADWORD   Size;//导出表大小}IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

    存储了导出表的地址(RVA)及其大小

    导出表结构:

    typedef struct _IMAGE_EXPORT_DIRECTORY {DWORD   Characteristics;    // 未使用DWORD   TimeDateStamp;                // 时间戳WORD    MajorVersion;                // 未使用WORD    MinorVersion;                // 未使用DWORD   Name;                                // 指向该导出表文件名字符串DWORD   Base;                                // 导出函数起始序号DWORD   NumberOfFunctions;        // 所有导出函数的个数DWORD   NumberOfNames;                // 以函数名字导出的函数个数DWORD   AddressOfFunctions;     // 导出函数地址表RVADWORD   AddressOfNames;         // 导出函数名称表RVADWORD   AddressOfNameOrdinals;  // 导出函数序号表RVA} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

    导出表中最重要的就是三张表,AddressOfFunctions、AddressOfNames、AddressOfNameOrdinals,分别存储了函数地址,函数名称,函数序号,这里有两种函数查找方式,分别是按照函数名称查找,还有按照序号查找。

    我们在移动的时候,要注意RVA和FOA的转换,RVA是在内存中拉伸后的偏移,需要我们转换成文件中的偏移FOA才可以在文件中进行复制和修改。

    导出函数名称表的移动是一个难点,导出函数名称表中存储了导出函数名称的偏移地址RVA,需要根据地址再进行访问函数名称。

    移动导出表的步骤:

    1、在PE文件中新增一个节,并且新增对应的节表;

    2、复制:AddressOfFunction(size:NumberOfFunctions * 4) 到新增的节;

    3、复制:AddressOfNameOrdinals(size:NumberOfNames * 2) 到新增的节中;

    4、复制:AddressOfNames(szie:NumberOfNames * 4) 到新增的节中;

    5、复制:AddressOfNames表中对应的所有地址,及其函数名,移动字符串时也需要注意大小结尾(每复制一个函数名就要计算偏移RVA添加到函数名称表的地址中);

    6、复制:导出表结构体到新增的节中;

    7、修改导出表结构中的对应:AddressOfFunctions、AddressOfNameOrdinals、AddressOfNames

    地址,其他的参数不影响运行;

    8、修复目录项中的VirtualAddress,指向新的导出表的地址。

    这里要梳理清楚在移动过程中的各个参数,在什么时候转换为FOA,什么时候转换为RVA。

    定位导出表的地址时,需要将IMAGE_SECTION_HEADER[0].VirtualAddress的RVA->FOA,以便于在文件中定位导出表的地址。

    在移动导出表的AddressOfFunction、AddressOfNameOrdinals、AddressOfNames时,需要将RVA->FOA以便于在文件中复制

    在移动AddressOfNames中的地址指向的函数名字符串时,要将AddressOfNames中的地址RVA->FOA,并且在移动完后,修改AddressOfNames中存储的地址时,要FOA->RVA。

    将导出表中的AddressOfFunctions、AddressOfNameOrdinals、AddressOfNames 地址修改为新复制的对应的地址时,要将新复制的对应的地址FOA->RVA。

    修复数据目录项中的VirualAddress时,要将新复制的导出表的存储地址FOA->RVA

    移动导出表

    DWORD RVATOFOA(DWORD RVA,LPVOID pFileBuffer)
    {
            DWORD FOA = NULL;
            PIMAGE_DOS_HEADER pDosHeader = NULL;
            PIMAGE_NT_HEADERS pNtHeaders = NULL;
            PIMAGE_FILE_HEADER pFileHeader = NULL;
            PIMAGE_OPTIONAL_HEADER pOptionalHeader = NULL;
            PIMAGE_SECTION_HEADER pSectionHeader = NULL;

            pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
            pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
            pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pNtHeaders + 4);
            pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);
            pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);

            if(RVA <= pOptionalHeader->SizeOfHeaders)
                    return RVA;
            for(;RVA > (pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize);pSectionHeader++);//定位到所在节
            FOA = RVA - pSectionHeader->VirtualAddress + pSectionHeader->PointerToRawData;
            return FOA;

    }

    DWORD FOATORVA(DWORD FOA,LPVOID pFileBuffer)
    {
            DWORD RVA = NULL;

            PIMAGE_DOS_HEADER pDosHeader = NULL;
            PIMAGE_NT_HEADERS pNtHeaders = NULL;
            PIMAGE_FILE_HEADER pFileHeader = NULL;
            PIMAGE_OPTIONAL_HEADER pOptionalHeader = NULL;
            PIMAGE_SECTION_HEADER pSectionHeader = NULL;

            pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
            pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
            pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pNtHeaders + 4);
            pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);
            pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);

            if(FOA <= pOptionalHeader->SizeOfHeaders)
                    return FOA;
            for(;FOA > (pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize);pSectionHeader++);//定位到所在节
            RVA = FOA - pSectionHeader->PointerToRawData + pSectionHeader->VirtualAddress ;
            return RVA;
    }

    BOOL MoveExport(LPVOID pFileBuffer)
    {
            PIMAGE_DOS_HEADER pDosHeader = NULL;//DOS头
            PIMAGE_NT_HEADERS pNtHeaders = NULL;//NT头
            PIMAGE_FILE_HEADER pFileHeader = NULL;//标准PE头
            PIMAGE_OPTIONAL_HEADER pOptionalHeader = NULL;//拓展PE头
            PIMAGE_SECTION_HEADER pSectionHeader = NULL;//节表
            PIMAGE_SECTION_HEADER pNewSec = NULL;//新节表结构
            PIMAGE_EXPORT_DIRECTORY pExportDirectory = NULL; //导出表结构体

            pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
            pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
            pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pNtHeaders + 4);
            pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);
            pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);

            //判断是否有足够的空间添加节表
            if ((pOptionalHeader->SizeOfHeaders - ((DWORD)pSectionHeader - (DWORD)pFileBuffer + pFileHeader->NumberOfSections * 40)) < 80)
            {
                    printf("空间不足");
                    return false;
            }

            //新增节表结构
            pNewSec = (PIMAGE_SECTION_HEADER)(pSectionHeader + pFileHeader->NumberOfSections);

            //修改节表内容
            memcpy(pNewSec->Name,".export",8);//修改节表名

            PIMAGE_SECTION_HEADER upSecHeader = (PIMAGE_SECTION_HEADER)(pSectionHeader + pFileHeader->NumberOfSections-1);

            if(upSecHeader->Misc.VirtualSize > upSecHeader->SizeOfRawData)//修改节表VrituallAddress
            {
                    pNewSec->VirtualAddress = upSecHeader->VirtualAddress + upSecHeader->Misc.VirtualSize;
            }else{
                    pNewSec->VirtualAddress = upSecHeader->VirtualAddress + upSecHeader->SizeOfRawData;
            }

            pNewSec->SizeOfRawData = 0x1000;//新增的节区的大小
            pNewSec->PointerToRawData = upSecHeader->PointerToRawData + upSecHeader->SizeOfRawData;//文件中的偏移
            pNewSec->Characteristics = 0x60000020;//修改属性(可执行)

            //在新增节表后增加40个字节的空白区
            memset(pNewSec+1, 0, 40);

            //修改NT头属性

            pFileHeader->NumberOfSections += 1;//修改NumberOfSection数量
            pOptionalHeader->SizeOfImage += 0x1000;//修改SizeOfImage大小

            LPVOID NewBuffer = malloc(pOptionalHeader->SizeOfImage);//申请内存
            memset(NewBuffer, 0, pOptionalHeader->SizeOfImage);//初始化内存

            memcpy(NewBuffer, pFileBuffer,pOptionalHeader->SizeOfImage);//复制内存

            //定位导出表
            DWORD ExportFoa = NULL;//导出表FOA
            PDWORD AddressOfNames = NULL;//导出函数名称表
            LPVOID AddressOfFunctions = NULL;//导出函数地址表
            PWORD AddressOfNameOrdinals = NULL;//导出函数序号表

            DWORD AddressOfNamesFOA = NULL;//导出函数名称表FOA
            DWORD AddressOfFunctionsFOA = NULL;//导出函数地址表FOA
            DWORD AddressOfNameOrdinalsFOA = NULL;//导出函数序号表FOA

            ExportFoa = RVATOFOA(pOptionalHeader->DataDirectory[0].VirtualAddress,pFileBuffer);//获取导出表的地址FOA

            pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pFileBuffer + ExportFoa);//定位导出表

            AddressOfNamesFOA = RVATOFOA(pExportDirectory->AddressOfNames,pFileBuffer);//获取导出函数名称表FOA
            AddressOfFunctionsFOA = RVATOFOA(pExportDirectory->AddressOfFunctions,pFileBuffer);//获取到处函数地址表FOA
            AddressOfNameOrdinalsFOA = RVATOFOA(pExportDirectory->AddressOfNameOrdinals,pFileBuffer);//获取导出函数序号表FOA

            AddressOfNames = (PDWORD)((DWORD)pFileBuffer + AddressOfNamesFOA);//定位导出函数名称表
            AddressOfFunctions = (LPVOID)((DWORD)pFileBuffer + AddressOfFunctionsFOA);//定位导出函数地址表
            AddressOfNameOrdinals = (PWORD)((DWORD)pFileBuffer + AddressOfNameOrdinalsFOA);//定位导出函数序号表

            ////复制AddressOfFunctions表
            LPVOID pNewSecAddr = (LPVOID)((DWORD)NewBuffer+pNewSec->PointerToRawData);//定位新节表的地址
            memcpy(pNewSecAddr,AddressOfFunctions,(pExportDirectory->NumberOfFunctions * 4));

            //复制AddressOfNameOrdinals表
            pNewSecAddr =(LPVOID)((DWORD)pNewSecAddr + (pExportDirectory->NumberOfFunctions * 4));
            memcpy(pNewSecAddr,AddressOfNameOrdinals,(pExportDirectory->NumberOfNames * 2));

            //复制AddressOfNames
            pNewSecAddr =(LPVOID)((DWORD)pNewSecAddr + (pExportDirectory->NumberOfNames * 2));
            PDWORD NameAddr = (PDWORD)pNewSecAddr;//这里存储一下函数地址名称表的地址,以便后边移动名称的时候修改相应地址
            memcpy(pNewSecAddr,AddressOfNames,(pExportDirectory->NumberOfNames * 4));

            //复制函数名称表中的名称
            pNewSecAddr =(LPVOID)((DWORD)pNewSecAddr + (pExportDirectory->NumberOfNames * 4));
            //每复制一个函数名就要计算偏移添加到名字表的地址里

            DWORD NameAddOffset = (DWORD)pNewSecAddr - (DWORD)NewBuffer;//函数名称偏移

            for(size_t i=0;i < pExportDirectory->NumberOfNames;i++,AddressOfNames++)
            {
                    DWORD NameOffset = (DWORD)*AddressOfNames;
                    PCHAR FName = (PCHAR)((DWORD)pFileBuffer + NameOffset);
                    size_t l = 0;

                    *NameAddr = FOATORVA(NameAddOffset,NewBuffer);//复制函数名称偏移到地址中
                    NameAddr = (PDWORD)((DWORD)NameAddr + 0x4);

                    while(FName[l] != '')
                    {
                            l += 1;
                    }

                    NameAddOffset += (l+1);
                    memcpy(pNewSecAddr,FName,(l+1));//复制函数名字符串到地址中

                    pNewSecAddr =(LPVOID)((DWORD)pNewSecAddr + l+1);
            }

            //复制IMAGE_EXPORT_DIRECTORY结构体
            memcpy(pNewSecAddr,pExportDirectory,pOptionalHeader->DataDirectory[0].Size);
            PIMAGE_EXPORT_DIRECTORY pNewExportDirectory = (PIMAGE_EXPORT_DIRECTORY)pNewSecAddr;//地址赋值给新的导出表结构体

            //修改新的导出表的AddressOfFunctions、AddressOfNameOrdinals、AddressOfNames地址,这里需要FOA->RVA
            pNewExportDirectory->AddressOfFunctions = FOATORVA(pNewSec->PointerToRawData,NewBuffer);
            pNewExportDirectory->AddressOfNameOrdinals = FOATORVA(pNewSec->PointerToRawData + pExportDirectory->NumberOfFunctions * 4,NewBuffer);
            pNewExportDirectory->AddressOfNames = FOATORVA(pNewSec->PointerToRawData + pExportDirectory->NumberOfFunctions * 4 + pExportDirectory->NumberOfNames * 2,NewBuffer);

            //修复目录项中的值,指向新的导出表的地址RVA
            pDosHeader = (PIMAGE_DOS_HEADER)NewBuffer;
            pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
            pFileHeader = (PIMAGE_FILE_HEADER)((DWORD)pNtHeaders + 4);
            pOptionalHeader = (PIMAGE_OPTIONAL_HEADER)((DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);
            pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionalHeader + pFileHeader->SizeOfOptionalHeader);
            pOptionalHeader->DataDirectory[0].VirtualAddress = FOATORVA((DWORD)pNewExportDirectory - (DWORD)NewBuffer,NewBuffer);

            FILE* fp = fopen("C:\testD.dll","wb+");
            fwrite(NewBuffer,pOptionalHeader->SizeOfImage,1,fp);
            fclose(fp);
            return true;

    }

    查看移动后的效果

    用工具打开,可以看到节表添加成功。

    十分钟教你轻松掌握「移动PE导出表」,快来学习!

     

    导出表也可以正常解析

    十分钟教你轻松掌握「移动PE导出表」,快来学习!

     

    用十六进制编辑器打开我们移动后的Dll文件进行查看,可以看到我们移动的各个位置如下:

    十分钟教你轻松掌握「移动PE导出表」,快来学习!

     

    在移动的过程中,如果遇到问题,也可以用十六进制编辑器打开我们移动后保存的文件,一步一步的对比调试。

    以上是今天分享的内容,大家看懂了吗?上期我们分享了一篇《导入表及导入表注入》的文章,错过的小伙伴可以点击下面的链接进行查看哦~

    十分钟教你轻松掌握「移动PE导出表」,快来学习!

     

    干货分享丨表哥带你学习导入表及导入表注入

  • 相关阅读:
    【分享】HTML5附件拖拽上传drop & google.gears
    【分享】return false,对阻止事件默认动作的一些测试
    【记录】随笔分类汇总
    【分享】微博 @ 符号的用户名提示效果。(想@到谁?)
    【记录】File, FileReader 和 Ajax 文件上传
    【动态】简单的JS动态加载单体
    【分享】简单页面提示插件第二版表单验证很简单
    【记录】GIT 常用命令
    【分享】jQuery animate自定义动画的简单实现
    【分享】 封装js操作textarea 方法集合(兼容很好)。
  • 原文地址:https://www.cnblogs.com/ichunqiu/p/12530338.html
Copyright © 2020-2023  润新知