利用PE数据目录的导入表获取函数名及其地址
PE文件是以64字节的DOS文件头开始的(IMAGE_DOS_HEADER),接着是一段小DOS程序,然后是248字节的
NT文件头(IMAGE_NT_HEADERS),NT的文件头位置由IMAGE_DOS_HEADER的e_lfanew给出!
NT文件头的前4个字节是文件签名(“PE00"字符串),紧接着是20字节的IMAGE_FILE_HEADER结构,它的
后面是224字节的IMAGE_OPTIONAL_HEADER结构,而就在这个结构里,里面有模块基地址,代码和数据大
小和基地址、线程堆栈和进程堆的配置,程序入口点的地址,还有数据目录表指针,PE文件还保留着16
个数据目录,常见的有导入表,导出表,资源和重定位表,而我们这里就是用的到了导入表
IMAGE_IMPORT_DESCRIPTOR,代码如下
- int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
- {
- int nRetCode = 0;
- // initialize MFC and print and error on failure
- if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0))
- {
- // TODO: change error code to suit your needs
- cerr << _T("Fatal Error: MFC initialization failed") << endl;
- nRetCode = 1;
- }
- else
- {
- // TODO: code your application's behavior here.
- CString strHello;
- strHello.LoadString(IDS_HELLO);
- cout << (LPCTSTR)strHello << endl;
- }
- //这里开始
- HMODULE hMod = ::GetModuleHandle(NULL);
- IMAGE_DOS_HEADER *pDosHeader = (IMAGE_DOS_HEADER *)hMod;
- IMAGE_OPTIONAL_HEADER *pOptHeader = (IMAGE_OPTIONAL_HEADER *)((BYTE *)hMod +
- pDosHeader->e_lfanew + 24);
- IMAGE_IMPORT_DESCRIPTOR *pImportDesc = (IMAGE_IMPORT_DESCRIPTOR *) ((BYTE *)hMod +
- pOptHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
- while(pImportDesc->FirstThunk)
- {
- char *pszDllName = (char *)((BYTE *)hMod + pImportDesc->Name);
- printf("/n模块名称:%s/n", pszDllName);
- IMAGE_THUNK_DATA *pThunk = (IMAGE_THUNK_DATA *)((BYTE *)hMod + pImportDesc
- ->OriginalFirstThunk);
- int n = 0;
- //MessageBox(NULL, "Test", "MESS", MB_OK);
- char *pszFunName = NULL;
- while(pThunk->u1.Function)
- {
- pszFunName = (char *)((BYTE *)hMod + (DWORD)pThunk-
- >u1.AddressOfData + 2);
- PDWORD lpAddr = (DWORD *)((BYTE *)hMod + pImportDesc->FirstThunk) +
- n;
- try
- {
- printf("function name : %-25s", (char *)pszFunName);
- }
- catch(...)
- {
- printf("function name :unknown!");
- }
- printf("addr :%0X/n", lpAddr);
- n++;
- pThunk++;
- }
- pImportDesc++;
- }
- return nRetCode;
- }
顺便带上一些检查是否为PE的代码
- CFileDialog dlg(true);
- if(dlg.DoModal() != IDOK)
- {
- return ;
- }
- HANDLE hFile = ::CreateFile(dlg.GetFileName(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
- if(hFile == INVALID_HANDLE_VALUE)
- {
- ::MessageBox(NULL, "INVALID FILE", "VALID PE", MB_OK);
- }
- IMAGE_DOS_HEADER dosHeader;
- IMAGE_NT_HEADERS ntHeader;
- bool bValid = false;
- DWORD dwRead;
- ::ReadFile(hFile, &dosHeader, sizeof(dosHeader), &dwRead, NULL);
- if(dwRead == sizeof(dosHeader))
- {
- if(dosHeader.e_magic == IMAGE_DOS_SIGNATURE)
- {
- if(::SetFilePointer(hFile, dosHeader.e_lfanew, NULL, FILE_BEGIN) != -1)
- {
- ::ReadFile(hFile, &ntHeader, sizeof(ntHeader), &dwRead, NULL);
- if( dwRead == sizeof(ntHeader) )
- {
- if( ntHeader.Signature == IMAGE_NT_SIGNATURE )
- {
- bValid = true;
- }
- }
- }
- }
- }
- if( bValid)
- ::MessageBox(NULL, "It's a PE", "PE FILE", MB_OK);
- else
- ::MessageBox(NULL, "It's not a PE", "PE FILE", MB_OK);
1、PE文件格式的背景和由来:
在开始介绍PE 文件结构之前,有必要向读者提一提常用的PE件结构分析工具:Win32 SDK
提供的 DUMPBIN 可以转储PE文件和COFF OBJ/LIB 件;Borland 的使用者可用TDUMP 观
察PE 文件,但TDUMP 不支持COFF OBJ。
2、PE 文件的顺序结构:
我们可以把PE 的内存映象结构用下面的图示简要的表示出来:
DOS MZ HEADER
DOS STUB
PE
Header Signature( “PE/0/0”)
FileHeader
OptionalHeader
Section Table(array of IMAGE_SECTION_HEADER)
.text
.data
.edata
.idata
.reloc
…
COFF Line Number
COFF Symbols
Code View Debug nformation
2.1、DOS header 和 DOS Stub:
所有的 PE 文件 (或32位的DLLS)都必须以一个简单的DOS MZ header 为起始
(IMAGE_DOS_HEADER 结构体)。在实际中,除了e_lfanew (PE header 的 件偏移量)我
们可以不必太关心其余成 数据。DOS Stub只是提供了PE 文件在DOS 下执行时 ,DOS
会把它当作有效的执行文件而顺利执行。通常会在屏幕上输出 " This program cannot
run in DOS mode " 之类的提示语。程序员也可以改变DOS Stub,根据自己的意图实现
完整的 DOS代码。
2.2、PE Header:
PE 表头内含程序代码和各种资料的大小位置、适用的操作系统、堆栈(stack)最初大小
等等重要信息。犹如执行文件的纲目。整个PEHeader是一个IMAGE_NT_HEADERS 结构体,
在Win32 SDK 中定义如下:
- typedef struct _IMAGE_NT_HEADERS
- {
- DWORD Signature;//PE 标记,值为50h,45h,00h,00h (ASCII:”PE/0/0”)
- IMAGE_FILE_HEADER FileHeader;
- IMAGE_OPTIONAL_HEADER32 OptionalHeader;
- }IMAGE_NT_HEADERS32,*PIMAGE_NT_HEADERS32;
PE Signature 为校验标记,由连接器产生。通常装载器通过指向此位的指针e_lfanew (DOS
header 中)来检验此文件是否为PE 格式。FileHeader域包含了关于PE 文件物理分布的
一般信息, opionalHeader域包含了关于PE 文件逻辑分布的信息。显然可以pNTHeader=
dosHeader + dosHeader->e_lfanew 获得pe 头的地址。
2.2.1、File Header 结构域:
在Win32 SDK 中File Header 定义如下:
- typedef struct _IMAGE_FILE_HEADER
- {
- WORD Machine;
- WORD NumberOfSections;
- DWORD TimeDateStamp;
- DWORD PointerToSymbolTable;
- DWORD NumberOfSymbols;
- WORD SizeOfOptionalHeader;
- WORD Characteristics;
- }IMAGE_FILE_HEADER,*PIMAGE_FILE_HEADER;
各个成 的说明列表如下:
成员说
Machine 该文件运行所要求的CPU。对于Intel平台,该值是
IMAGE_FILE_MACHINE_I386(14Ch)。
NumberOfSections 文件的节数目。如果我们要在文件中增加或删除一个节,就需要修改
这个值。
TimeDateStamp 连接器创建文件时刻。从1969/12/31 4:00 P.M. 之后的总秒数。
PointerToSymbolTable COFF 符号表格的偏移位置,用于调试。
NumberOfSymbols COFF 符号表格中符号的个数,用于调试。
SizeOfOptionalHeader指示紧随本结构之后的OptionalHeader结构大小,必须为有效值。
Characteristics 关于文件信息的标记,比如文件是exe(0x0002) 还是dll(0x2000)在
实际应用中,有三个域对我们有用:NumberOfSections,SizeOfOptionalHeader和
Characteristics。我们通常不会改变SizeOfOptionalHeader和Characteristics的值,
如果要遍历节表就得使用 NumberOfSections。
2. 2。2、Option Header 结构域:
这是PE 表头的第三个成分。对于PE 文件而言,这一部分其实并不是可有可无。因为COFF
格式允许不同的设计者在标准的File header之后定义一个结构。Optional header 正是
PE 设计者认为在基本的File header 信息之外还需要的一些重要的信息。完整的结构定
义可以参考Win32 SDK 中的WINNT.H 头文件,这里列举了其中的重要成员说明 :
成员说明
AddressOfEntryPoint PE 装载器准备运行的PE文件的第一条指令的RVA。若要改变整个
执行的流程,可以将该值指定到新的RVA,这样新RVA 处的指令首先被执行。
ImageBase PE文件的优先装载地址。如果该值是400000h,PE装载器将尝试把文件装到
虚拟地址空间的400000h 处。若该地址区域已被其他模块占用,那PE 装载器会选用其他
空闲地址,这个过程我们把它叫做基址重定位。
SectionAlignment 内存中节对齐的粒度(granularity)。一旦装载到内存中,每个节(s
ection)保证从一个此值的倍数的虚拟地址开始。如果该值是4096Byte(1000hByte),那么每节
的起始地址必须是4096 的倍数。
FileAlignment 文件中节对齐的粒度。文件中 成每个section 的原始数据(raw data)
保证是从一个此值的倍数的虚拟地址开始。如果该值是512Byte(200hByte),,那么每节的起始地址必
须是512 的倍数。
MajorSubsystemVersion
MinorSubsystemVersion win32子系统版本。若PE 文件是专门为Win32 设计的,该子系
统版本必定是4.0 否则不会有3维立体感对话框等。
SizeOfImage 内存中整个PE 映像体(map)的尺寸。它是所有头和节经过节对齐处理后的
大小。
SizeOfHeaders 所有头+节表的大小,也就等于文件尺寸减去文件中所有节的尺寸。可以
以此值作为PE 文件第一节的文件偏移量。
Subsystem NT 用来识别PE 文件属于哪个子系统。对于大多数Win32程序,只有两类值:
Windows GUI 和 Windows CUI (控制台)。
DataDirectory IMAGE_DATA_DIRECTORY 结构数 。每个结构给出一个重要数据结构的
RVA,比如引入地址表等。在PE header 中,有很多地址指针是用RVA 来表示的。 RVA 代
表相对虚拟地址 (Relative Virtual Address)。简言之,RVA 是虚拟空间中到参考点的
一段距离,类似文件偏移量。当然它是相对虚拟空间里的一个地址,而不是文件头部。举
例来说,如果PE 文件装入虚拟地址(VA)空间的400000h 处,且进程从虚址401000h 开始
执行,我们可以说进程执行起始地址在RVA 1000h。每个RVA 都是相对于模块的起始VA
(Virtual Address)而言的。
2.2.3、Option Header 的中的重要数据成员 Data Directory:
Data Directory 定义为IMAGE_DATA_DIRECTORY 结构体数 ,每个数据元素给出一个重
要数据结构的RVA (Relative Visual Address相对虚地址),比如引入表地址、重定位
表地址。通常共16个成员 。(详细的宏定义请参阅WIN32 SDK 的WINNT.H 头文件)
这个数据起到让装载器迅速的在内存中找到特定的节(section)的作用,省去了遍历节表
的麻烦。
2. 3、Section Header:
紧接在PE header 的是section table (IMAGE_SECTION_HEADER)。每个表项包含有该节
的属性、偏移量等。如果PE 文件里有3个节,那么此数据结构就有3个元素。为了更好的
理解PE header和Section header在PE 文件中的组织关系,我们可以把PE 文件看作一
逻辑磁盘,PE header 是boot 扇区而sections是各种文件,节表视为逻辑磁盘中的根目
录。节表定义如下:
- typedef truct _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;
- }IMAGE_SECTION_HEADER,*PIMAGE_SECTION_HEADER;
重要数据成员的说明列表如下:
成员说明
Name 节名 长度不超过8字节。仅仅是个标记而已,注意这里不用null结束。
VirtualAddres 本节的RVA (相对虚拟地址)。PE装载器将节映射至内存时会读取本值。
如果域值是1000h,而PE 文件装在地址400000h 处,那么本节就被载到401000h。
SizeOfRawData 经过文件对齐处理后节尺寸,PE 装载器提取本域值了解需映射入内存的
节字节数。
PointerToRawData 这是节基于文件的偏移量,PE装载器通过本域值找到节数据在文件中
的位置。
----------------------- Page 4-----------------------
Characteristics 包含标记以指示节属性,比如节是否含有可执行代码、初始化数据、未
初始数据,是否可写、可读等。
2.4、Section域
节(section)是PE 文件真正内容的划分。每一节是是拥有共同属性的数据的集合。每节
都有相应的命名,不过这个命名不是太重要,只要是相同属性的内容都可以放进一节,命
名只是便于识别。下面重点介绍一下一些重要的段。
2.4.1、text section
此节一般包含有连接器连接的所有obj 目标文件的执行代码。这个执行代码块是一个大
的.text。不同于在DOS下面的执行文件可以分成几部分。如果是使用的Borland C++,
其编译器将产生的代码存于名为CODE 的区域,连接器连接到名为CODE 而不是.text 的
节中。
2.4.2、data section
.data 是初始化的数据块。这些数据块包括编译时被初始化的字符串常量、全局(globle)
和静 (static)变量。
2.4.3、bss section
任何没有初始化的全局和局部变量都会存放到.bss节中。这个节并不占用文件的储藏空
间,所以 RawDataOffset 总是为0。
2.4.4、rsrc section
该节包含模块的全部资源。如图标、菜单、位图等等。
2.4.5、idata section
.idata 包含其他外来的如DLL 中的函数及数据信息。PE 文件的每一个输入函数都明确的
列于该节中。
2.4.6、edata section
与.idata对应,.edata 是该PE 文件输出函数和数据的列表,以供其他模块引用。有的
PE 文件没有引出函数或数据,也就没有该节。
3、其余部分:
在节 (Sections)的后面是COFF 符号表格、COFF调试信息、COFF 行号信息。这些域对
我们的作用不大,有兴趣的读者可参阅
利用PE数据目录的导入表获取函数名及其地址的更多相关文章
- PE知识复习之PE的绑定导入表
PE知识复习之PE的绑定导入表 一丶简介 根据前几讲,我们已经熟悉了导入表结构.但是如果大家尝试过打印导入表的结构. INT IAT的时候. 会出现问题. PE在加载前 INT IAT表都指向一个名称 ...
- 在C语言中以编程的方式获取函数名
仅仅为了获取函数名,就在函数体中嵌入硬编码的字符串,这种方法单调乏味还易导致错误,不如看一下怎样使用新的C99特性,在程序运行时获取函数名吧. 对象反射库.调试工具及代码分析器,经常会需要在运行时访问 ...
- python获取函数名
Date: 20140223Auth: Jin 参考: http://hi.baidu.com/greysign/item/d11919d325c4c2e6b2f777bf 获取函数名python中获 ...
- vc 获取函数名称真实地址
首先写一个很简单的main函数: int main(){ printf("main的地址(?):%08x",main); } 单步调试,可得知 main函数的真实入口地址是:00b ...
- PE文件 01 导入表
0x01 导入表结构 数据目录表中的第二个成员标记了导入表的RVA和Size大小,由此可以定位到导入表: typedef struct _IMAGE_DATA_DIRECTORY { DWORD ...
- Python3基础 from...import...as 解决局部导入时的函数名重复问题
Python : 3.7.3 OS : Ubuntu 18.04.2 LTS IDE : pycharm-community-2019.1.3 ...
- go源码分析(五) 获取函数名和调用者的函数名
参考资料 实现代码保存在我的github // input flag 1:FunName 2:CallerFunName func GetFuncName(flag int) string { ...
- 函数名&函数名取地址
有时看到如下的代码: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /*****************************/ #includ ...
- Windows PE 第四章 导入表
第四章 导入表 导入表是PE数据组织中的一个很重要的组成部分,它是为实现代码重用而设置的.通过分析导入表数据,可以获得诸如OE文件的指令中调用了多少外来函数,以及这些外来函数都存在于哪些动态链接库里等 ...
随机推荐
- 【转载!】关于C#的RawSocket编程的问题
Q:你好! 看过了你在csdn上发表的<用C#下的Raw Socket编程实现网络封包监视>,觉得很感兴趣,而且对我的帮助很大.不过在调试的过程中遇到一些问题,特此向你请教一下.谢谢! 首 ...
- 第五章 管理程序流(In .net4.5) 之 异常处理
1. 概述 本章包括.net4.5中异常处理相关的部分. 2. 主要内容 2.1 处理异常 ① try.cahtch.finally 机制,无需多言. ② 使用 Environment.FailFas ...
- java获取数据库里表的名字
一.Java方法 // 得到当前数据库下所有的表名 public void getTableNameByCon(Connection con) { try { DatabaseMetaData met ...
- EMVTag系列9《卡片管理数据》
Ø 5F30 服务码 F: n 3 T: 5F30 L: 2 -O(可选):可选数据元 按GB/T 17552标准,卡片中的服务码(5F30) 的值,要和二磁道等效数据57中的服务码的值完全一 ...
- DB2执行脚本
经常会遇到数据库脚本放在.sql文件中,那么怎么去执行这个脚本,而不需要将脚本中的东西粘贴出来再数据库链接工具中执行呢? 下面是DB2数据库脚本执行的办法 环境介绍: 脚本文件名:Script.sql ...
- Objective - C中属性和点语法的使用
一.属性 属性是Objective—C 2.0定义的语法,为实例变量提供了setter.getter方法的默认实现能在一定程度上简化程序代码,并且增强实例变量的访问安全性 ...
- rm -rf删除过多文件提示参数过长
cd /var/tmp/ find . -name "*.log"|xargs rm -rf "*.log"
- Entity Framework 学习第一天
文章是作为初学者记录之用,没有学习过的同学可以借鉴一下,至于用过和高手嘛,就算了吧.仅是入门.废话不多说了,马上新建个项目,添加Entity Framework,这个词以下将用EF代替. 本文使用的I ...
- As.net WebAPI CORS, 开启跨源访问,解决错误No 'Access-Control-Allow-Origin' header is present on the requested resource
默认情况下ajax请求是有同源策略,限制了不同域请求的响应. 例子:http://localhost:23160/HtmlPage.html 请求不同源API http://localhost:228 ...
- 67.ARP协议
ARP是地址解析协议Address Resolution Protocol的缩写.是一个位于TCP/IP协议栈中的低层协议,负责将某个IP地址解析程对应的MAC地址.在局域网中,网络实际传输的是“帧” ...