[读书][笔记]WINDOWS PE权威指南《二》PE的原理和基础 之 第二章 三个小工具的编写

521 阅读9分钟

我正在参加「掘金·启航计划」

@[toc]

前言

本节主要以下程序开发为目标: PEDump:PE文件字节码查看器 PEComp:PE文件比较器 PEInfo:PE文件结构查看器

2.1 构造基本窗口程序

2.1.1 构造窗口界面

要构造的窗口程序具备窗口图形界面的大部分元素,包含窗口、菜单、图标、工作区域等。

在这里插入图片描述

2.1.2 编写相关的资源文件

整个过程分为两个阶段:

  • 创建资源文件pe.rc
  • 生成资源目标文件pe.res

创建资源文件

在这里插入图片描述

pe.rc

#include <resource.h>
#include <windows.h>

#define ICO_MAIN  1000
#define DLG_MAIN  1000
#define IDC_INFO  1001
#define IDM_MAIN  2000
#define IDM_OPEN  2001
#define IDM_EXIT  2002

#define IDM_1    4000
#define IDM_2    4001
#define IDM_3    4002
#define IDM_4    4003


ICO_MAIN  ICON  "main.ico"

DLG_MAIN DIALOG 50,50,544,199
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "PE文件基本信息 by qixiaorui"
MENU IDM_MAIN
FONT 9,"宋体"
BEGIN
   CONTROL "",IDC_INFO,"RichEdit20A",196 | ES_WANTRETURN | WS_CHILD | ES_READONLY
               | WS_VISIBLE |WS_BORDER | WS_VSCROLL | WS_TABSTOP,0,0,540,396
END

IDM_MAIN menu discardable
BEGIN
  POPUP "文件(&F)"
  BEGIN
    menuitem "打开文件(&O)...",IDM_OPEN
    menuitem separator
    menuitem "退出(&x)",IDM_EXIT
  END

  POPUP "查看"
  BEGIN
    menuitem "源文件",IDM_1
    menuitem "窗口透明度",IDM_2
    menuitem separator
    menuitem "大小",IDM_3
    menuitem "宽度",IDM_4
  END

END
 

生成资源目标文件pe.res

接下来编译资源文件

rc -r pe.rc

生成pe.res

2.1.3 通用程序框架的实现

资源目标文件生成以后,接下来的工作就是实现通用程序框架。 主要分为三个阶段:

  • 编写源程序pe.asm
  • 编译生成目标文件pe.obj
  • 链接生成可执行文件pe.exe

编写pe.asm

pe.asm

.386
.model flat,stdcall
option casemap:none

include    windows.inc
include    user32.inc
includelib user32.lib
include    kernel32.inc
includelib kernel32.lib
include    comdlg32.inc
includelib comdlg32.lib


ICO_MAIN equ 1000
DLG_MAIN equ 1000
IDC_INFO equ 1001
IDM_MAIN equ 2000
IDM_OPEN equ 2001
IDM_EXIT equ 2002
IDM_1    equ 4000
IDM_2    equ 4001
IDM_3    equ 4002

.data
hInstance   dd ?
hRichEdit   dd ?
hWinMain    dd ?
hWinEdit    dd ?
szFileName  db MAX_PATH dup(?)

.const
szDllEdit   db 'RichEd20.dll',0
szClassEdit db 'RichEdit20A',0
szFont      db '宋体',0


.code

;----------------
;初始化窗口程序
;----------------
_init proc
  local @stCf:CHARFORMAT
  
  invoke GetDlgItem,hWinMain,IDC_INFO
  mov hWinEdit,eax
  invoke LoadIcon,hInstance,ICO_MAIN
  invoke SendMessage,hWinMain,WM_SETICON,ICON_BIG,eax       ;为窗口设置图标
  invoke SendMessage,hWinEdit,EM_SETTEXTMODE,TM_PLAINTEXT,0 ;设置编辑控件
  invoke RtlZeroMemory,addr @stCf,sizeof @stCf
  mov @stCf.cbSize,sizeof @stCf
  mov @stCf.yHeight,9*20
  mov @stCf.dwMask,CFM_FACE or CFM_SIZE or CFM_BOLD
  invoke lstrcpy,addr @stCf.szFaceName,addr szFont
  invoke SendMessage,hWinEdit,EM_SETCHARFORMAT,0,addr @stCf
  invoke SendMessage,hWinEdit,EM_EXLIMITTEXT,0,-1
  ret
_init endp


;-------------------
; 窗口程序
;-------------------
_ProcDlgMain proc uses ebx edi esi hWnd,wMsg,wParam,lParam
  mov eax,wMsg
  .if eax==WM_CLOSE
    invoke EndDialog,hWnd,NULL
  .elseif eax==WM_INITDIALOG  ;初始化
    push hWnd
    pop hWinMain
    call _init
  .elseif eax==WM_COMMAND  ;菜单
    mov eax,wParam
    .if eax==IDM_EXIT       ;退出
      invoke EndDialog,hWnd,NULL 
    .elseif eax==IDM_OPEN   ;打开文件
    .elseif eax==IDM_1  
    .elseif eax==IDM_2
    .elseif eax==IDM_3
    .endif
  .else
    mov eax,FALSE
    ret
  .endif
  mov eax,TRUE
  ret
_ProcDlgMain endp

start:
  invoke LoadLibrary,offset szDllEdit
  mov hRichEdit,eax
  invoke GetModuleHandle,NULL
  mov hInstance,eax
  invoke DialogBoxParam,hInstance,\
         DLG_MAIN,NULL,offset _ProcDlgMain,NULL
  invoke FreeLibrary,hRichEdit
  invoke ExitProcess,NULL
  end start

基本思路:

  • DialogBoxParam 注册窗口
  • _ProcDlgMain 作为回调函数 处理窗口消息 例如WM_CLOSE

编译生成目标文件pe.obj

在编写较大的程序时,通常会根据功能将源代码分别写到不同的文件里。有时为了分工合作,同一个项目中还会出现使用不同语言编写的源代码。这些源代码文件都需要在各自独立的环境中被编译成各自的目标文件。

目标文件符合通用对象文件格式(COFF),该格式的定制主要是为了方便混合编程。

生成目标文件的过程是处理源代码中可能会出现错误(如引入外部符号错误、源代码语法错误等)的过程,生成的目标文件最终会被链接程序拼接到最终的可执行文件中,当然,除了编译源代码生成的目标文件外,可执行文件还包含资源目标文件、外部引入的符号等信息。

在命令提示符下输入以下命令,编译源文件pe.asm:

ml -c -coff pe.asm

如果没有错误,则会在当前目录下生成目标文件pe.obj。

链接生成可执行文件pe.exe

在命令提示符下输入以下命令:

link -subsystem:windows pe.obj pe.res

上述命令指定了最终生成的EXE文件的运行平台为Windows,链接程序将根据pe.obj中的描述构造PE文件,并将相关资源内容附加到PE文件里,最终生成可执行的pe.exe。

运行

在命令提示符下输入“pe”,然后回车,即可看到最终的运行效果,如图2-1所示。 至此,一个基本的基于汇编语言的窗口程序就编写完成了。 在这里插入图片描述

接下来的工作就是在此基础上进行扩展,开发三个基于Windows PE的小工具,首先来看字节码查看器PEDump的编写。

2.2 PEDump的实现

PEDump是PE文件字节码查看器,利用它可以查看和阅读指定PE文件的十六进制字节码,帮助我们更好地分析PE结构。

2.2.1 编程思路

在这里插入图片描述 如图所示,最终输出包含三列内容。 第一列是地址。地址的值是第(n×16+1)个字节在文件中的位置。 第二列是由空格分隔符分隔的16个字节的十六进制显示。 第三列是这16个字节对应的ASCI码值。如果ASCI码中无对应值,或者这些值是一些功能键,则以“.”代替。 在这里插入图片描述

内存映射文件: 是指将硬盘上的文件不做修改地装载到内存中。这样,文件中字节与字节之间就是顺序排列的了。在硬盘上,文件被分割成若干簇,这些簇不一定会按照文件内容顺序排列在一起,当我们访问磁盘上的文件时,需要计算机首先将不同位置的内容读取到内存。有了内存映射文件,访问就会变得更轻松和快捷,由于读取磁盘的操作集中到了一起执行,读写效率会提高很多。被一次性读取到内存的文件字节按线性排序,访问相对简单,速度也提升了不少。所以,许多大型的编辑软件在设计中经常会使用内存映射文件存取磁盘文件。

PE内存映像: 是指将PE文件按照一定的规则装载到内存中,装入后的整个文件头内容不会发生变化,但PE文件的某一部分如节的内容会按照字段中的对齐方式在内存中对齐,从而使得内存中的PE映像与装载前的PE文件不同。那么,为什么PE内存映像不能和一般的内存映射文件一样呢? 答案很简单:PE文件是由操作系统装载进内存的,其目的是为了运行。为了配合操作系统的运行,方便调度,提高运行效率,PE映像必须按照一定的格式对齐,所以内存中的PE映像和原来硬盘上的文件是不同的,当然与内存映射文件也就不一样了。

2.2.2 PEDump编码

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文件;然后,获取指定文件的大小,并利用这个值通过函数CreateFileMapping在内存中建立该文件的映像;全局变量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.asm

.386
.model flat,stdcall
option casemap:none

include    windows.inc
include    user32.inc
includelib user32.lib
include    kernel32.inc
includelib kernel32.lib
include    comdlg32.inc
includelib comdlg32.lib


ICO_MAIN equ 1000
DLG_MAIN equ 1000
IDC_INFO equ 1001
IDM_MAIN equ 2000
IDM_OPEN equ 2001
IDM_EXIT equ 2002
IDM_1    equ 4000
IDM_2    equ 4001
IDM_3    equ 4002

.data
hInstance   dd ?   ; 进程句柄
hRichEdit   dd ?
hWinMain    dd ?   ; 弹出窗口句柄
hWinEdit    dd ?   ; 富文本框句柄
totalSize   dd ?   ; 文件大小
lpMemory    dd ?   ; 内存映像文件在内存的起始位置
szFileName  db MAX_PATH dup(?)               ;要打开的文件路径及名称名

lpServicesBuffer         db 100 dup(0)   ;所有内容
bufDisplay               db 50 dup(0)      ;第三列ASCII码字符显示
szBuffer                 db 200 dup(0)       ;临时缓冲区
lpszFilterFmt4           db  '%08x  ',0
lpszManyBlanks           db  '  ',0
lpszBlank                db  ' ',0
lpszSplit                db  '-',0
lpszScanFmt              db  '%02x',0
lpszHexArr               db  '0123456789ABCDEF',0
lpszReturn               db  0dh,0ah,0
lpszDoubleReturn         db  0dh,0ah,0dh,0ah,0
lpszOut1                 db  '文件大小:%d',0
dwStop                   dd  0
.const
szDllEdit   db 'RichEd20.dll',0
szClassEdit db 'RichEdit20A',0
szFont      db '宋体',0
szExtPe     db 'PE File',0,'*.exe;*.dll;*.scr;*.fon;*.drv',0
            db 'All Files(*.*)',0,'*.*',0,0
szErr       db '文件格式错误!',0
szErrFormat db '操作文件时出现错误!',0


.code

;----------------
;初始化窗口程序
;----------------
_init proc
  local @stCf:CHARFORMAT
  
  invoke GetDlgItem,hWinMain,IDC_INFO
  mov hWinEdit,eax
  invoke LoadIcon,hInstance,ICO_MAIN
  invoke SendMessage,hWinMain,WM_SETICON,ICON_BIG,eax       ;为窗口设置图标
  invoke SendMessage,hWinEdit,EM_SETTEXTMODE,TM_PLAINTEXT,0 ;设置编辑控件
  invoke RtlZeroMemory,addr @stCf,sizeof @stCf
  mov @stCf.cbSize,sizeof @stCf
  mov @stCf.yHeight,14*1440/96
  mov @stCf.dwMask,CFM_FACE or CFM_SIZE or CFM_BOLD
  invoke lstrcpy,addr @stCf.szFaceName,addr szFont
  invoke SendMessage,hWinEdit,EM_SETCHARFORMAT,0,addr @stCf
  invoke SendMessage,hWinEdit,EM_EXLIMITTEXT,0,-1
  ret
_init endp

;------------------
; 错误Handler
;------------------
_Handler proc _lpExceptionRecord,_lpSEH,\
              _lpContext,_lpDispathcerContext

  pushad
  mov esi,_lpExceptionRecord
  mov edi,_lpContext
  assume esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT
  mov eax,_lpSEH
  push [eax+0ch]
  pop [edi].regEbp
  push [eax+8]
  pop [edi].regEip
  push eax
  pop [edi].regEsp
  assume esi:nothing,edi:nothing
  popad
  mov eax,ExceptionContinueExecution
  ret
_Handler endp

;---------------------
; 往文本框中追加文本
;---------------------
_appendInfo proc _lpsz
  local @stCR:CHARRANGE

  pushad
  invoke GetWindowTextLength,hWinEdit
  mov @stCR.cpMin,eax  ;将插入点移动到最后
  mov @stCR.cpMax,eax
  invoke SendMessage,hWinEdit,EM_EXSETSEL,0,addr @stCR
  invoke SendMessage,hWinEdit,EM_REPLACESEL,FALSE,_lpsz
  popad
  ret
_appendInfo endp


;--------------------
; 打开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

          ;开始处理文件

          ;缓冲区初始化
          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

          
          ;处理文件结束

          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
;-------------------
; 窗口程序
;-------------------
_ProcDlgMain proc uses ebx edi esi hWnd,wMsg,wParam,lParam
  local @sClient

  mov eax,wMsg
  .if eax==WM_CLOSE
    invoke EndDialog,hWnd,NULL
  .elseif eax==WM_INITDIALOG  ;初始化
    push hWnd
    pop hWinMain
    call _init
  .elseif eax==WM_COMMAND  ;菜单
    mov eax,wParam
    .if eax==IDM_EXIT       ;退出
      invoke EndDialog,hWnd,NULL 
    .elseif eax==IDM_OPEN   ;打开文件
      mov dwStop,0
      invoke CreateThread,NULL,0,addr _openFile,addr @sClient,0,NULL
      ;invoke _openFile
    .elseif eax==IDM_1 
      mov dwStop,1 
    .elseif eax==IDM_2
    .elseif eax==IDM_3
    .endif
  .else
    mov eax,FALSE
    ret
  .endif
  mov eax,TRUE
  ret
_ProcDlgMain endp

start:
  invoke LoadLibrary,offset szDllEdit
  mov hRichEdit,eax
  invoke GetModuleHandle,NULL
  mov hInstance,eax
  invoke DialogBoxParam,hInstance,\
         DLG_MAIN,NULL,offset _ProcDlgMain,NULL
  invoke FreeLibrary,hRichEdit
  invoke ExitProcess,NULL
  end start

2.2.3 PEDump代码中的数据结构

(1)程序中用到的全局变量

在这里插入图片描述

db: define byte 8bit dw: define word 16bit dd: define dword(double word), 双字形 32bit

(2) 程序中用到的局部变量

在这里插入图片描述

LOCAL:定义局部变量用的 LOCAL 变量名:变量类型

2.2.4 运行PEDump

打开命令提示符窗口,在目录下执行如下命令:

  • rc-r pedump.rc(编译资源脚本文件)
  • ml-c-coff pedump.asm(编译PEDump.asm)
  • link -subsystem:windows pedump.res pedump.obj(链接生成可执行程序)
  • 运行PEDump.exe

最终效果如图 在这里插入图片描述

需要注意的是:

  • openFile在主进程时 文件数据过大 会出现卡死的情况,程序主线程的循环造成了系统消息堵塞,从而无法完成界面更新所致。 所以CreateProcess中执行openFile
  • 为了随时终止滚动,增加dwStop 随时停止界面的数据加载 字节码的显示 退出循环

2.3 PEComp的实现

2.4 PEInfo的实现

2.5 小结

本章主要学习了如何通过汇编语言来编写基于PE操作的三个小工具, 在后面对PE文件的分析中会经常使用这三个小工具。后面会讲到如何利用PEInfo遍历PE文件的导入表和导出表,那时还会用到本章的源代码。大家也可以对这些小工具进行扩展或整合,编写属于自己的PE分析工具。

值得一提的是,随编译器分发的可执行程序中有一个基于命令行的PE 文件结构分析工具dumpbin.exe,它是公认的最好的PE分析工具之一,如果你喜欢,也可以用它来代替我们的小程序。