首页
社区
课程
招聘
[原创]PE文件基础补注(VC + 纯API版)
发表于: 2006-7-27 16:02 9983

[原创]PE文件基础补注(VC + 纯API版)

2006-7-27 16:02
9983

PE文件基础补注

关键词: PE文件  地址转换 IAT  IMAGE_IMPORT_BY_NAME

前言: 最近学习PE, 略有心得, 拿来和大家分享.

感谢: 小虾斑斑, 非安全  ,Bookworm对我的帮助.

1.IMAGE_SECTION_HEADER小结:
       
1.1   获得节表数 :NumberOfSections = NtHeader->FileHeader.NumberOfSections;

1.2   节表获得方法
      
      方法1.因为NT头之后就是节表,故,节表头地址就是nt头地址加上NT结构大小.
      SectionHeader=(PIMAGE_SECTION_HEADER)((UINT32)NtHeader+(UINT32)(sizeof(IMAGE_NT_HEADERS)));
       
      方法2.或者用ImageBase+SizeOfHeaders的办法直接定位.
      SectionHeader=(PIMAGE_SECTION_HEADER)((UINT32)(NtHeader->OptionalHeader.ImageBase)+
                                          (UINT32)(NtHeader->OptionalHeader.SizeOfHeaders));
       
      方法3.既然节都是连在一起的,那么,也就可以这样:
              SectionHeader= (PIMAGE_SECTION_HEADER) (NtHeader + 1),
       
      方法4.论坛里面 hmimys 告诉的办法:   
              SectionHeader=(PIMAGE_SECTION_HEADER)((UINT32)NtHeader+0x18+
                                                   (UINT32)(NtHeader->FileHeader.SizeOfOptionalHeader));
              到现在我还没有弄懂为什么 hmimys 说最好要用方法4而不用方法3.
               
   
2. IMAGE_IMPORT_DECSRITOR 小结:

2.1:获得引入表结构起始地址:
     
     方法1:ImportDec = (PIMAGE_IMPORT_DESCRIPTOR)(NtHeader->OptionalHeader.DataDirectory[12].VirtualAddress);
            这个方法我觉得理论上是对的,但是我在运行的时候总是得不到正确的地址.后来知道,似乎不能用'12',而要用IMAGE_DIRECTORY_ENTRY_IAT这个宏

     方法2:ImportDes = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)(NtHeader->OptionalHeader.DataDirectory)+
                                                    (DWORD)(sizeof(IMAGE_DATA_DIRECTORY)*12));
   
     方法3 : ImportDes = (PIMAGE_IMPORT_DESCRIPTOR)(NtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress-
                            Offset + (PBYTE)pMapping);

     注 : 前两种方法都是从 IAT 中得出 IMAGE_IMPORT_DESCRIPTOR,而后面的那个是 非安全 大哥教的. 这里有个疑问:
          3种方法都可以得到 IMAGE_IMPORT_DESCRIPTOR 结构,都可以得到函数名, 区别在于前两种方法枚举的函数名不全.
          难道说两个结构都指向同一个结构PIMAGE_IMPORT_DESCRIPTOR?

2.2  IMAGE_IMPORT_DESCRIPTOR 结构既不是在Import Symbols中,也不是在IAT (IMAGE_IMPORT_ADDRESS_TABLE)中。它就是一个结构.
     我原来说:"IMAGE_IMPORT_DESCRIPTOR 结构不是在Import Symbols中,是在IAT (IMAGE_IMPORT_ADDRESS_TABLE)中。" 有问题.
     就是因为这个错误的理解, 让我走了好多死路.
     
     这个是Winnt.h中关于 IMAGE_SYNMBOL的结构信息

     typedef struct _IMAGE_SYMBOL {
            union {
        BYTE    ShortName[8];
        struct {
            DWORD   Short;     // if 0, use LongName
            DWORD   Long;      // offset into string table
        } Name;
        PBYTE   LongName[2];
     } N;
     DWORD   Value;
     SHORT   SectionNumber;
     WORD    Type;
     BYTE    StorageClass;
     BYTE    NumberOfAuxSymbols;
     } IMAGE_SYMBOL;
     
     typedef IMAGE_SYMBOL UNALIGNED *PIMAGE_SYMBOL;

     而下面的是IAT:

     typedef struct _IMAGE_IMPORT_BY_NAME {
            WORD    Hint;
            BYTE    Name[1];
     } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

     typedef struct _IMAGE_IMPORT_DESCRIPTOR {
            union {
                DWORD   Characteristics;            // 0 for terminating null import descriptor
                DWORD   OriginalFirstThunk;         // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
            };
            DWORD   TimeDateStamp;                  // 0 if not bound,
                                            // -1 if bound, and real date\time stamp
                                            //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
                                            // O.W. date/time stamp of DLL bound to (Old BIND)

            DWORD   ForwarderChain;                 // -1 if no forwarders
            DWORD   Name;
            DWORD   FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)
     } IMAGE_IMPORT_DESCRIPTOR;
     typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

     _IMAGE_IMPORT_DESCRIPTOR 结构联合中的OriginalFirstThunk , 就是到IMAGE_THUNK_DATA的RVA.
     如果像下面这样写,也许更明白

     typedef struct _IMAGE_THUNK_DATA {
        union {
            PBYTE ForwarderString;
            PDWORD Function;
            DWORD Ordinal;
            PIMAGE_IMPORT_BY_NAME AddressOfData;
        } ;
     } IMAGE_THUNK_DATA,*PIMAGE_THUNK_DATA;

     typedef struct _IMAGE_IMPORT_DESCRIPTOR {
        union {
            DWORD Characteristics;
            PIMAGE_THUNK_DATA OriginalFirstThunk;
        } ;
        DWORD TimeDateStamp;
        DWORD ForwarderChain;
        DWORD Name;
        PIMAGE_THUNK_DATA FirstThunk;
     } IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;
     

3. 地址转换小结(RVAToOffset):

   为什么要地址转换, 前人的文章说了很多,下面给出我的转换方法:  
   
   3.1 函数,它能给出RVA返回此RVA所在的节,来自 Matt Pietrek的书:

   PIMAGE_SECTION_HEADER GetEnclosingSectionHeader(DWORD rva){
        unsigned i;
            PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION32(NtHeader);
            for ( i=0; i < NtHeader->FileHeader.NumberOfSections; i++,section++){
               if ( (rva >=section->VirtualAddress) &&
             (rva < (section->VirtualAddress + section->Misc.VirtualSize)))
            return section;
            }
               return 0;
   }

   注: hnhuqiong 给的 ollydump300110 的源码里面也有类似函数,但是,
       很明显的有漏洞,那就是若RVA不在任何一个Section那么函数会返回最后
       一个Section, 而不是像这里返回 0 .下面是原始连接
   http://bbs.pediy.com/showthread.php?threadid=26520

   3.2 RVAToOffset:

   我一直没有注意的就是'Offset'这个词. Offset其实还是一个偏移,只不过是
   在文件中, 要想得到目标文件的IAT, 就要将这个值加上由 MapViewOfFile 返回
   的文件基址指针.

   Offset的的获得 :
            pSection = GetEnclosingSectionHeader(NtHeader->OptionalHeader.DataDirectory                                       

                                           [IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress);
            Offset = (DWORD) (pSection->VirtualAddress - pSection->PointerToRawData);
  
   以获得IMAGE_THUNK_DATA结构为例,给出用法:

        ThunkData = (PIMAGE_THUNK_DATA)((DWORD)ImportDes->OriginalFirstThunk -
                                       Offset + (PBYTE)pMapping);
   呵呵, (DWORD)ImportDes->OriginalFirstThunk -Offset 得到的只是文件中的偏移,
   注意加上由 MapViewOfFile 返回的pMapping. 如果你象我原来一样,加上的是
   NtHeader->OptionalHeader.ImageBase , 那么恭喜你, 访问错误.                        
       

4. 用VC 6.0 + API获得IMAGE_IMPORT_BY_NAME结构的一点问题.

   在 VC 里面,  在一个结构指针比如ThunkData后面加上'->'时, vc会自动的列出
   结构的成员供你选择, 十分方便. 但是, 通过ThunkData继续想获得IMAGE_IMPORT_BY_NAME
   结构的时候, 你在ThunkData后面加'->'时, 出来的是一个'u1'. 此时不要疑惑,
   这个'u1'就是 IMAGE_THUNK_DATA 里面的那个 union 的名称, 所以你可以这样得到
   IMAGE_IMPORT_BY_NAME结构:

       ImportBN = (PIMAGE_IMPORT_BY_NAME)((DWORD)(ThunkData->u1.AddressOfData)-
                                Offset +(PBYTE)pMapping);

5.   Iczelion的PE教程关于导入表的描述没有讲清楚,只是说用IMAGE_THUNK_DATA
      的每个数组元素和IMAGE_ORDINAL_FLAG32,比较可以推断如果某个函数是由函数序数引出的,
      我就误解成用ImportDes->OriginalFirstThunk或者ImportDes->FirstThunk 判断。是不是错的很远?
      参考(【翻译】“PE文件格式”1.9版 完整译文(附注释))http://bbs.pediy.com/showthread.php?threadid=21932,
      我们应该用IMAGE_THUNK_DATA结构里面的AddressOfData来判断。下面的代码可行:

      while(ThunkData->u1.AddressOfData!=NULL){
         ImportBN = (PIMAGE_IMPORT_BY_NAME)((DWORD)(ThunkData->u1.AddressOfData) - Offset +(PBYTE)pMapping);
         //显示导入函数
         if(((DWORD)ThunkData->u1.AddressOfData & IMAGE_ORDINAL_FLAG32) == 0){
                AddText(hEdit,TEXT("%03d:  %s\r\n"),i++,ImportBN->Name);
         }
         else{
                AddText(hEdit,TEXT("%03d:  Ord by Hint\r\n"),i++);
         }
         ThunkData ++;  
      }//End of while

6      导出表:
6.1    导出表的结构,
   typedef struct _IMAGE_EXPORT_DIRECTORY {
      DWORD   Characteristics;
      DWORD   TimeDateStamp;
      WORD    MajorVersion;
      WORD    MinorVersion;
      DWORD   Name;
      DWORD   Base;
      DWORD   NumberOfFunctions;
      DWORD   NumberOfNames;
      DWORD   AddressOfFunctions;     // RVA from base of image
      DWORD   AddressOfNames;         // RVA from base of image
      DWORD   AddressOfNameOrdinals;  // RVA from base of image
   } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

6.2   AddressOfNames 和AddressOfNameOrdinals 是一一对应的,只不过一个用于名字,
      一个用于序号, 同一个函数的索引都相同。

6.3   NumberOfFunctions ? NumberOfNames 应该就是由序号引出的函数数目了

6.4   对于由序号导出的函数,不知道有没有办法能通过序数找到函数名。个人考虑似乎不可能这样
      找函数名字,不然,微软未公开的函数就都被我们通过函数序数枚举出来了? :)

7:  把我的PE查看器修改了下, 原来的在处理用序号引出的函数时会出错.:)


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 7
支持
分享
最新回复 (7)
雪    币: 750
活跃值: (228)
能力值: ( LV9,RANK:780 )
在线值:
发帖
回帖
粉丝
2
呵呵,兄弟总结的不错,支持!
2006-7-27 17:34
0
雪    币: 221
活跃值: (11)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
3
但是对照你刚刚在我的另一篇帖子里的回复, IMAGE_IMPORT_DESCRIPTOR是

从Import Symbols,也就是Data Directory的第二个成员里面"指"出来的,

刚才一直在试, 想用Import Symbols得到IMAGE_IMPORT_DESCRIPTOR,结果

一直读不出来东西.我再试试, 搞不好我上面的帖子的第 2 部分就全错了.
2006-7-27 17:45
0
雪    币: 179
活跃值: (131)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
4
学习PE最好的方法就是做一个PE查看器,嘿嘿
2006-7-27 18:04
0
雪    币: 221
活跃值: (11)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
5
当然, 这个就是我在写查看器时的小心得. :)

PE基础搞清出了, 准备再手工打造一个PE文件,

那样对PE的了解就更上一个档次了吧.

大家一起努力!
2006-7-27 18:09
0
雪    币: 415
活跃值: (34)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
6
历害.比我理解的深刻多了
2006-7-27 19:55
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
程序中有一句
#pragma comment(lib,"Dbghelp.lib")
好象没用啊?我因没有dbghelp.lib,把它注释后编译也没出错。
2006-9-7 22:10
0
雪    币: 202
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
能不能搞个delphi版的
2006-9-8 09:18
0
游客
登录 | 注册 方可回帖
返回
//