滴水逆向-导出表-课堂笔记
相关知识点
导出表: 导出表结构分析 1、如何定位导出表: 数据目录项的第一个结构,就是导出表. typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; VirtualAddress 导出表的RVA Size 导出表大小 2、导出表结构 上面的结构,只是说明导出表在哪里,有多大,并不是真正的导出表. 如何在FileBuffer中找到这个结构呢?在VirtualAddress中存储的是RVA,如果想在FileBuffer中定位 必须要先将该RVA转换成FOA. 真正的导出表结构如下: typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; // 未使用 DWORD TimeDateStamp; // 时间戳 WORD MajorVersion; // 未使用 WORD MinorVersion; // 未使用 DWORD Name; // 指向该导出表文件名字符串 DWORD Base; // 导出函数起始序号 DWORD NumberOfFunctions; // 所有导出函数的个数 DWORD NumberOfNames; // 以函数名字导出的函数个数 DWORD AddressOfFunctions; // 导出函数地址表RVA DWORD AddressOfNames; // 导出函数名称表RVA DWORD AddressOfNameOrdinals; // 导出函数序号表RVA } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY; 导出函数两种方式: (1)名字导出 (2)序号导出 下面是重要参数选项: AddressOfFunctions AddressOfNameOrdinals AddressOfNames 0 Fn1地址 0 3 0 0x12345678 1 Fn2地址 1 1 1 0x12345678 2 Fn3地址 2 4 2 0x12345678 3 Fn4地址 3 7 3 0x12345678 4 Fn5地址 4 8 4 0x12345678 Fn6地址 6 0x12345678 Fn7地址 7 0x12345678 .. 0x12345678 .. 0x12345678 .. 0x12345678 .. 0x12345678 .. 0x12345678 Fnx地址 N 0x12345678 宽度4 宽度2 宽度4 数量:NumberOfFunctions 数量:NumberOfNames 数量:NumberOfNames 3、AddressOfFunctions说明: 该表中元素宽度为4个字节 该表中存储所有导出函数的地址 该表中个数由NumberOfFunctions决定 该表项中的值是RVA, 加上ImageBase才是函数真正的地址 定位: IMAGE_EXPORT_DIRECTORY->AddressOfFunctions 中存储的是该表的RVA 需要先转换成FOA 4、AddressOfNames说明: 该表中元素宽度为4个字节 该表中存储所有以名字导出函数的名字的RVA 该表项中的值是RVA, 指向函数真正的名称 AddressOfNames 特别说明: 0x12345678 1、函数的真正的名字在文件中位置是不确定的 0x12345678 DXXXXXXXXX 0x12345678 2、但函数名称表中是按名字排序的 0x12345678 0x12345678 AXXXXXXXXXXX 也就是说,A开头的函数在AddressOfNames排在最前面. 0x12345678 0x12345678 CXXXXXX 但AXXXXXX这个真正的名字,可能排在BXXXXX后面 0x12345678 0x12345678 BXXXXXXXXX 3、如果想打印名字,要先将AddressOfNames转换为FOA 0x12345678 0x12345678 0x12345678 0x12345678 5、AddressOfNameOrdinals 该表中元素宽度为2个字节 该表中存储的内容 + Base = 函数的导出序号 总结: 为什么要分成3张表? 1.函数导出的个数与函数名的个数未必一样.所以要将函数地址表和函数名称表分开. 2.函数地址表是不是一定大于函数名称表? 未必,一个相同的函数地址,可能有多个不同的名字. 3.如何根据函数的名字获取一个函数的地址? 函数VA = ImageBase + 0x1234 函数名称表 函数序号表 函数地址表 4.如何根据函数的导出序号获取一个函数的地址? 假设导出序号是10 Base 的值是5 函数地址:10 - 5 = 5 找出下标为5的函数地址即可; 课后练习 1.编写程序打印所有的导出表信息; typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; // 未使用 DWORD TimeDateStamp; // 时间戳 WORD MajorVersion; // 未使用 WORD MinorVersion; // 未使用 DWORD Name; // 指向该导出表文件名字符串 DWORD Base; // 导出函数起始序号 DWORD NumberOfFunctions; // 所有导出函数的个数 DWORD NumberOfNames; // 以函数名字导出的函数个数 DWORD AddressOfFunctions; // 导出函数地址表RVA DWORD AddressOfNames; // 导出函数名称表RVA DWORD AddressOfNameOrdinals; // 导出函数序号表RVA } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY; 地址空间:这个地址空间指的是PE文件被加载到内存的空间,是一个虚拟的地址空间, 之所以不是物理空间是因为数据在内存中的位置经常在变,这样既可以节约内存开支又可以避开错误的内存位置。 这个地址空间的大小为4G,但其中供程序装载的空间只有2G而且还是低2G空间,高2G空间则被用于装载内核DLL文件, 所以也被称作内核空间。 文件映射:PE文件在磁盘上的状态和在内存中的状态是不一样的,我们把PE文件在磁盘上的状态称作FileBuffer, 在内存中的状态称为ImageBuffer。当PE文件通过装载器装入内存是会经过“拉伸”的过程, 所以它在FileBuffer状态下和ImageBuffer状态下的大小是不一样的; VA:英文全称是Virual Address,简称VA,中文意思是虚拟地址。指的是文件被载入虚拟空间后的地址。 ImageBase:中文意思是基址,指的是程序在虚拟空间中被装载的位置。 RVA:英文全称是Relative Virual Address,简称RVA,中文意思是相对虚拟地址。 可以理解为文件被装载到虚拟空间(拉伸)后先对于基址的偏移地址。 计算方式:RVA = VA(虚拟地址) - ImageBase(基址)。它的对齐方式一般是以1000h为单位在虚拟空间中对齐的(传说中的4K对齐), 具体对齐需要参照IMAGE_OPTIONAL_HEADER32中的SectionAlignment成员; FOA:英文全称是File Offset Address,简称FOA,中文意思是文件偏移地址。 可以理解为文件在磁盘上存放时相对于文件开头的偏移地址。它的对齐方式一般是以200h为单位在硬盘中对齐的(512对齐), 具体对齐需要参照IMAGE_OPTIONAL_HEADER32中的FileAlignment成员; 下面是判断RVA和FOA的计算方法; <1> 得到RVA的值,RVA=内存地址-ImageBase(ImageBase是IMAGE_OPTION_HEADER中的成员); <2> 比较RVA与SizeofHeaders的大小,判断RVA是否位于PE头中,如果是的话,FOA=RVA,(SizeofHeaders是IMAGE_OPTION_HEADER中的成员); <3> 判断RVA位于哪个节, RVA >= 节.VirtualAddress RVA <= 节.VirtualAddress + 当前节内存对齐后的大小 差值 = RVA - 节.VirtualAddress <4> FOA = 节.PointerToRawData + 差值 总结: 总体来说,首先就是要判断某一个内存地址是否是在SizeOfHeaders里面,如果是,那么RVA=FOA,因为SizeOfHeaders在拉伸前后不变; 而如果不在SizeOfHeaders,里面那么就要判断这个内地址落在哪个节的范围,然后减去落在这个节VirtualAddress的地址,得到她们; 的差值,将得到的这个差值加上这个节中对应在文件中偏移的地址(PointerToRawData)结果就是FOA; 实例计算: 1.判断不带ImageBase地址0x00051EC0在哪个节里面; 2.通过计算查找,发现0x00051EC0是落在第二个节里面,因为:VirtualAddress + VirtualSize > 上面不带ImageBase的地址; 第二个节对应的VirtualAddress和VirtuallSIze 0x00046000+0x0000D74D=0x0005374D > 0x00051EC0; 3.确认在哪个节里面之后就可以根据上面的总结计算,下面是导出表给出的VirtualAddress地址加上ImageBase然后减去ImageBase; 对应的距离,刚好就是0x00051EC0 RVA = 0x00051EC0 + 0x00400000 - 0x00400000 = 0x00051EC0; 4.计算出她们之间的差值 差值 = 0x00051EC0 - 0x00046000 = 0x0000BEC0; 5.计算FOA的值 FOA = 对应节的PointerToRawData + 差值 FOA = 0x00046000 + 0x0000BEC0 = 0x00051EC0; 注意:这里的0x00046000是因为此程序中节里面的PointerToRawData=VirtualAddress=0x00046000; 2.GetFunctionAddrByName(FileBuffer指针,函数名指针) 3.GetFunctionAddrByOrdinals(FileBuffer指针,函数名导出序号)
上面计算FOA的过程验证
验证方式是使用winhex打开存储在硬盘位置的ipmsg.exe和意见打开的ipmsg.exe程序,找到其对应的导出表的VirtualAddress地址;
迷茫的人生,需要不断努力,才能看清远方模糊的志向!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2020-09-18 sqlilab-Less-9-12 测试writeup