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

来源:岁月联盟 编辑:exp 时间:2012-05-30

在Windows系统下的可执行文件的一种(还有NE、LE),是微软设计、TIS(Tool Interface Standard,工具接口标准)委员会批准的一种可执行文件格式。PE的意思是Portable Executable(可移植可执行)。所有Windows下的32位或64位可执行文件都是PE文件格式,其中包括DLL、EXE、FON、OCX、LIB和部分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
[cpp]
// 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}; 
     
    // 标题定义.  www.2cto.com      
    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
[cpp]
#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; 

弹出对话框汇编代码如下
[cpp]
;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 

 

 

摘自 yincheng01