From 141ec1158d85e49148e2d8eaeac6a65187ceba83 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 16 Aug 2024 11:38:48 +0200 Subject: [PATCH] Reduced size of Windows executables --- src/exe/pe/EXE.go | 56 ++++++++++++++++++++++------------------------- src/exe/pe/pe.md | 7 ++++++ 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/src/exe/pe/EXE.go b/src/exe/pe/EXE.go index 5e520c3..69034d1 100644 --- a/src/exe/pe/EXE.go +++ b/src/exe/pe/EXE.go @@ -24,7 +24,7 @@ type DLL struct { // Write writes the EXE file to the given writer. func Write(writer io.Writer, code []byte, data []byte) { - NumSections := 3 + NumSections := 2 HeaderEnd := DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections codeStart, codePadding := exe.Align(HeaderEnd, config.Align) dataStart, dataPadding := exe.Align(codeStart+len(code), config.Align) @@ -61,29 +61,36 @@ func Write(writer io.Writer, code []byte, data []byte) { } dllAddresses = append(dllAddresses, 0) - importStart, importPadding := exe.Align(dataStart+len(data), config.Align) - itblSize := DLLImportSize*len(dlls) + DLLImportSize - iatblSize := 8 * len(dllAddresses) - itblStart := importStart - iatblStart := itblStart + itblSize + + // Add the address table to the data section + functionAddressesStart := dataStart + len(data) + functionAddressesSize := 8 * len(dllAddresses) + data, err := binary.Append(data, binary.LittleEndian, &dllAddresses) + + if err != nil { + panic(err) + } dllImports = append(dllImports, DLLImport{ - RvaFunctionNameList: uint32(iatblStart), + RvaFunctionNameList: uint32(functionAddressesStart), TimeDateStamp: 0, ForwarderChain: 0, RvaModuleName: uint32(dataStart + dllName), - RvaFunctionAddressList: uint32(iatblStart), + RvaFunctionAddressList: uint32(functionAddressesStart), }) - dllImports = append(dllImports, DLLImport{ - RvaFunctionNameList: 0, - TimeDateStamp: 0, - ForwarderChain: 0, - RvaModuleName: 0, // must be zero - RvaFunctionAddressList: 0, - }) + dllImports = append(dllImports, DLLImport{}) // a zeroed structure marks the end of the list - imageSize := iatblStart + iatblSize + // Add imports to the data section + importsStart := dataStart + len(data) + importsSize := DLLImportSize * len(dllImports) + data, err = binary.Append(data, binary.LittleEndian, &dllImports) + + if err != nil { + panic(err) + } + + imageSize := functionAddressesStart + functionAddressesSize imageSize, _ = exe.Align(imageSize, config.Align) pe := &EXE{ @@ -102,7 +109,7 @@ func Write(writer io.Writer, code []byte, data []byte) { Characteristics: IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE, }, OptionalHeader64: OptionalHeader64{ - Magic: 0x020B, // PE32+ executable + Magic: 0x020B, // PE32+ / 64-bit executable MajorLinkerVersion: 0x0E, MinorLinkerVersion: 0x16, SizeOfCode: uint32(len(code)), @@ -133,7 +140,7 @@ func Write(writer io.Writer, code []byte, data []byte) { NumberOfRvaAndSizes: 16, DataDirectory: [16]DataDirectory{ {VirtualAddress: 0, Size: 0}, - {VirtualAddress: uint32(itblStart), Size: uint32(itblSize)}, // RVA of the imported function table + {VirtualAddress: uint32(importsStart), Size: uint32(importsSize)}, // RVA of the imported function table {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, @@ -144,7 +151,7 @@ func Write(writer io.Writer, code []byte, data []byte) { {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, - {VirtualAddress: uint32(iatblStart), Size: uint32(iatblSize)}, // RVA of the import address table + {VirtualAddress: uint32(functionAddressesStart), Size: uint32(functionAddressesSize)}, // RVA of the import address table {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, {VirtualAddress: 0, Size: 0}, @@ -167,14 +174,6 @@ func Write(writer io.Writer, code []byte, data []byte) { RawAddress: uint32(dataStart), Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ, }, - { - Name: [8]byte{'.', 'i', 'd', 'a', 't', 'a'}, - VirtualSize: uint32(iatblSize + itblSize), - VirtualAddress: uint32(importStart), - RawSize: uint32(iatblSize + itblSize), - RawAddress: uint32(importStart), - Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE, - }, }, } @@ -187,7 +186,4 @@ func Write(writer io.Writer, code []byte, data []byte) { writer.Write(code) writer.Write(bytes.Repeat([]byte{0x00}, int(dataPadding))) writer.Write(data) - writer.Write(bytes.Repeat([]byte{0x00}, int(importPadding))) - binary.Write(writer, binary.LittleEndian, &dllImports) - binary.Write(writer, binary.LittleEndian, &dllAddresses) } diff --git a/src/exe/pe/pe.md b/src/exe/pe/pe.md index 5fc034e..3d92799 100644 --- a/src/exe/pe/pe.md +++ b/src/exe/pe/pe.md @@ -5,6 +5,13 @@ Unlike Linux, Windows does not ignore zero-length sections at the end of a file and will fail loading them because they don't exist within the file. Adding a single byte to the section can fix this problem, but it's easier to just remove the section header entirely. +The solution used here is to guarantee that the data section is never empty by always +importing a few core functions from "kernel32.dll". + +## DLL function pointers + +The section where the DLL function pointers are stored does not need to be marked as writable. +The Windows executable loader resolves the pointers before they are loaded into memory. ## Links