导出表结构_十分钟教你轻松掌握移动PE导出表,快来学习!
今天的文章分享是 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导出表,快来学习!相关推荐
- c++ eos智能合约开发_十分钟教你开发EOS智能合约
EOS环境搭建和启动节点 下面从EOS入门的环境搭建.编译运行一个智能合约.发送一些Aigsen,给大家做一些展示,希望能让非技术人员也有一些收获. 首先下载EOS环境搭建和启动节点.这一步其实还是比 ...
- 的图片怎么循环渲染_十分钟教你做个炫酷的图片切换过度效果
做个炫酷的图片切换过度效果 首先,今天是520节日.到了520这类为情侣准备的节日,小编都会感到一万点暴击-- 首先酸一波,搞点事情(蹭波热度). 给大家分享一个520特效页面:看完记得回来为小编点个 ...
- 异形3×3魔方还原教程_五分钟教你轻松还原三阶金字塔异形魔方
说起金字塔魔方,相信大家都会非常熟悉,没错,它是一种非常简单,玩起来有非常"炫"的魔方.这种魔方是由角块.楞块和中心块组成.小编认为大家在玩魔方的过程中一定总结了不少方法,先层后角 ...
- python批量删缩进_鬼畜小姐姐+野狼disco,十分钟教你如何用Python剪辑一个牛逼的抖音小视频?...
鬼畜小姐姐+野狼disco,十分钟教你如何用Python剪辑一个牛逼的抖音小视频? 前言 半个月前,后台有个小伙伴问我,如何将视频中的音频提取出来,并且将声音转成文字写入到 word 中,正好接下来的 ...
- 十分钟教你开发EOS智能合约
十分钟教你开发EOS智能合约 在CSDN.柏链道捷(PDJ Education).HelloEOS.中关村区块链产业联盟主办的「EOS入门及最新技术解读」专场沙龙上,柏链道捷(PDJ Educatio ...
- 一分钟教你学会python_十分钟教你学会python编写小游戏
原标题:十分钟教你学会python编写小游戏 看过,估计大家都已经精通了吧,好的,话不多说,今天就活学活用,用python来编写纸牌游戏21点,江湖人称黑杰克,BLACK JACK-(注意法式卷舌). ...
- 十分钟教你配置frp实现内网穿透
十分钟教你配置frp实现内网穿透 一.frp的作用 利用处于内网或防火墙后的机器,对外网环境提供 http 或 https 服务. 对于 http, https 服务支持基于域名的虚拟主机,支持自定义 ...
- python编写小游戏17_十分钟教你学会python编写小游戏
原标题:十分钟教你学会python编写小游戏 看过,估计大家都已经精通了吧,好的,话不多说,今天就活学活用,用python来编写纸牌游戏21点,江湖人称黑杰克,BLACK JACK-(注意法式卷舌). ...
- 十分钟教你掌握CPU缓存
十分钟教你掌握CPU缓存 一. 基础知识 二. 缓存命中 三.缓存一致 四.程序性能 示例一 示例二 示例三 一. 基础知识 首先,大家都知道现在CPU的多核技术,都会有几级缓存,现在的CPU会 ...
最新文章
- 使用valgrind分析C程序调用线路图
- 从Zero到Hero,OpenAI重磅发布深度强化学习资源
- 片上网络NoC(一)—— 概述
- Linux 内核打印级别
- Mac Nginx 配置 Tomcat 配置 jdk环境变量 Nginx部署服务遇到的坑(2)
- TA能让你家的那些“哑”终端都“活”过来
- “跟童老师学编程”专栏目录
- NModBus的使用
- caxa线切割怎样画链轮,收藏:Autocad实战教程-线切割画链轮
- Python爬虫之BeautifulSoup
- IReport+JasperReport系列的坑(二)CloumnFooter与Detail之间有缝隙
- IOS把图片做成圆形效果
- QQ邮箱今天大面积出现无法下载附件的问题
- [QQ机器人]nonebot每日一言插件
- 单身狗福音:钢铁直男也可以用AI歌曲俘获女友芳心!
- Exception Triggered - Qt-Creator The inferior stopped because it triggered an exception.
- freetype的简单使用之 生成一个字体bmp
- 为什么我要选择使用 Yarn 来做 Docker 的调度引擎
- 为什么物理诺奖颁给量子信息科学?——量子信息的过去、现在和未来
- vs无法提示sourcetree的变基修改代码
热门文章
- window.location.href 跳转失败
- zabbix监控搭建以及客户端安装
- [转]Why Not Paxos
- How to shrink disk for KVM
- 一起谈.NET技术,用NuGet掌管你的Visual Studio扩展
- ubuntu上安装CLucene
- 织梦支持html5吗,自动更新HTML FOR DedeCMS V5.3(支持首页,列表页,文章页)
- 21天Jmeter打卡Day20 响应断言,JSON断言
- oracle查询保留小数点后三位,关于Oracle中查询的数字值的显示格式需要保留小数点后两位(或者三位,及其他位数)...
- tostring会空指针吗_追了多年的开发框架,你还认识指针吗?