问题描述
我已经为 x86-64 制作了(在汇编器中,没有链接器)一个 EXE,它在 Linux 下的 Wine 中运行得非常好。这是一个调用 MessageBoxA 和 ExitProcess 的基本 HelloWorld。
Windows 10 无法识别它,并说“此程序无法在您的计算机上执行,请与您的供应商联系以获取适合您计算机的版本”。
我使用 PE 格式阅读器(PE 工具和 CFF Explorer)来分析我的 PE EXE。 PE Optional 标头中的所有数字与其他工作 EXE(如操作系统版本、子系统版本)中的相同。只有特定于我的文件和部分的那些是不同的。而且 Windows 不会将该文件识别为我的计算机上的可执行文件。
除了 WINdows 错误消息,我还能从哪里开始看?是否有任何工具可以通过比“Bad exe”更具体的错误消息来检查 EXE 的有效性? (这是 xdbg 报告的内容。
在 Wine 上,我能够做到
WINEDEBUG=+all wine my.exe
这让我暗示出了什么问题,我能够修复它并使其正常工作。 Windows 中有这样的工具吗?
BITS 64
falign equ 1000h ; section file position modulo
imageBase equ 400000h
; MZ header
DOSHDR:
db 0x4D,0x5A,0x90,dd 3,4,0xFFFF,0xB8,0x40,0
dd PEHDR
db 0x0E,0x1F,0xBA,0x0E,0x00,0xB4,0x09,0xCD,0x21,0x01,0x4C,0x54,0x68,0x69,0x73,0x20,0x70,0x72,0x6F,0x67,0x61,0x6D,0x63,0x6E,0x74,0x62,0x65,0x75,0x44,0x4F,0x53,0x64,0x2E,0x0D,0x0A,0x24,0x00
ALIGN falign,db 33h
doshdrSize equ $ - DOSHDR
MetaM: ; MetaBlk for module M
msg db "Hello,Ann!",0
title db "Hello,Anna!",0
titlew dw 42Fh,44Ah,0
msgw dw 416h,42Bh,0
title2w dw 44Ah,42Fh,0
ALIGN 8,db 0FEh
MessageBoxA dq 0
MessageBoxW dq 0
ExitProcess dq 0
MessageBoxW0 dq 0 ; a duplicate entry for User32.MessageBoxW
ALIGN falign,db 11h
MetamSize equ $ - MetaM
CodeM:
BEGIN:
ENTRY:
sub rsp,28h
mov rcx,0 ; hWnd = HWND_DESKTOP
lea rdx,[imageBase + msg] ; LPCSTR lpText
lea r8,[imageBase + title] ; LPCSTR lpCaption
mov r9d,0 ; uType = MB_OK
mov rax,[imageBase + MessageBoxA]
call rax
mov rcx,[imageBase + msgw] ; LPCSTR lpText
lea r8,[imageBase + titlew] ; LPCSTR lpCaption
mov r9d,0 ; uType = MB_OK
call [imageBase + MessageBoxW]
mov rcx,[imageBase + title2w] ; LPCSTR lpCaption
mov r9d,0 ; uType = MB_OK
call [imageBase + MessageBoxW0]
mov ecx,eax
call [imageBase + ExitProcess]
END:
ALIGN falign,db 0AAh
codemSize equ $ - CodeM
IMPORTS:
; DLL names - iterate modules
user32dll db "USER32.DLL",0
kernel32dll db "KERNEL32.DLL",0
; Hint/Name entry - iterate externals
MessageBoxA_:
dq MessageBoxA__
dq 0
MessageBoxA__ db 0,"MessageBoxA",ExitProcess_ dq ExitProcess__
dq 0
ExitProcess__ db 0,"ExitProcess",1
MessageBoxW_:
dq MessageBoxW__
dq 0
MessageBoxW__ db 0,"MessageBoxW",0
ImportsDir:
; So this is the Directory,with one entry NOT for every imported DLL,; but rather one entry for every use of an external name by a CP module
; that is,if a name is used in N modules,it will have N entries in the directory
dd MessageBoxA_,user32dll,MessageBoxA
dd ExitProcess_,kernel32dll,ExitProcess
dd MessageBoxW_,MessageBoxW0
dd MessageBoxW_,MessageBoxW
dd 0,0
directorySize equ $ - ImportsDir
importsSize equ $ - IMPORTS
PEHDR:
db "PE",0 ; signature
dw 8664h ; machine
dw 3 ; # of sections
dd 0 ; timedatestamp
dd 0 ; pointer to symtab - deprecated
dd 0 ; # symtab entries
dw opthdrSize ; size of optional header
dw 203h ; flags - characteristics
OPTHDR:
dw 20Bh ; magic
db 0 ; maj linker ver
db 1 ; minor linker ver
dd codemSize ; total code size
dd MetamSize ; total init data size
dd 0 ; total uninit data size
dd ENTRY ; entrypoint RVA
dd ENTRY ; base of code
dq imageBase ; image base
dd 1000h ; section address alignment
dd falign ; section pos alignment
dw 5 ; major OS version
dw 2 ; minor OS version
dw 0 ; major image ver
dw 1 ; minor image ver
dw 5 ; major subsystem ver
dw 2 ; minor subsystem ver
dd 0 ; win32 version value = 0
dd fileSize ; size of image - that is,in memory!
dd ((doshdrSize + pehdrSize) + falign - 1) / falign * falign
; size of headers
dd 0 ; checksum
dw 2 ; subsystem: GUI = 2,CUI =3,NATIVE = 1
dw 0 ; dll characteristics
dq 1000000h ; max stack
dq 1000h ; min stack
dq 1000000h ; max heap
dq 1000h ; min heap
dd 0 ; loader flag = 0
; Directories
dd 2 ; number of directories
; export table hdr
dd 0,0
; import table hdr
dd ImportsDir ; addr of import table
dd directorySize ; size of import table
;times 14 dq 0 ; end of directories
opthdrSize equ $ - OPTHDR
pehdrSize equ $ - PEHDR
Sections:
; MetaM
db "F",0 ; null name
dd MetamSize ; size
dd MetaM ; addr RVA
dd MetamSize ; length
dd MetaM ; pos
dd 0 ; no relocations
dd 0 ; no linenum
dw 0
dw 0
dd 0C0000040h ; flags: datasection writeable readable
; CodeM
db "W",0 ; null name
dd codemSize ; size
dd CodeM ; addr RVA
dd codemSize ; length
dd CodeM ; pos
dd 0 ; no relocations
dd 0 ; no linenum
dw 0
dw 0
dd 0E0000020h ; flags: codesection writeable readable executable
; IMPORTS
db ".idata",0
dd importsSize ; size
dd IMPORTS ; addr RVA
dd importsSize ; length
dd IMPORTS ; pos
dd 0 ; no relocations
dd 0 ; no linenum
dw 0
dw 0
dd 0E0000020h ; flags: codesection writeable readable executable
fileSize equ $
;END:
解决方法
我想发布适用于 Wine 和 Windows 10 的最终版本。简而言之,这里是错误的:
- 未对齐的图像大小字段。必须页面对齐 (1000h)。我的错误。
- 标头大小计算错误。规格不佳。这帮助我弄清楚了,而不是规范:PE format walkthru
- 拥有 2 个目录不起作用,认为它与规范不矛盾。我没有试验 16 以外的数字。规格不佳。顺便说一下,目录中的 IAT 无关紧要;实际上,如果您导入 2 个 DLL,则每个都获取和 IAT;应该在目录中引用哪一个?答案:装载机不在乎。
- IAT 的第一个条目必须非零,否则此 IAT 将被忽略且不会填写。这是非常无证的。规格不佳。
从积极的方面来说,
- 我能够将 PE 标头和节列表放在文件末尾,这与通常将其放在 DOS 存根之后和节之前不同。
- 我能够以一种有点奇怪的方式组织导入,每个导入符号一个 IAT,而不是每个 DLL 一个 IAT。 在这两个问题中,我和加载程序都遵循规范的字母,这是一件好事。
BITS 64
; nasm -f bin -o pe.exe pe.asm && chmod +x pe.exe && ./pe.exe
salign equ 1000h ; section file position modulo
falign equ 1000h ; section file position modulo
imageBase equ 400000h
; MZ header
DOSHDR:
db 0x4D,0x5A,0x90,dd 3,4,0xFFFF,0xB8,0x40,0
dd PEHDR
db 0x0E,0x1F,0xBA,0x0E,0x00,0xB4,0x09,0xCD,0x21,0x01,0x4C,0x54,0x68,0x69,0x73,0x20,0x70,0x72,0x6F,0x67,0x61,0x6D,0x63,0x6E,0x74,0x62,0x65,0x75,0x44,0x4F,0x53,0x64,0x2E,0x0D,0x0A,0x24,0x00
ALIGN 8,db 0FFh
doshdrSize equ $ - DOSHDR
ALIGN falign,db 55h
MetaM: ; MetaBlk for module M
msg db "Hello,Ann!",0
title db "Hello,Anna!",0
titlew dw 42Fh,44Ah,0
msgw dw 416h,42Bh,0
title2w dw 44Ah,42Fh,0
ALIGN 8,db 0FEh
MessageBoxA dq 01
MessageBoxW dq 01
ExitProcess dq 01
MessageBoxW0 dq 01 ; a duplicate entry for User32.MessageBoxW
ALIGN falign,db 11h
metamSize equ $ - MetaM
CodeM:
BEGIN:
ENTRY:
; for PROXIES instead of IAT
sub rsp,28h
mov rcx,0 ; hWnd = HWND_DESKTOP
lea rdx,[imageBase + msg] ; LPCSTR lpText
lea r8,[imageBase + title] ; LPCSTR lpCaption
mov r9d,0 ; uType = MB_OK
mov rax,[imageBase + MessageBoxA]
call rax
mov rcx,[imageBase + msgw] ; LPCSTR lpText
lea r8,[imageBase + titlew] ; LPCSTR lpCaption
mov r9d,0 ; uType = MB_OK
call [imageBase + MessageBoxW]
mov rcx,[imageBase + title2w] ; LPCSTR lpCaption
mov r9d,0 ; uType = MB_OK
call [imageBase + MessageBoxW]
mov ecx,eax
call [imageBase + ExitProcess]
END:
ALIGN falign,db 0AAh
codemSize equ $ - CodeM
IMPORTS:
ImportsDir:
; So this is the Directory,with one entry NOT for every imported DLL,; but rather one entry for every use of an external name by a CP module
; that is,if a name is used in N modules,it will have N entries in the directory
dd MessageBoxA_,user32dll,MessageBoxA
dd ExitProcess_,kernel32dll,ExitProcess
dd MessageBoxW_,MessageBoxW0
dd MessageBoxW_,MessageBoxW
dd 0,0
directorySize equ $ - ImportsDir
; DLL names - iterate modules
user32dll db "USER32.DLL",0
kernel32dll db "KERNEL32.DLL",0
; Hint/Name entry - iterate externals
MessageBoxA_:
dq MessageBoxA__
dq 0
MessageBoxA__ db 0,"MessageBoxA",0
ExitProcess_ dq ExitProcess__
dq 0
ExitProcess__ db 0,"ExitProcess",0
MessageBoxW_:
dq MessageBoxW__
dq 0
MessageBoxW__ db 0,"MessageBoxW",0
importsSize equ $ - IMPORTS
ALIGN 8,db 99h
PEHDR:
db "PE",0 ; signature
dw 8664h ; machine
dw 3 ; # of sections
dd 0 ; timedatestamp
dd 0 ; pointer to symtab - deprecated
dd 0 ; # symtab entries
dw opthdrSize ; size of optional header
dw 203h ; flags - characteristics
OPTHDR:
dw 20Bh ; magic
db 0 ; maj linker ver
db 1 ; minor linker ver
dd codemSize ; total code size
dd metamSize ; total init data size
dd 0 ; total uninit data size
dd ENTRY ; entrypoint RVA
dd ENTRY ; base of code
dq imageBase ; image base
dd 1000h ; section address alignment
dd falign ; section pos alignment
dw 5 ; major OS version
dw 1 ; minor OS version
dw 0 ; major image ver
dw 1 ; minor image ver
dw 5 ; major subsystem ver
dw 0 ; minor subsystem ver
dd 0 ; win32 version value = 0
dd 4000h ;(*(fileSize + salign - 1) / salign * salign*)
; imageSize - that is,in memory!
dd salign
; size of headers
dd 0 ; checksum
dw 2 ; subsystem: GUI = 2,CUI =3,NATIVE = 1
dw 0 ; dll characteristics
dq 1000000h ; max stack
dq 1000h ; min stack
dq 1000000h ; max heap
dq 1000h ; min heap
dd 0 ; loader flag = 0
; Directories
dd 16 ; number of directories
; export table hdr
dd 0,0
; import table hdr
dd ImportsDir ; addr of import table
dd directorySize ; size of import table
times 14 dd 0,0 ; empty directories
; dd kernel32IAT ; IATs
; dd 5 * 8
;times 3 dd 0,0 ; empty directories
opthdrSize equ $ - OPTHDR
pehdrSize equ $ - PEHDR
Sections:
; MetaM
db "F***",0 ; null name
dd metamSize ; size
dd MetaM ; addr RVA
dd metamSize ; length
dd MetaM ; pos
dd 0 ; no relocations
dd 0 ; no linenum
dw 0
dw 0
dd 0C0000040h ; flags: datasection writeable readable
; CodeM
db "Windows",0 ; null name
dd codemSize ; size
dd CodeM ; addr RVA
dd codemSize ; length
dd CodeM ; pos
dd 0 ; no relocations
dd 0 ; no linenum
dw 0
dw 0
dd 0E0000020h ; flags: codesection writeable readable executable
; IMPORTS
db ".idata",0
dd importsSize ; size
dd IMPORTS ; addr RVA
dd importsSize ; length
dd IMPORTS ; pos
dd 0 ; no relocations
dd 0 ; no linenum
dw 0
dw 0
dd 0E0000020h ; flags: codesection writeable readable executable
fileSize equ $
;END.
附言我觉得很奇怪,编程中的一个事实上的标准是使用颜色来突出语法,而不是使用颜色来突出含义。甚至没有粗体/斜体。所以,我希望我可以对源代码中的关键部分进行着色或加粗 - 唉,不可能。在我的编程环境 - BlackBox Component Builder - 我可以随意使用颜色和粗体/斜体: