Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create tiny PE (Win32) executables using MinGW

I have the following C program:

#include <windows.h>
void __cdecl mainCRTStartup() {
  DWORD bw;
  HANDLE hfile = GetStdHandle(STD_OUTPUT_HANDLE);
  WriteFile(hfile, "Hello, World!\r\n", 15, &bw, 0);
  ExitProcess(0);  /* Needed for successful (0) exit. */
}

I compile it with GCC 4.8.2, using the following command line:

i686-w64-mingw32-gcc -s -Os -fno-ident -fno-stack-protector -fomit-frame-pointer \
-fno-unwind-tables -fno-asynchronous-unwind-tables -falign-functions=1  \
-mpreferred-stack-boundary=2 -falign-jumps=1 -falign-loops=1 -mconsole \
-nostdlib -nodefaultlibs -nostartfiles -o h.exe h.c -lkernel32

The generated .exe file is 2048 bytes long. How can I make it smaller using MinGW, preferably at most 1024 bytes, or (even better) at most 512 bytes?

I'd prefer a solution without writing assembly code, but I'm also interested in assembly solutions.

I've tried -Wl,-N to decrease the number of sections (segments), but that caused a segfault when running the .exe in Wine.

This article suggests that 480 bytes is possible. It uses the following settings:

#pragma comment(linker, "/FILEALIGN:16")
#pragma comment(linker, "/ALIGN:16")// Merge sections
#pragma comment(linker, "/MERGE:.rdata=.data")
#pragma comment(linker, "/MERGE:.text=.data")
#pragma comment(linker, "/MERGE:.reloc=.data")
#pragma optimize("gsy", on)

Unfortunately these #pragmas don't work with MinGW GCC. Are there equivalents?

In here I was able to find the GCC flags -Wl,--section-alignment,16,--file-alignment,16 which bring down the .exe size to 752 bytes. The .exe seems to work in Wine.

By modifying the linker script I was able to merge .data and .rdata, and go down to 736 bytes. I'm using these GCC flags in addition to those above: -Wl,--section-alignment,16,--file-alignment,16,-T,tinygccpe.scr.

I'm still looking for the MinGW equivalent of /MERGE.

This question is similar, but it doesn't attempt to go below 9000 bytes.

I'm also looking for a strip tool (the strip command in MinGW doesn't reduce the .exe size any further) which can remove the DOS stub (which is between offsets 0x40 and 0x80, it contains This program cannot be run in DOS mode., we could save 64 bytes). This code can remove it, but it also breaks all the absolute offsets in the .exe. Unfortunately the linker ld in MinGW isn't able to remove the DOS stub, it's hardcoded in the file bfd/peXXigen.c, just above the NT_SIGNATURE.

Is it possible to strip more headers from the .exe, i.e. headers which the loader doesn't use?

like image 490
pts Avatar asked Feb 03 '17 10:02

pts


People also ask

Does MinGW include G ++?

Gcc distributions such as mingw provide a gcc executable which can be used to compile files in any language the distribution supports. They also provide other compiler drivers, which in the case of mingw are executables such as g++, c++, g77, etc.

Can you compile EXE on Linux?

"I compiled a c file on my Linux machine and named it test.exe, is it possible to run that file on windows?" - It is absolutely possible to compile a Windows executable on Linux.

What is purpose of MinGW?

MinGW ("Minimalist GNU for Windows"), formerly mingw32, is a free and open source software development environment to create Microsoft Windows applications.


1 Answers

This question has extensive online literature, starting from about 1995.

Each version of 32-bit and 64-bit Windows has a different set of rules on what header values they accept in PE .exe executables. For example, Windows 7 accepts .exe files with 0 sections, section alignment 4, file alignment 4, and other versions of Windows (e.g. Windows XP and the most recent Windows 10 in 2020) reject these files.

It's possible to create working .exe files smaller than 2048 bytes though. Example:

  • hh6d.golden.exe (584 bytes) is portable: it works on all Win32 implementations released by Microsoft and also on Wine. (Tested on Windows NT 3.1, Windows 95, Windows XP and Windows 10, Wine 1.6.2, Wine 5.0.)
  • hh2d.golden.exe (408 bytes) works on Windows 95 and up. (Tested on Windows 95, Windows XP, Windows 7 and Windows 10).
  • hh1.golden.exe (268 bytes) doesn't work on Windows XP, it works on Windows 7, it doesn't work on Windows 10.
  • Assembly source code included in the pts-tinype repo.

Here is why it is unlikely that a portable Win32 PE .exe hello-world shorter than 584 bytes will be released:

  • Program code must be put to an executable section, and the earliest file byte offset for that is 512 because SectionAlignment must be at least 512, and the section can't start at the beginning of the file.
  • Minimum hello-world printer i386 Win32 machine code size is 36 bytes. (This excludes the message size, because the message can be stored in the header, within the first 512 bytes.)
  • Minimum size of IMAGE_IMPORT_DESCRIPTORS is 20 bytes, for KERNEL32.DLL. Windows 95 doesn't allow this inside the header, so it must be put after file byte offset 512.
  • Minimum size of IAT (import address table) is 16 bytes: 4 bytes for GetStdHandle, 4 bytes for WriteFile, 4 bytes for ExitProcess, 4 bytes for end-of-list. Since the loader modifies these addresses, they can't be put to the header (which is read-only), so they must be put after file byte offset 512.
  • If we add these up, we get 512 + 36 + 20 + 16 = 584 bytes.

The .exe files smaller than 268 bytes work only on Windows versions earlier than Windows XP, and they don't work on 64-bit Windows systems.

Related literature:

  • https://www.codejuggle.dj/creating-the-smallest-possible-windows-executable-using-assembly-language/
  • http://pferrie.host22.com/misc/tiny/pehdr.htm
  • https://drakopensulo.wordpress.com/2017/08/06/smallest-pe-executable-x64-with-every-byte-executed/
  • http://www.phreedom.org/research/tinype/
  • http://www.pouet.net/topic.php?which=9565
  • http://www.crinkler.net/
  • https://code.google.com/archive/p/corkami/wikis/PE.wiki
like image 52
pts Avatar answered Oct 02 '22 23:10

pts