Reduced size of Windows executables

This commit is contained in:
Eduard Urbach 2024-08-16 11:38:48 +02:00
parent 07bf488657
commit 141ec1158d
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
2 changed files with 33 additions and 30 deletions

View File

@ -24,7 +24,7 @@ type DLL struct {
// Write writes the EXE file to the given writer. // Write writes the EXE file to the given writer.
func Write(writer io.Writer, code []byte, data []byte) { func Write(writer io.Writer, code []byte, data []byte) {
NumSections := 3 NumSections := 2
HeaderEnd := DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections HeaderEnd := DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections
codeStart, codePadding := exe.Align(HeaderEnd, config.Align) codeStart, codePadding := exe.Align(HeaderEnd, config.Align)
dataStart, dataPadding := exe.Align(codeStart+len(code), 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) dllAddresses = append(dllAddresses, 0)
importStart, importPadding := exe.Align(dataStart+len(data), config.Align)
itblSize := DLLImportSize*len(dlls) + DLLImportSize // Add the address table to the data section
iatblSize := 8 * len(dllAddresses) functionAddressesStart := dataStart + len(data)
itblStart := importStart functionAddressesSize := 8 * len(dllAddresses)
iatblStart := itblStart + itblSize data, err := binary.Append(data, binary.LittleEndian, &dllAddresses)
if err != nil {
panic(err)
}
dllImports = append(dllImports, DLLImport{ dllImports = append(dllImports, DLLImport{
RvaFunctionNameList: uint32(iatblStart), RvaFunctionNameList: uint32(functionAddressesStart),
TimeDateStamp: 0, TimeDateStamp: 0,
ForwarderChain: 0, ForwarderChain: 0,
RvaModuleName: uint32(dataStart + dllName), RvaModuleName: uint32(dataStart + dllName),
RvaFunctionAddressList: uint32(iatblStart), RvaFunctionAddressList: uint32(functionAddressesStart),
}) })
dllImports = append(dllImports, DLLImport{ dllImports = append(dllImports, DLLImport{}) // a zeroed structure marks the end of the list
RvaFunctionNameList: 0,
TimeDateStamp: 0,
ForwarderChain: 0,
RvaModuleName: 0, // must be zero
RvaFunctionAddressList: 0,
})
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) imageSize, _ = exe.Align(imageSize, config.Align)
pe := &EXE{ 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, Characteristics: IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE,
}, },
OptionalHeader64: OptionalHeader64{ OptionalHeader64: OptionalHeader64{
Magic: 0x020B, // PE32+ executable Magic: 0x020B, // PE32+ / 64-bit executable
MajorLinkerVersion: 0x0E, MajorLinkerVersion: 0x0E,
MinorLinkerVersion: 0x16, MinorLinkerVersion: 0x16,
SizeOfCode: uint32(len(code)), SizeOfCode: uint32(len(code)),
@ -133,7 +140,7 @@ func Write(writer io.Writer, code []byte, data []byte) {
NumberOfRvaAndSizes: 16, NumberOfRvaAndSizes: 16,
DataDirectory: [16]DataDirectory{ DataDirectory: [16]DataDirectory{
{VirtualAddress: 0, Size: 0}, {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}, {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: 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}, {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), RawAddress: uint32(dataStart),
Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ, 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(code)
writer.Write(bytes.Repeat([]byte{0x00}, int(dataPadding))) writer.Write(bytes.Repeat([]byte{0x00}, int(dataPadding)))
writer.Write(data) writer.Write(data)
writer.Write(bytes.Repeat([]byte{0x00}, int(importPadding)))
binary.Write(writer, binary.LittleEndian, &dllImports)
binary.Write(writer, binary.LittleEndian, &dllAddresses)
} }

View File

@ -5,6 +5,13 @@
Unlike Linux, Windows does not ignore zero-length sections at the end of a file and will 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 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. 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 ## Links