看雪论坛
发新帖
1

PE结构学习之理论基础

skeep 2017-4-26 08:09 841

学习PE结构的相关知识已经有有一段时间,近几日开始尝试写一个简单的压缩壳,意在加深对PE结构中常用字段的理解,以及对数据目录表的理解。欲将收集到的资料,加上自己的理解与加工,整理出来,分享,大家共同交流。
part.1  PE结构简介
资源来源:
http://bbs.pediy.com/thread-203563.htm
鉴于PE的解析的帖子以及教程非常多,我这里只是介绍一下学习的过程与思路,具体的内容还是需要自己去记忆与理解。

首先上一张,我认为概括的比较直观的图,结合PEtool或者其他的PE解析工具以及压缩包中的PDF文件,首先将pe的每个字段所在位置,整体分布,由一个宏观的把握。个人感觉PE字段的命名有一定的规律,了解了文件偏移,虚拟地址,相对虚拟地址这些概念之后,一部分字段就可以猜测出含义,部分没有规律的,可以百度之。
以上的知识梳理过后,我们有了一个对于PE的宏观印象,如下图:

  

当然,也要注意在这个示意图中,没有显示出文件对齐与内存对齐。

Part2.PE文件的拉伸
就本人的经历而言,对于一些知识如果不加以利用,看看具有由哪些用处的话,很快就会遗忘掉。这里,我们写一个简单的小例子,简单模拟一下PE文件加载进内存的操作。这部分知识参考滴水公开的视频,不过海哥使用的是C语言的函数对PE文件进行读写操作,而我改成了使用系统API来实现。
大体思路与流程如下:
    ●  读取PE文件;
    ●  按照文件大小开辟空间A,全部初始化为0,然后将PE文件的内容写入开辟的新空间A中;
    ●  按照SizeOfImage这个参数开辟空间B,全部初始化为0;
    ●  将内存中的PE从A的开始部分往B拷贝SizeOfHeaders大小;
    ●  然后开始循环,把每个区段,写入到Misc这个位置,写入SizeOfRawData大小,一直拷贝完所有区段,
注意事项:
遇到RVA需要转化成原先的偏移,然后再加上现在的基址得到现在的地址
主要代码:

函数readpefile:将PE文件读取到内存中

DWORD readpefile(IN LPCWSTR lpszFile)

{


 DWORD ReadSize = 0;

 //打开文件

 HANDLE hFile = CreateFile(lpszFile,

  GENERIC_READ | GENERIC_WRITE,

  FILE_SHARE_READ | FILE_SHARE_WRITE,

  NULL,

  OPEN_EXISTING,

  FILE_ATTRIBUTE_NORMAL,

  NULL

  );

 if(hFile==INVALID_HANDLE_VALUE)

 {

  printf("文件打开失败\n"); 

  return false;

 }

 //获取文件大小

 m_dwFileSize = GetFileSize(hFile, NULL);

 printf("文件大小:%d",m_dwFileSize);

 //*m_buf=(char*)malloc(m_dwFileSize);

 m_buf = new char[m_dwFileSize];

 //读取文件

 ReadFile(hFile, m_buf, m_dwFileSize, &ReadSize, NULL);

 CloseHandle(hFile);

 return m_dwFileSize;

}

函数filebuffertoimagebuffer:在内存中将pe的内容,进行拉伸

DWORD filebuffertoimagebuffer()
{
	PIMAGE_DOS_HEADER pDosHeader=NULL;
	PIMAGE_NT_HEADERS pntheader=NULL;
	PIMAGE_FILE_HEADER ppeheader=NULL;
	PIMAGE_OPTIONAL_HEADER poptionheader=NULL;
	PIMAGE_SECTION_HEADER psectionheader=NULL;
	LPVOID ptempImageBuffer=NULL;
	if (m_buf==NULL)
	{
		printf("缓冲区指针无效");
		return 0;
	}
	//判断是否是有效的MZ标记
	if (*((PWORD)m_buf)!=IMAGE_DOS_SIGNATURE)
	{
		printf("不是有效的mz标志");
		return 0;
	}
	pDosHeader=(PIMAGE_DOS_HEADER)m_buf;
	//判断是否时有效的PE标志
	if (*((PDWORD)((DWORD)m_buf+pDosHead-z>e_lfanew))!=IMAGE_NT_SIGNATURE)
	{
		printf("不是有效的pe标志");
		return 0;
	}
	pntheader=(PIMAGE_NT_HEADERS)((DWORD)m_buf+pDosHead-z>e_lfanew);
	ppeheader=(PIMAGE_FILE_HEADER)(((DWORD)pntheader)+4);
	//可选pe头
	poptionheader=(PIMAGE_OPTIONAL_HEADER)((DWORD)ppeheader+IMAGE_SIZEOF_FILE_HEADER);
	//第一个节目录指针
	psectionheader=(PIMAGE_SECTION_HEADER)((DWORD)poptionheader+ppeheade->SizeOfOptionalHeader);
	//根据sizeofimage申请新的空间
	ptempImageBuffer=malloc(poptionheader->SizeOfImage);
	if (!ptempImageBuffer)
	{
		printf("申请空间失败");
		return 0;
	}
	//初始化新的缓冲区
	memset(ptempImageBuffer,0,poptionheader->SizeOfImage);
	//根据sizeofheaders 先拷贝PE头
	memcpy(ptempImageBuffer,pDosHeader,poptionheader->SizeOfHeaders);
	//遍历节表,循环copy节
	PIMAGE_SECTION_HEADER ptempsectionheader=psectionheader;
	for (int i=0;i<ppeheader->NumberOfSections;i++,ptempsectionheader++)
	{
		memcpy((void*)((DWORD)ptempImageBuffer+ptempsectionhead-z>VirtualAddress),(void*)((DWORD)pDosHeader+ptempsectionhead-z>PointerToRawData),ptempsectionheader->SizeOfRawData);
	}
	//返回数据
	m_dwImagesize=poptionheader->SizeOfImage;
	imagebuffer=(char*)ptempImageBuffer;
	ptempImageBuffer=NULL;
	return poptionheader->SizeOfImage;
}

这里也只是模拟加载的部分步骤,剩下的部分暂且不去讨论,今后补全了这部分知识,再发出来讨论。
总结,只是演示功能,为了省事,所以不少本来应该作为参数传出的变量,本人直接定义为全局的。(我才不会说是因为指针与引用使用不熟练呢)

Part.3  PE还原
大体思路与流程如下:
    ●  利用最后一个区段的SizeOfRawData加上最后一个区段的文件偏移PointerToRawData得到文件的大小
    ●  开辟一个空间C,先将空间B的PE头拷贝过来
    ●  然后循环遍历区段表,将所有区段按照区段的属性拷贝到空间C中
    ●  然后将空间C写入到一个新的文件中
这个过程就是Part2的逆过程。
主要代码:

imagebuffertonewfilebuffer:将拉伸后的PE文件还原到PE初始的状态

DWORD imagebuffertonewfilebuffer()
{
 PIMAGE_DOS_HEADER pDosHeader=NULL;
 PIMAGE_NT_HEADERS pntheader=NULL;
 PIMAGE_FILE_HEADER ppeheader=NULL;
 PIMAGE_OPTIONAL_HEADER poptionheader=NULL;
 PIMAGE_SECTION_HEADER psectionheader=NULL;
 LPVOID ptempImageBuffer=NULL;
 pDosHeader=(PIMAGE_DOS_HEADER)imagebuffer;
 pntheader=(PIMAGE_NT_HEADERS)((DWORD)imagebuffer+pDosHead-z>e_lfanew);
 ppeheader=(PIMAGE_FILE_HEADER)(((DWORD)pntheader)+4);
 //可选pe头
 poptionheader=(PIMAGE_OPTIONAL_HEADER)((DWORD)ppeheader+IMAGE_SIZEOF_FILE_HEADER);
 //第一个节目录指针
 psectionheader=(PIMAGE_SECTION_HEADER)((DWORD)poptionheader+ppeheade->SizeOfOptionalHeader);
 //根据sizeofimage申请新的空间
 ptempImageBuffer=malloc(poptionheader->SizeOfImage);
 if (!ptempImageBuffer)
 {
  printf("申请空间失败");
  return 0;
 }
 //初始化新的缓冲区
 memset(ptempImageBuffer,0,poptionheader->SizeOfImage);
 //根据sizeofheaders 先拷贝PE头
 memcpy(ptempImageBuffer,pDosHeader,poptionheader->SizeOfHeaders);
 //遍历节表,循环copy节
 PIMAGE_SECTION_HEADER ptempsectionheader=psectionheader;
 for (int i=0;i<ppeheader->NumberOfSections;i++,ptempsectionheader++)
 {
  memcpy((void*)((DWORD)ptempImageBuffer+ptempsectionhead-z>PointerToRawData),(void*)((DWORD)pDosHeader+ptempsectionhead-z>VirtualAddress),ptempsectionheader->SizeOfRawData);
  if (i==ppeheader->NumberOfSections-1)
  {
   m_newfilesize=((DWORD)ptempsectionheader->PointerToRawData)+(DWORD)ptempsectionheader->SizeOfRawData;
  }
   
 }
 //返回数据
 m_dwImagesize=poptionheader->SizeOfImage;
 new_file_buffer=(char*)ptempImageBuffer;
 ptempImageBuffer=NULL;
 return poptionheader->SizeOfImage;
}

  new_memerytofile:将还原过的PE写回到一个新文件中

void new_memerytofile( IN LPCWSTR new_filebuffer )

{

 DWORD writSize=0;

 DWORD imagetofileSize = 0;

 char * temp;

 //打开文件

 HANDLE hFile = CreateFile(new_filebuffer,

  GENERIC_READ | GENERIC_WRITE,

  FILE_SHARE_READ | FILE_SHARE_WRITE,

  NULL,

  CREATE_NEW,

  FILE_ATTRIBUTE_NORMAL,

  NULL

  );

 if(hFile==INVALID_HANDLE_VALUE)

 {

  printf("文件打开失败\n"); 

  return;

 }

 //获取文件大小


 imagetofileSize =m_newfilesize;

 //读取文件

 WriteFile(hFile, new_file_buffer, imagetofileSize, &writSize, NULL);

 CloseHandle(hFile);

 printf("文件大小:%d",imagetofileSize);

}

代码写的很渣,基本是怎么方便怎么来的,但是在附件中也展示出来,有需要的小伙伴可以参考改进

这部分知识,对于今后写壳的时候,有着启发性的作用,熟练的掌握了PE的结构,以及对于PE文件的读写操作,内存中的拷贝操作,这都是写壳所必备的知识,应当熟练掌握。

最后,感谢15PB任课老师以及辅导老师的耐心指导




本帖源码如下:

上传的附件:
本主题帖已收到 0 次赞赏,累计¥0.00
最新回复 (5)
空白即是正义 2017-4-26 08:58
2
写壳的难度感觉会更大呢  这里感谢楼主分享
wanc 2017-4-26 09:13
3
写壳难度不大,写一个好壳难度才大
1
skeep 2017-4-26 10:43
4
感觉写一个好壳真的难,我这也只能算是借用写壳来加深对PE的理解
zwhjyhm 2017-4-26 11:31
5
感谢楼主分享!
永远的天行 2017-9-1 20:47
6
滴水第三期的视频刚看到,正巧被卡这,谢楼主了
返回



©2000-2017 看雪学院 | Based on Xiuno BBS | 域名 加速乐 保护 | SSL证书 又拍云 提供 | 微信公众号:ikanxue
Time: 0.014, SQL: 11 / 京ICP备10040895号-17