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文件字节码查看器的编写完成。