利用masm32输出PE文件头的基本属性
注:本文章作者水平较低,本文仅为作者自主实验时的学习笔记,如有不妥之处,还请指正,文中斜体皆为参考权威书籍
参考:《X86汇编语言》王爽
《加密与解密》第四版
《逆向工程核心原理》
工具:PEView,ollydbg,masm32环境
一、masm32中的INVOKE
INVOKE伪指令仅在32位的情况下使用,将参数逆序(与声明参数顺序相反)入栈,它是call的方便替代品,在一行代码内就可以传送多个参数,例如:
;原型声明
Print PROTO stdcall:DWORD,:DWORD,:DWORD,:DWORD
;.....
.code
;过程定义
Print PROC res:DWORD,blank:DWORD,blank1:DWORD,t:DWORD
;......
ret
Print ENDP
在该例中,Print函数接收四个Dword类型参数:res,blank,blank1,t
其中 PROTO是原型伪指令,stdcall是调用约定,PROC和ENDP中间夹着的是过程的程序指令
1、PROTO
PROTO伪指令为现有的过程创建原型(名称和参数列表),MASM要求所有INVOKE调用的过程都有原型,所以,标准格式如下
func PROTO
;原型声明
INVOKE func
;过程调用
;实现
func PROC
;.....
func ENDP
2、调用约定
我们先来看看invoke调用过程的具体汇编代码:
.data
str1 BYTE "abcde",0
num1 DWORD 66h
func1 PROTO :DWORD,:DWORD
func2 PROTO stdcall :DWORD,:DWORD
.code
func1 PROC n1:DWORD,n2:DWORD
MOV eax,n1
ret
func1 ENDP
func2 PROC n1:DWORD,n2:DWORD
MOV eax,n2
ret
func2 ENDP
main PROC
INVOKE func1 ,num1,addr str1
INVOKE func2 ,num1,addr str1
INVOKE ExitProcess,0
main ENDP
END main
汇编以后放入OLLYDBG中
可以看到,在INVOKE经过汇编器后,转化成了正常的函数调用,即
①将参数和返回地址入栈②将程序流程转到对应函数
看到函数内部,INVOKE自动帮我们完成了栈帧的建立(每个函数前两句汇编),并且
①加入leave②将ret变成retn,帮助我们清除了栈帧
下面陈述栈帧与调用约定的关系
①在函数调用开始前,调用函数的程序将参数(arguments)压入栈中
②call指令执行,栈中压入call下一条指令的地址,并将EIP设置成函数第一条指令的地址
③那么函数是怎么描述自己参数的呢?答案是靠相对位置,栈底位置(地址)保存在ebp中,而栈顶位置保存在esp中,在新的函数中有新的临时变量和参数,程序需要利用参数和ebp相对的位置[ebp+x]描述参数或者变量所以需要更新旧的ebp。故,将last ebp入栈保存->push ebp。并将ebp设置为现在的栈底->mov ebp,esp(还没有往新的函数栈里面添加临时变量,所以栈顶栈底同位置)
④返回时,将ebp赋给esp,将栈顶瞬间缩至栈底,并且将旧ebp的值弹出至ebp,这两步在leave中实现
⑤最后retn是将返回地址弹出给EIP,再将栈缩小操作数个字节,把栈顶调到参数之下(图上的下)
至此:栈在函数调用前后保持一致,可继续工作!
而这种由被调用函数进行栈清空的方式叫做stdcall!
(摘自百度百科)__stdcall表示
1.参数从右向左压入堆栈
2.函数被调用者修改堆栈
3.函数名(在编译器这个层次)自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸
二、PE头格式
从上到下从左到右除蓝黑色以外的部分分别是(图中为小端序)
IMAGE_DOS_HEADER:
e_magic: 5a4d
e_lfanew:000000e8
IMAGE_NT_HEADER:
Signature:00004550
IMAGE_FILE_HEADE:
NumberOfSections: 0003
TimeDateStamp:45d69be5
Characteristic: 010f
IMAGE_OPTIONAL_HEADER:
AddressOfEntryPoint:000073a5
ImageBase:01000000
SectionAligment:00001000
FileAligment:00000200
条件一:其中,除了蓝黑色的DOS存根以外,IMAGE_DOS_HEADER,IMAGE_NT_HEADER,IMAGE_FILE_HEADER是的长度是定长的
条件二:IMAGE_OPTINONAL_HEADER中显示的几项距离IMAGE_OPTINONAL_HEADER起始的偏移也是一定的
所以仅仅需要用CreateFile SetFilePointer ReadFile将文件读入,按照先后顺序利用基址变址寻址即可实现文件任意部分的读取
例如读入文件数据的首地址为buf,则WORD PTR [buf+esi] (esi此时为0)读取的就是4D 5A即e_magic,后只需要改变esi和取值时PTR类型即可读取文件的后续部分
解决DOS存根:在DOS头中,红色部分为e_lfanew其中存储的是NT头的文件偏移,正因为DOS存根不定长,所以需要此属性,在读取该属性后,赋给存变址的寄存器,加上基址就是程序中读入的NT头偏移了
三、读取代码
读一个文件用到的Windows API函数有CreateFile、SetFilePointer、ReadFile、CloseHandle。
CreateFile的MSDN文档地址添加链接描述
SetFilePointer函数的MSDN文档地址 添加链接描述
ReadFile函数的MSDN文档地址添加链接描述
CloseHandle函数的MSDN文档地址
添加链接描述
通过路径得到句柄,打开程序和文件的联系->通过句柄得到指向文件的指针->通过句柄读取文件内容->关闭文件和程序的连接
.386
.model flat, stdcall
option casemap:none
include D:\masm32\include\windows.inc
include D:\masm32\include\kernel32.inc
include D:\masm32\include\masm32.inc
includelib D:\masm32\lib\kernel32.lib
includelib D:\masm32\lib\masm32.lib
include D:\masm32\include\user32.inc
includelib D:\masm32\lib\user32.lib
ExitProcess PROTO, dwEXITCODE: DWORD
.data
;待处理文件绝对路径
path BYTE "C:\Users\Em1ya\Desktop\Re\NOTE.exe",0
;存放待处理文件的句柄
filehandle DWORD ?
;以下数据全为显示字符,没有实际含义
str0 BYTE " ",0
str1 BYTE "IMAGE_DOS_HEADER:",0
str2 BYTE "IMAGE_NT_HEADER:",0
str3 BYTE "IMAGE_FILE_HEADE:",0
str4 BYTE "IMAGE_OPTIONAL_HEADER:",0
str5 BYTE "e_magic:",0
str6 BYTE "e_lfanew:",0
str7 BYTE "Signature:",0
str8 BYTE "NumberOfSections:",0
str9 BYTE "TimeDateStamp:",0
stra BYTE "Characteristic:",0
strb BYTE "AddressOfEntryPoint:",0
strc BYTE "ImageBase:",0
strd BYTE "SectionAligment:",0
stre BYTE "FileAligment:",0
strn BYTE 0ah,00h
filebuf BYTE 4000 DUP(0)
filebase DWORD ?
var DWORD ?
;下面声明的是把十六进制值转为字符串输出的函数
Print PROTO stdcall:DWORD,:DWORD,:DWORD
Res BYTE "00000000",0
table BYTE "0123456789ABCDEF",0
.code
Print PROC res:DWORD,hexdw:DWORD,t:DWORD
;参数res待写缓冲区,hexdw待转换十六进制值,t数据种类,控制最后输出的字符个数
MOV edx,0
MOV edi,hexdw
MOV ecx,8h
MOV eax,res
;edi指向待转换的值,ecx为循环变量,eax指向待写的缓冲区字符串
;循环将最高位保留做与运算,拿到最低位作为索引在table里面拿值
L2:
MOV ebx,edi
AND ebx,0f0000000h
SHR ebx,28
MOV dl,BYTE PTR [table+ebx]
MOV BYTE PTR[eax],dl
INC eax
SHL edi,4
LOOP L2
;将res的前t位变成空格
MOV ecx,t
MOV eax,res
L1:
CMP ecx,0h
JE L999
MOV BYTE PTR[eax],20h
INC eax
DEC ecx
JMP L1
L999:
;输出
INVOKE StdOut, res
MOV eax,hexdw
ret
Print ENDP
main PROC
INVOKE CreateFile,addr path,\
GENERIC_READ,\
FILE_SHARE_READ,\
0,\
OPEN_EXISTING,\
FILE_ATTRIBUTE_ARCHIVE,\
0
MOV filehandle,eax
INVOKE SetFilePointer, filehandle,\
0,\
0,\
FILE_BEGIN
INVOKE ReadFile, filehandle,\
addr filebuf,\
3900,\
0,\
0
MOV esi,OFFSET filebuf
;以上操作将文件的内容写到了filebuf,之后读取filebuf就可以拿到PE文件的内容
;以下所有,esi为目标量在内存中的地址,eax为该地址对应值,print函数把eax传入,输出对应8-t个字符串
INVOKE StdOut,addr str1
INVOKE StdOut,addr strn
MOV eax,0h
INVOKE StdOut,addr str0
INVOKE StdOut,addr str5
;读第一个量
MOV ax,WORD PTR [esi]
MOV filebase,esi
INVOKE Print,addr Res,\
eax,\
4
INVOKE StdOut,addr strn
ADD esi,3ch
INVOKE StdOut,addr str0
INVOKE StdOut,addr str6
;读第二个量
MOV eax,DWORD PTR [esi]
INVOKE Print,addr Res,\
eax,\
0
;此处要注意,该数据为NT头的偏移量,所以将esi调整至NT头->内存基址加上NT头偏移
ADD eax,filebase
MOV esi,eax
INVOKE StdOut,addr strn
INVOKE StdOut,addr str2
INVOKE StdOut,addr strn
INVOKE StdOut,addr str0
INVOKE StdOut,addr str7
;读第三个量
MOV eax,DWORD PTR [esi]
INVOKE Print,addr Res,\
eax,\
0
INVOKE StdOut,addr strn
INVOKE StdOut,addr str3
INVOKE StdOut,addr strn
ADD esi,6h
INVOKE StdOut,addr str0
INVOKE StdOut,addr str8
;读第四个量
MOV eax,0
MOV ax,WORD PTR [esi]
INVOKE Print,addr Res,\
eax,\
4
INVOKE StdOut,addr strn
ADD esi,2h
INVOKE StdOut,addr str0
INVOKE StdOut,addr str9
;读第五个量
MOV eax,DWORD PTR [esi]
INVOKE Print,addr Res,\
eax,\
0
INVOKE StdOut,addr strn
ADD esi,0Eh
INVOKE StdOut,addr str0
INVOKE StdOut,addr stra
MOV eax,0
;读第六个量
MOV ax,WORD PTR [esi]
INVOKE Print,addr Res,\
eax,\
4
INVOKE StdOut,addr strn
INVOKE StdOut,addr str4
INVOKE StdOut,addr strn
ADD esi,18
INVOKE StdOut,addr str0
INVOKE StdOut,addr strb
;读第七个量
MOV eax,DWORD PTR [esi]
INVOKE Print,addr Res,\
eax,\
0
INVOKE StdOut,addr strn
ADD esi,0ch
INVOKE StdOut,addr str0
INVOKE StdOut,addr strc
;读第八个量
MOV eax,DWORD PTR [esi]
INVOKE Print,addr Res,\
eax,\
0
INVOKE StdOut,addr strn
ADD esi,4h
INVOKE StdOut,addr str0
INVOKE StdOut,addr strd
;读第九个量
MOV eax,DWORD PTR [esi]
INVOKE Print,addr Res,\
eax,\
0
INVOKE StdOut,addr strn
ADD esi,4h
INVOKE StdOut,addr str0
INVOKE StdOut,addr stre
;读第十个量
MOV eax,DWORD PTR [esi]
INVOKE Print,addr Res,\
eax,\
0
INVOKE StdOut,addr strn
INVOKE CloseHandle,filehandle
INVOKE ExitProcess,0
main ENDP
END main
读取->转化->变址->读取
对照PEView验证