PEDump是PE文件字节码查看器,利用它可以查看阅读指定PE文件的十六进制字节码。

编程思路

步骤1:
打开PE文件。需要说明的是,打开的PE文件会被映射为内存文件。因为内存文件中的内容是线性存放的,存取方便,速度也快,并且操作起来比在文件中使用指针定位要更容易些。
步骤2:
使用API函数GetFileSize得到该PE文件的大小。
步骤3:
将第二步获取的值与16相除,商作为循环计数,余数则是字节码查看器最后一行的字节个数。在程序中构造一个循环,用来显示PE文件除最后一行以外其他行的字节内容。

PEDump编码

前面简单了解了程序的开发流程,接下来进入编码阶段。
此处将会用到前面的源程序文件pe.asm。
PEDump.asm在pe.asm的基础上增加了对菜单项IDM_OPEN的响应代码,如下所示:

.elseif eax==IDM_OPEN
	invoke _openFile

函数_openFile函数的实现如下所示:

;--------------------
; 打开PE文件并处理
;--------------------
_openFile proc
  local @stOF:OPENFILENAME
  local @hFile,@hMapFile
  local @bufTemp1   ; 十六进制字节码
  local @bufTemp2   ; 第一列
  local @dwCount    ; 计数,逢16则重新计
  local @dwCount1   ; 地址顺号
  local @dwBlanks   ; 最后一行空格数

  invoke RtlZeroMemory,addr @stOF,sizeof @stOF
  mov @stOF.lStructSize,sizeof @stOF
  push hWinMain
  pop @stOF.hwndOwner
  mov @stOF.lpstrFilter,offset szExtPe
  mov @stOF.lpstrFile,offset szFileName
  mov @stOF.nMaxFile,MAX_PATH
  mov @stOF.Flags,OFN_PATHMUSTEXIST or OFN_FILEMUSTEXIST
  invoke GetOpenFileName,addr @stOF  ;让用户选择打开的文件
  .if !eax
    jmp @F
  .endif
  invoke CreateFile,addr szFileName,GENERIC_READ,\
         FILE_SHARE_READ or FILE_SHARE_WRITE,NULL,\
         OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,NULL
  .if eax!=INVALID_HANDLE_VALUE
    mov @hFile,eax
    invoke GetFileSize,eax,NULL     ;获取文件大小
    mov totalSize,eax

    .if eax
      invoke CreateFileMapping,@hFile,\  ;内存映射文件
             NULL,PAGE_READONLY,0,0,NULL
      .if eax
        mov @hMapFile,eax
        invoke MapViewOfFile,eax,\
               FILE_MAP_READ,0,0,0
        .if eax
          mov lpMemory,eax              ;获得文件在内存的映象起始位置
          assume fs:nothing
          push ebp
          push offset _ErrFormat
          push offset _Handler
          push fs:[0]
          mov fs:[0],esp

          ;开始处理文件

         

          
          ;处理文件结束

          jmp _ErrorExit
 
_ErrFormat:
          invoke MessageBox,hWinMain,offset szErrFormat,NULL,MB_OK
_ErrorExit:
          pop fs:[0]
          add esp,0ch
          invoke UnmapViewOfFile,lpMemory
        .endif
        invoke CloseHandle,@hMapFile
      .endif
      invoke CloseHandle,@hFile
    .endif
  .endif
@@:        
  ret
_openFile endp

子程序_openFile 首先调用GetOpenFileName,显示一个文件选择对话框,让用户选择要打开的PE文件;然后获取指定文件的大小,并利用这个值通过CreatFileMapping在内存中建立该文件的映像;全局变量lpMemory指向了内存映像的起始地址。有了这个地址以后,对文件进行各种操作就简单了。
十六进制字节码查看器的主要代码如下:

;缓冲区初始化
          invoke RtlZeroMemory,addr @bufTemp1,10
          invoke RtlZeroMemory,addr @bufTemp2,20
          invoke RtlZeroMemory,addr lpServicesBuffer,100
          invoke RtlZeroMemory,addr bufDisplay,50

          mov @dwCount,1
          mov esi,lpMemory
          mov edi,offset bufDisplay
 
          ; 将第一列写入lpServicesBuffer
          mov @dwCount1,0
          invoke wsprintf,addr @bufTemp2,addr lpszFilterFmt4,@dwCount1
          invoke lstrcat,addr lpServicesBuffer,addr @bufTemp2

          ;求最后一行的空格数(16-长度%16)*3
          xor edx,edx
          mov eax,totalSize
          mov ecx,16
          div ecx
          mov eax,16
          sub eax,edx
          xor edx,edx
          mov ecx,3
          mul ecx
          mov @dwBlanks,eax

          ;invoke wsprintf,addr szBuffer,addr lpszOut1,totalSize
          ;invoke MessageBox,NULL,addr szBuffer,NULL,MB_OK

          .while TRUE
             .if totalSize==0  ;最后一行
                ;填充空格
                 .while TRUE
                     .break .if @dwBlanks==0
                     invoke lstrcat,addr lpServicesBuffer,addr lpszBlank
                     dec @dwBlanks
                 .endw
                 ;第二列与第三列中间的空格
                 invoke lstrcat,addr lpServicesBuffer,addr lpszManyBlanks  
                 ;第三列内容
                 invoke lstrcat,addr lpServicesBuffer,addr bufDisplay
                 ;回车换行符号
                 invoke lstrcat,addr lpServicesBuffer,addr lpszReturn
                 .break
             .endif
             ;将al翻译成可以显示的ascii码字符,注意不能破坏al的值
             mov al,byte ptr [esi]
             .if al>20h && al<7eh
                mov ah,al
             .else        ;如果不是ASCII码值,则显示“.”
                mov ah,2Eh
             .endif
             ;写入第三列的值
             mov byte ptr [edi],ah

            ;win2k不支持al字节级别,经常导致程序无故结束,
            ;因此用以下方法替代
            ;invoke wsprintf,addr @bufTemp1,addr lpszFilterFmt3,al
                         
             mov bl,al
             xor edx,edx
             xor eax,eax
             mov al,bl
             mov cx,16
             div cx   ;结果高位在al中,余数在dl中

             ;组合字节的十六进制字符串到@bufTemp1中,类似于:“7F \0”
             push edi
             xor bx,bx
             mov bl,al
             movzx edi,bx
             mov bl,byte ptr lpszHexArr[edi]
             mov byte ptr @bufTemp1[0],bl

             xor bx,bx
             mov bl,dl
             movzx edi,bx
             mov bl,byte ptr lpszHexArr[edi]
             mov byte ptr @bufTemp1[1],bl
             mov bl,20h
             mov byte ptr @bufTemp1[2],bl
             mov bl,0
             mov byte ptr @bufTemp1[3],bl
             pop edi

             ; 将第二列写入lpServicesBuffer
             invoke lstrcat,addr lpServicesBuffer,addr @bufTemp1

             .if @dwCount==16   ;已到16个字节,
                ;第二列与第三列中间的空格
                invoke lstrcat,addr lpServicesBuffer,addr lpszManyBlanks
                ;显示第三列字符 
                invoke lstrcat,addr lpServicesBuffer,addr bufDisplay        
                ;回车换行
                invoke lstrcat,addr lpServicesBuffer,addr lpszReturn 

                ;写入内容
                invoke _appendInfo,addr lpServicesBuffer
                invoke RtlZeroMemory,addr lpServicesBuffer,100           

                .break .if dwStop==1

                ;显示下一行的地址
                inc @dwCount1
                invoke wsprintf,addr @bufTemp2,addr lpszFilterFmt4,\
                                                            @dwCount1
                invoke lstrcat,addr lpServicesBuffer,addr @bufTemp2
                dec @dwCount1

                mov @dwCount,0
                invoke RtlZeroMemory,addr bufDisplay,50
                mov edi,offset bufDisplay
                ;为了能和后面的inc edi配合使edi正确定位到bufDisplay处
                dec edi 
             .endif

             dec totalSize
             inc @dwCount
             inc esi
             inc edi
             inc @dwCount1
          .endw

          ;添加最后一行
          invoke _appendInfo,addr lpServicesBuffer

最后编译链接生成PEDump.exe
至此,PE文件字节码查看器的编写完成。