www.xbdev.net
xbdev - software development
Monday January 5, 2026
Home | Contact | Support | PE File Format... Inside with NASM Assembler...
     
 

PE File Format...

Inside with NASM Assembler...

 

Little PE Executable


To get the ball rolling - let's write a bare minimum executable in assembly using NASM - I mean really bare - just the essentials to create a valid executable that runs and closes - we won't include any libraries or complex code - it will be just the PE structures (headers/offsets) - code to return and exit and that's all. This helps you focus solely on the basic PE structure without getting into the import table complexities.

Create the absolute minimal PE executable that does (almost
nothing — just starts, and then returns cleanly.
nasm -f bin hellope.asm -o hellope.exe

BITS 32
org 0x400000

start
:
    ; === 
DOS Header ===
    
db 'M','Z'                         DOS Signature
    times 58 db 0
    dd 0x80                            
Offset to PE header

    times 64
-62 db 0

    
; === PE Header ===
    
db 'P','E',0,0
    dw 0x014c                          
MachineIntel 386
    dw 1                               
Number of sections
    dd 0                               
Timestamp
    dd 0                               
Symbol table ptr
    dd 0                               
Symbol count
    dw 0x00e0                          
Optional header size
    dw 0x0002                          
CharacteristicsExecutable

    
; === Optional Header ===
    
dw 0x010b                          MagicPE32
    db 8
0                            Linker version
    dd 0x200                           
SizeOfCode
    dd 0x0                             
SizeOfInitData
    dd 0x0                             
SizeOfUninitData
    dd entry_point 
start            EntryPoint RVA
    dd text_section 
start           BaseOfCode
    dd 0x0                             
BaseOfData
    dd 0x400000                        
ImageBase
    dd 0x1000
0x200                   Alignment (VirtualFile)
    
dd 0x0                             OS version
    dd 0x0                             
Image version
    dd 0x0                             
Subsystem version
    dd 0x0                             
Win32 version
    dd 0x1000                          
SizeOfImage
    dd 0x200                           
SizeOfHeaders
    dd 0x0                             
Checksum
    dd 0x2                             
SubsystemGUI
    dw 0x0
0x0                        DLL Characteristics
    dd 0x100000
0x1000               Stack reserve commit
    dd 0x100000
0x1000               Heap reserve commit
    dd 0x0
0x10                       LoaderFlagsRVA count

    times 128 db 0                     
Null Data Directory entries

    
; === Section Header (.text) ===
    
db '.text'000                Name
    dd 0x200                           
Virtual size
    dd text_section 
start           Virtual address
    dd 0x200                           
SizeOfRawData
    dd 0x200                           
PointerToRawData
    times 12 db 0
    dd 0x60000020                      
Characteristics

    
; === Padding to align to 0x200 ===
    
times (0x200 - ($ - start)) db 0

; === .text section ===
text_section:
entry_point:
    ; Do 
nothing and return to system
    ret

    
Pad to 0x200 bytes
    times 
(0x200 - ($ - text_section)) db 0



You can run it on a Window's machine after assembling - you just double-click the
hellope.exe
- nothing will show up or happen - it'll just run and close.

This code sample might seem a bit useless - but it's a springboard - perfect for layering on more functionality as you explore the PE format.

Easy to add in more pieces - such as imports sections, .data section and so on.


Alert Box


To take things a bit further - let's write a complete executable (.exe) in the assembly. We can do it in a single file with no external libraries or resources. We can define all the headers, offsets and code - in raw hex/assembly. The exe will be for a Win32/64 - just something that runs and shows a message on screen.

You can type (or copy paste) the assembly into your favourite text editor - and type the nasm command to compile the exe.

The implementation is for a bare-bones Windows PE executable in raw x86 assembly that displays a MessageBox using MessageBoxA from user32.dll, with no includes or libraries. It manually crafts the import directory and uses a basic PE layout.

This will create a dialog box saying "Hello from ASM!":


A minimal Windows PE that shows a MessageBoxA using raw assembly.
Assemble withnasm -f bin messagebox.asm -o messagebox.exe

BITS 32
org 0x400000

start
:
    ; === 
DOS Header ===
    
db 'M','Z'                         DOS signature
    times 58 db 0
    dd 0x80                            
Offset to PE Header

    times 64 
- ($ - startdb 0       Padding

    
; === PE Signature COFF Header ===
    
db 'P','E',0,0
    dw 0x014c                          
Machine
    dw 1                               
NumberOfSections
    dd 0                               
TimeDateStamp
    dd 0                               
PointerToSymbolTable
    dd 0                               
NumberOfSymbols
    dw 0x00E0                          
SizeOfOptionalHeader
    dw 0x0102                          
Characteristics

    
; === Optional Header ===
    
dw 0x010b                          Magic
    db 8
0                            Linker version
    dd 0x200
0x2000                 SizesCodeDataBSS
    dd entry 
start                  Entry point RVA
    dd code_rva 
startdata_rva start
    dd 0x400000                        
ImageBase
    dd 0x1000
0x200                   Section and file alignment
    times 16 db 0                      
Padding
    dd 0x1000
0x200                   SizeOfImageSizeOfHeaders
    dd 0                               
Checksum
    dd 2                               
Subsystem (GUI)
    
dw 00                            DLL Characteristics
    dd 0x100000
0x1000                Stack reservecommit
    dd 0x100000
0x1000                Heap reservecommit
    dd 0
16                           Loader flagsRVA count

    
; === Data Directories ===
    
dd import_table start0x40      Import Table RVA/Size
    times 15 dd 0
,0

    
; === Section Header (.text) ===
    
db '.text'000
    dd 0x200
code_rva start         VirtualSizeRVA
    dd 0x200
0x200                    SizeOfRawDataPointerToRawData
    times 12 db 0
    dd 0x60000020                      
Characteristics

    
; === Align to 0x200 ===
    
times (0x200 - ($ - start)) db 0

; === Code section ===
code_rva:
entry:
    
call get_eip
get_eip
:
    
pop esi
    sub esi
get_eip import_lookup_user32

    push 0
    lea eax
, [esi msg_title import_lookup_user32]
    
push eax
    lea eax
, [esi msg_text import_lookup_user32]
    
push eax
    push 0
    mov eax
, [esi user32_iat import_lookup_user32]
    
call eax

    
; Exit via kernel32 ExitProcess
    push 0
    mov eax
, [esi kernel32_iat import_lookup_user32]
    
call eax

hang
:
    
jmp hang

; === Import section ===
import_table:
    
dd import_lookup_user32 start
    dd 0
0
    dd user32_name 
start
    dd user32_iat 
start

    dd import_lookup_kernel32 
start
    dd 0
0
    dd kernel32_name 
start
    dd kernel32_iat 
start

    dd 0
000

import_lookup_user32
:
    
dd hint_messagebox start
    dd 0

user32_iat
:
    
dd hint_messagebox start
    dd 0

import_lookup_kernel32
:
    
dd hint_exitprocess start
    dd 0

kernel32_iat
:
    
dd hint_exitprocess start
    dd 0

; === Hints ===
hint_messagebox:
    
dw 0
    db 
'MessageBoxA'0
    db 0

hint_exitprocess
:
    
dw 0
    db 
'ExitProcess'0
    db 0

; === Strings ===
msg_text:
    
db 'Hello from ASM!'0

msg_title
:
    
db 'Hi'0

user32_name
:
    
db 'USER32.dll'0

kernel32_name
:
    
db 'KERNEL32.dll'0

; === Data padding ===
data_rva:


It’ll pop up a nice little alert box when launched.









 
Advert (Support Website)

 
 Visitor:
Copyright (c) 2002-2025 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.