最近在学习罗云彬老师写的《Windows环境下32位汇编语言程序设计》书中的PE文件,着重分析了下如何在PE文件中添加可执行代码的问题。因为书上对这个程序没有做过多分析,这里我把自己的分析过程及一些问题进行讲解,希望能给初学者一点帮助。
众所周知,PE文件是Windows下的文件格式,包括exe、dll等,那么如何向PE文件中添加可执行代码呢?我们应该有以下这些问题:
1、 可执行代码应该添加到PE文件的什么位置?
2、 如何让添加的代码最先执行?
3、 执行完添加的代码后如何回到原PE文件的起始地址?
4、 添加的代码如何获取需要用到的API地址?
带着这些问题,我们的主题就开始了。
准备知识:
1、 首先你得知道PE文件结构,不然很多问题你不会很明白,附件中有一张我作的PE文件结构的思维导图,希望能对你有帮助。
2、 新加的代码功能很简单,就是在运行这个PE文件之前弹出一个选择对话框,询问是否继续。代码放在后面附件中,请读者先下载看看,后面的讲解中会涉及到。
3、 了解Windows环境下的程序设计,不然你会不知道我在说什么,呵呵。。
好了,废话不多说,我们进入正题。
1、 使用通用控件GetOpenFileName函数打开一个查找文件的对话框,选择想要添加代码的文件。获得文件句柄后,通过内存映射函数CreateFileMapping和MapViewOfFile获取PE文件的起始地址。然后通过对文件的MZ格式文件头和PE头分析该文件是不是有效的PE文件,如果不是直接退出,如果是则进入下一步。这部分的代码比较简单,就直接写在下面了。
local @stOF:OPENFILENAME
local @hFile,@dwFileSize,@hMapFile,@lpMemory
invoke RtlZeroMemory,addr @stOF,sizeof @stOF
;初始化OPENFILENAME结构,以便使用
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 @dwFileSize,eax
.if eax
;用获得的文件句柄建立文件mapping
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
; 检测 PE 文件是否有效
mov esi,@lpMemory
assume esi:ptr IMAGE_DOS_HEADER
;检查MZ格式文件头
.if [esi].e_magic != IMAGE_DOS_SIGNATURE
jmp _ErrFormat
.endif
add esi,[esi].e_lfanew
assume esi:ptr IMAGE_NT_HEADERS
;检查PE文件头
.if [esi].Signature != IMAGE_NT_SIGNATURE
jmp _ErrFormat
.endif
invoke _ProcessPeFile,@lpMemory,esi,@dwFileSize
;获取路径及文件名
invoke lstrcpy,addr @szNewFile,addr szFileName
invoke lstrlen,addr @szNewFile
;获取新文件名字符串的地址
lea ecx,@szNewFile
;eax是路径及文件名的长度,eax-4就是除去后面的.exe格式后的长度,然后再加上新文件的名字xxx_new.exe构成一个新的.exe文件。
mov byte ptr [ecx+eax-4],0
invoke lstrcat,addr @szNewFile,addr szExt
;复制文件内容,建立新文件成功。
invoke CopyFile,addr szFileName,addr @szNewFile,FALSE
;打开文件,获取句柄,并保存到@hFile变量中,以便后面使用。
invoke CreateFile,addr @szNewFile,GENERIC_READ or GENERIC_WRITE,FILE_SHARE_READ or \
FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,NULL
.if eax == INVALID_HANDLE_VALUE
invoke SetWindowText,hWinEdit,addr szErrCreate
jmp _Ret
.endif
mov @hFile,eax
mov esi,_lpPeHead
assume esi:ptr IMAGE_NT_HEADERS,edi:ptr IMAGE_NT_HEADERS
;分配内存空间
invoke GlobalAlloc,GPTR,[esi].OptionalHeader.SizeOfHeaders
mov @lpMemory,eax
mov edi,eax
;将PE文件头复制到新分配的内存中
invoke RtlMoveMemory,edi,_lpFile,[esi].OptionalHeader.SizeOfHeaders
;下面两句是为了让分配的内存指针指跳过DOS块,指向PE文件头。Edi是新分配内存的起始地址,esi是原文件的PE文件头,_lpFile是原文件的起始地址。
add edi,esi
sub edi,_lpFile
;确定两个指针
movzx eax,[esi].FileHeader.NumberOfSections
dec eax ;注意这减了1
mov ecx,sizeof IMAGE_SECTION_HEADER
mul ecx
;现在eax中的值是节的总大小减去一个节的大小
mov edx,edi
add edx,eax
add edx,sizeof IMAGE_NT_HEADERS
;现在edx指向最后一个节的开始
mov ebx,edx
add ebx,sizeof IMAGE_SECTION_HEADER
;现在ebx指向新加节的开始
assume ebx:ptr IMAGE_SECTION_HEADER,edx:ptr IMAGE_SECTION_HEADER
[ATTACH]67747[/ATTACH]
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)