首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

2.6.2  把MessageBoxA()代码写入PE文件的完整实例

根据以上的对MessageBoxA()的分析,可以直接把以上代码注入到一个PE可执行 文件中。为了使程序有通用性,这里编写了一个产生显示任意长度字符的对话框的函数WriteMessageBox()。

下面是用于注入MessageBoxA()代码的头文件,取名为Pe.h,其中用 #include包含了相关的文件头,定义了peHeader结构,且定义了CPe类,其源代码如下:

l           

// Pe.h: 定义CPe类

//

#ifndef _PE_H__INCLUDED

#define _PE_H__INCLUDED

#include <io.h>

#include <fcntl.h>

#include <sys\stat.h>

typedef struct PE_HEADER_MAP

{

    DWORD signature;

    IMAGE_FILE_HEADER _head;

    IMAGE_OPTIONAL_HEADER opt_head;

    IMAGE_SECTION_HEADER section_header[6];

} peHeader;

class CPe 

{

public:

    CPe();

    virtual ~CPe();

public:

    void CalcAddress(const void *base);

    void ModifyPe(CString strFileName,CString strMsg);

    void WriteFile(CString strFileName,CString strMsg);

    BOOL WriteNewEntry(int ret,long offset,DWORD dwAddress);

    BOOL WriteMessageBox(int ret,long offset,CString strCap,CString

    strTxt);

    CString StrOfDWord(DWORD dwAddress);

public:

    DWORD dwSpace;

    DWORD dwEntryAddress;

    DWORD dwEntryWrite;

    DWORD dwProgRAV;

    DWORD dwOldEntryAddress;

    DWORD dwNewEntryAddress;

    DWORD dwCodeOffset;

    DWORD dwPeAddress;

    DWORD dwFlagAddress;

    DWORD dwVirtSize;

    DWORD dwPhysAddress;

    DWORD dwPhysSize;

    DWORD dwMessageBoxAadaddress;

};

#endif

l           

其中peHeader结构是前面所讲的PE Header结构与节表(Section Table)头结构(6个表头成员)的总结构。因为它们在PE文件中是紧凑排列的,所以可以这样写。其实只用一个节表头就可以。

下面分别介绍CPe类成员函数的定义,它们包含在Pe.cpp文件中。在这个文件开始用#include包含了stdafx.h和Pe.h文件。用MFC VC++编译器编译时,必须包括stdafx.h文件,即使这个文件是空的,也需要包括它,这是编译器设置所致,除非修改MFC的编译器的默认设置。CPe类的构造和析构函数这里没有用上,对系统内存的访问和其他操作主要是通过主成员函数ModifyPe()来进行。它们的源代码如下:

l           

// 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);

}

其中对一个PE文件进行MessageBoxA()代码的注入是通过ModifyPe()函数进行,它的入口参数是要被修改的PE可执行文件名。在这个函数中,首先创建所修改文件的句柄,然后创建映射文件,再通过映射文件的句柄获得这个PE文件的文件头指针,最后把这个指针传给函数CalcAddress()。通过CalcAddress()函数来计算PE Header的开始偏移、保存旧的程序入口地址、计算新的程序入口地址和计算PE文件的空隙空间等。

CalcAddress()函数的源代码如下:

l           

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;

    }

}  

l           

下面的StrOfDWord()函数是把一个DWORD值转换成一个字符串,因为一个DWORD值占有4个字节,因此把一个DWORD值变成一个字符串,若保持数值不变,就变成了一个4个字节的字符串。同时把这个值的位置顺序颠倒,这是为了把一个实际的值变成按Little-endian的方式写入PE文件中,其转换方法如下:

l           

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;

}

l           

下面的WriteNewEntry()函数把新的入口点写入PE程序原来的入口点处,使PE装载器在载入程序时,直接跳入到MessageBoxA()的入口处,该函数的源代码如下:

l           

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;

}

l           

下面的WriteMessageBox()函数是把MessageBoxA()的机器代码写入到PE文件中。这个函数显示的对话框标题和显示的字符串内容和长度不是固定的。在这个函数中,首先就计算MessageBoxA()函数的地址和函数的返回地址,然后把重新生成的对话框代码写入到程序中。WriteMessageBox()函数的源代码如下:

l           

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+nTot

    Len-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;

}

l           

下面的WriteFile()函数是总的写入函数。在这个函数中,先打开被修改的PE文件,然后调用WriteNewEntry()和WriteMessageBox()函数。WriteFile()函数的源代码如下:

l           

void CPe::WriteFile(CString strFileName)

{

    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","We are the world!"))
return;

    _close(ret);

}

l           

仅仅利用以上CPe类还是不能对一个PE文件进行注入MessageBoxA()代码的修改,还必须要一个“载体程序”。例如:

l           

// Pefile.cpp:修改PE文件实例

//

#include "stdafx.h"

#include "Pe.h"

void main()

{

    CopyFile("..\\calc.exe","..\\calc_shell.exe",FALSE);

   

    CPe a;

    a.ModifyPe("..\\calc_shell.exe","We are the world!");

}

l           

这个修改后的PE文件运行时,就会先显示对话框,单击“确定”按钮后又继续执行。总之,在了解了PE文件格式后,就可以对某一个PE文件进行修改。本实例只是对PE文件处理的一种应用,在实际中还有更多的其他方面的应用。

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论