星绽紫辉(rawdata)的Blog

快乐地学习,快乐地工作!

  C++博客 :: 首页 :: 联系 :: 聚合  :: 管理
  16 Posts :: 0 Stories :: 37 Comments :: 0 Trackbacks

常用链接

留言簿(5)

我参与的团队

搜索

  •  

最新评论

阅读排行榜

评论排行榜

        
       原创:星绽紫辉(rawdata)
http://www.cppblog.com/rawdata  2009-1-20
       转载请注明出处

       关键字: PE   增加  区段  section 文件格式

       现在我要给一PE文件增加区段(section),但是增加区段后我不希望影响PE文件的正常使用,那么应该怎么

做呢?我写这个教程的目的,希望能帮学习PE格式,当然也作为我以后参考的笔记。

        简单地说:PE文件和普通文件没有什么区别,只是存在格式上的差异。另外,当你双击某个.exe文件

时,Windows Shell 程序将会尝试解析文件并运行它的PE代码。所以第一步,你必须对PE格式比较熟悉。现在

网上的PE教程我认为最好的就是罗云斌主页上的汇编教程了,我在这里长话短说,只是讨论和我们要解决的

问题相关的方面。

         一般来讲,PE由以下几个部分依次排列下来:

          1.   Dos 头

          2.   Dos stub    (通常你不必关心它的内容,重建PE只需完全拷贝即可) 
 
          3.   NT 头
    
          4.    节表

          5.    文件对齐间隙

          6.    第一节

          7.    第二节

          8.    ...

          9.    第 n  节 (文件结尾)


          对于PE文件,如果没有文件对齐和内存对齐,那将是非常简单了。(但是,太简单了就不安全了,不是

吗?对于这种格式,当初的设计者还是动了不少脑筋的,呵呵~)。我们可以用非常简单的读文件函数,读取

各部分结构,然后把它重组还原PE。如果你是初次接触,建议你这样实践一下。

        
         然后我们讨论一下如何增加区段,我们将尽量保证维持原来的PE结构的数据,然后在此基础上增加我们

的数据。现在我把这个过程步骤化:

        一、读取Dos头

        二、读取Dos stub

        三、读取NT头,根据Dos头定位到此

        四、读取NT头

        五、读取节表

        六、遍历读取所有节块


      现在,我们把一个PE文件读到缓冲区了。然后我们进行修改:试验证明,只需要某些特征项即可,而不

必对所有参数进行修改,这样PE文件(如.exe)还是能正常运行。对于具体的节块,我们必须给某些关键的

参数赋值,否则将破坏PE结构,导致不能运行。


      在开始修改前,有一些非常关键的东西我们必须知道:文件对齐和内存对齐。实际上,所有的section区

段都是文件对齐的(你把每一节当成一个块,具有起始文件位置和块大小),比如:第一节的文件偏移为

1024KB,节块大小为1000KB,那么第二节的文件偏移将是2048KB,而不是2024KB(文件偏移即是节块的开始

位置)。但这其实不是一成不变的。你可以修改它,只要满足文件对齐,但是带来的麻烦是,你必须同时也

修改它的定位目录----->节表里的PointerToRawData文件指针,否则将由于找不到对应的节块而产生错误。

       我常常思考这样一个问题,到底要不要把PE装载到内存,然后在内存中增加区段,然后把PE内存dump成

新的PE文件达到增加区段的目的。实际上,这是完全可行的。但是,直接修改PE文件也是可以的,可以不把

PE装载到内存。因为我们仅仅是增加区段,不需要修改引入表之类的东西。在我增加区段成功后,对比发

现,节表里面的Virtual Address 实际上等于PointerToRawData,节表里面的Virtual Size 实际上等于SizeofRawData

(呵呵,我的网名就是rawdata)。于是,我想,这样的设计实际上是为了简化PE程序的设计的复杂性。内存对

齐转化为文件对齐,一旦设计好文件对齐,那么内存对齐就设计好了。但是,你千万不要认为两者一定总是相

等的,实际它有很大的灵活性,你可以随意设计,只要满足NT头里面的指定的内存对齐值参数。

      还有一个关键的要注意的地方:必须重新修改NT头里面的pINH->OptionalHeader.SizeOfImage值。即整个PE

在内存的全部的映像的大小。我们可以这样给它赋予新的值:增加1个新的区段,就在原来的SizeOfImage值基

础上再加上该节块大小的文件对齐值,增加了几个区段,就累加几次,你应该明白了吧?

    
      如果你感觉我的语言表达很糟糕,请你谅解。不过,请你放心,我后面会给出源代码。其实主要做的工

作就是在文件尾加上一些数据,然后修改文件头的一些参数,仅此而已,没有什么神奇的地方,还等什么?

赶快去写一个PE加区工具吧~~~ 

     代码清单如下:    (平台:  console)
     把其中的PE文件名该为你想加区的PE文件名就可以了。

     后记:
     原来的程序是只使用于raw和rva相等的情况,正确的修改如下:
     
     把第295行修改为:
     //Rva与raw不匹配的情况。。。
   
   ppISH[i]->VirtualAddress = ppISH[i-1]->VirtualAddress +\
   ((ppISH[i-1]->SizeOfRawData/pINH->OptionalHeader.SectionAlignment)+1)*\
   (pINH->OptionalHeader.SectionAlignment);

 

  1/**************************************************************************
  2*    文件名:        Main.cpp
  3*    日  期:        2009年1月13日
  4*    作  者:        rawdata
  5*    描  述:        
  6***************************************************************************/

  7
  8#include <windows.h>
  9#include <windowsx.h>
 10#include <winnt.h>
 11#include <iostream>
 12using namespace std;
 13
 14#pragma warning(disable:4312)
 15#pragma warning(disable:4311)
 16#pragma warning(disable:4244)
 17
 18int main(int argc,char**argv)
 19{
 20    //------------------- 主要缓冲区定义 ----------------------
 21    
 22    BYTE* pDos = NULL;        //Dos头和Stub区
 23    DWORD dwSizeDos = 0;    //Dos头 大小
 24    DWORD dwSizeStub = 0;    //Dos Stub区大小
 25
 26    BYTE* pNT = NULL;        //NT头
 27    DWORD dwSizeNT = 0;        //该区大小
 28
 29    BYTE* pSecH = NULL;        //节表
 30    DWORD dwSizeSecH = 0;    //该区大小
 31    WORD dwSizeAdd = 3;        //增加的节的个数
 32
 33    BYTE* pDelta = NULL;    //文件对齐填充数据
 34    DWORD dwSizeDelta = 0;    //该区大小
 35
 36    BYTE* pPrevSec = NULL;    //节块
 37    DWORD dwSizePrevSec = 0;//该区大小
 38
 39    BYTE* pAddSec = NULL;    //新增的节块
 40    DWORD dwSizeAddSec = 0;    //该区大小
 41
 42    //---------------------- 其他临时定义 --------------------
 43    
 44    const char* pszFilePath = NULL;            //文件路径
 45    HANDLE hFile = NULL;                    //文件句柄
 46
 47    PIMAGE_DOS_HEADER        pIDH = NULL;    //Dos头
 48    PIMAGE_NT_HEADERS        pINH = NULL;    //NT 头
 49    PIMAGE_SECTION_HEADER*    ppISH = NULL;    //节表
 50
 51    DWORD dwRealRead = 0;                    //实际每次文件读取的字节数
 52    DWORD dwMiniPointer = 0;                //第一个section的位置
 53    BOOL bNeedModify = FALSE;                //是否有必要需要修改节块的文件指针
 54
 55    DWORD dwRawDataSize = 10*1024;                //增加区段的数据大小
 56
 57    DWORD dwTmp=0,dwTmp2=0,dwTmp3 = 0;
 58    
 59    //Dos 头
 60    //===============================================================================
 61    //打开文件、读取Dos头
 62
 63    pszFilePath = argv[0];
 64    pszFilePath = "BeingInjued.exe";//Test
 65
 66    cout<<"PE FileName:"<<pszFilePath<<endl;
 67
 68    if(0 == lstrcmp(pszFilePath,""))
 69    {
 70        cout<<"文件路径不能为空!"<<endl;
 71        return -1;
 72    }

 73
 74    hFile = CreateFile(pszFilePath,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,
 75        FILE_ATTRIBUTE_NORMAL,NULL);
 76
 77    if(INVALID_HANDLE_VALUE == hFile)
 78    {
 79        cout<<"打开文件失败!"<<endl;
 80        hFile = NULL;
 81        goto Error;
 82    }

 83
 84    dwSizeDos = sizeof(IMAGE_DOS_HEADER);
 85    pDos = new BYTE[dwSizeDos];
 86    memset(pDos,0,dwSizeDos);
 87
 88    if(!ReadFile(hFile,pDos,dwSizeDos,&dwRealRead,NULL))
 89    {
 90        cout<<"读取Dos头失败!"<<endl;
 91        goto Error;
 92    }

 93    cout<<"Dos Header: "<<dwRealRead