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

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

导出表概述

导出表一般多见于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表中对应的所有地址,及其函数名,移动字符串时也需要注意大小\0结尾(每复制一个函数名就要计算偏移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] != '\0')                {                        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;

}

查看移动后的效果

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

导出表也可以正常解析

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

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

文章素材来源于i春秋社区

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

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

对网络安全感兴趣、或者想从事渗透测试相关工作的小伙伴,i春秋学院渗透测试工程师线下就业班( 成都、北京)火热报名中,具体开班时间请咨询招生老师,想要学习的小伙伴抓紧时间报名了。

快速咨询入口

现在报名缴费成功的学员,还有手办相送哦,快来咨询报名吧!

文末右下角点个“在看”再走哦~

i春秋官方公众号为大家提供

前沿的网络安全技术

简单易懂的实用工具

紧张刺激的安全竞赛

还有网络安全大讲堂

更多技能等你来解锁

导出表结构_十分钟教你轻松掌握移动PE导出表,快来学习!相关推荐

  1. c++ eos智能合约开发_十分钟教你开发EOS智能合约

    EOS环境搭建和启动节点 下面从EOS入门的环境搭建.编译运行一个智能合约.发送一些Aigsen,给大家做一些展示,希望能让非技术人员也有一些收获. 首先下载EOS环境搭建和启动节点.这一步其实还是比 ...

  2. 的图片怎么循环渲染_十分钟教你做个炫酷的图片切换过度效果

    做个炫酷的图片切换过度效果 首先,今天是520节日.到了520这类为情侣准备的节日,小编都会感到一万点暴击-- 首先酸一波,搞点事情(蹭波热度). 给大家分享一个520特效页面:看完记得回来为小编点个 ...

  3. 异形3×3魔方还原教程_五分钟教你轻松还原三阶金字塔异形魔方

    说起金字塔魔方,相信大家都会非常熟悉,没错,它是一种非常简单,玩起来有非常"炫"的魔方.这种魔方是由角块.楞块和中心块组成.小编认为大家在玩魔方的过程中一定总结了不少方法,先层后角 ...

  4. python批量删缩进_鬼畜小姐姐+野狼disco,十分钟教你如何用Python剪辑一个牛逼的抖音小视频?...

    鬼畜小姐姐+野狼disco,十分钟教你如何用Python剪辑一个牛逼的抖音小视频? 前言 半个月前,后台有个小伙伴问我,如何将视频中的音频提取出来,并且将声音转成文字写入到 word 中,正好接下来的 ...

  5. 十分钟教你开发EOS智能合约

    十分钟教你开发EOS智能合约 在CSDN.柏链道捷(PDJ Education).HelloEOS.中关村区块链产业联盟主办的「EOS入门及最新技术解读」专场沙龙上,柏链道捷(PDJ Educatio ...

  6. 一分钟教你学会python_十分钟教你学会python编写小游戏

    原标题:十分钟教你学会python编写小游戏 看过,估计大家都已经精通了吧,好的,话不多说,今天就活学活用,用python来编写纸牌游戏21点,江湖人称黑杰克,BLACK JACK-(注意法式卷舌). ...

  7. 十分钟教你配置frp实现内网穿透

    十分钟教你配置frp实现内网穿透 一.frp的作用 利用处于内网或防火墙后的机器,对外网环境提供 http 或 https 服务. 对于 http, https 服务支持基于域名的虚拟主机,支持自定义 ...

  8. python编写小游戏17_十分钟教你学会python编写小游戏

    原标题:十分钟教你学会python编写小游戏 看过,估计大家都已经精通了吧,好的,话不多说,今天就活学活用,用python来编写纸牌游戏21点,江湖人称黑杰克,BLACK JACK-(注意法式卷舌). ...

  9. 十分钟教你掌握CPU缓存

    十分钟教你掌握CPU缓存 一. 基础知识 二. 缓存命中 三.缓存一致 四.程序性能 示例一 示例二 示例三 一. 基础知识    首先,大家都知道现在CPU的多核技术,都会有几级缓存,现在的CPU会 ...

最新文章

  1. 使用valgrind分析C程序调用线路图
  2. 从Zero到Hero,OpenAI重磅发布深度强化学习资源
  3. 片上网络NoC(一)—— 概述
  4. Linux 内核打印级别
  5. Mac Nginx 配置 Tomcat 配置 jdk环境变量 Nginx部署服务遇到的坑(2)
  6. TA能让你家的那些“哑”终端都“活”过来
  7. “跟童老师学编程”专栏目录
  8. NModBus的使用
  9. caxa线切割怎样画链轮,收藏:Autocad实战教程-线切割画链轮
  10. Python爬虫之BeautifulSoup
  11. IReport+JasperReport系列的坑(二)CloumnFooter与Detail之间有缝隙
  12. IOS把图片做成圆形效果
  13. QQ邮箱今天大面积出现无法下载附件的问题
  14. [QQ机器人]nonebot每日一言插件
  15. 单身狗福音:钢铁直男也可以用AI歌曲俘获女友芳心!
  16. Exception Triggered - Qt-Creator The inferior stopped because it triggered an exception.
  17. freetype的简单使用之 生成一个字体bmp
  18. 为什么我要选择使用 Yarn 来做 Docker 的调度引擎
  19. 为什么物理诺奖颁给量子信息科学?——量子信息的过去、现在和未来
  20. vs无法提示sourcetree的变基修改代码

热门文章

  1. window.location.href 跳转失败
  2. zabbix监控搭建以及客户端安装
  3. [转]Why Not Paxos
  4. How to shrink disk for KVM
  5. 一起谈.NET技术,用NuGet掌管你的Visual Studio扩展
  6. ubuntu上安装CLucene
  7. 织梦支持html5吗,自动更新HTML FOR DedeCMS V5.3(支持首页,列表页,文章页)
  8. 21天Jmeter打卡Day20 响应断言,JSON断言
  9. oracle查询保留小数点后三位,关于Oracle中查询的数字值的显示格式需要保留小数点后两位(或者三位,及其他位数)...
  10. tostring会空指针吗_追了多年的开发框架,你还认识指针吗?