滴水三期:day36.1-导出表

【滴水三期】打印导出表、重定位表 阅读详情
AI摘要

文章详细介绍了PE文件结构中的导出表,包括导出表的作用、结构、常见误区,以及如何通过导出表按名字和序号查找函数地址。还提供了程序示例来打印导出表信息和按名字、序号查找函数地址。

一、导出表概述

1.引入导出表

  • 前面学过一个Win32下的.exe文件,是由多个PE文件组成。比如通过OD打开一个Ipmsg.exe,查看模块M,会发现一个ipmsg.exe和多个.dll(即模块)构成。

    image-20230404185140430
  • 这种以动态链接库.dll的方式导出函数时,如果此.exe需要用到某个.dll中的函数,那么.exe怎么知道:

    1. 引入的.dll中有哪些函数可以供.exe使用
    2. .dll中的各种函数都存在哪里
  • 所以.dll中应当有一个目录或者结构提供给.exe文件,来记录.dll中的函数有哪些,函数的起始地址等信息。所以如果.dll或者.exe文件想要给别的程序提供函数,就必须同时给别的程序一个“清单”,这个“清单”就是导出表

  • 故当我们分析上面例子中的ole32.dll,或者LPK.dll等.dll文件结构时,就可以看到有导出表

2.常见误区

  • 不是只有.dll才提供函数给别的程序使用,有些.exe也可以提供函数
  • 有些.exe的导出表数据目录不是全0,表示有此.exe程序有导出表,即可对外提供函数;有些没有导出表,就不对外提供函数
  • 所以一个PE文件(如.dll/.exe/.sys)如果对外提供函数,那么就需要有导出表,且需要遵守一定的格式
  • 一个安全的PE文件,是不会显示导出表的,但可以对外提供函数:正常情况下如果一个PE文件不提供导出表,别人是无法使用其函数的;但是逆向一个游戏时,如果知道了别人的导出表,就不用找call了(不现实)。所以游戏会做安全加固,我们不知道哪些函数是导出的,就算给我函数地址,我也没办法拿这个地址去调用,只能分析反汇编代码,分析程序入口是什么,分析函数的参数是什么

3.导出表在哪里

PE文件结构图

  • 先定位到可选PE头 --> 再找到可选PE头的最后一个成员:即一个结构体数组,第一个结构体就是导出表数据目录,第一个成员是导出表的内存偏移地址RVA,第二个成员是导出表的大小

    1 image-20230404191027376

  • 通过导出表数据目录的RVA转FOA就可以找到导出表

  • 导出表其实就在这个PE文件的某个节中(后面要学的各种表也是在某个节中)

  • 注意事项:学到现在解析PE结构时就不用拉伸了,用FileBuffer分析即可。因为拉伸只是有助于理解,如果直接在FileBuffer中做,只是需要多使用一个==RVA–>FOA==的地址转换函数,每次遇到RVA地址时转换一下即可,就省去了将文件整个拉伸的过程,会减少很多代码量和多余操作,而且市面上关于PE的书籍几乎都是直接用FileBuffer讲解操作的,所以现在开始,我们就要学会不拉伸的情况下去分析

4.导出表的结构

struct _IMAGE_EXPORT_DIRECTORY{
   
      //40字节		
    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  *						
};
1.Name
  • 指向该导出表文件名字符串的RVA,比如一个DBGHELP.dll的PE文件提供函数,那么这个PE文件的Name指向的字符串为为dbghelp.dll

    image-20230407011302040

    可以用LordPE软件,选择PE Editor,选择你要打开的PE文件,查看其导出表

2.Base
  • 导出函数起始序号(最小的序号)

    比如有序号为14、6、10、8的导出函数,那么Base的值为6

3.NumberOfFunctions
  • 所有导出函数的个数

  • 注意:day35中讲过,这个值是通过导出函数的最大序号 - 最小序号 + 1算出来的;正常来说这个值是多少,那么此PE文件中导出函数的个数就是多少。但是如果使用自定义序号,序号定义时不是连续的,而是中间有空缺的序号,那么此时NumberOfFunctions的值会比实际的定义的导出函数个数多

    • 比如在.def中定义为Plus @12 、Sub @15 NONAME、 Mul @13 、Div @16
    • 那么NumberOfFunctions值 = 16 - 12 + 1 = 5;而不是4!
4.NumberOfNames
  • 以函数名字导出的函数个数:比如以动态链接库的方式导出(注意和只以序号导出函数区分)
  • 如果导出时加了NONAME关键字,那么就不计数
5.AddressOfFunctions
  • 导出函数地址表RVA,即这个地址指向一个表!这个表中记录了此PE文件的所有导出函数的地址

    image-20230404193714462
  • 该表中元素宽度:4个字节

  • 元素个数:由NumberOfFunctions决定

  • 注意:day35中讲过导出函数是有序号的,上图中的下标等于导出函数的相对序号

    (参考上图)比如使用自定义序号导出函数,即.def的方式导出,定义了序号13、14、16、17、19的导出函数,那么Base的值应为13,那么序号为13的函数相对相对下标就是0,序号14的导出函数相对下标就是1,序号为15的导出函数虽然没有,但是会把位置空出来,只是地址值为NULL,即0x00000000,序号16的导出函数相对下标就是3…以此类推

    image-20230404222130061
6.AddressOfNames
  • 导出函数名称表RVA(拉伸后的内存地址偏移,所以要先转成FOA),即这个地址指向一个表,这个表中记录的是导出函数的名称字符串地址!!不是直接存储名称(且此字符串地址也是RVA)

    就像C语言中的字符串char* name = "abc";,name变量值为abc这个字符串的首地址。所以导出函数名称表中存储的是name值(即名称的地址),而不是直接存储的abc这个名称

    image-20230404202707507
  • 该表中元素宽度:4个字节

  • 表中元素的数量:由NumberOfNames决定

  • 注意:如果函数导出时添加了NONAME,即函数没有名称,那么这个表中就不会出现这个函数名地址

    • 所以AddressOfNames表中元素个数可能比AddressOfFunctions表中元素个数少!(AddressOfFunctions表中不管导出函数有没有名字,都会有地址)
    • 也有可能AddressOfNames表中元素个数比AddressOfFunctions表中元素个数多!因为导出函数时可以让多个不同名字的函数指向同一个函数地址
  • 函数名称表中是按名字排序的(根据ASCII码排序)

7.AddressOfNameOrdinals
  • 导出函数序号表RVA,即这个地址指向一个表,表中存的是导出函数的相对序号

    image-20230404211521937
  • 即==该表中存储的内容 + Base = 函数的导出序号==

  • 表中元素个数:由NumberOfNames决定,且和AddressOfName表一一对应的

    image-20230404212302495

    比如:现在要找名字叫Sub的导出函数,经查找在AddressOfNames表的下标为1的位置,那么这个函数所对应的相对序号则在AddressOfNameOrdinals表中下标为1的位置,即由上图可知Sub函数对应的序号为0x0001 + Base

  • 该表中元素宽度:2个字节

5.NONAME导出函数注意事项

  • 如果导出时,定义的无名字函数,即Div @13 NONAME,那么函数名称表中就不会有指向Div函数名的元素,同样函数序号表中也不会有Div的相对序号,但是在函数地址表中会留出来一个元素位置存储Div函数地址

二、由导出表获取函数地址

1.按名字找函数地址

  • 找到导出表后,先根据AddressOfNames,将RVA转成FOA,即可定位到函数名称表,遍历函数名称表,用名字依次与函数名称表每个元素指向的字符串做比较,直到名字匹配,记录下此时元素在函数名称表的下标i
  • 再根据AddressOfNameOrdinals,将RVA转成FOA,即可定位到函数序号表,得到此表下标为i的元素值n
  • 最后根据AddressOfFunctions,将RVA转成FOA,即可定位到函数地址表,则此表下标为n的元素值就是该名字对应函数的RVA地址,将RVA转成FOA就得到了该导出函数在FileBuffer中的地址

​ image-20230404232004619

比如要根据名字查找导出函数mul(),假设在函数名称表中下标为2指向的位置找了"mul"字符串与函数名称匹配,所以再去查找函数序号表下标为2的元素为0x0004,最后再函数地址表中找下标为4的元素值,即为mul函数的RVA,转成FOA即可

2.按序号找函数地址

  • 找到导出表后,根据AddressOfFunctions,将RVA转成FOA,定位到函数地址表

  • 再用给定的序号 - Base = 相对序号i,得到相对序号i

  • 最后找函数地址表中下标为i的元素值即为该序号对应函数RVA地址,将RVA转成FOA就得到了该函数在FileBuffer中的地址

    image-20230404232004619
  • 比如要根据序号找导出函数mul(),因为mul的序号为17,Base值为13,那么17 - 13 = 4,所以直接再函数地址表中找到下标为4的元素值,即为mul函数的RVA,最后转成FOA即可

  • 所以根据导出序号找,跟函数序号表没有关系!函数序号表的存在就是为了用于按名字查找

  • 上图可以发现,t_method函数用NONAME导出,所以函数名称表和函数序号表都没有t_method的信息,但是由于此函数序号为19,相对序号为19 - 13 = 6,所以会在函数地址表中下标为6的位置存储t_method函数的RVA

三、作业

1.编写程序打印导出表的信息

#include "stdafx.h"
#include <stdlib.h>

typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef unsigned char BYTE;

#define MZ 0x5A4D
#define PE 0x4550
#define IMAGE_SIZEOF_SHORT_NAME 8

//DOS头
struct _IMAGE_DOS_HEADER {
   
   
	WORD e_magic;  //MZ标记
	WORD e_cblp;
	WORD e_cp;
	WORD e_crlc;
	WORD e_cparhdr;
	WORD e_minalloc;
	WORD e_maxalloc;
	WORD e_ss;
	WORD e_sp;
	WORD e_csum;
	WORD e_ip;
	WORD e_cs;
	WORD e_lfarlc;
	WORD e_ovno;
	WORD e_res[4];
	WORD e_oemid;
	WORD e_oeminfo;
	WORD e_res2[10];
	DWORD e_lfanew;  //PE文件真正开始的偏移地址
};

//标准PE头
struct _IMAGE_FILE_HEADER {
   
   
	WORD Machine;  //文件运行平台
	WORD NumberOfSections;  //节数量
	DWORD TimeDateStamp;  //时间戳
	DWORD PointerToSymbolTable;
	DWORD NumberOfSymbols;
	WORD SizeOfOptionalHeader;  //可选PE头大小
	WORD Characteristics;  //特征值
};

//数据目录
struct _IMAGE_DATA_DIRECTORY{
   
   
    DWORD VirtualAddress;
    DWORD Size;
};

//可选PE头
struct _IMAGE_OPTIONAL_HEADER {
   
   
	WORD Magic;  //文件类型
	BYTE MajorLinkerVersion;
	BYTE MinorLinkerVersion;
	DWORD SizeOfCode;   //代码节文件对齐后的大小
	DWORD SizeOfInitializedData;  //初始化数据文件对齐后的大小
	DWORD SizeOfUninitializedData;  //未初始化数据文件对齐后大小
	DWORD AddressOfEntryPoint;  //程序入口点(偏移量)
	DWORD BaseOfCode;  //代码基址
	DWORD BaseOfData;  //数据基址
	DWORD ImageBase;   //内存镜像基址
	DWORD SectionAlignment;  //内存对齐粒度
	DWORD FileAlignment;  //文件对齐粒度
	WORD MajorOperatingSystemVersion;
	WORD MinorOperatingSystemVersion;
	WORD MajorImageVersion;
	WORD MinorImageVersion;
	WORD MajorSubsystemVersion;
	WORD MinorSubsystemVersion;
	DWORD Win32VersionValue;
	DWORD SizeOfImage;  //文件装入虚拟内存后大小
	DWORD SizeOfHeaders;  //DOS、NT头和节表大小
	DWORD CheckSum;  //校验和
	WORD Subsystem;
	WORD DllCharacteristics;
	DWORD SizeOfStackReserve;  //预留堆栈大小
	DWORD SizeOfStackCommit;  //实际分配堆栈大小
	DWORD SizeOfHeapReserve;  //预留堆大小
	DWORD SizeOfHeapCommit;  //实际分配堆大小
	DWORD LoaderFlags;
	DWORD NumberOfRvaAndSizes;  //目录项数目
	_IMAGE_DATA_DIRECTORY DataDirectory[16]; //数据目录
};

//NT头
struct _IMAGE_NT_HEADERS {
   
   
	DWORD Signature;  //PE签名
	_IMAGE_FILE_HEADER FileHeader;
	_IMAGE_OPTIONAL_HEADER OptionalHeader;
};

//节表
struct _IMAGE_SECTION_HEADER{
   
   
	BYTE Name[IMAGE_SIZEOF_SHORT_NAME];  //节表名
	union{
   
   
		DWORD PhysicalAddress;
		DWORD VirtualSize;  //内存中未对齐大小
	}Misc;
	DWORD VirtualAddress;  //该节在内存中偏移地址
	DWORD SizeOfRawData;  //该节在硬盘上文件对齐后大小
	DWORD PointerToRawData;  //该节在硬盘上文件对齐后偏移地址
	DWORD PointerToRelocations;
	DWORD PointerToLinenumbers;
	WORD NumberOfRelocations;
	WORD NumberOfLinenumbers;
	DWORD Characteristics;  //该节特征属性
};

//导出表
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  *						
};

/*计算文件大小函数
参数:文件绝对路径
返回值:返回文件大小(单位字节)
*/
int compute_file_size(char* filePath){
   
   
	FILE* fp = fopen(filePath,"rb");
    if(!fp){
   
   
		printf("打开文件失败");
		exit(0);
	}
    fseek(fp,0,2);
	int size = ftell(fp);
	//fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了
	fclose(fp);
	return size;
}

/*将文件读入FileBuffer函数
参数:文件绝对路径
返回值:FileBuffer起始地址
*/
char* to_FileBuffer(char* filePath){
   
   
    FILE* fp = fopen(filePath,"rb");
    if(!fp){
   
   
		printf("打开文件失败");
		exit(0);
	}
    int size = compute_file_size(filePath);
    
	char* mp = (char*)malloc(sizeof(char) * size);  /
飞鸽传书IPMsg局域网文件传输全面指南 在当今数字化工作环境中,局域网文件传输软件已经成为IT专业人士和企业员工间交流信息和资源共享不可或缺的工具。这些软件使得在公司内部网络中高效、快速地传输文件成为可能,无论这些文件是文本、图像还是大型媒体文件。选择合适的局域网文件传输软件,将直接影响工作流程的效率和数据交换的安全性。在本章中,我们将简要概述局域网文件传输软件的作用和重要性,同时为读者提供一个关于如何选择最适合自己需求的软件的初步指南。我们还将介绍一些在业界广泛使用、拥有良好口碑的解决方案,以及它们的主要特点和优势。 阅读详情

相关推荐

飞秋IPMsg局域网即时通讯工具实战应用

飞秋IPMsg(又称飞鸽传书)是一款基于UDP广播协议的轻量级局域网即时通信工具,采用无中心服务器架构,通过ARP、UDP广播实现主机发现与消息投递。其通信模型遵循“发送即送达”的设计理念,在同一广播域内可快速定位目标IP并建立单播或广播通信链路。系统无需账号注册,利用主机名+IP地址自动构建联系人列表,极大简化了部署流程。

weixin_34535286的博客 491

IPMSG.exe 软件

IPMSG.exe IPMSG.exe IPMSG.exe

飞鸽传书3.10绿色经典版(IPMsg3.10chs)

经典版,可直接运行ipmsg.exe使用,也可进行安装setup.exe,随系统启动自运行。

ipmsg 飞鸽传书 2.0.9.0(无毒单一文件)

这个版本自己用了好久,新版本的 很好用。兼容WIN7

飞鸽传书(免安装)ipmsg.exe

IPMsg 是一款局域网内即时通信软件, 基于TCP/IP(UDP). 可运行于多种操作平台(Win/Mac/UNIX/Java), 并实现跨平台信息交流.

飞鸽传书2007 IPMSG2007.exe

局域网通讯,不用多说啦~~~~~~~~~~~~~~~~~~我觉得很好用,传输速度很快!

IPMSG.exe

飞鸽传书,局域网极其实用的一个传输软件

IPMSG200.exe

IPMSG200.exe飞鸽传书,普遍适用于局域网中。

虚拟机安装Centos8.5

Centos8.5

weixin_63604047的博客 958

Impala>Impala安装部署

文章目录1.安装前提2.下载安装包、依赖包3.虚拟机新增磁盘(可选)4.配置本地yum源5.安装Impala6.修改Hadoop、Hive配置7.修改impala配置8.启动、关闭impala服务 1.安装前提 集群提前安装好hadoop,hive。 hive安装包scp在所有需要安装impala的节点上,因为impala需要引用hive的依赖包。 hadoop框架需要支持C程序访问接口,查看下...

qq_44509920的博客 838

【FOC无刷电机控制】六步换向、FOC,STM32cubemx从零开始搭建BLDC六步换相代码、FOC代码(基于霍尔传感器)

FOC、六步换相,无刷电机控制

xifan的博客 3万+

EMC案例:解决千兆网口RTL8211F EMI问题

摘要:本文分析了Amlogic S812平台OEM项目中RTL8211F-CG千兆网卡EMI辐射超标问题。发现百兆测试时25MHz时钟有3dB余量,但千兆125MHz时钟导致超标。问题根源包括:未关闭PHY芯片悬空CLKOUT引脚、TX/RXCLK端RC位置错误、包地不完整及参考地缺失。解决方案涉及软件关闭无用时钟、优化RC布局、加强包地与打孔、调整共模电感位置,并在HDMI 3.3V加磁珠。关键结论指出千兆网125MHz时钟需严格包地打孔,且RC取值需平衡EMI与通信性能。

电子绿洲的博客 1278

【机器学习】实验2,基于回归分析的大学综合得分预测(完整代码实现+分析)

大学排名是一个非常重要同时也极富挑战性与争议性的问题,一所大学的综合实力涉及科研、师资、学生等方方面面。目前全球有上百家评估机构会评估大学的综合得分进行排序,而这些机构的打分也往往并不一致。在这些评分机构中,世界大学排名中心(Center for World University Rankings,缩写CWUR)以评估教育质量、校友就业、研究成果和引用,而非依赖于调查和大学所提交的数据著称,是非常有影响力的一个。

高丽大学人工智能硕士在读|专注大模型、RAG、OCR与AI工程实践 1974

webrtc进阶-- FlexFEC 实现原理与调试分析(m80 代码对齐版)

WebRTC 中 FlexFEC 技术解析与调试指南 摘要:本文深入分析 WebRTC 的 FlexFEC(灵活前向纠错)技术实现原理,重点探讨其在 m80 版本中的协商机制、接收侧恢复流程以及 SFU 场景下的特殊处理。文章指出 FlexFEC 通过动态冗余策略提升弱网抗丢包能力,详细解析了编解码器分配、SSRC 映射、包恢复等关键环节,并提供了针对 XR+SFU 场景的调试建议。调试时可重点关注 FEC 包接收与媒体包恢复日志,以验证链路有效性。

HOSTEN的博客 48

基于ArduPilot的旋翼式无人机飞行器开发系列(二,学习路线)

一,热爱 作为一名耿直的技术男,在这里我只想说一句,热爱是可持续学习的第一要素,也只有热爱才能让你坚持到最后。 至于最后能得到什么,只有你自己知道。常说书中自有颜如玉,书中自有黄金屋。如果你是为了颜如玉,我相信大多数颜如玉做不了你的红颜知己。如果是为了黄金屋,当你持续投入时间和金钱却看不到回报的时候,相信退堂鼓早已隆隆作响。 我给自己的信条就是为当下努力工作,为将来持续学习。当前飞控行业并没有爆发,如果想赚块钱,去互联网汽车,去VR/AR吧,去元宇宙吧。 那么我热爱...

从0到1,突破自己 1667

操作系统实验四 进程运行轨迹的跟踪与统计(哈工大李治军)

实验4 进程运行轨迹的跟踪与统计 实验目的 掌握 Linux 下的多进程编程技术; 通过对进程运行轨迹的跟踪来形象化进程的概念; 在进程运行轨迹跟踪的基础上进行相应的数据统计,从而能对进程调度算法进行实际的量化评价,更进一步加深对调度和调度算法的理解,获得能在实际操作系统上对调度算法进行实验数据对比的直接经验。 实验内容 进程从创建(Linux 下调用 fork())到结束的整个过程就是进程的生命期,进程在其生命期中的运行轨迹实际上就表现为进程状态的多次切换,如进程创建以后会成为就绪态;当该进程被调度以

leoabcd12的博客 4653

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

上一篇: 滴水三期:day26.2-C文件操作的函数
下一篇: 滴水三期:day37.1-重定位表
EdimadeZhou
博客等级 码龄5年 241粉丝 128原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值