PE工具编写

前几天把PE结构认真看了一下,顺便写了下类似peinfo的工具练练手,只不过是控制台的(windows sdk没怎么学,win32汇编也忘的差不多了,所以还是写成控制台的吧。。)。以前了解pe文件的时候总是走马观花,所以基本上印象全无。这次认认真真写写代码,收获还是比较大的。
说实话,pe结构的学习真的不难,但是非常考验耐心,特别是数据目录那里和资源结构那里,一层套一层,一定要耐心才行。
记下几个我认为重要的点,或者说收获:
0.rva和raw的转化
先看rva在哪个块(块表里面有一个块的起始rva和VirtualSize),块的起始raw也是可以得到的(也在对应的块表中)。然后raw=块起始raw+(rva-块起始rva)
简单吧。

1.c语言的指针

大家都知道,c语言的指针是所指向的变量的地址,那么这个地址到底是什么地址呢?va?还是rva?
由于我用的是SetFilePointer定位文件光标,再用ReadFile()把相关数据结构读取到内存,所以涉及到怎么定位光标的问题。这就需要知道要获取的数据结构的文件偏移,也就是raw。但是如下图所示,IMAGE_THUNK_DATA里面的AddressOfData是指向IMAGE_IMPORT_BY_NAME的指针,而不是raw。怎么办呢,我用winhex仔细看了下PE文件(把这个值当成rva,转化为raw,发现可以找到对应的IMAGE_IMPORT_BY_NAME),发现其实这个指针的值就是rva。
也就是说:c语言的指针就是所指向变量的rva

2.IMAGE_SECTION_HEADER中的MISC的VirtualSize域,下面这张图中说是未对齐时的真实大小,我觉得应该已经是内存地址空间中按页对齐时的大小(目前的看法)

3.关于IMAGE_IMPORT_DESCRIPTOR

简单来说,每个IMAGE_IMPORT_DESCRIPTOR对应一个导入的dll。以一个全0的IMAGE_IMPORT_DESCRIPTOR结束。

4.最后记一下pe加载到内存时windows加载器对导入表的修改

(1)windows加载器遍历pe文件,IMAGE_DOS_HEADER.elfanew—->IMAGE_NT_HEADERS.IMAGE_OPTIONAL_HEADER32.IMAGE_DATA_DIRECTORY[1].VirtualAddress—->IMAGE_IMPORT_DESCRIPTOR
IMAGE_IMPORT_DESCRIPTOR可能有多个,每个对应一个dll。
(2)然后根据IMAGE_IMPORT_DESCRIPTOR内的Name找到dll的名字,用loadlibrary加载这个dll。
(3)根据IMAGE_IMPORT_DESCRIPTOR的FirstThunk(是IAT数组的rva)找到IAT数组
(4)根据IAT数组元素IMAGE_THUNK_DATA32的AddressOfData找到IMAGE_IMPORT_BY_NAME(注意:IMAGE_THUNK_DATA32里面是一个联合体)
(5)这样就得到了导入函数的名字,然后用GetProcAdress()获取该函数的实际地址,填入IAT数组元素IMAGE_THUNK_DATA32,这样之后IAT数组里面的就是函数的真实(va)地址了

5.dll的加载:

显示:在需要的时候用LoadLibrary()和GetProcAddress()动态加载
隐式:windows加载器在PE文件映射到内存时填充IAT,加载对应dll文件

写的比较挫,暂时先主要把输入表中的函数名字显示出来,要显示其他内容也是一样的

—————————————————————————-
2015.2.6
突然觉得我写复杂了,其实可以一次性把PE文件全部读到一个字符串里面,不用一点一点读文件,不过这样也有好处,文件太大时前者行不通

#include<stdio.h>
#include<string.h>
#include<windows.h>
HANDLE hfile;
IMAGE_DOS_HEADER dos_head;
IMAGE_NT_HEADERS pe_head;
PIMAGE_FILE_HEADER pfile_head;
PIMAGE_OPTIONAL_HEADER32 poptional_head;
PIMAGE_DATA_DIRECTORY pdata_dir;
IMAGE_SECTION_HEADER sec_head[30];
IMAGE_IMPORT_DESCRIPTOR import[110];
IMAGE_THUNK_DATA32 thunk;//IAT
int dll_num;//导入的dll的个数
int sec_num;//块的个数
int ophead_size,dos_and_nt;
DWORD readsize;
DWORD rva2raw(DWORD rva)
{
	int i;
	DWORD sec_begin,sec_end;
	for(i=1;i<=sec_num;i++)
	{
		sec_begin=sec_head[i].VirtualAddress;sec_end=sec_head[i].VirtualAddress+sec_head[i].Misc.VirtualSize;
		if(rva<sec_begin || rva>sec_end) continue;
		return sec_head[i].PointerToRawData+rva-sec_head[i].VirtualAddress;
	}
	return 0;
}
void read_error(int ret)
{
	if(!ret) 
	{
		printf("read error\n");
		CloseHandle(hfile);
		exit(1);
		return;
	}
	return;
}
void read_sechead()
{
	int i,j;
	for(i=1;i<=sec_num;i++)
	{
		SetFilePointer(hfile,dos_and_nt+(i-1)*sizeof(IMAGE_SECTION_HEADER),0,FILE_BEGIN);
		int ret=ReadFile(hfile,&sec_head[i],sizeof(sec_head[i]),&readsize,0);
		read_error(ret);
	}
	return;
}
bool judge(IMAGE_IMPORT_DESCRIPTOR _import)//判断IMAGE_IMPORT_DESCRIPTOR是否读取完毕
{
	if(_import.OriginalFirstThunk==0 && _import.FirstThunk==0 && _import.ForwarderChain==0 && _import.Name==0 && _import.TimeDateStamp==0) return 0;
	return 1;
}
void read_import()
{
	dll_num=0;
	int src=rva2raw(pdata_dir[1].VirtualAddress);
	int offset=sizeof(IMAGE_IMPORT_DESCRIPTOR);
	while(1)
	{
		dll_num++;
		SetFilePointer(hfile,src+(dll_num-1)*offset,0,FILE_BEGIN);
		int ret=ReadFile(hfile,&import[dll_num],sizeof(IMAGE_IMPORT_DESCRIPTOR),&readsize,0);
		read_error(ret);
		if(!judge(import[dll_num])) {dll_num-=1;break;}
	}
	return;
}
void print_exp_and_imp()
{
	printf("type		rva			size\n");
	printf("导出表		%x			%x\n",pdata_dir[0].VirtualAddress,pdata_dir[0].Size);
	printf("导入表		%x			%x\n",pdata_dir[1].VirtualAddress,pdata_dir[1].Size);
	return;
}
void print_sec_info()
{
	int i;
	printf("the information of section:\n");
	printf("%10s%8s%15s%6s%14s%18s\n","name","rva","VirtualSize","raw","SizeOfRawData","Characteristics");
	for(i=1;i<=sec_num;i++)
	{
		printf("%10s%10x%10x%10x%10x%18x\n",sec_head[i].Name,sec_head[i].VirtualAddress,sec_head[i].Misc.VirtualSize,sec_head[i].PointerToRawData,sec_head[i].SizeOfRawData,sec_head[i].Characteristics);
	}

}
void get_dllname(int i,char*dll_name)
{
	SetFilePointer(hfile,rva2raw(import[i].Name),0,FILE_BEGIN);
	int ret=ReadFile(hfile,dll_name,30,&readsize,0);
	read_error(ret);
	return;
}
void print_dllfunc(int i)
{
	DWORD iat_entry=import[i].FirstThunk;
	iat_entry=rva2raw(iat_entry);
	int cc=0;
	while(1)
	{
		cc++;
		SetFilePointer(hfile,iat_entry+(cc-1)*sizeof(thunk),0,FILE_BEGIN);
		int ret=ReadFile(hfile,&thunk,sizeof(thunk),&readsize,0);
		read_error(ret);
		DWORD fname_rva=(DWORD)thunk.u1.AddressOfData;//注意:c语言的指针本质是rva
		if(fname_rva==0) break;
		DWORD fname_raw=rva2raw(fname_rva);//fname_raw为IMAGE_IMPORT_BY_NAME的raw
		char*s[100];
		SetFilePointer(hfile,fname_raw+2,0,FILE_BEGIN);//fname_raw+2是IMAGE_IMPORT_BY_NAME的Name[1]的raw
		ret=ReadFile(hfile,s,100,&readsize,0);
		read_error(ret);
		printf("%s\n",s);
	}
	printf("\n");
	return;
}
void print_import()
{
	int i,j;
	char dll_name[30];
	printf("以下是导入表函数信息:\n");
	for(i=1;i<=dll_num;i++)
	{
		get_dllname(i,dll_name);
		printf("%s\n",dll_name);
		print_dllfunc(i);
	}
	return;
}
int main()
{
	int i,j;
	char filename[1010];
	printf("请输入PE文件的绝对路径:");
	while(~scanf("%s",filename))
	{
		hfile=CreateFile(filename,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
		if(hfile==INVALID_HANDLE_VALUE)
		{
			printf("file_open_error\n");
			return 0;
		}

		int ret=ReadFile(hfile,&dos_head,sizeof(dos_head),&readsize,0);
		read_error(ret);
		if(dos_head.e_magic!=0x5a4d)
		{
			printf("this is't a valid pe file\n");
			CloseHandle(hfile);
			return 0;
		}
		
		SetFilePointer(hfile,dos_head.e_lfanew,0,FILE_BEGIN);
		ret=ReadFile(hfile,&pe_head,sizeof(pe_head),&readsize,0);
		read_error(ret);
		if(pe_head.Signature!=0x4550)
		{
			printf("this is't a valid pe file\n");
			CloseHandle(hfile);
			return 0;
		}

		pfile_head=&(pe_head.FileHeader);
		poptional_head=&(pe_head.OptionalHeader);
		printf("the EP is %x\n",poptional_head->AddressOfEntryPoint);
		pdata_dir=poptional_head->DataDirectory;

		print_exp_and_imp();
		
		sec_num=pfile_head->NumberOfSections;
		ophead_size=pfile_head->SizeOfOptionalHeader;
		dos_and_nt=dos_head.e_lfanew+4+sizeof(*pfile_head)+ophead_size;//dos头和nt头总大小(不包括块表)
		
		read_sechead();
		print_sec_info();
		read_import();
		print_import();
		


		CloseHandle(hfile);
		printf("请输入PE文件的绝对路径:");
	}
	return 0;
}