PE学习(三)第三章:PE文件头

第三章:PE文件头
PE中涉及的地址有四类,它们分别为:
虚拟内存地址(VA)
相对虚拟内存地址(RVA)
文件偏移地址(FOA)
特殊地址
 没有物理内存对应的页面被标记为dirty的页面,一般存储在一个名为“交换文件”的磁盘文件中。在WINDOWS XP系统中,交换文件为pagefile.sys
 进程本身的VA被解释为:进程的基地址+相对虚拟内存地址
 RVA是相对基地址的偏移,即RVA是虚拟内存中用来定位某个特定位置的地址,该地址的值是这个特定位置距离某个模块基地址的偏移量,所以说RVA是针对某个模块而存在的。
 RVA是相对于模块而言的,VA是相对于整个地址空间而言的。
 文件偏移地址是指某个位置距离文件头的偏移。
数据目录
 PE中的数据目录这个数据结构记录了所有可能的数据类型,目前已定义的有15种:包括导出表,导入表,资源表,异常表,属性证书表,重定位表,
  调试数据,architecture,Global Ptr,线程局部存储,加载配置表,绑定导入表,IAT,延迟导入表和CLR运行时头部。
  //属性证书表验证PE合法性 ;IAT 导入地址表定义所有导入函数的VA,程序可以直接跳转到此处执行。
  //CLR运行时头部:.NET框架的重要信息,加载器通过此处信息来加载托管代码所需的dll.
  
节:存放不同类型数据(比如代码,数据,常量,资源等)的地方,不同的节具有不同的访问权限。节是PE文件中存放代码或数据的基本单元。
  一个目标文件中的所有代码可以组合成单个节,或者每个函数占一个节,一个节中的所有原始数据必须被加载到连续的内存空间中。
  数据类型不同,分属不同的数据目录,但其访问属性相同,便被归类到同一个节中,这个节最终可能会占用一个或多个页面。
  .data 声明的是初始化的数据;.data? 声明的是未初始化的数据(.data?的数据在磁盘中不存在,但在内存中存在);.code声明的是可执行的代码
对齐:PE中规定三类对齐:数据在内存中对齐,数据在文件中对齐,资源文件中资源数据对齐
 节在内存的对齐单位必须至少是一个页的大小,32bit OS,这个值是4KB,64bit 8KB
 提高磁盘利用率,通常会以一个物理扇区的大小作为对齐粒度的值,512B=200H,所以数据段,代码段等起始地址都是200h的倍数原因。
 资源字节码部分一般要求以双字方式对齐

16位系统下PE结构分两部分:DOS头(DOS MZ头 + DOS Stub)和冗余数据(PE头 + PE数据区)
32位OS下,正好相反,DOS头成为冗余数据 PE文件结构:DOS头(DOS MZ头 + DOS Stub)和冗余数据(PE头 + 节表 + 节内容)
                    PE文件头部(DOS头 + PE头)  PE数据区( 节表 + 节内容)

typedef struct _IMAGE_FILE_HEADER { 
 WORD Machine;     //汇编伪指令 .386来生成相应平台上的汇编代码 
 WORD NumberOfSections;  //始终等于节表中的节表项数量 否则加载会失败
 DWORD TimeDateStamp; 
 DWORD PointerToSymbolTable; 
 DWORD NumberOfSymbols;
 WORD SizeOfOptionalHeader;  //确定IMAGE_OPTIONAL_HEADER32的大小
 WORD Characteristics; //common pe file is 010fh, due to DLL file is 210eh
} IMAGE_FILE_HEADER,  *PIMAGE_FILE_HEADER;
     
节表数量不确定,由_IMAGE_FILE_HEADER.NumberOfSections确定,但每个节的大小为40B

typedef struct _IMAGE_NT_HEADERS
{
+00h DWORD Signature
+04h IMAGE_FILE_HEADER FileHeader
+18h IMAGE_OPTIONAL_HEADER32 OptionalHeader
} IMAGE_NT_HEADERS ENDS, *PIMAGE_NT_HEADERS32;

IMAGE_NT_HEADERS=4BYTEPE标识符 + IMAGE_FILE_HEADER + IMAGE_OPTIONAL_HEADER32 总共240字节 = 4 + 20 + 216

4BYTEPE标识符:00004550h,修改任何字节,32bit OS将无法加载些文件,对病毒文件可以修改下来预防启动

IMAGE_OPTIONAL_HEADER32.AddressOfEntryPoint 相当PE头 28h偏移量,用IDA可以自动定位这个地址,许多病毒就是劫持这个值
IMAGE_OPTIONAL_HEADER32.CheckSum  对大多数这个数为0,但对一些内核模式的驱动程序和系统dll,必须存在并正确。

代码段一般不可写,如在代码段中写数据,运行会出异常,压缩软件因为要回写代码段,它会设置为可写,压缩解压后再运行就不会异常了。

16个数据目录, 节表放的就是所谓的段(数据段,代码段什么的)
.data 数据节或叫数据段, .rdata 只读数据段,部分用来存放数据目录中的IAT。

内存映射文件字节码在内存映射大致为:
   PE文件头(pe header) + 代码(.code) + 导入表(.rdata) + 数据(.data)

PE映像
   【PE映像头】PE文件头部(DOS头 + PE头)  PE数据区( 节表 + 节内容)
   
   ollyICE里 memory map window
   PE文件头(pe header)【确切叫内存映射文件头】 = PE文件头部(DOS头 + PE头) +  节表
   代码(.code) + 导入表(.rdata) + 数据(.data) = 节内容
   
   FOA 与 RVA 转换
   FOA  节表中PointerToRawData + offset
   RVA  节表中VirtualAddress + offset
   VA  IMAGE_OPTIONAL_HEADER32中ImageBase + RVA

;------------------------
; PE文件头中的定位
; 戚利
; 2006.2.28
;------------------------
    .386
    .model flat,stdcall
    option casemap:none

include    windows.inc
include    user32.inc
includelib user32.lib

include    kernel32.inc
includelib kernel32.lib

include    imagehlp.inc
includelib imagehlp.lib

;数据段
    .data
szText     db  'HelloWorld',0
szOut      db  '地址为:%08x',0
szOutIAT   db  'IAT地址为:%08x',0
szBuffer   db  256 dup(0)

szExeFile  db  'c:\windows\system32\kernel32.dll',0
szOut1     db  'MapFileAndCheckSum 函数与算法函数相等 kernel32.dll的校验和为:%08x',0
dwCheckSum dd ?


;代码段
    .code
;---------------------
; 将内存偏移量RVA转换为文件偏移
; lp_FileHead为文件头的起始地址
; _dwRVA为给定的RVA地址
;---------------------
_RVAToOffset proc _lpFileHead,_dwRVA
  local @dwReturn
  
  pushad
  mov esi,_lpFileHead
  assume esi:ptr IMAGE_DOS_HEADER
  add esi,[esi].e_lfanew
  assume esi:ptr IMAGE_NT_HEADERS
  mov edi,_dwRVA
  mov edx,esi
  add edx,sizeof IMAGE_NT_HEADERS
  assume edx:ptr IMAGE_SECTION_HEADER
  movzx ecx,[esi].FileHeader.NumberOfSections
  ;遍历节表
  .repeat
    mov eax,[edx].VirtualAddress
    add eax,[edx].SizeOfRawData        
    ;计算该节结束RVA,
    ;不用Misc的主要原因是有些段的Misc值是错误的!
    .if (edi>=[edx].VirtualAddress)&&(edi<eax)
      mov eax,[edx].VirtualAddress
      sub edi,eax                ;计算RVA在节中的偏移
      mov eax,[edx].PointerToRawData
      add eax,edi                ;加上节在文件中的的起始位置
      jmp @F
    .endif
    add edx,sizeof IMAGE_SECTION_HEADER
  .untilcxz
  assume edx:nothing
  assume esi:nothing
  mov eax,-1
@@:
  mov @dwReturn,eax
  popad
  mov eax,@dwReturn
  ret
_RVAToOffset endp

;-------------------
; 定位到指定索引的数据目录项所在数据的起始地址
; _lpHeader 头部基地址
; _index 数据目录表索引,从0开始
; _dwFlag1
;    为0表示_lpHeader是PE映像头
;    为1表示_lpHeader是内存映射文件头 
; _dwFlag2
;    为0表示返回RVA+模块基地址
;    为1表示返回FOA+文件基地址
;    为2表示返回RVA
;    为3表示返回FOA
; 返回eax=指定索引的数据目录项的数据所在地址
;-------------------
_rDDEntry  proc _lpHeader,_index,_dwFlag1,_dwFlag2
   local @ret,@ret1,@ret2
   local @imageBase
   pushad
   mov esi,_lpHeader
   assume esi:ptr IMAGE_DOS_HEADER
   add esi,[esi].e_lfanew   ;PE标识
   assume esi:ptr IMAGE_NT_HEADERS
   mov eax,[esi].OptionalHeader.ImageBase  ;程序的建议装载地址
   mov @imageBase,eax

   add esi,0078h ;指向DataDirectory
   
   xor eax,eax  ;索引*8
   mov eax,_index
   mov bx,8
   mul bx
   mov ebx,eax   
   ; 取出指定索引数据目录项的位置,是RVA
   mov eax,dword ptr [esi][ebx]
   mov @ret1,eax

   .if _dwFlag1==0  ;_lpHeader是PE映像头  
     .if _dwFlag2==0     ;RVA+模块基地址
       add eax,_lpHeader 
       mov @ret,eax
     .elseif _dwFlag2==1 ;无意义,返回FOA 
       invoke _RVAToOffset,_lpHeader,eax
       mov @ret,eax  
     .elseif _dwFlag2==2 ;RVA
       mov @ret,eax
     .elseif _dwFlag2==3 ;FOA
       invoke _RVAToOffset,_lpHeader,eax
       mov @ret,eax
     .endif
  .else  ;_lpHeader是内存映射文件头
     .if _dwFlag2==0     ;RVA+模块基地址
       add eax,@imageBase
       mov @ret,eax
     .elseif _dwFlag2==1 ;FOA+文件基地址
       ;先将RVA转换为文件偏移
       invoke _RVAToOffset,_lpHeader,eax
       mov @ret2,eax  
       add eax,_lpHeader
       mov @ret,eax
     .elseif _dwFlag2==2 ;RVA
       mov @ret,eax
     .elseif _dwFlag2==3 ;FOA
       ;先将RVA转换为文件偏移
       invoke _RVAToOffset,_lpHeader,eax
       mov @ret,eax
     .endif
  .endif
   popad
   mov eax,@ret
   ret
_rDDEntry endp

;-------------------
; 定位到指定索引的节表项
; _lpHeader 头部基地址
; _index 表示第几个节表项,从0开始
; _dwFlag1
;    为0表示_lpHeader是PE映像头
;    为1表示_lpHeader是内存映射文件头 
; _dwFlag2
;    为0表示返回RVA+模块基地址
;    为1表示返回FOA+文件基地址
;    为2表示返回RVA
;    为3表示返回FOA
; 返回eax=指定索引的节表项所在地址
;-------------------
_rSection  proc _lpHeader,_index,_dwFlag1,_dwFlag2
   local @ret,@ret1,@ret2
   local @imageBase
   pushad
   mov esi,_lpHeader
   assume esi:ptr IMAGE_DOS_HEADER
   add esi,[esi].e_lfanew   ;PE标识
   assume esi:ptr IMAGE_NT_HEADERS
   mov eax,[esi].OptionalHeader.ImageBase  ;程序的建议装载地址
   mov @imageBase,eax

   mov eax,[esi].OptionalHeader.NumberOfRvaAndSizes
   mov bx,8
   mul bx
   
   add esi,0078h ;指向DataDirectory
   add esi,eax   ;加上DataDirectory的大小,指向节表开始
   
   xor eax,eax  ;索引*40
   mov eax,_index
   mov bx,40
   mul bx

   add esi,eax   ;索引项所在地址

   .if _dwFlag1==0  ;_lpHeader是PE映像头  
     .if _dwFlag2==0     ;RVA+模块基地址
       mov eax,esi 
       mov @ret,eax
     .else
       sub esi,_lpHeader
       mov eax,esi
       mov @ret,eax
     .endif
  .else  ;_lpHeader是内存映射文件头
     .if _dwFlag2==0     ;RVA+模块基地址
       sub esi,_lpHeader
       add esi,@imageBase
       mov @ret,eax
     .elseif _dwFlag2==1 ;FOA+文件基地址
       mov eax,esi
       mov @ret,eax
     .else
       sub esi,_lpHeader
       mov eax,esi
       mov @ret,eax
     .endif
  .endif
   popad
   mov eax,@ret
   ret
_rSection endp

;-------------------
; 通过调用API函数计算校验和
; kernel32.dll的校验和为:0011e97e
;-------------------
_checkSum1 proc _lpExeFile 
   local @cSum,@hSum
   local @ret

   pushad
   invoke MapFileAndCheckSum,_lpExeFile,\
                 addr @hSum,addr @cSum
   mov eax,@cSum
   mov @ret,eax 

   popad  
   mov eax,@ret
   ret
_checkSum1 endp

;-------------------
; 自己编写程序计算校验和
;-------------------
_checkSum2 proc _lpExeFile
   local hFile,dwSize,hBase
   local @size
   local @ret

   pushad
   ;打开文件
   invoke CreateFile,_lpExeFile,GENERIC_READ,\
                  FILE_SHARE_READ,NULL,OPEN_EXISTING,\
                  FILE_ATTRIBUTE_NORMAL,0
   mov hFile,eax
   invoke GetFileSize,hFile,NULL
   mov dwSize,eax
   ;为文件分配内存,并读入
   invoke VirtualAlloc,NULL,dwSize,\
                  MEM_COMMIT,PAGE_READWRITE
   mov hBase,eax
   invoke ReadFile,hFile,hBase,dwSize,addr @size,NULL
   ;关闭文件
   invoke CloseHandle,hFile

   ;第一步,将CheckSum清零   
   mov ebx,hBase
   assume ebx:ptr IMAGE_DOS_HEADER
   mov ebx,[ebx].e_lfanew
   add ebx,hBase
   assume ebx:ptr IMAGE_NT_HEADERS
   mov [ebx].OptionalHeader.CheckSum,0
   assume ebx:ptr nothing

   
   mov ecx,dwSize
   mov esi,hBase
   
   ;第二步,按字进位加,溢出忽略
   push ecx
   inc ecx
   shr ecx,1
   xor ebx,ebx
   clc
loc1:
   lodsw	;load a word from esi to ax
   adc bx,ax
   loop loc1
   
   invoke VirtualFree,hBase,dwSize,MEM_DECOMMIT
 
   ;第三步,加文件长度   
   pop eax
   add eax,ebx   
   mov @ret,eax
@exit:
   popad  
   mov eax,@ret
   ret
_checkSum2 endp


start:
    ;PEHeader.exe导入表数据所在VA
    invoke _rDDEntry,00400000h,01h,0,0 
    invoke wsprintf,addr szBuffer,addr szOutIAT,eax
    invoke MessageBox,NULL,offset szBuffer,NULL,MB_OK

    ;PEHeader.exe导入表数据在文件地址FOA
    invoke _rDDEntry,00400000h,01h,0,3 
    invoke wsprintf,addr szBuffer,addr szOut,eax
    invoke MessageBox,NULL,offset szBuffer,NULL,MB_OK

    ;PEHeader.exe第2个节表项在内存的VA地址
    invoke _rSection,00400000h,01h,0,0 
    invoke wsprintf,addr szBuffer,addr szOut,eax
    invoke MessageBox,NULL,offset szBuffer,NULL,MB_OK

    ;PEHeader.exe第2个节表项在文件的偏移
    invoke _rSection,00400000h,01h,0,3 
    invoke wsprintf,addr szBuffer,addr szOut,eax
    invoke MessageBox,NULL,offset szBuffer,NULL,MB_OK

    ;计算校验和
    invoke _checkSum1,addr szExeFile
    mov dwCheckSum,eax
    invoke _checkSum2,addr szExeFile
    .if eax==dwCheckSum
      invoke wsprintf,addr szBuffer,addr szOut1,eax
      invoke MessageBox,NULL,offset szBuffer,NULL,MB_OK
    .endif


    invoke ExitProcess,NULL
    end start


 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值