关于PE可执行文件的修改.docx
- 文档编号:8605328
- 上传时间:2023-02-01
- 格式:DOCX
- 页数:15
- 大小:21.04KB
关于PE可执行文件的修改.docx
《关于PE可执行文件的修改.docx》由会员分享,可在线阅读,更多相关《关于PE可执行文件的修改.docx(15页珍藏版)》请在冰豆网上搜索。
关于PE可执行文件的修改
关于PE可执行文件的修改
原创:
ilsy(ILSY)
在windows9x、NT、2000下,所有的可执行文件都是基于Microsoft设计的一种新的文件格式PortableExecutableFileFormat(可移植的执行体),即PE格式。
有一些时候,我们需要对这些可执行文件进行修改,下面文字试图详细的描述PE文件的格式及对PE格式文件的修改。
1、PE文件框架构成
DOSMZheader
DOSstub
PEheader
Sectiontable
Section1
Section2
Section...
Sectionn
上表是PE文件结构的总体层次分布。
所有PE文件(甚至32位的DLLs)必须以一个简单的DOSMZheader开始,在偏移0处有DOS下可执行文件的“MZ标志”,有了它,一旦程序在DOS下执行,DOS就能识别出这是有效的执行体,然后运行紧随MZheader之后的DOSstub。
DOSstub实际上是个有效的EXE,在不支持PE文件格式的操作系统中,它将简单显示一个错误提示,类似于字符串"ThisprogramcannotruninDOSmode"或者程序员可根据自己的意图实现完整的DOS代码。
通常DOSstub由汇编器/编译器自动生成,对我们的用处不是很大,它简单调用中断21h服务9来显示字符串"ThisprogramcannotruninDOSmode"。
紧接着DOSstub的是PEheader。
PEheader是PE相关结构IMAGE_NT_HEADERS的简称,其中包含了许多PE装载器用到的重要域。
可执行文件在支持PE文件结构的操作系统中执行时,PE装载器将从DOSMZheader的偏移3CH处找到PEheader的起始偏移量。
因而跳过了DOSstub直接定位到真正的文件头PEheader。
PE文件的真正内容划分成块,称之为sections(节)。
每节是一块拥有共同属性的数据,比如“.text”节等,那么,每一节的内容都是什么呢?
实际上PE格式的文件把具有相同属性的内容放入同一个节中,而不必关心类似“.text”、“.data”的命名,其命名只是为了便于识别,所有,我们如果对PE格式的文件进行修改,理论上讲可以写入任何一个节内,并调整此节的属性就可以了。
PEheader接下来的数组结构sectiontable(节表)。
每个结构包含对应节的属性、文件偏移量、虚拟偏移量等。
如果PE文件里有5个节,那么此结构数组内就有5个成员。
以上就是PE文件格式的物理分布,下面将总结一下装载一PE文件的主要步骤:
1、PE文件被执行,PE装载器检查DOSMZheader里的PEheader偏移量。
如果找到,则跳转到PEheader。
2、PE装载器检查PEheader的有效性。
如果有效,就跳转到PEheader的尾部。
3、紧跟PEheader的是节表。
PE装载器读取其中的节信息,并采用文件映射方法将这些节映射到内存,同时付上节表里指定的节属性。
4、PE文件映射入内存后,PE装载器将处理PE文件中类似importtable(引入表)逻辑部分。
上述步骤是一些前辈分析的结果简述。
2、PE文件头概述
我们可以在winnt.h这个文件中找到关于PE文件头的定义:
typedefstruct_IMAGE_NT_HEADERS{
DWORDSignature;
//PE文件头标志:
“PE\0\0”。
在开始DOSheader的偏移3CH处所指向的地址开始
IMAGE_FILE_HEADERFileHeader;//PE文件物理分布的信息
IMAGE_OPTIONAL_HEADER32OptionalHeader;//PE文件逻辑分布的信息
}IMAGE_NT_HEADERS32,*PIMAGE_NT_HEADERS32;
typedefstruct_IMAGE_FILE_HEADER{
WORDMachine;//该文件运行所需要的CPU,对于Intel平台是14Ch
WORDNumberOfSections;//文件的节数目
DWORDTimeDateStamp;//文件创建日期和时间
DWORDPointerToSymbolTable;//用于调试
DWORDNumberOfSymbols;//符号表中符号个数
WORDSizeOfOptionalHeader;//OptionalHeader结构大小
WORDCharacteristics;//文件信息标记,区分文件是exe还是dll
}IMAGE_FILE_HEADER,*PIMAGE_FILE_HEADER;
typedefstruct_IMAGE_OPTIONAL_HEADER{
WORDMagic;//标志字(总是010bh)
BYTEMajorLinkerVersion;//连接器版本号
BYTEMinorLinkerVersion;//
DWORDSizeOfCode;//代码段大小
DWORDSizeOfInitializedData;//已初始化数据块大小
DWORDSizeOfUninitializedData;//未初始化数据块大小
DWORDAddressOfEntryPoint;//PE装载器准备运行的PE文件的第一个指令的RVA,若要改变整个执行的流程,可以将该值指定到新的RVA,这样新RVA处的指令首先被执行。
(许多文章都有介绍RVA,请去了解)
DWORDBaseOfCode;//代码段起始RVA
DWORDBaseOfData;//数据段起始RVA
DWORDImageBase;//PE文件的装载地址
DWORDSectionAlignment;//块对齐
DWORDFileAlignment;//文件块对齐
WORDMajorOperatingSystemVersion;//所需操作系统版本号
WORDMinorOperatingSystemVersion;//
WORDMajorImageVersion;//用户自定义版本号
WORDMinorImageVersion;//
WORDMajorSubsystemVersion;//win32子系统版本。
若PE文件是专门为Win32设计的
WORDMinorSubsystemVersion;//该子系统版本必定是4.0否则对话框不会有3维立体感
DWORDWin32VersionValue;//保留
DWORDSizeOfImage;//内存中整个PE映像体的尺寸
DWORDSizeOfHeaders;//所有头+节表的大小
DWORDCheckSum;//校验和
WORDSubsystem;//NT用来识别PE文件属于哪个子系统
WORDDllCharacteristics;//
DWORDSizeOfStackReserve;//
DWORDSizeOfStackCommit;//
DWORDSizeOfHeapReserve;//
DWORDSizeOfHeapCommit;//
DWORDLoaderFlags;//
DWORDNumberOfRvaAndSizes;//
IMAGE_DATA_DIRECTORYDataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
//IMAGE_DATA_DIRECTORY结构数组。
每个结构给出一个重要数据结构的RVA,比如引入地址表等
}IMAGE_OPTIONAL_HEADER32,*PIMAGE_OPTIONAL_HEADER32;
typedefstruct_IMAGE_DATA_DIRECTORY{
DWORDVirtualAddress;//表的RVA地址
DWORDSize;//大小
}IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;
PE文件头后是节表,在winnt.h下如下定义
typedefstruct_IMAGE_SECTION_HEADER{
BYTEName[IMAGE_SIZEOF_SHORT_NAME];//节表名称,如“.text”
union{
DWORDPhysicalAddress;//物理地址
DWORDVirtualSize;//真实长度
}Misc;
DWORDVirtualAddress;//RVA
DWORDSizeOfRawData;//物理长度
DWORDPointerToRawData;//节基于文件的偏移量
DWORDPointerToRelocations;//重定位的偏移
DWORDPointerToLinenumbers;//行号表的偏移
WORDNumberOfRelocations;//重定位项数目
WORDNumberOfLinenumbers;//行号表的数目
DWORDCharacteristics;//节属性
}IMAGE_SECTION_HEADER,*PIMAGE_SECTION_HEADER;
以上结构就是在winnt.h中关于PE文件头的定义,如何我们用C/C++来进行PE可执行文件操作,就要用到上面的所有结构,它详细的描述了PE文件头的结构。
3、修改PE可执行文件
现在让我们把一段代码写入任何一个PE格式的可执行文件,代码如下:
--test.asm--
.386p
.modelflat,stdcall
optioncasemap:
none
include\masm32\include\windows.inc
include\masm32\include\user32.inc
includelib\masm32\lib\user32.lib
.code
start:
INVOKEMessageBoxA,0,0,0,MB_ICONINFORMATIONorMB_OK
ret
endstart
以上代码只显示一个MessageBox框,编译后得到二进制代码如下:
unsignedcharwriteline[18]={
0x6a,0x40,0x6a,0x0,0x6a,0x0,0x6a,0x0,0xe8,0x01,0x0,0x0,0x0,0xe9,0x0,0x0,0x0,0x0
};
好,现在让我们看看该把这些代码写到那。
现在用Tdump.exe显示一个PE格式得可执行文件信息,可以发现如下描述:
Objecttable:
#NameVirtSizeRVAPhysSizePhysoffFlags
--------------------------------------------------
01.text0000CCC0000010000000CE000000060060000020[CER]
02.data000046280000E00000002C000000D400C0000040[IRW]
03.rsrc000003C800013000000004000001000040000040[IR]
Keytosectionflags:
C-containscode
E-executable
I-containsinitializeddata
R-readable
W-writeable
上面描述此文件中存在3个段及每个段得信息,实际上我们的代码可以写入任何一个段,这里我选择“.text”段。
用如下代码得到一个PE格式可执行文件的头信息:
//writePE.cpp
#include
#include
#include
#include
#include
#include
unsignedcharwriteline[18]={
0x6a,0x40,0x6a,0x0,0x6a,0x0,0x6a,0x0,0xe8,0x01,0x0,0x0,0x0,0xe9,0x0,0x0,0x0,0x0
};
DWORDspace;
DWORDentryaddress;
DWORDentrywrite;
DWORDprogRAV;
DWORDoldentryaddress;
DWORDnewentryaddress;
DWORDcodeoffset;
DWORDpeaddress;
DWORDflagaddress;
DWORDflags;
DWORDvirtsize;
DWORDphysaddress;
DWORDphyssize;
DWORDMessageBoxAadaddress;
intmain(intargc,char**argv)
{
HANDLEhFile,hMapping;
void*basepointer;
FILETIME*Createtime;
FILETIME*Accesstime;
FILETIME*Writetime;
Createtime=newFILETIME;
Accesstime=newFILETIME;
Writetime=newFILETIME;
if((hFile=CreateFile(argv[1],GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,0,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,0))==INVALID_HANDLE_VALUE)//打开要修改的文件
{
puts("(couldnotopen)");
returnEXIT_FAILURE;
}
if(!
GetFileTime(hFile,Createtime,Accesstime,Writetime))
{
printf("\nerrorgetfiletime:
%d\n",GetLastError());
}
//得到要修改文件的创建、修改等时间
if(!
(hMapping=CreateFileMapping(hFile,0,PAGE_READONLY|SEC_COMMIT,0,0,0)))
{
puts("(mappingfailed)");
CloseHandle(hFile);
returnEXIT_FAILURE;
}
if(!
(basepointer=MapViewOfFile(hMapping,FILE_MAP_READ,0,0,0)))
{
puts("(viewfailed)");
CloseHandle(hMapping);
CloseHandle(hFile);
returnEXIT_FAILURE;
}
//把文件头映象存入baseointer
CloseHandle(hMapping);
CloseHandle(hFile);
map_exe(basepointer);//得到相关地址
UnmapViewOfFile(basepointer);
printaddress();
printf("\n\n");
if(space<50)
{
printf("\n空隙太小,数据不能写入.\n");
}
else
{
writefile();//写文件
}
if((hFile=CreateFile(argv[1],GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,0,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,0))==INVALID_HANDLE_VALUE)
{
puts("(couldnotopen)");
returnEXIT_FAILURE;
}
if(!
SetFileTime(hFile,Createtime,Accesstime,Writetime))
{
printf("errorsettime:
%d\n",GetLastError());
}
//恢复修改后文件的建立时间等
deleteCreatetime;
deleteAccesstime;
deleteWritetime;
CloseHandle(hFile);
return0;
}
voidmap_exe(constvoid*base)
{
IMAGE_DOS_HEADER*dos_head;
dos_head=(IMAGE_DOS_HEADER*)base;
#include
typedefstructPE_HEADER_MAP
{
DWORDsignature;
IMAGE_FILE_HEADER_head;
IMAGE_OPTIONAL_HEADERopt_head;
IMAGE_SECTION_HEADERsection_header[];
}peHeader;
#include
if(dos_head->e_magic!
=IMAGE_DOS_SIGNATURE)
{
puts("unknowntypeoffile");
return;
}
peHeader*header;
header=(peHeader*)((char*)dos_head+dos_head->e_lfanew);//得到PE文件头
if(IsBadReadPtr(header,sizeof(*header))
{
puts("(noPEheader,probablyDOSexecutable)");
return;
}
DWORDmods;
chartmpstr[4]={0};
DWORDtmpaddress;
DWORDtmpaddress1;
if(strstr((constchar*)header->section_header[0].Name,".text")!
=NULL)
{
virtsize=header->section_header[0].Misc.VirtualSize;
//此段的真实长度
physaddress=header->section_header[0].PointerToRawData;
//此段的物理偏移
physsize=header->section_header[0].SizeOfRawData;
//此段的物理长度
peaddress=dos_head->e_lfanew;
//得到PE文件头的开始偏移
peHeaderpeH;
tmpaddress=(unsignedlong)&peH;
//得到结构的偏移
tmpaddress1=(unsignedlong)&(peH.section_header[0].Characteristics);
//得到变量的偏移
flagaddress=tmpaddress1-tmpaddress+2;
//得到属性的相对偏移
flags=0x8000;
//一般情况下,“.text”段是不可读写的,如果我们要把数据写入这个段需要改变其属性,实际上这个程序并没有把数据写入“.text”段,所以并不需要更改,但如果你实现复杂的功能,肯定需要数据,肯定需要更改这个值,
space=physsize-virtsize;
//得到代码段的可用空间,用以判断可不可以写入我们的代码
//用此段的物理长度减去此段的真实长度就可以得到
progRAV=header->opt_head.ImageBase;
//得到程序的装载地址,一般为400000
codeoffset=header->opt_head.BaseOfCode-physaddress;
//得到代码偏移,用代码段起始RVA减去此段的物理偏移
//应为程序的入口计算公式是一个相对的偏移地址,计算公式为:
//代码的写入地址+codeoffset
entrywrite=header->section_header[0].PointerToRawData+header->section_header[0].Misc.VirtualSize;
//代码写入的物理偏移
mods=entrywrite%16;
//对齐边界
if(mods!
=0)
{
entrywrite+=(16-mods);
}
oldentryaddress=header->opt_head.AddressOfEntryPoint;
//保存旧的程序入口地址
newentryaddress=entrywrite+codeoffset;
//计算新的程序入口地址
return;
}
voidprintaddress()
{
HINSTANCEgLibMsg=NULL;
DWORDfunaddress;
gLibMsg=LoadLibrary("user32.dll");
funaddress=(DWORD)GetProcAddress(gLibMsg,"MessageBoxA");
MessageBoxAadaddress=funaddress;
gLibAMsg=LoadLibrary("kernel32.dll");
//得到MessageBox在内存中的地址,以便我们使用
}
voidwritefile()
{
intret;
longretf;
DWORDaddress;
inttmp;
unsignedcharwaddress[4]={0};
ret=_open(filename,_O_RDWR|_O_CREAT|_O_BINARY,_S_IREAD|_S_IWRITE);
if(!
ret)
{
printf("erro
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 关于 PE 可执行文件 修改
![提示](https://static.bdocx.com/images/bang_tan.gif)