flat assembler
Message board for the users of flat assembler.
![]() Goto page 1, 2, 3 Next |
Author |
|
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. |
|||
![]() |
|
vid 04 Oct 2006, 12:58
i thought PE file format description says that it's size should be multiply of file align (512)
|
|||
![]() |
|
karl 04 Oct 2006, 13:04
(i should mention that i only got my code working by studying babyboy's example here)
|
|||
![]() |
|
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...
|
|||
![]() |
|
karl 04 Oct 2006, 13:09
do you know how i can look at the code for the loader?
|
|||
![]() |
|
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...
|
|||
![]() |
|
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.
|
|||
![]() |
|
f0dder 04 Oct 2006, 15:10
512 is indeed the smallest file-align that will work across all windows versions.
|
|||
![]() |
|
rugxulo 05 Oct 2006, 00:26
karl wrote:
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! ![]() |
|||
![]() |
|
karl 05 Oct 2006, 07:47
thanx rugxulo, you're right!
|
|||
![]() |
|
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!! |
|||
![]() |
|
f0dder 05 Oct 2006, 08:26
Quote:
By empirical testing ![]() 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 ![]() |
|||
![]() |
|
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 |
|||
![]() |
|
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
![]() 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. |
|||
![]() |
|
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 ![]() |
|||
![]() |
|
vid 05 Oct 2006, 09:05
i've read about some with 8k... but never saw it
![]() |
|||
![]() |
|
f0dder 05 Oct 2006, 09:25
vid wrote: i've read about some with 8k... but never saw it 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 |
|||
![]() |
|
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
![]() 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 ![]() 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 |
|||
![]() |
|
F9 05 Oct 2006, 11:27
Works perfectly under win95b
Congratulation!!! |
|||
![]() |
|
Goto page 1, 2, 3 Next < Last Thread | Next Thread > |
Forum Rules:
|
Copyright © 1999-2025, Tomasz Grysztar. Also on GitHub, YouTube.
Website powered by rwasa.