Flattened package hierarchy

This commit is contained in:
2024-08-25 20:38:22 +02:00
parent 2d8fe15abb
commit b35b17bb32
89 changed files with 42 additions and 42 deletions

72
src/pe/Constants.go Normal file
View File

@ -0,0 +1,72 @@
package pe
// CPU
const (
IMAGE_FILE_MACHINE_AMD64 = 0x8664
IMAGE_FILE_MACHINE_ARM64 = 0xAA64
IMAGE_FILE_MACHINE_RISCV64 = 0x5064
)
// Subsystems
const (
IMAGE_SUBSYSTEM_UNKNOWN = 0
IMAGE_SUBSYSTEM_NATIVE = 1
IMAGE_SUBSYSTEM_WINDOWS_GUI = 2
IMAGE_SUBSYSTEM_WINDOWS_CUI = 3
IMAGE_SUBSYSTEM_OS2_CUI = 5
IMAGE_SUBSYSTEM_POSIX_CUI = 7
IMAGE_SUBSYSTEM_NATIVE_WINDOWS = 8
IMAGE_SUBSYSTEM_WINDOWS_CE_GUI = 9
IMAGE_SUBSYSTEM_EFI_APPLICATION = 10
IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER = 11
IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER = 12
IMAGE_SUBSYSTEM_EFI_ROM = 13
IMAGE_SUBSYSTEM_XBOX = 14
IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION = 16
)
// Characteristics
const (
IMAGE_FILE_RELOCS_STRIPPED = 0x0001
IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002
IMAGE_FILE_LINE_NUMS_STRIPPED = 0x0004
IMAGE_FILE_LOCAL_SYMS_STRIPPED = 0x0008
IMAGE_FILE_AGGRESIVE_WS_TRIM = 0x0010
IMAGE_FILE_LARGE_ADDRESS_AWARE = 0x0020
IMAGE_FILE_BYTES_REVERSED_LO = 0x0080
IMAGE_FILE_32BIT_MACHINE = 0x0100
IMAGE_FILE_DEBUG_STRIPPED = 0x0200
IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP = 0x0400
IMAGE_FILE_NET_RUN_FROM_SWAP = 0x0800
IMAGE_FILE_SYSTEM = 0x1000
IMAGE_FILE_DLL = 0x2000
IMAGE_FILE_UP_SYSTEM_ONLY = 0x4000
IMAGE_FILE_BYTES_REVERSED_HI = 0x8000
)
// DLL characteristics
const (
IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA = 0x0020
IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040
IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY = 0x0080
IMAGE_DLLCHARACTERISTICS_NX_COMPAT = 0x0100
IMAGE_DLLCHARACTERISTICS_NO_ISOLATION = 0x0200
IMAGE_DLLCHARACTERISTICS_NO_SEH = 0x0400
IMAGE_DLLCHARACTERISTICS_NO_BIND = 0x0800
IMAGE_DLLCHARACTERISTICS_APPCONTAINER = 0x1000
IMAGE_DLLCHARACTERISTICS_WDM_DRIVER = 0x2000
IMAGE_DLLCHARACTERISTICS_GUARD_CF = 0x4000
IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE = 0x8000
)
// Section characteristics
const (
IMAGE_SCN_CNT_CODE = 0x00000020
IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040
IMAGE_SCN_CNT_UNINITIALIZED_DATA = 0x00000080
IMAGE_SCN_LNK_COMDAT = 0x00001000
IMAGE_SCN_MEM_DISCARDABLE = 0x02000000
IMAGE_SCN_MEM_EXECUTE = 0x20000000
IMAGE_SCN_MEM_READ = 0x40000000
IMAGE_SCN_MEM_WRITE = 0x80000000
)

11
src/pe/DLLImport.go Normal file
View File

@ -0,0 +1,11 @@
package pe
const DLLImportSize = 20
type DLLImport struct {
RvaFunctionNameList uint32
TimeDateStamp uint32
ForwarderChain uint32
RvaModuleName uint32
RvaFunctionAddressList uint32
}

11
src/pe/DOSHeader.go Normal file
View File

@ -0,0 +1,11 @@
package pe
const DOSHeaderSize = 64
// DOSHeader is at the beginning of each EXE file and nowadays just points
// to the PEHeader using an absolute file offset.
type DOSHeader struct {
Magic [4]byte
_ [56]byte
NTHeaderOffset uint32
}

6
src/pe/DataDirectory.go Normal file
View File

@ -0,0 +1,6 @@
package pe
type DataDirectory struct {
VirtualAddress uint32
Size uint32
}

201
src/pe/EXE.go Normal file
View File

@ -0,0 +1,201 @@
package pe
import (
"bytes"
"encoding/binary"
"io"
"git.akyoto.dev/cli/q/src/config"
"git.akyoto.dev/cli/q/src/dll"
"git.akyoto.dev/cli/q/src/exe"
)
// EXE is the portable executable format used on Windows.
type EXE struct {
DOSHeader
NTHeader
OptionalHeader64
Sections []SectionHeader
}
// Write writes the EXE file to the given writer.
func Write(writer io.Writer, code []byte, data []byte, dlls dll.List) {
if len(data) == 0 {
data = []byte{0}
}
NumSections := 3
HeaderEnd := DOSHeaderSize + NTHeaderSize + OptionalHeader64Size + SectionHeaderSize*NumSections
codeStart, codePadding := exe.Align(HeaderEnd, config.Align)
dataStart, dataPadding := exe.Align(codeStart+len(code), config.Align)
importsStart, importsPadding := exe.Align(dataStart+len(data), config.Align)
subSystem := IMAGE_SUBSYSTEM_WINDOWS_CUI
if dlls.Contains("user32") {
subSystem = IMAGE_SUBSYSTEM_WINDOWS_GUI
}
imports := make([]uint64, 0)
dllData := make([]byte, 0)
dllImports := []DLLImport{}
for _, library := range dlls {
functionsStart := len(imports) * 8
dllNamePos := len(dllData)
dllData = append(dllData, library.Name...)
dllData = append(dllData, ".dll"...)
dllData = append(dllData, 0x00)
dllImports = append(dllImports, DLLImport{
RvaFunctionNameList: uint32(importsStart + functionsStart),
TimeDateStamp: 0,
ForwarderChain: 0,
RvaModuleName: uint32(dllNamePos),
RvaFunctionAddressList: uint32(importsStart + functionsStart),
})
for _, fn := range library.Functions {
if len(dllData)&1 != 0 {
dllData = append(dllData, 0x00) // align the next entry on an even boundary
}
offset := len(dllData)
dllData = append(dllData, 0x00, 0x00)
dllData = append(dllData, fn...)
dllData = append(dllData, 0x00)
imports = append(imports, uint64(offset))
}
imports = append(imports, 0)
}
dllDataStart := importsStart + len(imports)*8
for i := range imports {
if imports[i] == 0 {
continue
}
imports[i] += uint64(dllDataStart)
}
for i := range dllImports {
dllImports[i].RvaModuleName += uint32(dllDataStart)
}
dllImports = append(dllImports, DLLImport{}) // a zeroed structure marks the end of the list
importDirectoryStart := dllDataStart + len(dllData)
importDirectorySize := DLLImportSize * len(dllImports)
importSectionSize := len(imports)*8 + len(dllData) + importDirectorySize
imageSize := importsStart + importSectionSize
imageSize, _ = exe.Align(imageSize, config.Align)
pe := &EXE{
DOSHeader: DOSHeader{
Magic: [4]byte{'M', 'Z', 0, 0},
NTHeaderOffset: DOSHeaderSize,
},
NTHeader: NTHeader{
Signature: [4]byte{'P', 'E', 0, 0},
Machine: IMAGE_FILE_MACHINE_AMD64,
NumberOfSections: uint16(NumSections),
TimeDateStamp: 0,
PointerToSymbolTable: 0,
NumberOfSymbols: 0,
SizeOfOptionalHeader: OptionalHeader64Size,
Characteristics: IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE,
},
OptionalHeader64: OptionalHeader64{
Magic: 0x020B, // PE32+ / 64-bit executable
MajorLinkerVersion: 0x0E,
MinorLinkerVersion: 0x16,
SizeOfCode: uint32(len(code)),
SizeOfInitializedData: 0,
SizeOfUninitializedData: 0,
AddressOfEntryPoint: config.CodeOffset,
BaseOfCode: config.CodeOffset,
ImageBase: config.BaseAddress,
SectionAlignment: config.Align, // power of 2, must be greater than or equal to FileAlignment
FileAlignment: config.Align, // power of 2
MajorOperatingSystemVersion: 0x06,
MinorOperatingSystemVersion: 0,
MajorImageVersion: 0,
MinorImageVersion: 0,
MajorSubsystemVersion: 0x06,
MinorSubsystemVersion: 0,
Win32VersionValue: 0,
SizeOfImage: uint32(imageSize),
SizeOfHeaders: config.CodeOffset, // section bodies begin here
CheckSum: 0,
Subsystem: uint16(subSystem),
DllCharacteristics: IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA | IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, // IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
SizeOfStackReserve: 0x100000,
SizeOfStackCommit: 0x1000,
SizeOfHeapReserve: 0x100000,
SizeOfHeapCommit: 0x1000,
LoaderFlags: 0,
NumberOfRvaAndSizes: 16,
DataDirectory: [16]DataDirectory{
{VirtualAddress: 0, Size: 0},
{VirtualAddress: uint32(importDirectoryStart), Size: uint32(importDirectorySize)}, // 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},
{VirtualAddress: 0, Size: 0},
{VirtualAddress: 0, Size: 0},
{VirtualAddress: 0, Size: 0},
{VirtualAddress: 0, Size: 0},
{VirtualAddress: uint32(importsStart), Size: uint32(len(imports) * 8)}, // RVA of the import address table
{VirtualAddress: 0, Size: 0},
{VirtualAddress: 0, Size: 0},
{VirtualAddress: 0, Size: 0},
},
},
Sections: []SectionHeader{
{
Name: [8]byte{'.', 't', 'e', 'x', 't'},
VirtualSize: uint32(len(code)),
VirtualAddress: config.CodeOffset,
RawSize: uint32(len(code)), // must be a multiple of FileAlignment
RawAddress: config.CodeOffset, // must be a multiple of FileAlignment
Characteristics: IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ,
},
{
Name: [8]byte{'.', 'r', 'd', 'a', 't', 'a'},
VirtualSize: uint32(len(data)),
VirtualAddress: uint32(dataStart),
RawSize: uint32(len(data)),
RawAddress: uint32(dataStart),
Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ,
},
{
Name: [8]byte{'.', 'i', 'd', 'a', 't', 'a'},
VirtualSize: uint32(importSectionSize),
VirtualAddress: uint32(importsStart),
RawSize: uint32(importSectionSize),
RawAddress: uint32(importsStart),
Characteristics: IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ,
},
},
}
binary.Write(writer, binary.LittleEndian, &pe.DOSHeader)
binary.Write(writer, binary.LittleEndian, &pe.NTHeader)
binary.Write(writer, binary.LittleEndian, &pe.OptionalHeader64)
binary.Write(writer, binary.LittleEndian, &pe.Sections)
writer.Write(bytes.Repeat([]byte{0x00}, int(codePadding)))
writer.Write(code)
writer.Write(bytes.Repeat([]byte{0x00}, int(dataPadding)))
writer.Write(data)
writer.Write(bytes.Repeat([]byte{0x00}, int(importsPadding)))
binary.Write(writer, binary.LittleEndian, &imports)
binary.Write(writer, binary.LittleEndian, &dllData)
binary.Write(writer, binary.LittleEndian, &dllImports)
}

12
src/pe/EXE_test.go Normal file
View File

@ -0,0 +1,12 @@
package pe_test
import (
"io"
"testing"
"git.akyoto.dev/cli/q/src/pe"
)
func TestWrite(t *testing.T) {
pe.Write(io.Discard, nil, nil, nil)
}

14
src/pe/NTHeader.go Normal file
View File

@ -0,0 +1,14 @@
package pe
const NTHeaderSize = 24
type NTHeader struct {
Signature [4]byte
Machine uint16
NumberOfSections uint16
TimeDateStamp uint32
PointerToSymbolTable uint32
NumberOfSymbols uint32
SizeOfOptionalHeader uint16
Characteristics uint16
}

View File

@ -0,0 +1,36 @@
package pe
const OptionalHeader64Size = 240
type OptionalHeader64 struct {
Magic uint16
MajorLinkerVersion uint8
MinorLinkerVersion uint8
SizeOfCode uint32
SizeOfInitializedData uint32
SizeOfUninitializedData uint32
AddressOfEntryPoint uint32
BaseOfCode uint32
ImageBase uint64
SectionAlignment uint32
FileAlignment uint32
MajorOperatingSystemVersion uint16
MinorOperatingSystemVersion uint16
MajorImageVersion uint16
MinorImageVersion uint16
MajorSubsystemVersion uint16
MinorSubsystemVersion uint16
Win32VersionValue uint32
SizeOfImage uint32
SizeOfHeaders uint32
CheckSum uint32
Subsystem uint16
DllCharacteristics uint16
SizeOfStackReserve uint64
SizeOfStackCommit uint64
SizeOfHeapReserve uint64
SizeOfHeapCommit uint64
LoaderFlags uint32
NumberOfRvaAndSizes uint32
DataDirectory [16]DataDirectory
}

16
src/pe/SectionHeader.go Normal file
View File

@ -0,0 +1,16 @@
package pe
const SectionHeaderSize = 40
type SectionHeader struct {
Name [8]byte
VirtualSize uint32
VirtualAddress uint32
RawSize uint32
RawAddress uint32
PointerToRelocations uint32
PointerToLineNumbers uint32
NumberOfRelocations uint16
NumberOfLineNumbers uint16
Characteristics uint32
}

27
src/pe/pe.md Normal file
View File

@ -0,0 +1,27 @@
## Portable Executable
## Notes
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.
The stack must be 16 byte aligned before a DLL function is called.
## Links
- https://learn.microsoft.com/en-us/windows/win32/debug/pe-format
- https://learn.microsoft.com/en-us/previous-versions/ms809762(v=msdn.10)
- https://learn.microsoft.com/en-us/archive/msdn-magazine/2002/february/inside-windows-win32-portable-executable-file-format-in-detail
- https://learn.microsoft.com/en-us/archive/msdn-magazine/2002/march/inside-windows-an-in-depth-look-into-the-win32-portable-executable-file-format-part-2
- https://blog.kowalczyk.info/articles/pefileformat.html
- https://keyj.emphy.de/win32-pe/
- https://corkamiwiki.github.io/PE
- https://github.com/ayaka14732/TinyPE-on-Win10