• 基于VC++实现PE的修改编程


    Windows系统下的可执行文件的一种(还有NELE),是微软设计、TIS(Tool Interface Standard,工具接口标准)委员会批准的一种可执行文件格式。PE的意思是Portable Executable(可移植可执行)。所有Windows下的32位或64位可执行文件都是PE文件格式,其中包括DLLEXEFONOCXLIB和部分SYS文件。

    DOS-stub(DOS-头)

      DOS-根的概念很早从16位windows的可执行文件(当时是“NE”格式)时就广为人知了。根原来是用于OS/2系统的可执行文件的,也用于自解压档案文件和其它的应用程序。对于PE文件来说,它是一个总是由大约100个字节所组成的和MS-DOS 2.0兼容的可执行体,用来输出象“This program needs windows NT”之类的错误信息。
      你可以通过确认DOS-头部分是否为一个IMAGE_DOS_HEADER(DOS头)结构来认出DOS-根,它的前两个字节必须为连续的两个字母“MZ”(有一个#define IMAGE_DOS_SIGNATURE的定义是针对这个WORD单元的)。
      你可以通过跟在后面的签名来将一个PE二进制文件和其它含有根的二进制文件区分开来,跟在后面的签名可由头成员'e_lfanew'(它是从字节偏移地址60处开始的,有32字节长)所设定的偏移地址找到。对于OS/2系统和Windows系统的二进制文件来说,签名是一个16位的word单元;对于PE文件来说,它是一个按照8位字节边界对齐的32位的longword单元,并且IMAGE_NT_SIGNATURE(NT签名)的值已由#defined定义为0x00004550(即字母“PE/0/0”)。

    file-header(文件头)

      要到达IMAGE_FILE_HEADER(文件头)结构,请先确认DOS-头“MZ”(起始的2个字节),然后找出DOS-根的头部的成员“e_lfanew”,并从文件开始处跳过那么多的字节。在核实你在那里找到的签名后,IMAGE_FILE_HEADER(文件头)结构的文件头就紧跟其后开始了。

    optional header(可选头)

      紧跟在文件头后面的就是IMAGE_OPTIONAL_HEADER(尽管它名叫“可选头”,它却一直都在那里)。它包含有怎样去准确处理PE文件的信息。

    data directories(数据目录)

      IMAGE_NUMBEROF_DIRECTORY_ENTRIES (16)(映象文件目录项数目)个IMAGE_DATA_DIRECTORY(映象文件数据目录)数组。这些目录中的每一个目录都描述了一个特定的、位于目录项后面的某一节中的信息的位置(32位的RVA,叫“VirtualAddress(虚拟地址)”)和大小(也是32位,叫“Size(大小)”)。
      例如,安全目录能在索引4中给定的RVA处发现并具有索引4中给定的大小。

    section headers(节头)

      节由两个主要部分组成:首先,是一个节描述(IMAGE_SECTION_HEADER[意为“节头”]类型的),然后是原始的节数据。因此,我们会在数据目录后发现一“NumberOfSections”个节头组成的数组,它们按照各节的RVA排序。

    sections(节数据)

      所有的节在载入内存后都按“SectionAlignment”(节对齐)对齐,在文件中则以“FileAlignment”(文件对齐)对齐。节由节头中的相关项来描述:在文件中你可通过“PointerToRawData”(原始数据指针)来找到,在内存中你可通过“VirtualAddress”(虚拟地址)来找到;长度由“SizeOfRawData”(原始数据长度)决定。

      根据节中包含的内容,可分为好几种节。大多数(并非所有)情况下,节中至少由一个数据目录,并在可选头的数据目录数组中有一个指针指向它。

    下面我们实现编程修改PE

    // Pe.cpp: 实现 CPe类.
    //
    #include "stdafx.h"
    #include "Pe.h"
    
    CPe::CPe()
    {
    }
    
    CPe::~CPe()
    {
    }
    
    void CPe::ModifyPe(CString strFileName,CString strMsg)
    {
    	CString strErrMsg;
    
    	HANDLE hFile, hMapping;
    	void *basepointer;
    	
    	// 打开要修改的文件.
    	if ((hFile = CreateFile(strFileName, GENERIC_READ|GENERIC_WRITE, 
    		FILE_SHARE_READ|FILE_SHARE_WRITE, 0, 
    		OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0)) == INVALID_HANDLE_VALUE)
    	{
    		AfxMessageBox("Could not open file.");
    		return;
    	}
    
    	// 创建一个映射文件.
    	if (!(hMapping = CreateFileMapping(hFile, 0, PAGE_READONLY | SEC_COMMIT, 0, 0, 0)))
    	{
    		AfxMessageBox("Mapping failed.");
    		CloseHandle(hFile);
    		return;
    	}
    
    	// 把文件头映象存入baseointer.
    	if (!(basepointer = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0)))
    	{
    		AfxMessageBox("View failed.");
    		CloseHandle(hMapping);
    		CloseHandle(hFile);
    		return;
    	}
    
    	CloseHandle(hMapping);
    	CloseHandle(hFile);
    
    	CalcAddress(basepointer); // 得到相关地址.
    	UnmapViewOfFile(basepointer);
    	
    	if(dwSpace<50)
    	{
    		AfxMessageBox("No room to write the data!");
    	}
    	else
    	{
    		WriteFile(strFileName,strMsg); // 写文件.
    	}
    	
    	if ((hFile = CreateFile(strFileName, GENERIC_READ|GENERIC_WRITE, 
    		FILE_SHARE_READ|FILE_SHARE_WRITE, 0, 
    		OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, 0)) == INVALID_HANDLE_VALUE)
    	{
    		AfxMessageBox("Could not open file.");
    		return;
    	}
    	
    	CloseHandle(hFile);
    }
    
    void CPe::CalcAddress(const void *base)
    {
    	IMAGE_DOS_HEADER * dos_head =(IMAGE_DOS_HEADER *)base;
    
    	if (dos_head->e_magic != IMAGE_DOS_SIGNATURE)
    	{
    		AfxMessageBox("Unknown type of file.");
    		return;
    	}
    	
    	peHeader * header;
    
    	// 得到PE文件头.
    	header = (peHeader *)((char *)dos_head + dos_head->e_lfanew);
    
    	if(IsBadReadPtr(header, sizeof(*header)))
    	{
    		AfxMessageBox("No PE header, probably DOS executable.");
    		return;
    	}
    
    	DWORD mods;
    	char tmpstr[4]={0};
    	if(strstr((const char *)header->section_header[0].Name,".text")!=NULL)
    	{
    		// 此段的真实长度.
    		dwVirtSize=header->section_header[0].Misc.VirtualSize;
    
    		// 此段的物理偏移.
    		dwPhysAddress=header->section_header[0].PointerToRawData;
    
    		// 此段的物理长度.
    		dwPhysSize=header->section_header[0].SizeOfRawData;
    		
    		// 得到PE文件头的开始偏移.
    		dwPeAddress=dos_head->e_lfanew; 
    		
    		// 得到代码段的可用空间,用以判断可不可以写入我们的代码
    		// 用此段的物理长度减去此段的真实长度就可以得到.
    		dwSpace=dwPhysSize-dwVirtSize;
    
    		// 得到程序的装载地址,一般为0x400000.
    		dwProgRAV=header->opt_head.ImageBase; 
    
    		// 得到代码偏移,用代码段起始RVA减去此段的物理偏移
    		// 应为程序的入口计算公式是一个相对的偏移地址,计算公式为:
    		// 代码的写入地址+dwCodeOffset.
    		dwCodeOffset=header->opt_head.BaseOfCode-dwPhysAddress;
    		
    		// 代码写入的物理偏移.
    		dwEntryWrite=header->section_header[0].PointerToRawData+header->
    			section_header[0].Misc.VirtualSize;
    
    		//对齐边界.
    		mods=dwEntryWrite%16;
    
    		if(mods!=0)
    		{
    			dwEntryWrite+=(16-mods);
    		}
    		
    		// 保存旧的程序入口地址.
    		dwOldEntryAddress=header->opt_head.AddressOfEntryPoint;
    
    		// 计算新的程序入口地址.        
    		dwNewEntryAddress=dwEntryWrite+dwCodeOffset;
    		return;
    	}
    }	
    
    CString CPe::StrOfDWord(DWORD dwAddress)
    {
    	unsigned char waddress[4]={0};
    	
    	waddress[3]=(char)(dwAddress>>24)&0xFF;
    	waddress[2]=(char)(dwAddress>>16)&0xFF;
    	waddress[1]=(char)(dwAddress>>8 )&0xFF;
    	waddress[0]=(char)(dwAddress    )&0xFF;
       
    	return waddress;
    }
    
    BOOL CPe::WriteNewEntry(int ret,long offset, DWORD dwAddress)
    {
    	CString strErrMsg;
    	long retf;
    	unsigned char waddress[4]={0};
    
    	retf=_lseek(ret,offset,SEEK_SET);
    	if(retf==-1)
    	{
    		AfxMessageBox("Error seek.");
    		return FALSE;
    	}
    
        memcpy(waddress,StrOfDWord(dwAddress),4);
    	retf=_write(ret,waddress,4);
    	
    	if(retf==-1)
    	{
    		strErrMsg.Format("error write: %d",GetLastError());
    		AfxMessageBox(strErrMsg);
    		return FALSE;
    	}
    
    	return TRUE;
    }
    
    BOOL CPe::WriteMessageBox(int ret,long offset,CString strCap,CString strTxt)
    {
    	CString strAddress1,strAddress2;
    	unsigned char waddress[4]={0};
    	DWORD dwAddress;
    
    	// 获取MessageBox在内存中的地址.
    	HINSTANCE gLibMsg=LoadLibrary("user32.dll"); 
    	dwMessageBoxAadaddress=(DWORD)GetProcAddress(gLibMsg,"MessageBoxA");
    
        // 计算校验位. 
    	int nLenCap1 =strCap.GetLength()+1;   // 加上字符串后面的结束位. 
    	int nLenTxt1 =strTxt.GetLength()+1;   // 加上字符串后面的结束位. 
    	int nTotLen=nLenCap1+nLenTxt1+24;
    
        // 重新计算MessageBox函数的地址.
    	dwAddress=dwMessageBoxAadaddress-(dwProgRAV+dwNewEntryAddress+nTotLen-5);
       	strAddress1=StrOfDWord(dwAddress);
    
    	// 计算返回地址.
    	dwAddress=0-(dwNewEntryAddress-dwOldEntryAddress+nTotLen);
    	strAddress2=StrOfDWord(dwAddress);
    
        // 对话框头代码(固定).
    	unsigned char cHeader[2]={0x6a,0x40};
        
    	// 标题定义.	
    	unsigned char cDesCap[5]={0xe8,nLenCap1,0x00,0x00,0x00};
        
    	// 内容定义.
    	unsigned char cDesTxt[5]={0xe8,nLenTxt1,0x00,0x00,0x00};
        
    	// 对话框后部分的代码段. 
    	unsigned char cFix[12]
    		 ={0x6a,0x00,0xe8,0x00,0x00,0x00,0x00,0xe9,0x00,0x00,0x00,0x00};
        
    	// 修改对话框后部分的代码段. 
    	for(int i=0;i<4;i++)
    		cFix[3+i]=strAddress1.GetAt(i);
    
    	for(i=0;i<4;i++)
    		cFix[8+i]=strAddress2.GetAt(i);
    
    	char* cMessageBox=new char[nTotLen];
    	char* cMsg; 
    
    	// 生成对话框命令字符串.
    	memcpy((cMsg  = cMessageBox),(char*)cHeader,2);
    	memcpy((cMsg += 2),cDesCap,5);
    	memcpy((cMsg += 5),strCap,nLenCap1);
    	memcpy((cMsg += nLenCap1),cDesTxt,5);
    	memcpy((cMsg += 5),strTxt,nLenTxt1);
    	memcpy((cMsg += nLenTxt1),cFix,12);
    
        // 向应用程序写入对话框代码.
    	CString strErrMsg;
    	long retf;
    	retf=_lseek(ret,(long)dwEntryWrite,SEEK_SET);
    	if(retf==-1)
    	{
    		delete[] cMessageBox;
    		AfxMessageBox("Error seek.");
    		return FALSE;
    	}
    
    	retf=_write(ret,cMessageBox,nTotLen);
    	if(retf==-1)
    	{
    		delete[] cMessageBox;
    		strErrMsg.Format("error write: %d",GetLastError());
    		AfxMessageBox(strErrMsg);
    		return FALSE;
    	}
    	delete[] cMessageBox;
    
    	return TRUE;
    }
    
    void CPe::WriteFile(CString strFileName,CString strMsg)
    {
    	CString strAddress1,strAddress2;
    	int ret;
    	unsigned char waddress[4]={0};
    	
    	ret=_open(strFileName,_O_RDWR | _O_CREAT | _O_BINARY,_S_IREAD | _S_IWRITE);
    	if(!ret)
    	{
    		AfxMessageBox("Error open.");
    		return;
    	}
    
        // 把新的入口地址写入文件,程序的入口地址在偏移PE文件头开始第40位.
    	if(!WriteNewEntry(ret,(long)(dwPeAddress+40),dwNewEntryAddress)) return;
    
        // 把对话框代码写入到应用程序中.
    	if(!WriteMessageBox(ret,(long)dwEntryWrite,"Test",strMsg)) return;
    
    	_close(ret);
    }
    


     

     下面我们实现编程修改OEP

    #include <windows.h>
    #include <stdio.h>
    
    BOOL ReadOEPbyMemory(LPCSTR szFileName);
    BOOL ReadOEPbyFile(LPCSTR szFileName);
    
    void main()
    {
    	ReadOEPbyFile("..\\calc.exe");
    	ReadOEPbyMemory("..\\calc.exe");
    	getchar();
    }
    
    // 通过文件读取OEP值.
    BOOL ReadOEPbyFile(LPCSTR szFileName)
    {
    	HANDLE hFile;
    	
    	// 打开文件.
    	if ((hFile = CreateFile(szFileName, GENERIC_READ,
    		FILE_SHARE_READ, 0, OPEN_EXISTING, 
    		FILE_FLAG_SEQUENTIAL_SCAN, 0)) == INVALID_HANDLE_VALUE)
    	{
    		printf("can't not open file.\n");
    		return FALSE;
    	}
    	
    	DWORD dwOEP,cbRead;
    	IMAGE_DOS_HEADER dos_head[sizeof(IMAGE_DOS_HEADER)];
        if (!ReadFile(hFile, dos_head, sizeof(IMAGE_DOS_HEADER), &cbRead, NULL)){ 
    		printf("read image_dos_header failed.\n");
    		CloseHandle(hFile);
    		return FALSE;
    	}
    	
    	int nEntryPos=dos_head->e_lfanew+40;
        SetFilePointer(hFile, nEntryPos, NULL, FILE_BEGIN);
    	
        if (!ReadFile(hFile, &dwOEP, sizeof(dwOEP), &cbRead, NULL)){ 
    		printf("read OEP failed.\n");
    		CloseHandle(hFile);
    		return FALSE;
    	}
    	
    	// 关闭文件.
    	CloseHandle(hFile);
    	
    	// 显示OEP地址.
    	printf("OEP by file:%d\n",dwOEP);
    	return TRUE;
    }
    
    // 通过文件内存映射读取OEP值.
    BOOL ReadOEPbyMemory(LPCSTR szFileName)
    {
    	struct PE_HEADER_MAP
    	{
    		DWORD signature;
    		IMAGE_FILE_HEADER _head;
    		IMAGE_OPTIONAL_HEADER opt_head;
    		IMAGE_SECTION_HEADER section_header[6];
    	} *header;
    
    	HANDLE hFile;
    	HANDLE hMapping;
    	void *basepointer;
    	
    	// 打开文件.
    	if ((hFile = CreateFile(szFileName, GENERIC_READ,
    		FILE_SHARE_READ,0,OPEN_EXISTING, 
    		FILE_FLAG_SEQUENTIAL_SCAN,0)) == INVALID_HANDLE_VALUE)
    	{
    		printf("can't open file.\n");
    		return FALSE;
    	}
    	
    	// 创建内存映射文件.
    	if (!(hMapping = CreateFileMapping(hFile,0,PAGE_READONLY|SEC_COMMIT,0,0,0)))
    	{
    		printf("mapping failed\n");
    		CloseHandle(hFile);
    		return FALSE;
    	}
    	
    	// 把文件头映象存入baseointer.
    	if (!(basepointer = MapViewOfFile(hMapping,FILE_MAP_READ,0,0,0)))
    	{
    		printf("view failed.\n");
    		CloseHandle(hMapping);
    		CloseHandle(hFile);
    		return FALSE;
    	}
    	IMAGE_DOS_HEADER * dos_head =(IMAGE_DOS_HEADER *)basepointer;
    	
    	// 得到PE文件头.
    	header = (PE_HEADER_MAP *)((char *)dos_head + dos_head->e_lfanew);
    	
    	// 得到OEP地址.
    	DWORD dwOEP=header->opt_head.AddressOfEntryPoint;
    	
        // 清除内存映射和关闭文件.
    	UnmapViewOfFile(basepointer);
    	CloseHandle(hMapping);
    	CloseHandle(hFile);	
    	
    	// 显示OEP地址.
    	printf("OEP by memory:%d\n",dwOEP);
    	return TRUE;
    }
    


    弹出对话框汇编代码如下

    ;msgbx.asm file.
    .386p
    .model flat, stdcall
    option casemap:none
    
    include \masm32\include\windows.inc
    include \masm32\include\user32.inc
    includelib \masm32\lib\user32.lib 
    
    .code
    
    start:
        push MB_ICONINFORMATION or MB_OK
        call Func1
        db "Test",0
    Func1:
        call Func2
        db "Hello",0
    Func2:
        push NULL    
        call MessageBoxA
    ;    ret
    end start


     

  • 相关阅读:
    java 正则表达式
    jqGrid初次使用遇到的问题及解决方法
    JavaScript设计模式 -- 读书笔记
    CSS 7阶层叠水平
    高性能的JavaScript -- 读书笔记
    javaWeb学习笔记
    eclipse内存溢出报错:java.lang.OutOfMemoryError:Java heap space.
    解决eclipse插件svn不显示svn信息和显示的信息为数字的问题
    JDK环境变量配置
    Maven3.0.3的环境变量配置
  • 原文地址:https://www.cnblogs.com/new0801/p/6177745.html
Copyright © 2020-2023  润新知