📄 关于pe可执行文件的修改.htm
字号:
SizeOfHeapReserve; //<BR>DWORD
SizeOfHeapCommit; //<BR>DWORD
LoaderFlags; //<BR>DWORD
NumberOfRvaAndSizes; //<BR>IMAGE_DATA_DIRECTORY
DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];<BR>//IMAGE_DATA_DIRECTORY
结构数组。每个结构给出一个重要数据结构的RVA,比如引入地址表等<BR>} IMAGE_OPTIONAL_HEADER32,
*PIMAGE_OPTIONAL_HEADER32;<BR><BR>typedef struct _IMAGE_DATA_DIRECTORY
{<BR>DWORD
VirtualAddress; //表的RVA地址<BR>DWORD
Size; //大小<BR>}
IMAGE_DATA_DIRECTORY,
*PIMAGE_DATA_DIRECTORY;<BR><BR>PE文件头后是节表,在winnt.h下如下定义<BR>typedef struct
_IMAGE_SECTION_HEADER
{<BR>BYTE Name[IMAGE_SIZEOF_SHORT_NAME];//节表名称,如“.text”<BR>union
{<BR> DWORD
PhysicalAddress; //物理地址 <BR> DWORD
VirtualSize; //真实长度<BR>}
Misc;<BR>DWORD
VirtualAddress; //RVA<BR>DWORD
SizeOfRawData; //物理长度<BR>DWORD
PointerToRawData; //节基于文件的偏移量<BR>DWORD
PointerToRelocations; //重定位的偏移<BR>DWORD
PointerToLinenumbers; //行号表的偏移<BR>WORD NumberOfRelocations; //重定位项数目<BR>WORD NumberOfLinenumbers; //行号表的数目<BR>DWORD
Characteristics; //节属性<BR>}
IMAGE_SECTION_HEADER,
*PIMAGE_SECTION_HEADER;<BR><BR>以上结构就是在winnt.h中关于PE文件头的定义,如何我们用C/C++来进行PE可执行文件操作,就要用到上面的所有结构,它详细的描述了PE文件头的结构。<BR><BR>3、修改PE可执行文件<BR>现在让我们把一段代码写入任何一个PE格式的可执行文件,代码如下:<BR>--
test.asm --<BR>.386p<BR>.model flat, stdcall<BR>option
casemap:none<BR><BR>include \masm32\include\windows.inc<BR>include
\masm32\include\user32.inc<BR>includelib \masm32\lib\user32.lib
<BR><BR>.code<BR><BR>start:<BR> INVOKE
MessageBoxA,0,0,0,MB_ICONINFORMATION or
MB_OK<BR> ret<BR>end
start<BR><BR>以上代码只显示一个MessageBox框,编译后得到二进制代码如下:<BR>unsigned char
writeline[18]={<BR>0x6a,0x40,0x6a,0x0,0x6a,0x0,0x6a,0x0,0xe8,0x01,0x0,0x0,0x0,0xe9,0x0,0x0,0x0,0x0<BR>};<BR><BR>好,现在让我们看看该把这些代码写到那。现在用Tdump.exe显示一个PE格式得可执行文件信息,可以发现如下描述:<BR>Object
table:<BR>#
Name VirtSize RVA
PhysSize Phys off Flags
<BR>-- -------- -------- -------- -------- -------- --------<BR>01 .text
0000CCC0 00001000 0000CE00 00000600 60000020
[CER]<BR>02 .data
00004628 0000E000 00002C00 0000D400 C0000040
[IRW]<BR>03 .rsrc
000003C8 00013000 00000400 00010000 40000040
[IR]<BR><BR>Key to section flags:<BR> C - contains
code<BR> E - executable<BR> I - contains initialized
data<BR> R - readable<BR> W -
writeable<BR><BR>上面描述此文件中存在3个段及每个段得信息,实际上我们的代码可以写入任何一个段,这里我选择“.text”段。<BR><BR>用如下代码得到一个PE格式可执行文件的头信息:<BR><BR>//writePE.cpp<BR><BR>#include
<windows.h><BR>#include <stdio.h><BR>#include
<io.h><BR>#include <fcntl.h><BR>#include
<time.h><BR>#include <SYS\STAT.H><BR><BR>unsigned char
writeline[18]={<BR>0x6a,0x40,0x6a,0x0,0x6a,0x0,0x6a,0x0,0xe8,0x01,0x0,0x0,0x0,0xe9,0x0,0x0,0x0,0x0<BR>};<BR><BR>DWORD
space;<BR>DWORD entryaddress;<BR>DWORD entrywrite;<BR>DWORD
progRAV;<BR>DWORD oldentryaddress;<BR>DWORD newentryaddress;<BR>DWORD
codeoffset;<BR>DWORD peaddress;<BR>DWORD flagaddress;<BR>DWORD
flags;<BR><BR>DWORD virtsize;<BR>DWORD physaddress;<BR>DWORD
physsize;<BR>DWORD MessageBoxAadaddress;<BR><BR>int main(int argc,char * *
argv)<BR>{<BR>HANDLE hFile, hMapping;<BR>void *basepointer;<BR>FILETIME *
Createtime;<BR>FILETIME * Accesstime;<BR>FILETIME *
Writetime;<BR>Createtime = new FILETIME;<BR>Accesstime = new
FILETIME;<BR>Writetime = new FILETIME;<BR><BR>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)//打开要修改的文件<BR>{<BR>puts("(could not
open)");<BR>return
EXIT_FAILURE;<BR>}<BR>if(!GetFileTime(hFile,Createtime,Accesstime,Writetime))<BR>{<BR>printf("\nerror
getfiletime: %d\n",GetLastError());<BR>}<BR>//得到要修改文件的创建、修改等时间<BR>if
(!(hMapping = CreateFileMapping(hFile, 0, PAGE_READONLY | SEC_COMMIT, 0,
0, 0)))<BR>{<BR>puts("(mapping failed)");<BR>CloseHandle(hFile);<BR>return
EXIT_FAILURE;<BR>}<BR>if (!(basepointer = MapViewOfFile(hMapping,
FILE_MAP_READ, 0, 0, 0)))<BR>{<BR>puts("(view
failed)");<BR>CloseHandle(hMapping);<BR>CloseHandle(hFile);<BR>return
EXIT_FAILURE;<BR>}<BR>//把文件头映象存入baseointer<BR>CloseHandle(hMapping);<BR>CloseHandle(hFile);<BR>map_exe(basepointer);//得到相关地址<BR>UnmapViewOfFile(basepointer);<BR>printaddress();<BR>printf("\n\n");<BR>if(space<50)<BR>{<BR>printf("\n空隙太小,数据不能写入.\n");<BR>}<BR>else<BR>{<BR>writefile();//写文件<BR>}<BR><BR>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)<BR>{<BR>puts("(could not open)");<BR>return
EXIT_FAILURE;<BR>}<BR><BR>if(!SetFileTime(hFile,Createtime,Accesstime,Writetime))<BR>{<BR>printf("error
settime : %d\n",GetLastError());<BR>}<BR>//恢复修改后文件的建立时间等<BR>delete
Createtime;<BR>delete Accesstime;<BR>delete
Writetime;<BR>CloseHandle(hFile);<BR>return 0;<BR>}<BR><BR>void
map_exe(const void *base)<BR>{<BR>IMAGE_DOS_HEADER * dos_head;<BR>dos_head
=(IMAGE_DOS_HEADER *)base;<BR>#include <pshpack1.h><BR>typedef
struct PE_HEADER_MAP<BR>{<BR>DWORD signature;<BR>IMAGE_FILE_HEADER
_head;<BR>IMAGE_OPTIONAL_HEADER opt_head;<BR>IMAGE_SECTION_HEADER
section_header[];<BR>} peHeader;<BR>#include <poppack.h><BR><BR>if
(dos_head->e_magic != IMAGE_DOS_SIGNATURE)<BR>{<BR>puts("unknown type
of file");<BR>return;<BR>}<BR><BR>peHeader * header;<BR>header = (peHeader
*)((char *)dos_head + dos_head->e_lfanew);//得到PE文件头<BR>if
(IsBadReadPtr(header, sizeof(*header))<BR>{<BR>puts("(no PE header,
probably DOS executable)");<BR>return;<BR>}<BR><BR>DWORD mods;<BR>char
tmpstr[4]={0};<BR>DWORD tmpaddress;<BR>DWORD tmpaddress1;<BR><BR>if(strstr((const
char
*)header->section_header[0].Name,".text")!=NULL)<BR>{<BR>virtsize=header->section_header[0].Misc.VirtualSize;<BR>//此段的真实长度<BR>physaddress=header->section_header[0].PointerToRawData;<BR>//此段的物理偏移<BR>physsize=header->section_header[0].SizeOfRawData;<BR>//此段的物理长度<BR>peaddress=dos_head->e_lfanew;
<BR>//得到PE文件头的开始偏移<BR><BR>peHeader peH;<BR>tmpaddress=(unsigned long
)&peH;<BR>//得到结构的偏移<BR>tmpaddress1=(unsigned long
)&(peH.section_header[0].Characteristics);<BR>//得到变量的偏移<BR>flagaddress=tmpaddress1-tmpaddress+2;<BR>//得到属性的相对偏移<BR>flags=0x8000;<BR>//一般情况下,“.text”段是不可读写的,如果我们要把数据写入这个段需要改变其属性,实际上这个程序并没有把数据写入“.text”段,所以并不需要更改,但如果你实现复杂的功能,肯定需要数据,肯定需要更改这个值,<BR><BR>space=physsize-virtsize;<BR>//得到代码段的可用空间,用以判断可不可以写入我们的代码<BR>//用此段的物理长度减去此段的真实长度就可以得到<BR>progRAV=header->opt_head.ImageBase;
<BR>//得到程序的装载地址,一般为400000<BR>codeoffset=header->opt_head.BaseOfCode-physaddress;<BR>//得到代码偏移,用代码段起始RVA减去此段的物理偏移<BR>//应为程序的入口计算公式是一个相对的偏移地址,计算公式为:<BR>//代码的写入地址+codeoffset<BR><BR>entrywrite=header->section_header[0].PointerToRawData+header->section_header[0].Misc.VirtualSize;<BR>//代码写入的物理偏移<BR>mods=entrywrite%16;<BR>//对齐边界<BR>if(mods!=0)<BR>{<BR>entrywrite+=(16-mods);<BR>}<BR>oldentryaddress=header->opt_head.AddressOfEntryPoint;<BR>//保存旧的程序入口地址<BR>newentryaddress=entrywrite+codeoffset;<BR>//计算新的程序入口地址 <BR>return;<BR>}<BR><BR>void
printaddress()<BR>{<BR>HINSTANCE gLibMsg=NULL;<BR>DWORD
funaddress;<BR>gLibMsg=LoadLibrary("user32.dll");
<BR>funaddress=(DWORD)GetProcAddress(gLibMsg,"MessageBoxA");<BR>MessageBoxAadaddress=funaddress;<BR>gLibAMsg=LoadLibrary("kernel32.dll");
<BR>//得到MessageBox在内存中的地址,以便我们使用<BR>}<BR><BR>void writefile()<BR>{<BR>int
ret;<BR>long retf;<BR>DWORD address;<BR>int tmp;<BR>unsigned char
waddress[4]={0};<BR><BR>ret=_open(filename,_O_RDWR | _O_CREAT |
_O_BINARY,_S_IREAD | _S_IWRITE);<BR>if(!ret)<BR>{<BR>printf("error
open\n");<BR>return;<BR>}<BR> <BR>retf=_lseek(ret,(long)peaddress+40,SEEK_SET);<BR>//程序的入口地址在PE文件头开始的40处<BR>if(retf==-1)<BR>{<BR>printf("error
seek\n");<BR>return;<BR>}<BR>address=newentryaddress;<BR>tmp=address>>24;<BR>waddress[3]=tmp;<BR>tmp=address<<8;<BR>tmp=tmp>>24;<BR>waddress[2]=tmp;<BR>tmp=address<<16;<BR>tmp=tmp>>24;<BR>waddress[1]=tmp;<BR>tmp=address<<24;<BR>tmp=tmp>>24;<BR>waddress[0]=tmp;<BR>retf=_write(ret,waddress,4);<BR>//把新的入口地址写入文件<BR>if(retf==-1)<BR>{<BR>printf("error
write:
%d\n",GetLastError());<BR>return;<BR>}<BR> <BR>retf=_lseek(ret,(long)entrywrite,SEEK_SET);<BR>if(retf==-1)<BR>{<BR>printf("error
seek\n");<BR>return;<BR>}<BR>retf=_write(ret,writeline,18);<BR>if(retf==-1)<BR>{<BR>printf("error
write:
%d\n",GetLastError());<BR>return;<BR>}<BR>//把writeline写入我们计算出的空间<BR><BR>retf=_lseek(ret,(long)entrywrite+9,SEEK_SET);<BR>//更改MessageBox函数地址,它的二进制代码在writeline[10]处<BR>if(retf==-1)<BR>{<BR>printf("error
seek\n");<BR>return;<BR>}<BR><BR>address=MessageBoxAadaddress-(progRAV+newentryaddress+9+4);<BR>//重新计算MessageBox函数的地址,MessageBox函数的原地址减去程序的装载地址加上新的入口地址加9(它的二进制代码相对偏移)加上4(地址长度)<BR>tmp=address>>24;<BR>waddress[3]=tmp;<BR>tmp=address<<8;<BR>tmp=tmp>>24;<BR>waddress[2]=tmp;<BR>tmp=address<<16;<BR>tmp=tmp>>24;<BR>waddress[1]=tmp;<BR>tmp=address<<24;<BR>tmp=tmp>>24;<BR>waddress[0]=tmp;<BR>retf=_write(ret,waddress,4);<BR>//写入重新计算的MessageBox地址<BR>if(retf==-1)<BR>{<BR>printf("error
write:
%d\n",GetLastError());<BR>return;<BR>}<BR><BR>retf=_lseek(ret,(long)entrywrite+14,SEEK_SET);<BR>//更改返回地址,用jpm返回原程序入口地址,其它的二进制代码在writeline[15]处<BR>if(retf==-1)<BR>{<BR>printf("error
seek\n");<BR>return;<BR>}<BR><BR>address=0-(newentryaddress-oldentryaddress+4+15);<BR>//返回地址计算的方法是新的入口地址减去老的入口地址加4(地址长度)加15(二进制代码相对偏移)后取反<BR>tmp=address>>24;<BR>waddress[3]=tmp;<BR>tmp=address<<8;<BR>tmp=tmp>>24;<BR>waddress[2]=tmp;<BR>tmp=address<<16;<BR>tmp=tmp>>24;<BR>waddress[1]=tmp;<BR>tmp=address<<24;<BR>tmp=tmp>>24;<BR>waddress[0]=tmp;<BR>retf=_write(ret,waddress,4);<BR>//写入返回地址<BR>if(retf==-1)<BR>{<BR>printf("error
write:
%d\n",GetLastError());<BR>return;<BR>}<BR><BR>_close(ret);<BR>printf("\nall
done...\n");<BR>return;<BR>}<BR><BR>//end<BR>由于在PE格式的文件中,所有的地址都使用RVA地址,所以一些函数调用和返回地址都要经过计算才可以得到,以上是我在实践中的心得,如果你有更好的办法,真心的希望你能告诉我。<BR><BR>如果存在错误,请告诉我,以免误导看这篇文章的人。<BR>写的较乱,请原谅。<BR><BR>ilsy@netguard.com.cn
</TD></TR></TBODY></TABLE>
<DIV class=footer>Copyright © 1998-2003 XFOCUS Team. All Rights Reserved
</DIV></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -