PE目录项之导出表(解析、移动)

PE目录项之导出表(解析、移动)

0.说明

观看滴水逆向视频总结(部分截图来自于滴水课件)

编译器:vc++6.0

编写语言:c

欢迎大家留言交流或询问😃^ __ ^

有不懂的直接留言,我必回!😄🤣

1.简述导出表

导出表,顾名思义就是要导出的函数的表。为别人提供函数,供别人调用。就要根据这种表,将函数导出。

所以这张表,就是提供一个要导出的函数的清单,方便导出。

一般是dll为exe提供函数,dll有导出表,但是exe自身也可以为别人提供函数,也是通过导出表。

a.位置

目录项的第一个结构就是导出表

在这里插入图片描述

IMAGE_DIRECTORY_ENTRy_EXPORt意思
VirtualAddress这个表结构的RVA
Size这个表结构的大小

我们根据他给的VirtualAddress找到对应的表结构。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KZ1SV13t-1592919607115)(E:%5CTypora%5Cimage%5Cimage-20200622140942338.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L7DLcGhK-1592919607116)(E:\Typora\image\image-20200622173546610.png)]

b.导出表的结构

struct_IMAGE_EXPORT_DIRECTORY作用
Characteristics(未使用)
TimeDateStamp时间戳
MajorVersion(未使用)
MinorVersion(未使用)
Name指向该导出表文件名字符串
Base导出函数起始序号(基值)
NumberOfFuctions所有导出函数的个数
NumberOfNames以函数名字导出的函数个数
AddressOfFunctions所有导出函数的地址表的RVA
AddressOfNames导出函数名称表的RVA
AddressOfNameOrdinals导出函数序号表的RVA

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fg6u8dyv-1592919607119)(E:\Typora\image\image-20200622141132071.png)]

前面四个不用怎么管,后面红色的要全部记熟,结构中最后面的三个RVA分别指向三个表结构。根据箭头指向,表结构大概如图所示。

注意:AddressOfNameOrdinals指向的表结构,宽度为2个字节,也就是每个序号站2个字节。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kq2y4eoM-1592919607121)(E:\Typora\image\image-20200622144150278.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qqCcVJXN-1592919607122)(E:\Typora\image\image-20200622144239851.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mNSuPLl3-1592919607123)(E:\Typora\image\image-20200622144258392.png)]

c.导出表的工作方式

导出表的意义是为别人 提供一个要导出的函数的清单,方便别人调用。一般有两种方式。

① 根据函数名导出

  1. 一般用调用者提供一个函数名,根据这个函数名,

  2. 挨个遍历AddressOfName里面的RVA指向的字符串,去对比这个函数名

  3. 若相等,再根据这个RVA在AddressOfNames表中的排序序号,对照获得AddressOfNameOrdinals表中相同排序序号的值,

  4. 再根据这个值作为排序序号查找AddressOfFunctions表中相同排序序号的值, 这个值是RVA,指向这个函数的真正地址。

注意这里的排序序号都是从0开始的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ny3QY7QZ-1592919607124)(E:\Typora\image\image-20200622145330767.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3WjiY2la-1592919607125)(E:\Typora\image\image-20200622145425550.png)]

② 根据序号导出函数

  1. 调用者提供一个序号
  2. 用这个序号值减去base值得到一个新值
  3. 根据这个新值作为AddressOfFuctions表中的排序序号,直接查找函数RVA,即指向这个函数的真正地址。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NX4Bp9UC-1592919607126)(E:\Typora\image\image-20200622150338229.png)]

③ 总结

  1. 我们发现,虽然导出表最后有三张表,但是,如果按照序号导出函数的话,只需要一张表AddressOfFuctions,而另外两张表,我完全是为了方便通过函数名导出函数而建造的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wd3TnOqX-1592919607127)(E:\Typora\image\image-20200622150702059.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kzj1NheK-1592919607128)(E:\Typora\image\image-20200622150719742.png)]

2.解析导出表

a.注意要点

  1. 导出表的结构比较复杂,整个结构是牵连在一起的,要自己去画图或者自己去解析依次后才能有一个抽象到具体的认识。
  2. 导出表中涉及到的VirtualAddress全是RVA,在内存中寻址时,要注意先把RVA转换成FOA,在通过FOA这个偏移去寻址。所以自己编写一个独立的函数ConvertRvaToFoa将RVA转换为FOA。(具体见源代码中)
  3. 对指针的运用要求比较高
  4. 函数中会涉及到很多结构体的定义,均来自头文件windows.h。(准确的来说是winnt.h)。

b.源代码

编写语言:C

编译环境:VC++6.0

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xYmMBr2H-1592919607129)(E:\Typora\image\image-20200622192154553.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LzdTDqWC-1592919607130)(E:\Typora\image\image-20200622194155681.png)]

#include<stdio.h>
#include<windows.h>

char FileName[200]={0};//用于存放我们要解析的程序的地址

//功能:将相应程序读取到内存
//参数:指向程序名字(地址)指针
//返回值:指向该程序被读取到内存的指针
LPVOID ReadPeFile( char FileName[]);

//功能:解析、打印导出表
//参数:指向该程序被读取到内存的指针
//返回值:无
void ReadPE( LPVOID pFileBuffer );

//功能:将RVA转化为FOA
//参数:参数1:要转换的RVA的值(size_t);参数2:指向文件缓冲区的指针(LPVOID)
//返回值:成功则返回对应的FOA,失败则返回0(这个RVA是没有对应的FOA,为内存拉伸后系统填充的地址,或者RVA超出内存范围)。
size_t ConvertRvaToFoa( size_t RVA , LPVOID pFileBuffer );

int main()
{
	LPVOID pFileBuffer = NULL;

	printf("please input:    (for example: D:/user/Desktop/学习笔记/2020.6.22_PE目录项之导出表/myDll.dll  )\n");
	
	gets(FileName);
	
	pFileBuffer = ReadPeFile( FileName );

	if( pFileBuffer )
		ReadPE( pFileBuffer );
	
}

//功能:将相应程序读取到内存
//参数:指向程序名字(地址)指针
//返回值:指向该程序被读取到内存的指针
LPVOID ReadPeFile( char FileName[] )
{
	LPVOID pFileBuffer = NULL;
	FILE* pFile = NULL;
	DWORD FileSize = 0;

	pFile = fopen( FileName , "rb");
	if(!pFile)
	{
		printf("open file failure!\n");
		return NULL;
	}

	fseek(pFile , 0 , SEEK_END);
	FileSize = ftell(pFile);
	fseek(pFile , 0 , SEEK_SET);
	
	pFileBuffer = calloc(1 , FileSize);
	if(!pFileBuffer)
	{
		printf("Failure to allocate memory space!\n");
		fclose(pFile);
		return NULL;
	}

	fread(pFileBuffer , FileSize , 1 ,pFile);

	fclose(pFile);

	return pFileBuffer ;
}


//功能:解析、打印导出表
//参数:指向该程序被读取到内存的指针
//返回值:无
void ReadPE( LPVOID pFileBuffer )
{
	PIMAGE_DOS_HEADER pDosHeader = NULL;
	PIMAGE_NT_HEADERS32 pNtHeader = NULL;
	PIMAGE_FILE_HEADER pFileHeader = NULL;
	PIMAGE_OPTIONAL_HEADER pOptionHeader = NULL;
	
	PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;//定义目录项导出表结构的指针
	PIMAGE_EXPORT_DIRECTORY pExportDirectory = NULL;//定义指向真正导出表的指针

	size_t i;
	PDWORD pAddressOfFunctions;//指向函数地址表
	PWORD pAddressOfNameOrdinals;//指向函数序号表
	PDWORD pAddressOfNames;//指向函数名字表

	pDosHeader = (PIMAGE_DOS_HEADER )pFileBuffer ; 
	pNtHeader = (PIMAGE_NT_HEADERS32 )( (DWORD)pFileBuffer + pDosHeader->e_lfanew );
	pFileHeader = (PIMAGE_FILE_HEADER )( (DWORD)pNtHeader + 4 );
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER )( (DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER );
	
	pDataDirectory = (PIMAGE_DATA_DIRECTORY)pOptionHeader->DataDirectory;//目录项结构


	//注意将RVA转换为FOA
	pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)( (DWORD)pDosHeader+ ConvertRvaToFoa(pDataDirectory->VirtualAddress, pFileBuffer ) );
	
	printf("\n");
	
	//解析导出表的结构
	printf("Name: %X\n",pExportDirectory->Name );
	printf("Base: %X\n",pExportDirectory->Base );
	printf("NumberOfFunctions: %X\n",pExportDirectory->NumberOfFunctions );
	printf("NumberOfNames : %x\n\n",pExportDirectory->NumberOfNames );
	
	//打印AddressOfFunctions这个表结构。
	printf("****** AddressOfFuctions ******\n");
	pAddressOfFunctions = (PDWORD)( (DWORD)pDosHeader + ConvertRvaToFoa( pExportDirectory->AddressOfFunctions , pFileBuffer ) ) ;
	for(i=0 ; i < pExportDirectory->NumberOfFunctions ; i++ ,pAddressOfFunctions++ )
		printf("RVA_%d : %x\n",i, *pAddressOfFunctions );
	printf("*******************************\n\n");

	//打印AddressOfNameOrdinals这个表结构
	printf("****** AddressOfNameOrdinals ******\n");
	pAddressOfNameOrdinals = (PWORD)( (DWORD)pDosHeader + ConvertRvaToFoa( pExportDirectory->AddressOfNameOrdinals , pFileBuffer ) );
	for(i=0 ; i<pExportDirectory->NumberOfNames ; i++ ,pAddressOfNameOrdinals++ )
		printf("Ordinal_%d : %d\n",i, *pAddressOfNameOrdinals );
	printf("***********************************\n\n");
	
	//打印AddressOfNames这表结构
	printf("****** AddressOfNames ******\n");
	pAddressOfNames = (PDWORD)( (DWORD)pDosHeader + ConvertRvaToFoa( pExportDirectory->AddressOfNames , pFileBuffer ) );
	for(i=0 ; i<pExportDirectory->NumberOfNames ; i++ ,pAddressOfNames++)
	{
	//	printf("Name_%d : ",i);
	//	printf("%x ",*pAddressOfNames);
		printf("Name_%d : %-10s ( RVA :%-8x)\n",i ,(PDWORD)( (DWORD)pDosHeader+ ConvertRvaToFoa(*pAddressOfNames ,pFileBuffer) ) , *pAddressOfNames );
	
	}
	printf("****************************\n\n");

	free( pFileBuffer );
}

//功能:将RVA转化为FOA
//参数:参数1:要转换的RVA的值(size_t);参数2:指向文件缓冲区的指针(LPVOID)
//返回值:成功则返回对应的FOA,失败则返回0(这个RVA是没有对应的FOA,为内存拉伸后系统填充的地址,或者RVA超出内存范围)。
size_t ConvertRvaToFoa( size_t RVA , LPVOID pFileBuffer )
{
	int i ;//用于遍历节表
//	size_t FOA = 0;

	//定义表头指针
	PIMAGE_DOS_HEADER pDosHeader = NULL;
	PIMAGE_NT_HEADERS32 pNtHeader = NULL;
	PIMAGE_FILE_HEADER pFileHeader = NULL;
	PIMAGE_OPTIONAL_HEADER pOptionHeader = NULL;
	PIMAGE_SECTION_HEADER pSectionHeader = NULL;	

	//给表头赋初值
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	pNtHeader = (PIMAGE_NT_HEADERS32)((DWORD)pDosHeader + pDosHeader->e_lfanew);
	pFileHeader = (PIMAGE_FILE_HEADER)( (DWORD)pNtHeader+4 );
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER)( (DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);	
	//第一个节表头
	pSectionHeader = (PIMAGE_SECTION_HEADER)( (DWORD)pOptionHeader + pFileHeader->SizeOfOptionalHeader);
	
	if(RVA < pSectionHeader->VirtualAddress)//判断RVA是否在PE头区
	{
		if(RVA < pSectionHeader->PointerToRawData)
			return RVA;//此时FOA == RVA
		else
			return 0;
	}

	for(i=0 ; i<pFileHeader->NumberOfSections ; i++)//循环遍历节表头
	{
		if( i )//遍历节表头,第一次不遍历,
			pSectionHeader = (PIMAGE_SECTION_HEADER)( (DWORD)pSectionHeader + IMAGE_SIZEOF_SECTION_HEADER );
		
		if(RVA >= pSectionHeader->VirtualAddress )//是否大于这个节表的RVA
		{
			if( RVA <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData )//判断是否在这个节区
				return ( RVA - pSectionHeader->VirtualAddress ) + pSectionHeader->PointerToRawData;//确定节区后,计算FOA
		}
		else//RVA不可能此时的pSectionHeader->VirtuallAddress小,除非是返回值为0的情况。
			return 0;		
	}
		
	return 0;
}

3.移动导出表所有结构到新建的节区

a.移动导出表的原因

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1OQrZh2k-1592919607131)(E:\Typora\image\image-20200622231321796.png)]

b.大概流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t42abtKI-1592919607132)(E:\Typora\image\image-20200622230947327.png)]

新增节的部分前面几章讲过,这里就就直接贴代码过来(稍稍修改)IncreaseSection。

c.注意事项

  1. 有很多个地方,寻址的时候要将RVA转换为FOA在转换为现在内存中的真实位置。
  2. 在修正RVA的时候,注意要将此时的FOA转为RVA
  3. 反正有就是FOA和RVA互转,很多。
  4. 表中的地址是个RVA,并且是DWORD类型的,所以要转为指针才能调用,也有很多地方要强转类型,特别繁琐。
  5. 特别注意:在复制字符串的时候,一定要在字符串的末尾加上一个0,代表字符串末尾。

可能刚开始有很多疑问,建议直接动手写。

d.源代码

如何其他数据都移动成功了,但是新的dll还是调用不了,那很有可能是新增节那一块出问题了!

#include<stdio.h>
#include<windows.h>
#include<string.h>

char FileName[200]={0};
DWORD FileSize = 0;
DWORD NewFileSize = 0;

//功能:将相应程序读取到内存
//参数:指向程序名字(地址)指针
//返回值:指向该程序被读取到内存的指针
LPVOID ReadPeFile( char FileName[]);

//功能:移动导出表中所有结构到新建的节区
//参数:指向该程序被读取到内存的指针
//返回值:返回一个新的pNewFileBuffer(重点:末尾节区已经复制的导出表的所有结构),失败则返回NULL。
LPVOID MoveExportDirectory( LPVOID pFileBuffer );

//功能:将RVA转化为FOA
//参数:参数1:要转换的RVA的值(DWORD);参数2:指向文件缓冲区的指针(LPVOID)
//返回值:成功则返回对应的FOA,失败则返回0(这个RVA是没有对应的FOA,为内存拉伸后系统填充的地址,或者RVA超出内存范围)
DWORD ConvertRvaToFoa( size_t RVA , LPVOID pFileBuffer );

//功能:将FOA转换为RVA
//参数:参数1:要转换的FOA值(DWORD);参数2:指向文件内存的指针(LPVOID);
//返回值:成功则返回对应的RVA,失败返回0(此时为FOA越界)。
DWORD ConvertFoaToRva( size_t FOA , LPVOID pFileBuffer );

//功能:添加一个节表和节区
//参数:指向文件读取到内存的指针
//返回值:返回一个新的pNewFileBUffer(重点:末尾有个新增节且空白),失败则返回NULL
LPVOID IncreaseSection( LPVOID pFileBuffer );

//功能:将内存中的数据写入文件
//参数:指向内存中数据的指针
//返回值:无
void WritePeFile(LPVOID pFileBuffer);


int main()
{
	LPVOID pFileBuffer = NULL;
	
	LPVOID pNewFileBuffer = NULL;

	printf("please input:    (for example: D:/user/Desktop/学习笔记/2020.6.22_PE目录项之导出表/myDll.dll  )\n");
	
	gets(FileName);
	
	pFileBuffer = ReadPeFile( FileName );

	if( pFileBuffer )
		pNewFileBuffer = MoveExportDirectory( pFileBuffer );

	if( pNewFileBuffer )
		WritePeFile( pNewFileBuffer );


}

//功能:将相应程序读取到内存
//参数:指向程序名字(地址)指针
//返回值:指向该程序被读取到内存的指针
LPVOID ReadPeFile( char FileName[] )
{
	LPVOID pFileBuffer = NULL;
	FILE* pFile = NULL;


	pFile = fopen( FileName , "rb");
	if(!pFile)
	{
		printf("open file failure!\n");
		return NULL;
	}

	fseek(pFile , 0 , SEEK_END);
	FileSize = ftell(pFile);
	fseek(pFile , 0 , SEEK_SET);
	
	pFileBuffer = calloc(1 , FileSize);
	if(!pFileBuffer)
	{
		printf("Failure to allocate memory space!\n");
		fclose(pFile);
		return NULL;
	}

	fread(pFileBuffer , FileSize , 1 ,pFile);

	fclose(pFile);

	return pFileBuffer ;
}


//功能:移动导出表中所有结构到新建的节区
//参数:指向该程序被读取到内存的指针
//返回值:返回一个新的pNewFileBuffer(重点:末尾节区已经复制的导出表的所有结构),失败则返回NULL。
LPVOID MoveExportDirectory( LPVOID pFileBuffer )
{
	LPVOID pNewFileBuffer ;

	PIMAGE_DOS_HEADER pDosHeader = NULL;
	PIMAGE_NT_HEADERS32 pNtHeader = NULL;
	PIMAGE_FILE_HEADER  pFileHeader = NULL;
	PIMAGE_OPTIONAL_HEADER pOptionHeader = NULL;

	PIMAGE_DATA_DIRECTORY pDataDirectory = NULL;
	PIMAGE_EXPORT_DIRECTORY pExportDirectory =NULL;

	DWORD i;
//	PDWORD pAddressOfFunctions = NULL;//函数地址表指针
//	PWORD pAddressOfNameOrdinals = NULL;//函数序号表指针
	PDWORD pAddressOfNames = NULL;//指向函数名字表指针

	DWORD Offset = 0;//复制导出表结构的位置至DosHeader的偏移,相当于FOA

	//第一步:在dll新增一个节区,并返回新增节区的FOA
	pNewFileBuffer = IncreaseSection( pFileBuffer );
	if(!pNewFileBuffer)
	{
		printf("sorry , increase section failure!\n");
		free( pFileBuffer );
		return NULL;
	}	
	Offset = FileSize ;//偏移至新建的节区。
	

	//读取这个PE结构
	pDosHeader = (PIMAGE_DOS_HEADER)pNewFileBuffer ;
	pNtHeader = (PIMAGE_NT_HEADERS32)( (DWORD)pDosHeader + pDosHeader->e_lfanew );
	pFileHeader = (PIMAGE_FILE_HEADER)( (DWORD)pNtHeader + 4);
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER)( (DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);

	pDataDirectory = (PIMAGE_DATA_DIRECTORY)pOptionHeader->DataDirectory ;
	pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)( (DWORD)pDosHeader +  ConvertRvaToFoa( pDataDirectory->VirtualAddress , pNewFileBuffer) );//RVA转FOA再转现在内存中的真实地址



	
	//第二步:将导出表那张大的数据表给复制到新增节区
	//同时修正对应目录项中的VirtualAddress
	memcpy( (PDWORD)( (DWORD)pDosHeader + Offset) , pExportDirectory , pDataDirectory->Size );
	pDataDirectory->VirtualAddress = ConvertFoaToRva( Offset , pNewFileBuffer );//注意FOA转为RVA


	//修正内存中导出表的位置
	pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)( (DWORD)pDosHeader + Offset) ;

	//修正偏移
	Offset += pDataDirectory->Size ;
	
//第三步:移动导出表所在的文件名到新到新节区
	//同时修正导出表中的Name
	memcpy( (PDWORD)( (DWORD)pDosHeader + Offset) , (PDWORD)( (DWORD)pDosHeader + ConvertRvaToFoa( pExportDirectory->Name ,pNewFileBuffer) ) , strlen( (PDWORD)( (DWORD)pDosHeader + ConvertRvaToFoa( pExportDirectory->Name ,pNewFileBuffer) ) )  );//注意RVA转FOA再转现在内存中的真实地址
	pExportDirectory->Name = ConvertFoaToRva( Offset , pNewFileBuffer );
	
	//修正偏移
	Offset += strlen( (PDWORD)( (DWORD)pDosHeader + ConvertRvaToFoa( pExportDirectory->Name , pNewFileBuffer) )  ) + 1;

	//第四步:将函数地址表挨着给复制过去
	//同时修正导出表中的AddressOfFunctions
	memcpy( (PDWORD)( (DWORD)pDosHeader + Offset) , (PDWORD)( (DWORD)pDosHeader + ConvertRvaToFoa( pExportDirectory->AddressOfFunctions ,pNewFileBuffer) ) , 4 * pExportDirectory->NumberOfFunctions );//注意RVA转FOA再转现在内存中的真实地址
	pExportDirectory->AddressOfFunctions = ConvertFoaToRva( Offset , pNewFileBuffer );//注意FOA转为RVA

	//修正偏移
	Offset += 4 * pExportDirectory->NumberOfFunctions ;

	//第五步:将函数序号表挨着复制过去
	//同时修正导出表中的AddressOfNameOrdinals
	memcpy( (PDWORD)( (DWORD)pDosHeader + Offset) , (PDWORD)( (DWORD)pDosHeader + ConvertRvaToFoa( pExportDirectory->AddressOfNameOrdinals , pNewFileBuffer ) ) , 2 * pExportDirectory->NumberOfNames );//注意RVA转FOA再转现在内存中的真实地址
	pExportDirectory->AddressOfNameOrdinals = ConvertFoaToRva( Offset , pNewFileBuffer );//注意FOA转RVA

	//修正偏移
	Offset += 2 * pExportDirectory->NumberOfNames ;

	//第六步:将函数名称表挨着复制过去
	//同时修正导出表中的AddressOfNames
	memcpy( (PDWORD)( (DWORD)pDosHeader + Offset) , (PDWORD)( (DWORD)pDosHeader + ConvertRvaToFoa( pExportDirectory->AddressOfNames ,pNewFileBuffer) ) , 4 * pExportDirectory->NumberOfNames  );//注意RVA转FOA再转现在内存中的真实地址
	pExportDirectory->AddressOfNames = ConvertFoaToRva( Offset , pNewFileBuffer ) ;//注意FOA转RVA

	//修正偏移
	Offset += 4 * pExportDirectory->NumberOfNames ;

	//第七步:将函数名称表中指向的名称也挨着复制过去
	//同时修正函数名称表
	pAddressOfNames = (PDWORD)( (DWORD)pDosHeader + ConvertRvaToFoa( pExportDirectory->AddressOfNames ,pNewFileBuffer) );//注意RVA转FOA再转现在内存中的真实地址
	for( i = pExportDirectory->NumberOfNames ; i>0 ; i--,pAddressOfNames++ )//遍历函数名字表,循环复制函数名过来
	{
		memcpy( (PDWORD)( (DWORD)pDosHeader + Offset) , (PDWORD)( (DWORD)pDosHeader + ConvertRvaToFoa( *pAddressOfNames , pNewFileBuffer) ), strlen( (PDWORD)( (DWORD)pDosHeader + ConvertRvaToFoa( *pAddressOfNames ,pNewFileBuffer) ) )    );//注意RVA转FOA再转现在内存中的真实地址
		*pAddressOfNames = ConvertFoaToRva( Offset ,pNewFileBuffer );	//注意FOA转RVA
		//修正偏移
		Offset += strlen( (PDWORD)( (DWORD)pDosHeader + ConvertRvaToFoa( *pAddressOfNames ,pNewFileBuffer) ) ) +1;//偏移多加1,给名字留个末尾0
	}
	
	pDataDirectory->Size = 0x500 ;//NewFileSize - FileSize ;

	return pNewFileBuffer;
}

//功能:将RVA转化为FOA
//参数:参数1:要转换的RVA的值(DWORD);参数2:指向文件缓冲区的指针(LPVOID)
//返回值:成功则返回对应的FOA,失败则返回0(这个RVA是没有对应的FOA,为内存拉伸后系统填充的地址,或者RVA超出内存范围)。
DWORD ConvertRvaToFoa( size_t RVA , LPVOID pFileBuffer )
{
	int i ;//用于遍历节表
//	size_t FOA = 0;

	//定义表头指针
	PIMAGE_DOS_HEADER pDosHeader = NULL;
	PIMAGE_NT_HEADERS32 pNtHeader = NULL;
	PIMAGE_FILE_HEADER pFileHeader = NULL;
	PIMAGE_OPTIONAL_HEADER pOptionHeader = NULL;
	PIMAGE_SECTION_HEADER pSectionHeader = NULL;	

	//给表头赋初值
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	pNtHeader = (PIMAGE_NT_HEADERS32)((DWORD)pDosHeader + pDosHeader->e_lfanew);
	pFileHeader = (PIMAGE_FILE_HEADER)( (DWORD)pNtHeader+4 );
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER)( (DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);	
	//第一个节表头
	pSectionHeader = (PIMAGE_SECTION_HEADER)( (DWORD)pOptionHeader + pFileHeader->SizeOfOptionalHeader);
	
	if(RVA < pSectionHeader->VirtualAddress)//判断RVA是否在PE头区
	{
		if(RVA < pSectionHeader->PointerToRawData)
			return RVA;//此时FOA == RVA
		else
			return 0;
	}

	for(i=0 ; i<pFileHeader->NumberOfSections ; i++)//循环遍历节表头
	{
		if( i )//遍历节表头,第一次不遍历,
			pSectionHeader = (PIMAGE_SECTION_HEADER)( (DWORD)pSectionHeader + IMAGE_SIZEOF_SECTION_HEADER );
		
		if(RVA >= pSectionHeader->VirtualAddress )//是否大于这个节表的RVA
		{
			if( RVA <= pSectionHeader->VirtualAddress + pSectionHeader->SizeOfRawData )//判断是否在这个节区
				return ( RVA - pSectionHeader->VirtualAddress ) + pSectionHeader->PointerToRawData;//确定节区后,计算FOA
		}
		else//RVA不可能此时的pSectionHeader->VirtuallAddress小,除非是返回值为0的情况。
			return 0;		
	}
		
	return 0;
}

//功能:将FOA转换为RVA
//参数:参数1:要转换的FOA值(DWORD);参数2:指向文件内存的指针(LPVOID);
//返回值:成功则返回对应的RVA,失败返回0(此时为FOA越界)。
DWORD ConvertFoaToRva( size_t FOA , LPVOID pFileBuffer )
{
	int i = 0;//用于遍历节表。
//	size_t RVA = 0;
	
	//定义表头指针
	PIMAGE_DOS_HEADER pDosHeader = NULL;
	PIMAGE_NT_HEADERS32 pNtHeader = NULL;
	PIMAGE_FILE_HEADER pFileHeader = NULL;
	PIMAGE_OPTIONAL_HEADER pOptionHeader = NULL;
	PIMAGE_SECTION_HEADER pSectionHeader = NULL;

	//给表头赋初值
	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	pNtHeader = (PIMAGE_NT_HEADERS32)( (DWORD)pDosHeader + pDosHeader->e_lfanew  );
	pFileHeader = (PIMAGE_FILE_HEADER)( (DWORD)pNtHeader + 4 );
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER)( (DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER);
	//第一个节表头
	pSectionHeader = (PIMAGE_SECTION_HEADER)( (DWORD)pOptionHeader + pFileHeader->SizeOfOptionalHeader );

	if( FOA < pSectionHeader->PointerToRawData )//判断是否位于 头区
		return FOA ; //这是RVA == FOA ;
	
	for(i=0 ; i<pFileHeader->NumberOfSections ; i++)//循环遍历节表头
	{
		if( i )//遍历节表头,第一次不遍历,
			pSectionHeader = (PIMAGE_SECTION_HEADER)( (DWORD)pSectionHeader + IMAGE_SIZEOF_SECTION_HEADER);

		if( FOA >= pSectionHeader->PointerToRawData )//是否大于这个节表的FOA
		{
			if( FOA < pSectionHeader->PointerToRawData + pSectionHeader->SizeOfRawData )//判断是否在这个节表区域
				return (FOA - pSectionHeader->PointerToRawData ) + pSectionHeader->VirtualAddress ;//计算并返回RVA
		}			
	}

	return 0;
}



//功能:添加一个节表和节区
//参数:指向文件读取到内存的指针
//返回值:返回一个新的pNewFileBUffer(重点:末尾有个新增节且空白),失败则返回NULL
LPVOID IncreaseSection( LPVOID pFileBuffer )
{
	LPVOID pNewFileBuffer = NULL;

	PIMAGE_DOS_HEADER pDosHeader = NULL;
	PIMAGE_NT_HEADERS32 pNtHeader = NULL;
	PIMAGE_FILE_HEADER pFileHeader = NULL;
	PIMAGE_OPTIONAL_HEADER pOptionHeader = NULL; 
	PIMAGE_SECTION_HEADER pSectionHeader = NULL;

	PIMAGE_SECTION_HEADER pSectionHeader_First = NULL;//多增加一个节表指针指向第一个节表首地址,查看PointerToRawData。
	PIMAGE_SECTION_HEADER pSectionHeader_Last = NULL;//定义最后一个节表指针,方便获取VirtualAddress 和 SizeOfRawData

	int i = 0;//循环节

	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
	
	pNtHeader = (PIMAGE_NT_HEADERS32)( (DWORD)pDosHeader + pDosHeader->e_lfanew );

	pFileHeader = (PIMAGE_FILE_HEADER)( (DWORD)pNtHeader + 4 );

	pOptionHeader = (PIMAGE_OPTIONAL_HEADER)( (DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER );

	pSectionHeader = (PIMAGE_SECTION_HEADER)( (DWORD)pOptionHeader + pFileHeader->SizeOfOptionalHeader );

	pSectionHeader_First = pSectionHeader ;

	//循环遍历,指向倒数第一个节表再往后移,指向我们将要添加节表 头 的位置 ,i>0。
	for(i=pFileHeader->NumberOfSections ; i>0 ; i-- )
		pSectionHeader = (PIMAGE_SECTION_HEADER)( (DWORD)pSectionHeader + IMAGE_SIZEOF_SECTION_HEADER );

	//先判断是否有节表的区域是否还有空间 用于添加节表。确保在SizeOfRawData范围内。
	//这里一般要空出两个节表位置,一个用于我们添加新节表,一个用于空的空间填充0(约定俗成)。
	if(  ((DWORD)pDosHeader + pSectionHeader_First->PointerToRawData) - (DWORD)pSectionHeader < 2*IMAGE_SIZEOF_SECTION_HEADER   )
	{	
		//如果没有足够空间,就考虑缩短 pDosHeader->e_lfanew,有没有足够空间,用于开辟新的空间。0x40为DOS头的大小
		if(  (pDosHeader->e_lfanew - 0x40) +  (  ((DWORD)pDosHeader + pSectionHeader_First->PointerToRawData) - (DWORD)pSectionHeader  ) > 2*IMAGE_SIZEOF_SECTION_HEADER  )
		{
			memcpy( (LPVOID)( (DWORD)pDosHeader+ 0x40) , pNtHeader , ( (DWORD)pSectionHeader- (DWORD)pFileHeader ) ); 
		
			//修改e_lfanew
			pDosHeader->e_lfanew = 0x40;

			//重新计算各头区的偏移
			pNtHeader = (PIMAGE_NT_HEADERS32)( (DWORD)pDosHeader + pDosHeader->e_lfanew );
			pFileHeader = (PIMAGE_FILE_HEADER)( (DWORD)pNtHeader + 4 );
			pOptionHeader = (PIMAGE_OPTIONAL_HEADER)( (DWORD)pFileHeader + IMAGE_SIZEOF_FILE_HEADER );
			pSectionHeader = (PIMAGE_SECTION_HEADER)( (DWORD)pOptionHeader + pFileHeader->SizeOfOptionalHeader );

			pSectionHeader_First = pSectionHeader ;

			//同样  //循环遍历,指向倒数第一个节表再往后移,指向我们将要添加节表的位置 ,i>0。
			for(i=pFileHeader->NumberOfSections ; i>0 ; i-- )
				pSectionHeader = (PIMAGE_SECTION_HEADER)( (DWORD)pSectionHeader + IMAGE_SIZEOF_SECTION_HEADER );

			//此时已经符合条件,有足够空间用于新增节表头。
			//即退出if语句后。
		}
		else//没有足够空间则不能添加节,flag=0,失败
		{
			free(pFileBuffer);//释放空间
			printf("Sorry,There is not enough space of new section header!\n");
			return NULL; ; 
		}
	}

	//复制一个节表头过来。
	memcpy( pSectionHeader , pSectionHeader_First , IMAGE_SIZEOF_SECTION_HEADER );

	//更改节表名.NewSec
	strcpy( pSectionHeader->Name,".NewSec");

	//Misc.VirtualSize = 0,那下面构造内存3的文件状态时,就不会往节区填充垃圾数据
	pSectionHeader->Misc.VirtualSize = 0;

	//SizeOfRawData = FileAlignment
	pSectionHeader->SizeOfRawData = pOptionHeader->FileAlignment ;
	
	//RVA
	pSectionHeader_Last = (PIMAGE_SECTION_HEADER)( (DWORD)pSectionHeader - IMAGE_SIZEOF_SECTION_HEADER);
	pSectionHeader->VirtualAddress = pSectionHeader_Last->VirtualAddress + ( (pSectionHeader_Last->SizeOfRawData-1)  / pOptionHeader->SectionAlignment + 1 )*pOptionHeader->SectionAlignment ;

	//PointerToRawData
	pSectionHeader->PointerToRawData = pSectionHeader_Last->PointerToRawData + pSectionHeader_Last->SizeOfRawData;

	//Characteristics,权限,相 或 即可,我给他加上数据段的权限0x42000040
	pSectionHeader->Characteristics = 0x40000040;

	//SizeOfImage 
	pOptionHeader->SizeOfImage += ( (pSectionHeader->SizeOfRawData-1) / pOptionHeader->FileAlignment +1 ) * pOptionHeader->SectionAlignment ;

	//NumberOfSection
	pFileHeader->NumberOfSections += 1;


//*********************************************************
//注意:下面的代码是调整后,新加的,需要特别注意。

	//更改文件大小,方便开辟新的空间 和 存盘。
	NewFileSize = FileSize + pSectionHeader->SizeOfRawData ;

	pNewFileBuffer = calloc( 1 , NewFileSize );
	if(!pNewFileBuffer)
	{
		printf("Sorry , Allocte space failed!\n");
		return NULL;
	}
	
	memcpy( pNewFileBuffer , pFileBuffer , FileSize);
	
	//释放原来的文件缓冲区。
	free(pFileBuffer);

	return pNewFileBuffer;
}


//功能:将内存中的数据写入文件
//参数:指向内存中数据的指针
//返回值:无
void WritePeFile(LPVOID pNewFileBuffer)
{
	FILE* pNewFile = NULL;

	char NewFileName[10]="_New";
	int len = 0;//文件名长度

	//更改新的文件名
	len = strlen( FileName );
	strcat(NewFileName , FileName + len - 4 );//构造一个文件尾
	strcpy(FileName + len - 4 , NewFileName );

	pNewFile = fopen( FileName , "wb");
	if(!pNewFile)
	{
		printf("file creation failed!\n");
		return ;
	}

	fwrite(pNewFileBuffer , NewFileSize , 1 , pNewFile);
	
	fclose(pNewFile);

	free(pNewFileBuffer);

}

欢迎大家留言交流^ _ ^

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值