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
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);
}