• Advertisement
Sign in to follow this  

PE header and number of RVAs

This topic is 4344 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

With some of the talk on this forum the past few days regarding PE headers, I got a renewed interest again in some of the stuff I'd been working on with hacking them apart and such. So I dug out some of my old tools and decided to try to create the smallest PE header I could. :) Ideally, I'd like to use it as a stub for decompression for a 4k demo. I know most current methods actually use a 16-bit DOS .com stub to decompress then launch a Win32 EXE, but that just seems silly to me. In any case, without too much trouble I was able to get a simple "Hello World!" to print out on the console in a whopping 448 bytes. The only problem with this tiny EXE is that it only works under Windows 2000. XP complains that it is not a valid executable... *sigh* The largest part of the PE header that wastes the most space is the RVA table. In the IMAGE_OPTIONAL_HEADER struct a field called NumberOfRvaAndSizes is supposed to specify the number of these RVA entries. Since my EXE only needs to import external symbols, I figured I could set this to 2 and save around 100 bytes or so, since I wouldn't need to store the unnecessary RVA entries. It seems that this is where I broke compatibility with 2000 and XP, even though Microsoft's own MSDN page says "The number of directories is not fixed. Check the NumberOfRvaAndSizes member before looking for a specific directory." in the Remarks section. In case anyone is interested, a zip is available here which contains three EXEs, each a subsequent step after the other that I used to trim down the PE header:
    tiny.exe  (608 bytes): Minimal MZ header, works fine on both 2K and XP
    tiny2.exe (480 bytes): Chopped RVA table to only 2 entries, 2K only
    tiny3.exe (448 bytes): Combined .code and .data sections into one, 2K only
So, does anyone know if the NumberOfRvaAndSizes is absolutely _required_ to be set to 16? Or maybe there's something I overlooked and didn't set properly? I've tried to use the various tools out there for checking EXEs -- PE Explorer has no problem with it, but Microsoft's PEDUMP crashes (seems to be coded to expect 16 entries -- although it really looks like a bug). Thanks for any help anyone can provide!

Share this post


Link to post
Share on other sites
Advertisement
Quote:
I know I can't be the only one around here that does this kind of stuff

Correct :D

I've written a 1500 byte demo (3d chess game with demo playback; similar screenshot) that also messed with the PE header.

It declared 2 data dirs and indeed worked under Win2k but (after dusting off) no longer on WinXP SP2. The error returned is "app failed to initialize (0xC0000005)", which comes up twice. No wonder - IIRC it flouted the PE spec and was the bare minimum that Win2k would deign to give a PID.

Looks like you'll have to expand to 16 entries, then. But fine and good they worked on the loader since Win2k, because I remember BSODs when raising an exception in code beyond OptionalHeader.SizeOfRawData.

Quote:
I know most current methods actually use a 16-bit DOS .com stub to decompress then launch a Win32 EXE, but that just seems silly to me.

Why silly? .com packers abound (IIRC I used APACK) and the dropper itself only adds 27 bytes with a little cheating:

; exe dropper
; Jan Wassenberg 2003

; free mem
mov ah, 0x49
int 0x21

; fopen
mov ah, 0x3c
pop cx ; cx = 0 (attrib)
mov dx, 0x100+exe ; filename == "MZ."
int 0x21

; fwrite
xchg ax, bx
mov ah, 0x40
dec cx ; 64 kb
int 0x21

; fclose
mov ah, 0x3e
int 0x21

; exec
mov ax, 0x4b00
int 0x21

exe:
incbin "w.exe"


(the EXE data can be misused as filename: just stash ".\0" right after the initial MZ to get dropped filename="MZ")

Quote:
In any case, without too much trouble I was able to get a simple "Hello World!" to print out on the console in a whopping 448 bytes.

Not bad, but can be significantly reduced yet. While drunk^H^H^H^H^Hinspired, I figured out the MZ header and PE optional header can be interleaved! Unfortunately I never drew up a diagram of the headers side by side, but the critical insight is: only signature and e_lfanew fields are needed in the MZ header; the former can be skipped by starting PE header at RVA 4 and the latter falls over OptionalHeader.SectionAlignment, for which it is a valid value.

Wacky header follows: (note: indented comments indicate data embedded into apparently unused header fields)

db 'MZ' ; e_magic
db '.',0 ; dropped file name
dd 'PE' ; Signature
dw 0x014c ; Machine = x86
dw 1 ; NumberOfSections
dd 0,0,0
dw 0x0070 ; SizeOfOptionalHeader
dw 0x010f ; Characteristics = 32 bit exe
dw 0x010b ; Magic
db 'glu32.dll',0 ; Name (RVA 30)
dd 0
dd rva(start) ; AddressOfEntryPoint
dd 0,0
dd 0x400000 ; ImageBase
dd 4 ; SectionAlignment, e_lfanew
dd 4 ; FileAlignment
dd 0,0
dw 4 ; MajorSubsystemVersion
db 'EDIT',0 ; wnd class name (RVA 78)
db 0
dd 0x10000 ; SizeOfImage (virtual)
dd 0xa4 ; SizeOfHeaders
dd 0
dw 2 ; Subsystem = WIN32_GUI
dw 0
dd 0x400000 ; SizeOfStackReserve
dd 0x400000 ; SizeOfStackCommit
dd 30 ; Name (RVA 108)
dd rva(imp_tbl) ; FirstThunk
dd 0
dd 2 ; NumberOfRvaAndSizes (# data dirs)
dd 0 ; Export VirtualAddress
dd 0 ; Export Size, End Import
dd 96 ; Import VirtualAddress
dd 40 ; Import Size
dd 0,0
dd 0 ; VirtualSize
dd 0 ; VirtualAddress
dd 0x10000 ; SizeOfRawData FIXME?
dd 0 ; PointerToRawData



This craziness yields a 160 byte executable :)

--

E8 17 00 42 CE DC D2 DC E4 EA C4 40 CA DA C2 D8 CC 40 CA D0 E8 40
E0 CA CA 96 5B B0 16 50 D7 D4 02 B2 02 86 E2 CD 21 58 48 79 F2 C3

Share this post


Link to post
Share on other sites
Guest Anonymous Poster
While this is most likely a fun exercize, working at the edges of the spec like this might cause incompatibilities in the future - it even happened in the case abive. Just one of the reasons PE encryptors and compressors will never be used by me...

Share this post


Link to post
Share on other sites
Quote:
Original post by Jan Wassenberg
Quote:
I know most current methods actually use a 16-bit DOS .com stub to decompress then launch a Win32 EXE, but that just seems silly to me.

Why silly? .com packers abound (IIRC I used APACK) and the dropper itself only adds 27 bytes with a little cheating:
*** Source Snippet Removed ***
(the EXE data can be misused as filename: just stash ".\0" right after the initial MZ to get dropped filename="MZ")

I don't like the idea of writing an EXE to disk at all. While I agree it works for size, it's a bit unclean... hence the reason I wanted to try to shrink the PE header down as much as possible, while still trying to maintain some sort of compatibility.

Quote:
Not bad, but can be significantly reduced yet. While drunk^H^H^H^H^Hinspired, I figured out the MZ header and PE optional header can be interleaved!

I had considered that myself too, but then decided against it since I already had some incompatibility issues between 2k and XP. Plus, I wasn't sure if a section alignment of 4 was actually valid or not. :)

Oh well, since it seems that XP requires 16 RVA entires, I probably won't bother with messing with the header too much more.

Thanks for the help, Jan!

Quote:
Original post by Anonymous Poster
While this is most likely a fun exercize, working at the edges of the spec like this might cause incompatibilities in the future - it even happened in the case abive. Just one of the reasons PE encryptors and compressors will never be used by me...

It's fun, definitely. :)

Although you should note that what I'm trying to do is much lower level than what "PE encryptors and compressors" do. AFAIK, no EXE packer in their right mind would try to muck with the PE header itself as much as we have.

Share this post


Link to post
Share on other sites
Quote:
I don't like the idea of writing an EXE to disk at all. While I agree it works for size, it's a bit unclean... hence the reason I wanted to try to shrink the PE header down as much as possible, while still trying to maintain some sort of compatibility.

I see where you're coming from. Droppers also usually do not delete the file afterwards, which isn't nice.
In my case, I was gunning for absolute minimum size and such considerations had little weight :)

Quote:
I had considered that myself too, but then decided against it since I already had some incompatibility issues between 2k and XP.

Yep, grr. Worst one I stumbled over is that they removed DllInitialize from opengl32.dll, so anyone who linked by ordinal (which is very tempting, given the many functions that must be imported) got screwed over.

Quote:
Plus, I wasn't sure if a section alignment of 4 was actually valid or not. :)

It is on NT systems, but not on 9x.

Quote:
Oh well, since it seems that XP requires 16 RVA entires, I probably won't bother with messing with the header too much more.

Aww. Interleaving might still be viable (even if it crashes several debuggers), and you could try stashing code/data inside those directories.

Quote:
Thanks for the help, Jan!

Glad to :)

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement