flat assembler
Message board for the users of flat assembler.

Index > Windows > smallest flexible PE

Goto page 1, 2, 3  Next
Author
Thread Post new topic Reply to topic
karl



Joined: 07 Feb 2006
Posts: 63
Location: South Africa
karl 04 Oct 2006, 12:56
i've read a lot here and here and here about making small win32 PE's by handcoding the PE structure. however, all of them use hardcoded API addresses, so they aren't portable. to be portable you need an import section so that windows loads in the correct API addresses every time.

by following the windows PE format exactly (best description i've found is here) the smallest size I can get that performs a MessageBox is 520 bytes:

Code:
 ;
 ;      manual PE
 ;
        image_base      equ 0x400000
        alignment       equ 0x4
        stack_reserve   equ 0x1000
        stack_commit    equ 0x1000
        heap_reserve    equ 0x1000
        heap_commit     equ 0x1000
 ;
 ;      dos header
 ;      pe header
 ;      optional header
 ;      directory entries
 ;      import header
 ;      code directory
 ;      import directory
 ;      
        use32
        
        dos_header:
        
                dw 'MZ'                 ; DOS signature
                db 0x3A dup(0)          ; unused
                dd 0x40                 ; PE header address
        
        pe_header:
        
                db 'PE',0,0             ; PE signature
                dw 0x014C               ; cpu (386)
                dw 1                    ; number of sections
                dd 0                    ; timestamp
                dd 0                    ; symbol table address
                dd 0                    ; number of symbols
                dw sizeof.header        ; size of optional header
                dw 0x010F               ; characteristics
                
        optional_header:
                
                dw 0x010B               ; magic
                dw 0                    ; linker version
                dd 0                    ; size of code section
                dd 0                    ; size of initialised data
                dd 0                    ; size of uninitialise data
                dd code_directory       ; entry point address
                dd 0                    ; base of code
                dd 0                    ; base of data
                dd image_base           ; base of image
                dd alignment            ; section alignment
                dd alignment            ; file alignment
                dw 0                    ; os version major
                dw 0                    ; os version minor
                dw 0                    ; image version major
                dw 0                    ; image version minor
                dw 4                    ; subsystem version major
                dw 0                    ; subsystem version minor
                dd 0                    ; win32 version (reserved)
                dd sizeof.image         ; image size
                dd code_directory       ; header size
                dd 0                    ; checksum
                dw 0x0002               ; subsystem (GUI)
                dw 0                    ; dll characteristics
                dd stack_reserve        ; stack reserve size
                dd stack_commit         ; stack commit size
                dd heap_reserve         ; heap reserve size
                dd heap_commit          ; heap commit size
                dd 0                    ; loader flags (obsolete)
                dd 16                   ; number of directory entries
                
        directory_entries:
                
                dq 0                    ; export
                dd import_directory     ; import section rva
                dd sizeof.import        ; import section size
                dq 14 dup(0)            ; the rest
                
        import_header:
                
                dq '.import'            ; name
                dd sizeof.import        ; virtual size
                dd code_directory       ; rva
                dd sizeof.import        ; raw size
                dd code_directory       ; raw pointer to data
                dd 0                    ; pointer to relocations
                dd 0                    ; pointer to line numbers
                dw 0                    ; number of relocations
                dw 0                    ; number of line numbers
                dd 0x0E0000020          ; characteristics
                align alignment
                
        code_directory:
                
                push 0
                push title+image_base
                push message+image_base
                push 0
                call[MessageBox+image_base]
                push 0
                call[ExitProcess+image_base]
                title: db 'Title',0
                message: db 'Hello',0
                
        import_directory:
                
                dd 0,0,0,kernel_name,kernel_table
                dd 0,0,0,user_name,user_table
                dd 0,0,0,0,0
                kernel_name db 'KERNEL32.DLL',0
                user_name db 'USER32.DLL', 0
                kernel_table:
                ExitProcess dd _ExitProcess
                dd 0
                user_table:
                MessageBox dd _MessageBox+0000h
                dd 0
                _MessageBox db 0, 0, 'MessageBoxA', 0
                _ExitProcess db 0,0,'ExitProcess',0
                
        file_end:
                
                sizeof.import = file_end-import_directory
                sizeof.header = import_header-optional_header
                sizeof.image = file_end
    


(to execute from cmd: fasm code.asm && move code.bin code.exe && code.exe)

this should execute on all windows versions that support PE (95 to vista). i think.

the 2 places that waste most space is the dos_header section and the directory_entries section. i've found you can reduce the number_of_directory_entries to 2 but no less. and it seems most disassemblers/debuggers have a problem with it (they assume there are 16 sections), and can't open the .exe. however, the msdn specifications say that this change is allowed.

for the dos_header, you can write the PE_header into the dos_header, so long as byte number 3C points to the start of the PE_header. from what I understand, when windows loads a PE it first checks that the first 2 bytes of the .exe are 'MZ' and then it assumes that byte 3C points to the beginning of the PE header that says 'PE'.

if windows doesn't change this behaviour in the future, then this program should be portable too:

Code:
 ;      
 ;      manual PE
 ;      
        image_base      equ 0x400000
        alignment       equ 0x4
        stack_reserve   equ 0x1000
        stack_commit    equ 0x1000
        heap_reserve    equ 0x1000
        heap_commit     equ 0x1000
 ;      
 ;      dos header
 ;      pe header
 ;      optional header
 ;      directory entries
 ;      import header
 ;      code directory
 ;      import directory
 ;      
        use32
        
        dos_header:
                
                db 'MZ',0,0             ; DOS signature
                
        pe_header:
                
                db 'PE',0,0             ; PE signature
                dw 0x014C               ; cpu (386)
                dw 1                    ; number of sections
                dd 0                    ; timestamp
                dd 0                    ; symbol table address
                dd 0                    ; number of symbols
                dw sizeof.header        ; size of optional header
                dw 0x010F               ; characteristics
                
        optional_header:
                
                dw 0x010B               ; magic
                dw 0                    ; linker version
                dd 0                    ; size of code section
                dd 0                    ; size of initialised data
                dd 0                    ; size of uninitialise data
                dd code_directory       ; entry point address
                dd 0                    ; base of code
                dd 0                    ; base of data
                dd image_base           ; base of image
                dd alignment            ; section alignment
                dd alignment            ; file alignment
                dw 0                    ; os version major
                dw 0                    ; os version minor
                dw 0                    ; image version major
                dw 0                    ; image version minor
                dw 4                    ; subsystem version major
                dw 0                    ; subsystem version minor
                dd 0                    ; win32 version (reserved)
                dd sizeof.image         ; image size
                dd code_directory       ; header size
                dd 0                    ; checksum
                dw 0x0002               ; subsystem (GUI)
                dw 0                    ; dll characteristics
                dd stack_reserve        ; stack reserve size
                dd stack_commit         ; stack commit size
                dd heap_reserve         ; heap reserve size
                dd heap_commit          ; heap commit size
                dd 0                    ; loader flags (obsolete)
                dd 2                    ; number of directory entries
                
        directory_entries:
                
                dq 0                    ; export
                dd import_directory     ; import section rva
                dd sizeof.import        ; import section size
                dq 0                    ; the rest
                
        import_header:
                
                dq '.import'            ; name
                dd sizeof.import        ; virtual size
                dd code_directory       ; rva
                dd sizeof.import        ; raw size
                dd code_directory       ; raw pointer to data
                dd 0                    ; pointer to relocations
                dd 0                    ; pointer to line numbers
                dw 0                    ; number of relocations
                dw 0                    ; number of line numbers
                dd 0x0E0000020          ; characteristics
                align alignment
                
        code_directory:
                
                push 0
                push title+image_base
                push message+image_base
                push 0
                call[MessageBox+image_base]
                push 0
                call[ExitProcess+image_base]
                title: db 'Title',0
                message: db 'Hello',0
                
        import_directory:
                
                dd 0,0,0,kernel_name,kernel_table
                dd 0,0,0,user_name,user_table
                dd 0,0,0,0,0
                kernel_name db 'KERNEL32.DLL',0
                user_name db 'USER32.DLL', 0
                kernel_table:
                ExitProcess dd _ExitProcess
                dd 0
                user_table:
                MessageBox dd _MessageBox+0000h
                dd 0
                _MessageBox db 0, 0, 'MessageBoxA', 0
                _ExitProcess db 0,0,'ExitProcess',0
                
        file_end:
                
                sizeof.import = file_end-import_directory
                sizeof.header = import_header-optional_header
                sizeof.image = file_end
    


when compiled, byte 3C is in the optional header at file_alignment, which is set to 4. and that's where the 'PE' starts. so it works. and compiles to 356 bytes. but obviously if you change the alignment it won't work.

any comments/suggestions/corrections? i've only just started understanding the PE structure (the microsoft description is terrible!) so any help would be appreciated.

anyone think it can get smaller?
thanks in advance.
Post 04 Oct 2006, 12:56
View user's profile Send private message Reply with quote
vid
Verbosity in development


Joined: 05 Sep 2003
Posts: 7103
Location: Slovakia
vid 04 Oct 2006, 12:58
i thought PE file format description says that it's size should be multiply of file align (512)
Post 04 Oct 2006, 12:58
View user's profile Send private message Visit poster's website AIM Address MSN Messenger ICQ Number Reply with quote
karl



Joined: 07 Feb 2006
Posts: 63
Location: South Africa
karl 04 Oct 2006, 13:01
file align? you set the file align in optional_header. and it works with just 4... and just tried 1 and still works. i don't understand why.


Last edited by karl on 04 Oct 2006, 13:06; edited 1 time in total
Post 04 Oct 2006, 13:01
View user's profile Send private message Reply with quote
karl



Joined: 07 Feb 2006
Posts: 63
Location: South Africa
karl 04 Oct 2006, 13:04
(i should mention that i only got my code working by studying babyboy's example here)
Post 04 Oct 2006, 13:04
View user's profile Send private message Reply with quote
vid
Verbosity in development


Joined: 05 Sep 2003
Posts: 7103
Location: Slovakia
vid 04 Oct 2006, 13:05
wow, file aligns 4 and 1 works? windows loader is very varying over different windowses, some allow something which they shouldn't and disallow something they should etc...
Post 04 Oct 2006, 13:05
View user's profile Send private message Visit poster's website AIM Address MSN Messenger ICQ Number Reply with quote
karl



Joined: 07 Feb 2006
Posts: 63
Location: South Africa
karl 04 Oct 2006, 13:09
do you know how i can look at the code for the loader?
Post 04 Oct 2006, 13:09
View user's profile Send private message Reply with quote
karl



Joined: 07 Feb 2006
Posts: 63
Location: South Africa
karl 04 Oct 2006, 13:12
also, could anyone reading this try running these progs and tell me if it works. and if not try adjusting the alignment at the beginning to something like 0x200...
Post 04 Oct 2006, 13:12
View user's profile Send private message Reply with quote
vid
Verbosity in development


Joined: 05 Sep 2003
Posts: 7103
Location: Slovakia
vid 04 Oct 2006, 13:14
karl: hmmm, i am not sure now. i saw one reversed lately. and still, they vary much about windozes.
Post 04 Oct 2006, 13:14
View user's profile Send private message Visit poster's website AIM Address MSN Messenger ICQ Number Reply with quote
f0dder



Joined: 19 Feb 2004
Posts: 3174
Location: Denmark
f0dder 04 Oct 2006, 15:10
512 is indeed the smallest file-align that will work across all windows versions.
Post 04 Oct 2006, 15:10
View user's profile Send private message Reply with quote
rugxulo



Joined: 09 Aug 2005
Posts: 2341
Location: Usono (aka, USA)
rugxulo 05 Oct 2006, 00:26
karl wrote:

(to execute from cmd: fasm code.asm && move code.bin code.exe && code.exe)

this should execute on all windows versions that support PE (95 to vista). i think.


Well, your examples do indeed work for me (Win XP Home SP2), no surprise there.

I just wanted to nitpick and say that you can do fasm code.asm code.exe & code.exe to save some cmdline space! Smile
Post 05 Oct 2006, 00:26
View user's profile Send private message Visit poster's website Reply with quote
karl



Joined: 07 Feb 2006
Posts: 63
Location: South Africa
karl 05 Oct 2006, 07:47
thanx rugxulo, you're right!
Post 05 Oct 2006, 07:47
View user's profile Send private message Reply with quote
karl



Joined: 07 Feb 2006
Posts: 63
Location: South Africa
karl 05 Oct 2006, 08:07
fodder: how do you know 512 is the minimum?

if that's true, that totally sucks! it makes my prog 680 bytes! the smallest windows-complient prog that calls the api! i hate it, this is suppose to be assembly! so long, 256byte compo (as in here).

... just read. the msdn library says 512 is the minimum. oh well. Damn You Microsoft!!
Post 05 Oct 2006, 08:07
View user's profile Send private message Reply with quote
f0dder



Joined: 19 Feb 2004
Posts: 3174
Location: Denmark
f0dder 05 Oct 2006, 08:26
Quote:

f0dder: how do you know 512 is the minimum?

By empirical testing Smile
During the years, I've run win95, win95b, win98, win98se, win2k (and various service packs), winxp (and various service packs). PE files are handled slightly different on each windows version, with different quirks here and there. This is why I always smile a bit and shake my head when I see those "smallest PE file omg!" examples, since most of them will break on at least one system.

Hardcoded API addresses? Will of course fail.
Executables without imports? Fails on at least win2k.
etc.

And it's quite silly anyway, since you'll always take up an entire cluster... which will be at least 4k for performance reasons. Sure, it's fun to try and produce smallest possible exe, size optimization is cute for 4k intros etc. But for real life stuff, it's mostly useless Smile
Post 05 Oct 2006, 08:26
View user's profile Send private message Reply with quote
F9



Joined: 29 Sep 2006
Posts: 17
F9 05 Oct 2006, 08:33
Win95b read Karl_356.exe is improperly linked with alignment less than 0x1000 ... Karl_520.exe reads the same...

Both work on xp Pro 2002
Post 05 Oct 2006, 08:33
View user's profile Send private message Reply with quote
karl



Joined: 07 Feb 2006
Posts: 63
Location: South Africa
karl 05 Oct 2006, 08:55
thanks for the reality check, fodder. i must say, though: i'm programming in assembly because of my rediculous perfectionism! and smaller is better, for me Smile. i just wanna know if i'm at the limit. but i agree, it's not pragmatic. kinda adolescent, but fun!

what do you mean by a cluster? is this a memory page?

F9: thanks for the check. what i'm thinking now is that the safest bet is having file alignment to 512 (0x200) and section alignment to 4096 (0x1000). in my code i just set them both the same. that's so that i don't need to work out the relative virtual address. check out babyboy's example again. he keeps adding 0E00 to stuff. it took me forever to work out that that was the section_alignment-file_alignment. so i think you can get the 680 byte one to work in win95, but it will take up 4k of memory (which is the minimum, if i understand fodder correctly). changing the code now, let me know if you wanna see it.
Post 05 Oct 2006, 08:55
View user's profile Send private message Reply with quote
f0dder



Joined: 19 Feb 2004
Posts: 3174
Location: Denmark
f0dder 05 Oct 2006, 09:03
A cluster is a filesystem thing, present in at least FAT and NTFS (but with other names in most other filesystems, I'd guess). A sector (512 bytes on just about any harddrive) is the smallest physical chunk you can address. For performance issues (and originally for some filesystem overhead reason), these are "clustered together" for filesystems, so a file will always take a multiple of the cluster size on disk. Higher cluster size means smaller files will be "penalized" more, but also that there'll be less disk fragmentation...

Section alignment always needs to be a multiple of 4096 - and while I haven't checked, I wouldn't be surprised if there's some windows version that'll only work with 4096. 4k is magic ever since i386 Wink
Post 05 Oct 2006, 09:03
View user's profile Send private message Reply with quote
vid
Verbosity in development


Joined: 05 Sep 2003
Posts: 7103
Location: Slovakia
vid 05 Oct 2006, 09:05
i've read about some with 8k... but never saw it Smile
Post 05 Oct 2006, 09:05
View user's profile Send private message Visit poster's website AIM Address MSN Messenger ICQ Number Reply with quote
f0dder



Joined: 19 Feb 2004
Posts: 3174
Location: Denmark
f0dder 05 Oct 2006, 09:25
vid wrote:
i've read about some with 8k... but never saw it Smile


wouldn't be hard testing anyway - use MS link.exe /ALIGN: instead of /FILEALIGN, then try the executable on various windows versions. I've only got XP here, currently, and win98 in vmware.

_________________
carpe noctem
Post 05 Oct 2006, 09:25
View user's profile Send private message Reply with quote
karl



Joined: 07 Feb 2006
Posts: 63
Location: South Africa
karl 05 Oct 2006, 10:52
thanx for the info, fodder. now i understand why windows says my code.exe Size On Disk is 4096 bytes Smile

for anyone interested, this code lets you change both alignments (in the previous code they had to be the same). currently it's set to what's suppose to be the windows minimum (section 0x1000, file 0x200), and assembles to 680 bytes. makes me wonder why i don't just say format PE gui, output 1024 bytes Smile. still, now i'm closer to being able to make my own assembler!

F9: could you check this code on 95?

Code:
 ;
 ;      manual PE
 ;
        image_base      equ 0x400000
        section_align   equ 0x1000
        file_align      equ 0x200
        stack_reserve   equ 0x1000
        stack_commit    equ 0x1000
        heap_reserve    equ 0x1000
        heap_commit     equ 0x1000
 ;
 ;      dos header
 ;      pe header
 ;      optional header
 ;      directory entries
 ;      import header
 ;      code directory
 ;      import directory
 ;      
        use32
        
        dos_header:
        
                dw 'MZ'                 ; DOS signature
                db 0x3A dup(0)          ; unused
                dd 0x40                 ; PE header address
        
        pe_header:
        
                db 'PE',0,0             ; PE signature
                dw 0x014C               ; cpu (386)
                dw 1                    ; number of sections
                dd 0                    ; timestamp
                dd 0                    ; symbol table address
                dd 0                    ; number of symbols
                dw sizeof.header        ; size of optional header
                dw 0x010F               ; characteristics
                
        optional_header:
                
                dw 0x010B               ; magic
                dw 0                    ; linker version
                dd 0                    ; size of code section
                dd 0                    ; size of initialised data
                dd 0                    ; size of uninitialise data
                dd code_section+adjust  ; entry point address
                dd 0                    ; base of code
                dd 0                    ; base of data
                dd image_base           ; base of image
                dd section_align        ; section alignment
                dd file_align           ; file alignment
                dw 0                    ; os version major
                dw 0                    ; os version minor
                dw 0                    ; image version major
                dw 0                    ; image version minor
                dw 4                    ; subsystem version major
                dw 0                    ; subsystem version minor
                dd 0                    ; win32 version (reserved)
                dd sizeof.image         ; image size
                dd code_section         ; header size
                dd 0                    ; checksum
                dw 0x0002               ; subsystem (GUI)
                dw 0                    ; dll characteristics
                dd stack_reserve        ; stack reserve size
                dd stack_commit         ; stack commit size
                dd heap_reserve         ; heap reserve size
                dd heap_commit          ; heap commit size
                dd 0                    ; loader flags (obsolete)
                dd 16                   ; number of directory entries
                
        directory_entries:
                
                dq 0                            ; export
                dd import_directory+adjust      ; import section rva
                dd sizeof.import                ; import section size
                dq 14 dup(0)                    ; the rest
                
        import_header:
                
                dq '.import'            ; name
                dd sizeof.import        ; virtual size
                dd code_section+adjust  ; rva
                dd sizeof.import        ; raw size
                dd code_section         ; raw pointer to data
                dd 0                    ; pointer to relocations
                dd 0                    ; pointer to line numbers
                dw 0                    ; number of relocations
                dw 0                    ; number of line numbers
                dd 0x0E0000020          ; characteristics
                align file_align        ; align to file_align
                
        code_section:
                
                push 0
                push title
                push message
                push 0
                call dword[MessageBox]
                push 0
                call dword[ExitProcess]
                
        data_section:
                
                _title: db 'Title',0
                _message: db 'Hello',0
                
        import_directory:
                
                dd 0,0,0,kernel_name+adjust,kernel_table+adjust
                dd 0,0,0,user_name+adjust,user_table+adjust
                dd 0,0,0,0,0
                kernel_table:
                _ExitProcess dd __ExitProcess+adjust
                dd 0
                user_table:
                _MessageBox dd __MessageBox+adjust
                dd 0
                kernel_name db 'KERNEL32.DLL',0
                user_name db 'USER32.DLL', 0
                __MessageBox db 0, 0, 'MessageBoxA', 0
                __ExitProcess db 0,0,'ExitProcess',0
                
        equates:
                
                sizeof.import = $-import_directory
                sizeof.header = import_header-optional_header
                sizeof.image = section_align+$
                adjust = section_align-file_align
                title = _title+image_base+adjust
                message = _message+image_base+adjust
                ExitProcess = _ExitProcess+image_base+adjust
                MessageBox = _MessageBox+image_base+adjust
    
Post 05 Oct 2006, 10:52
View user's profile Send private message Reply with quote
F9



Joined: 29 Sep 2006
Posts: 17
F9 05 Oct 2006, 11:27
Works perfectly under win95b

Congratulation!!!
Post 05 Oct 2006, 11:27
View user's profile Send private message Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  
Goto page 1, 2, 3  Next

< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2025, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.