Search code examples
winapiimportx86nasmportable-executable

procedure entry point MessageBoxA could not be located when importing from dll in a custom PE file


I've been trying to get a grasp of the PE file format (just to teach myself something new) and attempted to create a simple .exe which shows a message box and then exits, I've used tinyPE as a base and then built on top of it using stuff from the microsoft docs on the format.

The problem is that when I try to run it I get a The procedure entry point MessageBoxA could not be located in the dynamic link library D:\path\to\my\test.exe popup (which sounds like windows tried to import the function from the exe itself, but I don't understand why it would be doing that)

I tried rearranging stuff and playing with headers but nothing helped. I also checked the imports using PE Explorer but it seems to correctly read the import and says it should be imported from kernel32.dll as intended

Here's my code:

bits 32

align 1, db 0

mz_header:
    dw "MZ"                       ; e_magic
    dw 0                          ; e_cblp
    dw 0                          ; e_cp
    dw 0                          ; e_crlc
    dw 0                          ; e_cparhdr
    dw 0                          ; e_minalloc
    dw 0                          ; e_maxalloc
    dw 0                          ; e_ss
    dw 0                          ; e_sp
    dw 0                          ; e_csum
    dw 0                          ; e_ip
    dw 0                          ; e_cs
    dw 0                          ; e_lsarlc
    dw 0                          ; e_ovno
    times 4 dw 0                  ; e_res
    dw 0                          ; e_oemid
    dw 0                          ; e_oeminfo
    times 10 dw 0                 ; e_res2
    dd pe_header                  ; e_lfanew

pe_header:
    dd "PE"
    dw 0x014C                     ; Machine (Intel 386)
    dw 2                          ; NumberOfSections
    dd 0x4545BE5D                 ; TimeDateStamp
    dd 0                          ; PointerToSymbolTable
    dd 0                          ; NumberOfSymbols
    dw pe_optional_header_size    ; SizeOfOptionalHeader
    dw 0x103                      ; Characteristics (no relocations, executable, 32 bit)

pe_optional_header:
    dw 0x10B                      ; Magic (PE32)
    db 8                          ; MajorLinkerVersion
    db 0                          ; MinorLinkerVersion
    dd text_size                  ; SizeOfCode
    dd 0                          ; SizeOfInitializedData
    dd 0                          ; SizeOfUninitializedData
    dd _main                      ; AddressOfEntryPoint
    dd text_begin                 ; BaseOfCode
    dd filesize                   ; BaseOfData
    dd 0x400000                   ; ImageBase
    dd 1                          ; SectionAlignment
    dd 1                          ; FileAlignment
    dw 4                          ; MajorOperatingSystemVersion
    dw 0                          ; MinorOperatingSystemVersion
    dw 0                          ; MajorImageVersion
    dw 0                          ; MinorImageVersion
    dw 4                          ; MajorSubsystemVersion
    dw 0                          ; MinorSubsystemVersion
    dd 0                          ; Win32VersionValue
    dd filesize                   ; SizeOfImage
    dd header_total_size          ; SizeOfHeaders
    dd 0                          ; CheckSum
    dw 2                          ; Subsystem (Win32 GUI)
    dw 0x400                      ; DllCharacteristics
    dd 0x100000                   ; SizeOfStackReserve
    dd 0x1000                     ; SizeOfStackCommit
    dd 0x100000                   ; SizeOfHeapReserve
    dd 0x1000                     ; SizeOfHeapCommit
    dd 0                          ; LoaderFlags
    dd 4                          ; NumberOfRvaAndSizes

rva:
    dd 0
    dd 0
    dd import_dir_table
    dd import_dir_table_size
    times 12 dd 0, 0              ; This is necessary for a valid executable, probably as padding

pe_optional_header_size equ $ - pe_optional_header

; Section table
text_section:
    db ".text", 0, 0, 0           ; Section name
    dd text_size                  ; Size when loaded
    dd header_total_size          ; Adress when loaded
    dd text_size                  ; Size
    dd text_begin                 ; Points to section beginning
    dd 0, 0                       ; Pointer to relocations and line numbers
    dw 0, 0                       ; Count of relocations and line numbers
    dd 0x60000020                 ; Characteristics
rdata_section:
    db ".rdata", 0, 0             ; Section name
    dd rdata_size                 ; Size when loaded
    dd rdata_begin                ; Adress when loaded
    dd rdata_size                 ; Size
    dd rdata_begin                ; Points to section beginning
    dd 0, 0                       ; Pointer to relocations and line numbers
    dw 0, 0                       ; Count of relocations and line numbers
    dd 0x40000040                 ; Characteristics
    
header_total_size equ $ - $$

; .text section
text_begin:
; Entry function
_main:
    ; try to call the imported function
    push dword 0
    push dword mbox_message + 0x400000 
    push dword 0
    push dword 1
    call [import_adress_table + 0x400000]
    
    mov eax, 42
    ret

text_size equ $ - text_begin

; .rdata section
rdata_begin equ $ - $$

import_dir_table:
    dd import_lookup_table                    ; Import lookup table
    dd 0, 0                                   ; Timestamp and forwarder chain, unused
    dd kernel32                               ; Name of dll
    dd import_adress_table                    ; Import adress table

    ; Empty entry to signify end of import dir table
    dd 0, 0, 0, 0, 0

import_dir_table_size equ $ - import_dir_table

; Like the lookup table, but entries are replaced
; with real adresses of imported functions
import_adress_table:
    dd namehint_table
    dd 0

; Stores DWORD pointers to Name/Hint tables
import_lookup_table:
    dd namehint_table
    dd 0

; Stores a hint (?) and a function name to import
namehint_table:
    db 0, 0
    db "MessageBoxA", 0   ; Function name
    
; String used to import the kernel32.dll
kernel32:
    db "kernel32.dll", 0
    
; Other .rdata stuff

mbox_message:
    db "Hello, World!", 0

rdata_size equ $ - rdata_begin
    
filesize equ $ - $$

I'm assembling it using NASM 2.15.5: nasm -f bin -o test.exe test.asm

So where did I go wrong? Thank you in advance


Solution

  • The original problem was solved by RbMm in the comments, I mistakenly imported kernel32.dll instead of user32.dll (left over from when I tried to call ExitProcess)

    Importing the correct library solved the error but to make it work and the message box appear I had to import all functions called by MessageBoxA, to find those, simply create a test program that calls MessageBoxA and then look at the imports MSVC generates:

    #include <winuser.h>
    int main()
    {
        return MessageBoxA(NULL, "Test", NULL, 0);
    }