Windows 10 无法识别可在 WINE 中运行的手工制作的 PE 可执行文件

问题描述

我已经为 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 的最终版本。简而言之,这里是错误的:

  1. 未对齐的图像大小字段。必须页面对齐 (1000h)。我的错误。
  2. 标头大小计算错误。规格不佳。这帮助我弄清楚了,而不是规范:PE format walkthru
  3. 拥有 2 个目录不起作用,认为它与规范不矛盾。我没有试验 16 以外的数字。规格不佳。顺便说一下,目录中的 IAT 无关紧要;实际上,如果您导入 2 个 DLL,则每个都获取和 IAT;应该在目录中引用哪一个?答案:装载机不在乎。
  4. IAT 的第一个条目必须非零,否则此 IAT 将被忽略且不会填写。这是非常无证的。规格不佳。

从积极的方面来说,

  1. 我能够将 PE 标头和节列表放在文件末尾,这与通常将其放在 DOS 存根之后和节之前不同。
  2. 我能够以一种有点奇怪的方式组织导入,每个导入符号一个 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 - 我可以随意使用颜色和粗体/斜体:

arbitrary color in source