Switch remote deploy to vendored source builds

Move remote deployment to a vendored source bundle built on the target host via Docker so redeploys no longer require local cross-compilation or host Go installation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
GitHub Actions
2026-05-08 12:19:18 +08:00
parent bb27566e38
commit c1a0fe2949
1320 changed files with 497125 additions and 11 deletions

View File

@@ -0,0 +1,25 @@
package loader
// Module represents a loaded Windows module.
type Module interface {
// Proc returns a procedure by symbol name. Returns nil if the symbol is
// not found.
Proc(name string) Proc
// Ordinal returns a procedure by ordinal.
Ordinal(ordinal uint64) Proc
// Free closes the module and frees the memory. After this, GetProcAddress
// will stop working and procedures will no longer function.
Free() error
}
// Loader represents a named module loader implementation.
type Loader interface {
Load(libname string) (Module, error)
}
// MemLoader represents a memory module loader implementation.
type MemLoader interface {
LoadMem(module []byte) (Module, error)
}

View File

@@ -0,0 +1,53 @@
package loader
import "io"
// Proc represents a procedure in memory.
type Proc interface {
// Call calls the procedure. r1 and r2 contain the return value. lastErr
// contains the Windows error value after calling.
Call(a ...uint64) (r1, r2 uint64, lastErr error)
// Returns the raw address of this function.
Addr() uint64
}
// Memory is an interface for a block of allocated virtual memory.
type Memory interface {
io.ReadWriteSeeker
io.ReaderAt
io.WriterAt
// Free frees the virtual memory region.
Free()
// Addr returns the address of the region of memory in the virtual address
// space.
Addr() uint64
// Clear zeros the entire region of memory.
Clear()
// Protect changes the memory protection for a subregion of this allocated
// block. It should match the semantics of the VirtualProtect function on
// Windows.
Protect(addr, size uint64, protect int) error
}
// Machine is an abstract machine interface.
type Machine interface {
// IsArchitectureSupported returns whether or not an architecture is
// supported by this abstract machine. Machine is a PE machine ID.
IsArchitectureSupported(machine int) bool
// GetPageSize returns the size of a memory page on this abstract machine.
GetPageSize() uint64
// Alloc performs virtual memory allocation. It should match the semantics
// of VirtualAlloc/VirtualFree on Windows.
Alloc(addr, size uint64, allocType, protect int) Memory
// MemProc returns an object for interfacing with a procedure at addr in
// the abstract machine's virtual memory space.
MemProc(addr uint64) Proc
}

View File

@@ -0,0 +1,42 @@
package memloader
import (
"strings"
"github.com/jchv/go-winloader/internal/loader"
)
// Cache implements a memory cache for PE modules.
type Cache struct {
next loader.Loader
cache map[string]loader.Module
}
// NewCache creates a new cache with the specified options.
func NewCache(next loader.Loader) *Cache {
return &Cache{
next: next,
cache: make(map[string]loader.Module),
}
}
// Load implements loader.Loader by loading from cache or falling back.
func (c *Cache) Load(libname string) (loader.Module, error) {
if m, ok := c.cache[strings.ToLower(libname)]; ok {
return m, nil
}
if m, ok := c.cache[strings.ToLower(libname)+".dll"]; ok {
return m, nil
}
return c.next.Load(libname)
}
// Add adds a module to the cache.
func (c *Cache) Add(libname string, m loader.Module) error {
libname = strings.ToLower(libname)
if strings.HasSuffix(libname, ".dll") {
libname = libname[0 : len(libname)-4]
}
c.cache[libname] = m
return nil
}

View File

@@ -0,0 +1,253 @@
package memloader
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"github.com/jchv/go-winloader/internal/loader"
"github.com/jchv/go-winloader/internal/pe"
"github.com/jchv/go-winloader/internal/vmem"
"github.com/jchv/go-winloader/internal/winloader"
)
// module implements a module for the memory loader.
type module struct {
machine loader.Machine
memory loader.Memory
pemod *pe.Module
exports *pe.ExportTable
hinstance uint64
}
// Proc implements loader.Module
func (m *module) Proc(name string) loader.Proc {
addr := m.exports.Proc(name)
if addr == 0 {
return nil
}
return m.machine.MemProc(addr)
}
// Ordinal implements loader.Module
func (m *module) Ordinal(ordinal uint64) loader.Proc {
addr := m.exports.Ordinal(uint16(ordinal))
if addr == 0 {
return nil
}
return m.machine.MemProc(addr)
}
// Free implements loader.Module
func (m *module) Free() error {
// Execute entrypoint for detach.
entry := m.machine.MemProc(m.memory.Addr() + uint64(m.pemod.Header.OptionalHeader.AddressOfEntryPoint))
entry.Call(uint64(m.memory.Addr()), 0, 0)
// Free memory.
m.memory.Free()
return nil
}
// Loader implements a memory loader for PE files.
type Loader struct {
next loader.Loader
machine loader.Machine
pebhacks bool
prochinst bool
}
// Options contains the options for creating a new memory loader.
type Options struct {
// Next specifies the loader to use for recursing to resolve modules by
// name.
Next loader.Loader
// Machine specifies the machine the module should be loaded into.
Machine loader.Machine
// HintAddModuleToPEB specifies that the memory loader should try to add
// the loaded module into the PEB so that certain things function as
// expected.
// NOTE: This is not implemented yet and may not be possible.
HintAddModuleToPEB bool
// HintUseProcessHInstance specifies that the memory loader should use the
// host process's HINSTANCE value for calling into entrypoints.
HintUseProcessHInstance bool
}
// New creates a new loader with the specified options.
func New(opts Options) loader.MemLoader {
return &Loader{
next: opts.Next,
machine: opts.Machine,
}
}
// LoadMem implements the loader.MemLoader interface.
func (l *Loader) LoadMem(data []byte) (loader.Module, error) {
bin, err := pe.LoadModule(bytes.NewReader(data))
if err != nil {
return nil, err
}
if !l.machine.IsArchitectureSupported(int(bin.Header.FileHeader.Machine)) {
return nil, fmt.Errorf("image architecture not %04x not supported by this machine", bin.Header.FileHeader.Machine)
}
pageSize := l.machine.GetPageSize()
imageSize := vmem.RoundUp(uint64(bin.Header.OptionalHeader.SizeOfImage), pageSize)
var mem loader.Memory
// If the image is not movable, allocate it at its preferred address.
if bin.Header.OptionalHeader.DllCharacteristics&pe.ImageDLLCharacteristicsDynamicBase == 0 {
mem = l.machine.Alloc(bin.Header.OptionalHeader.ImageBase, imageSize, vmem.MemCommit|vmem.MemReserve, vmem.PageExecuteReadWrite)
if mem == nil {
return nil, fmt.Errorf("image could not be mapped at preferred base 0x%08x and cannot be relocated", bin.Header.OptionalHeader.ImageBase)
}
}
// Allocate anywhere, so as long as the image won't span a 4 GiB
// alignment boundary.
failedAllocs := []loader.Memory{}
for mem == nil || mem.Addr()>>32 != (mem.Addr()+imageSize)>>32 {
if mem != nil {
failedAllocs = append(failedAllocs, mem)
}
if mem = l.machine.Alloc(0, imageSize, vmem.MemCommit|vmem.MemReserve, vmem.PageExecuteReadWrite); mem == nil {
return nil, fmt.Errorf("allocation of %d bytes failed", imageSize)
}
}
for _, i := range failedAllocs {
i.Free()
}
realBase := mem.Addr()
hdrsize := uint64(bin.Header.OptionalHeader.SizeOfHeaders)
vmem.Alloc(realBase, hdrsize, vmem.MemCommit, vmem.PageReadWrite).Write(data[0:hdrsize])
// Map sections into memory
for _, section := range bin.Sections {
addr := realBase + uint64(section.VirtualAddress)
if section.SizeOfRawData == 0 {
size := uint64(bin.Header.OptionalHeader.SectionAlignment)
if size != 0 {
vmem.Alloc(addr, size, vmem.MemCommit, vmem.PageReadWrite).Clear()
}
} else {
sectionData := data[section.PointerToRawData : section.PointerToRawData+section.SizeOfRawData]
vmem.Alloc(addr, uint64(section.SizeOfRawData), vmem.MemCommit, vmem.PageReadWrite).Write(sectionData)
}
// TODO: need to set Misc.PhysicalAddress?
}
// TODO: Detect native byte order for relocations.
order := binary.LittleEndian
machine := int(bin.Header.FileHeader.Machine)
// Perform relocations
relocs := pe.LoadBaseRelocs(bin, mem)
if err := pe.Relocate(machine, relocs, uint64(realBase), bin.Header.OptionalHeader.ImageBase, mem, order); err != nil {
return nil, err
}
// Perform runtime linking
if err := pe.LinkModule(bin, mem, l.next); err != nil {
return nil, err
}
// Set access flags.
for _, section := range bin.Sections {
executable := section.Characteristics&pe.ImageSectionCharacteristicsMemoryExecute != 0
readable := section.Characteristics&pe.ImageSectionCharacteristicsMemoryRead != 0
writable := section.Characteristics&pe.ImageSectionCharacteristicsMemoryWrite != 0
protect := vmem.PageNoAccess
switch {
case !executable && !readable && !writable:
protect = vmem.PageNoAccess
case !executable && !readable && writable:
protect = vmem.PageWriteCopy
case !executable && readable && !writable:
protect = vmem.PageReadOnly
case !executable && readable && writable:
protect = vmem.PageReadWrite
case executable && !readable && !writable:
protect = vmem.PageExecute
case executable && !readable && writable:
protect = vmem.PageExecuteWriteCopy
case executable && readable && !writable:
protect = vmem.PageExecuteRead
case executable && readable && writable:
protect = vmem.PageExecuteReadWrite
}
size := uint64(section.SizeOfRawData)
if size == 0 {
size = uint64(bin.Header.OptionalHeader.SectionAlignment)
}
err := mem.Protect(uint64(section.VirtualAddress), size, protect)
if err != nil {
return nil, err
}
}
// Handle HINSTANCE setup.
hinstance := realBase
if l.pebhacks {
// TODO: implement PEB loader hacks, see if it works.
}
if l.prochinst {
if prochinst, err := winloader.GetProcessHInstance(); err == nil {
hinstance = uint64(prochinst)
}
}
m := &module{
machine: l.machine,
memory: mem,
pemod: bin,
hinstance: hinstance,
}
// Execute TLS callbacks.
tlsdir := bin.Header.OptionalHeader.DataDirectory[pe.ImageDirectoryEntryTLS]
if tlsdir.Size > 0 {
mem.Seek(int64(tlsdir.VirtualAddress), io.SeekStart)
dir := pe.ImageTLSDirectory64{}
b := [8]byte{}
psize := 4
if bin.IsPE64 {
psize = 8
binary.Read(mem, binary.LittleEndian, &dir)
} else {
dir32 := pe.ImageTLSDirectory32{}
binary.Read(mem, binary.LittleEndian, &dir32)
dir = dir32.To64()
}
mem.Seek(int64(dir.AddressOfCallBacks), io.SeekStart)
if dir.AddressOfCallBacks != 0 {
for {
mem.Read(b[:psize])
addr := binary.LittleEndian.Uint64(b[:])
if addr == 0 {
break
}
cb := l.machine.MemProc(realBase + addr)
cb.Call(hinstance, 1, 0)
}
}
}
// Execute entrypoint for attach.
entry := l.machine.MemProc(realBase + uint64(bin.Header.OptionalHeader.AddressOfEntryPoint))
entry.Call(hinstance, 1, 0)
m.exports, err = pe.LoadExports(bin, mem, realBase)
if err != nil {
return nil, err
}
return m, nil
}

View File

@@ -0,0 +1,76 @@
package pe
import (
"encoding/binary"
"io"
)
// ExportTable is a table of module exports.
type ExportTable struct {
symbols map[string]uint64
ordinals map[uint16]uint64
}
// Proc returns an exported function address by symbol, or 0 if it is not found.
func (t *ExportTable) Proc(symbol string) (addr uint64) {
return t.symbols[symbol]
}
// Ordinal returns an exported function address by ordinal, or 0 if it is not found.
func (t *ExportTable) Ordinal(ordinal uint16) (addr uint64) {
return t.ordinals[ordinal]
}
// LoadExports returns a symbol table.
func LoadExports(m *Module, mem io.ReadWriteSeeker, base uint64) (*ExportTable, error) {
table := &ExportTable{
symbols: map[string]uint64{},
ordinals: map[uint16]uint64{},
}
dir := m.Header.OptionalHeader.DataDirectory[ImageDirectoryEntryExport]
if dir.Size == 0 {
return table, nil
}
// Load export directory header
header := ImageExportDirectory{}
mem.Seek(int64(dir.VirtualAddress), io.SeekStart)
binary.Read(mem, binary.LittleEndian, &header)
// Load addresses
addresses := make([]uint32, header.NumberOfFunctions)
mem.Seek(int64(header.AddressOfFunctions), io.SeekStart)
for i := range addresses {
b := [4]byte{}
mem.Read(b[:])
addresses[i] = binary.LittleEndian.Uint32(b[:])
table.ordinals[uint16(i)] = base + uint64(addresses[i])
}
// Load name ordinals
nameords := make([]uint16, header.NumberOfNames)
mem.Seek(int64(header.AddressOfNameOrdinals), io.SeekStart)
for i := range nameords {
b := [2]byte{}
mem.Read(b[:])
nameords[i] = binary.LittleEndian.Uint16(b[:])
}
// Load name addresses
nameaddrs := make([]uint32, header.NumberOfNames)
mem.Seek(int64(header.AddressOfNames), io.SeekStart)
for i := range nameaddrs {
b := [4]byte{}
mem.Read(b[:])
nameaddrs[i] = binary.LittleEndian.Uint32(b[:])
}
// Load names
for i, nameaddr := range nameaddrs {
mem.Seek(int64(nameaddr), io.SeekStart)
table.symbols[readsz(mem)] = base + uint64(addresses[nameords[i]])
}
return table, nil
}

View File

@@ -0,0 +1,522 @@
package pe
// CONSTANTS / MAGIC NUMBERS
// MZSignature is the signature of the MZ format. This is the value of the
// Signature field in ImageDOSHeader.
var MZSignature = [2]byte{'M', 'Z'}
// PESignature is the signature of the PE format. This is the value of the
// Signature field in ImageNTHeaders32 and ImageNTHeaders64.
var PESignature = [4]byte{'P', 'E', 0, 0}
// Enumeration of magic numbers
const (
// ImageNTOptionalHeader32Magic is the magic number for 32-bit optional
// header (ImageOptionalHeader32)
ImageNTOptionalHeader32Magic = 0x010b
// ImageNTOptionalHeader64Magic is the magic number for 64-bit optional
// header (ImageOptionalHeader64)
ImageNTOptionalHeader64Magic = 0x020b
)
// Enumeration of structure lengths.
const (
// SizeOfImageDOSHeader is the on-disk size of the ImageDOSHeader
// structure.
SizeOfImageDOSHeader = 64
// SizeOfImageFileHeader is the on-disk size of the ImageFileHeader
// structure.
SizeOfImageFileHeader = 20
// SizeOfImageOptionalHeader32 is the on-disk size of the
// ImageOptionalHeader32 structure.
SizeOfImageOptionalHeader32 = 224
// SizeOfImageOptionalHeader64 is the on-disk size of the
// ImageOptionalHeader64 structure.
SizeOfImageOptionalHeader64 = 240
// SizeOfImageNTHeaders32 is the on-disk size of the ImageNTHeaders32
// structure.
SizeOfImageNTHeaders32 = 248
// SizeOfImageNTHeaders64 is the on-disk size of the ImageNTHeaders64
// structure.
SizeOfImageNTHeaders64 = 264
// SizeOfImageDataDirectory is the on-disk size of the ImageDataDirectory
// structure.
SizeOfImageDataDirectory = 8
)
// Enumeration of useful field offsets.
const (
// OffsetOfOptionalHeaderFromNTHeader is the offset from the start of
// the NT header to the optional header magic value. This is helpful for
// determining if the PE file is PE32 or PE64.
OffsetOfOptionalHeaderFromNTHeader = 0x18
)
// Enumeration of fixed-size array lengths in PE
const (
// NumDirectoryEntries specifies the number of data directory entries.
NumDirectoryEntries = 16
// SectionNameLength is the size of a section short name.
SectionNameLength = 8
)
// Enumeration of known Windows Loader limits. (Some of these may not be
// imposed by the format itself and purely by Windows runtime.)
const (
// MaxNumSections specifies the maximum number of sections that are
// allowed. This is imposed by Windows Loader.
MaxNumSections = 96
)
// ENUMERATION VALUES
// Enumeration of machine values for the file header.
const (
ImageFileMachineUnknown = 0x0000
ImageFileMachineTargetHost = 0x0001
ImageFileMachinei386 = 0x014c
ImageFileMachineR3000BE = 0x0160
ImageFileMachineR3000 = 0x0162
ImageFileMachineR4000 = 0x0166
ImageFileMachineR10000 = 0x0168
ImageFileMachineWCEMIPSv2 = 0x0169
ImageFileMachineAlpha = 0x0184
ImageFileMachineSH3 = 0x01a2
ImageFileMachineSH3DSP = 0x01a3
ImageFileMachineSH3E = 0x01a4
ImageFileMachineSH4 = 0x01a6
ImageFileMachineSH5 = 0x01a8
ImageFileMachineARM = 0x01c0
ImageFileMachineTHUMB = 0x01c2
ImageFileMachineARMNT = 0x01c4
ImageFileMachineAM33 = 0x01d3
ImageFileMachinePowerPC = 0x01F0
ImageFileMachinePowerPCFP = 0x01f1
ImageFileMachineIA64 = 0x0200
ImageFileMachineMIPS16 = 0x0266
ImageFileMachineAlpha64 = 0x0284
ImageFileMachineMIPSFPU = 0x0366
ImageFileMachineMIPSFPU16 = 0x0466
ImageFileMachineAXP64 = ImageFileMachineAlpha64
ImageFileMachineTricore = 0x0520
ImageFileMachineCEF = 0x00CE
ImageFileMachineEBC = 0x0EBC
ImageFileMachineAMD64 = 0x8664
ImageFileMachineM32R = 0x9041
ImageFileMachineARM64 = 0xAA64
ImageFileMachineCEE = 0x0C0E
ImageFileMachineRISCV32 = 0x5032
ImageFileMachineRISCV64 = 0x5064
ImageFileMachineRISCV128 = 0x5128
)
// Enumeration of charateristics values for the file header.
const (
ImageFileRelocsStripped = 0x0001
ImageFileExecutableImage = 0x0002
ImageFileLineNumsStripped = 0x0004
ImageFileLocalSymsStripped = 0x0008
ImageFileAggressiveWSTrim = 0x0010
ImageFileLargeAddressAware = 0x0020
ImageFileBytesReversedLo = 0x0080
ImageFile32BitMachine = 0x0100
ImageFileDebugStripped = 0x0200
ImageFileRemovableRunFromSwap = 0x0400
ImageFileNetRunFromSwap = 0x0800
ImageFileSystem = 0x1000
ImageFileDLL = 0x2000
ImageFileUPSystemOnly = 0x4000
ImageFileBytesReversedHi = 0x8000
)
// Enumeration of image subsystem values.
const (
ImageSubsystemUnknown = 0
ImageSubsystemNative = 1
ImageSubsystemWindowsGUI = 2
ImageSubsystemWindowsCUI = 3
ImageSubsystemOS2CUI = 5
ImageSubsystemPOSIXCUI = 7
ImageSubsystemNativeWindows = 8
ImageSubsystemWindowsCEGUI = 9
ImageSubsystemEFIApplication = 10
ImageSubsystemEFIBootServiceDriver = 11
ImageSubsystemEFIRuntimeDriver = 12
ImageSubsystemEFIROM = 13
ImageSubsystemXBox = 14
ImageSubsystemWindowsBootApplication = 16
ImageSubsystemXBoxCodeCatalog = 17
)
// Enumeration of DLL characteristics values.
const (
ImageDLLCharacteristicsHighEntropyVA = 0x0020
ImageDLLCharacteristicsDynamicBase = 0x0040
ImageDLLCharacteristicsForceIntegrity = 0x0080
ImageDLLCharacteristicsNXCompat = 0x0100
ImageDLLCharacteristicsNoIsolation = 0x0200
ImageDLLCharacteristicsNoSEH = 0x0400
ImageDLLCharacteristicsNoBind = 0x0800
ImageDLLCharacteristicsAppContainer = 0x1000
ImageDLLCharacteristicsWDMDriver = 0x2000
ImageDLLCharacteristicsGuardCF = 0x4000
ImageDLLCharacteristicsTerminalServerAware = 0x8000
)
// Enumeration of image directory entry indexes. These represent indices into
// the data directory array of the optional header.
const (
ImageDirectoryEntryExport = 0
ImageDirectoryEntryImport = 1
ImageDirectoryEntryResource = 2
ImageDirectoryEntryException = 3
ImageDirectoryEntrySecurity = 4
ImageDirectoryEntryBaseReloc = 5
ImageDirectoryEntryDebug = 6
ImageDirectoryEntryCopyright = 7
ImageDirectoryEntryArchitecture = 7
ImageDirectoryEntryGlobalPtr = 8
ImageDirectoryEntryTLS = 9
ImageDirectoryEntryLoadConfig = 10
ImageDirectoryEntryBoundImport = 11
ImageDirectoryEntryIAT = 12
ImageDirectoryEntryDelayImport = 13
ImageDirectoryEntryCOMDescriptor = 14
)
// Enumeration of image section characteristics.
const (
ImageSectionCharacteristicsNoPad = 0x00000008
ImageSectionCharacteristicsContainsCode = 0x00000020
ImageSectionCharacteristicsContainsInitializedData = 0x00000040
ImageSectionCharacteristicsContainsUninitailizedData = 0x00000080
ImageSectionCharacteristicsLinkOther = 0x00000100
ImageSectionCharacteristicsLinkInfo = 0x00000200
ImageSectionCharacteristicsLinkRemove = 0x00000800
ImageSectionCharacteristicsLinkCOMDAT = 0x00001000
ImageSectionCharacteristicsNoDeferSpecExc = 0x00004000
ImageSectionCharacteristicsGPRel = 0x00008000
ImageSectionCharacteristicsMemoryFarData = 0x00008000
ImageSectionCharacteristicsMemoryPurgeable = 0x00020000
ImageSectionCharacteristicsMemory16Bit = 0x00020000
ImageSectionCharacteristicsMemoryLocked = 0x00040000
ImageSectionCharacteristicsMemoryPreload = 0x00080000
ImageSectionCharacteristicsAlign1Bytes = 0x00100000
ImageSectionCharacteristicsAlign2Bytes = 0x00200000
ImageSectionCharacteristicsAlign4Bytes = 0x00300000
ImageSectionCharacteristicsAlign8Bytes = 0x00400000
ImageSectionCharacteristicsAlign16Bytes = 0x00500000
ImageSectionCharacteristicsAlign32Bytes = 0x00600000
ImageSectionCharacteristicsAlign64Bytes = 0x00700000
ImageSectionCharacteristicsAlign128Bytes = 0x00800000
ImageSectionCharacteristicsAlign256Bytes = 0x00900000
ImageSectionCharacteristicsAlign512Bytes = 0x00A00000
ImageSectionCharacteristicsAlign1024Bytes = 0x00B00000
ImageSectionCharacteristicsAlign2048Bytes = 0x00C00000
ImageSectionCharacteristicsAlign4096Bytes = 0x00D00000
ImageSectionCharacteristicsAlign8192Bytes = 0x00E00000
ImageSectionCharacteristicsAlignMask = 0x00F00000
ImageSectionCharacteristicsLinkNumRelocOverflow = 0x01000000
ImageSectionCharacteristicsMemoryDiscardable = 0x02000000
ImageSectionCharacteristicsMemoryNotCached = 0x04000000
ImageSectionCharacteristicsMemoryNotPaged = 0x08000000
ImageSectionCharacteristicsMemoryShared = 0x10000000
ImageSectionCharacteristicsMemoryExecute = 0x20000000
ImageSectionCharacteristicsMemoryRead = 0x40000000
ImageSectionCharacteristicsMemoryWrite = 0x80000000
)
// Enumeration of TLS characteristics.
const (
ImageSectionTLSCharacteristicsScaleIndex = 0x00000001
)
// Enumeration of relocation types.
const (
ImageRelBasedAbsolute = 0
ImageRelBasedHigh = 1
ImageRelBasedLow = 2
ImageRelBasedHighLow = 3
ImageRelBasedHighAdj = 4
ImageRelBasedMachineSpecific5 = 5
ImageRelBasedReserved = 6
ImageRelBasedMachineSpecific7 = 7
ImageRelBasedMachineSpecific8 = 8
ImageRelBasedMachineSpecific9 = 9
ImageRelBasedDir64 = 10
)
// ImageDOSHeader is the structure of the DOS MZ Executable format. All PE
// files contain at least a valid stub DOS MZ executable at the top; the PE
// format itself starts at the address specified by NewHeaderAddr.
type ImageDOSHeader struct {
Signature [2]byte
LastPageBytes uint16
CountPages uint16
CountRelocs uint16
HeaderLen uint16
MinAlloc uint16
MaxAlloc uint16
InitialSS uint16
InitialSP uint16
Checksum uint16
InitialIP uint16
InitialCS uint16
RelocAddr uint16
OverlayNum uint16
Reserved [4]uint16
OEMID uint16
OEMInfo uint16
Reserved2 [10]uint16
NewHeaderAddr uint32
}
// ImageFileHeader contains some of the basic attributes about the PE/COFF
// file, including the number of sections and the machine type.
type ImageFileHeader struct {
Machine uint16
NumberOfSections uint16
TimeDateStamp uint32
PointerToSymbolTable uint32
NumberOfSymbols uint32
SizeOfOptionalHeader uint16
Characteristics uint16
}
// ImageOptionalHeader32 contains the optional header for 32-bit PE images. It
// is only 'optional' in the sense that not all PE/COFF binaries have it,
// however it is required for executables and DLLs.
type ImageOptionalHeader32 struct {
Magic uint16
MajorLinkerVersion uint8
MinorLinkerVersion uint8
SizeOfCode uint32
SizeOfInitializedData uint32
SizeOfUninitializedData uint32
AddressOfEntryPoint uint32
BaseOfCode uint32
BaseOfData uint32
ImageBase uint32
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 uint32
SizeOfStackCommit uint32
SizeOfHeapReserve uint32
SizeOfHeapCommit uint32
LoaderFlags uint32
NumberOfRvaAndSizes uint32
DataDirectory [NumDirectoryEntries]ImageDataDirectory
}
// ImageOptionalHeader64 contains the optional header for 64-bit PE images.
type ImageOptionalHeader64 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 [NumDirectoryEntries]ImageDataDirectory
}
// To64 converts the ImageOptionalHeader32 to an ImageOptionalHeader64.
func (i ImageOptionalHeader32) To64() ImageOptionalHeader64 {
return ImageOptionalHeader64{
Magic: i.Magic,
MajorLinkerVersion: i.MajorLinkerVersion,
MinorLinkerVersion: i.MinorLinkerVersion,
SizeOfCode: i.SizeOfCode,
SizeOfInitializedData: i.SizeOfInitializedData,
SizeOfUninitializedData: i.SizeOfUninitializedData,
AddressOfEntryPoint: i.AddressOfEntryPoint,
BaseOfCode: i.BaseOfCode,
ImageBase: uint64(i.ImageBase),
SectionAlignment: i.SectionAlignment,
FileAlignment: i.FileAlignment,
MajorOperatingSystemVersion: i.MajorOperatingSystemVersion,
MinorOperatingSystemVersion: i.MinorOperatingSystemVersion,
MajorImageVersion: i.MajorImageVersion,
MinorImageVersion: i.MinorImageVersion,
MajorSubsystemVersion: i.MajorSubsystemVersion,
MinorSubsystemVersion: i.MinorSubsystemVersion,
Win32VersionValue: i.Win32VersionValue,
SizeOfImage: i.SizeOfImage,
SizeOfHeaders: i.SizeOfHeaders,
CheckSum: i.CheckSum,
Subsystem: i.Subsystem,
DllCharacteristics: i.DllCharacteristics,
SizeOfStackReserve: uint64(i.SizeOfStackReserve),
SizeOfStackCommit: uint64(i.SizeOfStackCommit),
SizeOfHeapReserve: uint64(i.SizeOfHeapReserve),
SizeOfHeapCommit: uint64(i.SizeOfHeapCommit),
LoaderFlags: i.LoaderFlags,
NumberOfRvaAndSizes: i.NumberOfRvaAndSizes,
DataDirectory: i.DataDirectory,
}
}
// ImageNTHeaders32 contains the PE file headers for 32-bit PE images.
type ImageNTHeaders32 struct {
// Signature identifies the PE format; Always "PE\0\0".
Signature [4]byte
FileHeader ImageFileHeader
OptionalHeader ImageOptionalHeader32
}
// ImageNTHeaders64 contains the PE file headers for 64-bit PE images.
type ImageNTHeaders64 struct {
// Signature identifies the PE format; Always "PE\0\0".
Signature [4]byte
FileHeader ImageFileHeader
OptionalHeader ImageOptionalHeader64
}
// To64 converts the ImageNTHeaders32 to an ImageNTHeaders64.
func (i ImageNTHeaders32) To64() ImageNTHeaders64 {
return ImageNTHeaders64{
Signature: i.Signature,
FileHeader: i.FileHeader,
OptionalHeader: i.OptionalHeader.To64(),
}
}
// ImageDataDirectory holds a record for the given data directory. Each data
// directory contains information about another section, such as the import
// table. The index of the directory entry determines which section it
// pertains to.
type ImageDataDirectory struct {
VirtualAddress uint32
Size uint32
}
// ImageSectionHeader is the header for a section. Windows Loader uses these
// entries to configure the memory mapping of the executable. A series of
// these structures immediately follow the headers.
type ImageSectionHeader struct {
Name [SectionNameLength]byte
PhysicalAddressOrVirtualSize uint32
VirtualAddress uint32
SizeOfRawData uint32
PointerToRawData uint32
PointerToRelocations uint32
PointerToLinenumbers uint32
NumberOfRelocations uint16
NumberOfLinenumbers uint16
Characteristics uint32
}
// ImageBaseRelocation holds the header for a single page of base relocation
// data. The .reloc section of the binary contains a series of blocks of
// base relocation data, each starting with this header and followed by n
// 16-bit values that each represent a single relocation. The 4 most
// significant bits specify the type of relocation, while the 12 least
// significant bits contain the lower bits of the address (which is combined
// with the virtual address of the page.) The SizeOfBlock value specifies the
// size of an entire block, in bytes, including its header.
type ImageBaseRelocation struct {
VirtualAddress uint32
SizeOfBlock uint32
}
// The ImageImportDescriptor contains information about an imported module.
type ImageImportDescriptor struct {
OriginalFirstThunk uint32
TimeDateStamp uint32
ForwarderChain uint32
Name uint32
FirstThunk uint32
}
// The ImageExportDirectory contains information about the module's exports.
type ImageExportDirectory struct {
Characteristics uint32
TimeDateStamp uint32
MajorVersion uint16
MinorVersion uint16
Name uint32
Base uint32
NumberOfFunctions uint32
NumberOfNames uint32
AddressOfFunctions uint32
AddressOfNames uint32
AddressOfNameOrdinals uint32
}
// ImageTLSDirectory32 contains information about the module's thread local
// storage callbacks (in PE32)
type ImageTLSDirectory32 struct {
StartAddressOfRawData uint32
EndAddressOfRawData uint32
AddressOfIndex uint32
AddressOfCallBacks uint32
SizeOfZeroFill uint32
Characteristics uint32
}
// ImageTLSDirectory64 contains information about the module's thread local
// storage callbacks (in PE64)
type ImageTLSDirectory64 struct {
StartAddressOfRawData uint64
EndAddressOfRawData uint64
AddressOfIndex uint64
AddressOfCallBacks uint64
SizeOfZeroFill uint32
Characteristics uint32
}
// To64 converts the ImageTLSDirectory32 to an ImageTLSDirectory64.
func (i ImageTLSDirectory32) To64() ImageTLSDirectory64 {
return ImageTLSDirectory64{
StartAddressOfRawData: uint64(i.StartAddressOfRawData),
EndAddressOfRawData: uint64(i.EndAddressOfRawData),
AddressOfIndex: uint64(i.AddressOfIndex),
AddressOfCallBacks: uint64(i.AddressOfCallBacks),
SizeOfZeroFill: i.SizeOfZeroFill,
Characteristics: i.Characteristics,
}
}

View File

@@ -0,0 +1,106 @@
package pe
import (
"encoding/binary"
"fmt"
"io"
"github.com/jchv/go-winloader/internal/loader"
)
// LinkModule links a PE module in-memory.
func LinkModule(m *Module, mem io.ReadWriteSeeker, ldr loader.Loader) error {
dir := m.Header.OptionalHeader.DataDirectory[ImageDirectoryEntryImport]
if dir.Size == 0 {
return nil
}
// Determine pointer size based on whether we're PE32 or PE64.
psize := 4
if m.IsPE64 {
psize = 8
}
// Load import descriptors
descs := []ImageImportDescriptor{}
mem.Seek(int64(dir.VirtualAddress), io.SeekStart)
for {
desc := ImageImportDescriptor{}
binary.Read(mem, binary.LittleEndian, &desc)
if desc.Name == 0 {
break
}
descs = append(descs, desc)
}
// Load modules.
for _, desc := range descs {
thunk := int64(desc.OriginalFirstThunk)
iat := int64(desc.FirstThunk)
if thunk == 0 {
thunk = iat
}
// Read module name
mem.Seek(int64(desc.Name), io.SeekStart)
// Load library
libname := readsz(mem)
lib, err := ldr.Load(libname)
if err != nil {
return err
}
// Read thunk addrs
b := [8]byte{}
thunks := []uint64{}
mem.Seek(thunk, io.SeekStart)
for {
mem.Read(b[:psize])
thunk := binary.LittleEndian.Uint64(b[:])
if thunk == 0 {
break
}
thunks = append(thunks, thunk)
}
// Resolve thunks
resolved := []uint64{}
for _, thunk := range thunks {
thunkord := int64(-1)
if (m.IsPE64 && thunk&0x8000000000000000 != 0) || (!m.IsPE64 && thunk&0x80000000 != 0) {
thunkord = int64(thunk & 0xFFFF)
}
if thunkord != -1 {
// Import by ordinal
if proc := lib.Ordinal(uint64(thunkord)); proc != nil {
resolved = append(resolved, proc.Addr())
} else {
return fmt.Errorf("could not resolve ordinal %d in module %q", thunkord, libname)
}
} else {
// Read name
mem.Seek(int64(thunk+2), io.SeekStart)
fnname := readsz(mem)
// Import by name
if proc := lib.Proc(fnname); proc != nil {
resolved = append(resolved, proc.Addr())
} else {
return fmt.Errorf("could not resolve symbol %q in module %q", fnname, libname)
}
}
}
// Write resolved IAT
mem.Seek(iat, io.SeekStart)
for _, fn := range resolved {
binary.LittleEndian.PutUint64(b[:], uint64(fn))
mem.Write(b[:psize])
}
}
return nil
}

View File

@@ -0,0 +1,91 @@
package pe
import (
"encoding/binary"
"errors"
"io"
)
var (
// ErrBadMZSignature is returned when the MZ signature is invalid.
ErrBadMZSignature = errors.New("mz: bad signature")
// ErrBadPESignature is returned when the PE signature is invalid.
ErrBadPESignature = errors.New("pe: bad signature")
// ErrUnknownOptionalHeaderMagic is returned when the optional header
// magic field has an unknown value.
ErrUnknownOptionalHeaderMagic = errors.New("pe: unknown optional header magic")
)
// Module contains a parsed and loaded PE file.
type Module struct {
IsPE64 bool
DOSHeader ImageDOSHeader
Header ImageNTHeaders64
Sections []ImageSectionHeader
}
// LoadModule loads a PE module into memory.
func LoadModule(r io.ReadSeeker) (*Module, error) {
m := &Module{}
r.Seek(0, io.SeekStart)
dos := ImageDOSHeader{}
if err := binary.Read(r, binary.LittleEndian, &dos); err != nil {
return nil, err
}
if dos.Signature != MZSignature {
return nil, ErrBadMZSignature
}
m.DOSHeader = dos
r.Seek(int64(dos.NewHeaderAddr), io.SeekStart)
pesig := [4]byte{}
if err := binary.Read(r, binary.LittleEndian, &pesig); err != nil {
return nil, err
}
if pesig != PESignature {
return nil, ErrBadPESignature
}
optmagic := uint16(0)
r.Seek(int64(dos.NewHeaderAddr)+OffsetOfOptionalHeaderFromNTHeader, io.SeekStart)
if err := binary.Read(r, binary.LittleEndian, &optmagic); err != nil {
return nil, err
}
r.Seek(int64(dos.NewHeaderAddr), io.SeekStart)
switch optmagic {
case ImageNTOptionalHeader32Magic:
m.IsPE64 = false
nt := ImageNTHeaders32{}
if err := binary.Read(r, binary.LittleEndian, &nt); err != nil {
return nil, err
}
m.Header = nt.To64()
case ImageNTOptionalHeader64Magic:
m.IsPE64 = true
nt := ImageNTHeaders64{}
if err := binary.Read(r, binary.LittleEndian, &nt); err != nil {
return nil, err
}
m.Header = nt
default:
return nil, ErrUnknownOptionalHeaderMagic
}
// Seek past end of optional headers.
r.Seek(int64(dos.NewHeaderAddr)+OffsetOfOptionalHeaderFromNTHeader+int64(m.Header.FileHeader.SizeOfOptionalHeader), io.SeekStart)
for i := uint16(0); i < m.Header.FileHeader.NumberOfSections; i++ {
section := ImageSectionHeader{}
if err := binary.Read(r, binary.LittleEndian, &section); err != nil {
return nil, err
}
m.Sections = append(m.Sections, section)
}
return m, nil
}

View File

@@ -0,0 +1,177 @@
package pe
import (
"encoding/binary"
"errors"
"fmt"
"io"
)
// BaseRelocation is a parsed PE base relocation.
type BaseRelocation struct {
// Relative virtual address of offset (that is, 0 == first byte of PE.)
Offset uint64
// Type of base relocation using the ImageRelBased* constants.
Type int
}
// LoadBaseRelocs loads relocations from memory.
func LoadBaseRelocs(m *Module, mem io.ReadSeeker) []BaseRelocation {
relocs := []BaseRelocation{}
dir := m.Header.OptionalHeader.DataDirectory[ImageDirectoryEntryBaseReloc]
if dir.Size == 0 {
return relocs
}
n := uint32(0)
mem.Seek(int64(dir.VirtualAddress), io.SeekStart)
for n < dir.Size {
hdr := ImageBaseRelocation{}
binary.Read(mem, binary.LittleEndian, &hdr)
data := make([]uint16, hdr.SizeOfBlock/2-4)
binary.Read(mem, binary.LittleEndian, &data)
for _, i := range data {
relocs = append(relocs, BaseRelocation{
Offset: uint64(hdr.VirtualAddress) + uint64(i&0xFFF),
Type: int(i >> 12),
})
}
n += hdr.SizeOfBlock
}
return relocs
}
// Relocate performs a series of relocations on m, where address is the load
// address and original is the original address. The ReadWriteSeeker is
// assumed to have the PE image at offset 0.
func Relocate(machine int, rels []BaseRelocation, address uint64, original uint64, m io.ReadWriteSeeker, o binary.ByteOrder) error {
b := [8]byte{}
delta := address - original
if delta == 0 {
return nil
}
read := func(off uint64, cb int) error {
m.Seek(int64(off), io.SeekStart)
err := readfully(m, b[0:cb])
if err != nil {
return err
}
m.Seek(int64(-cb), io.SeekCurrent)
return nil
}
for _, rel := range rels {
switch rel.Type {
case ImageRelBasedAbsolute:
break
case ImageRelBasedHigh:
if err := read(rel.Offset, 2); err != nil {
return err
}
o.PutUint16(b[0:2], uint16(uint64(o.Uint16(b[:2]))+(delta>>16)))
if _, err := m.Write(b[0:2]); err != nil {
return err
}
break
case ImageRelBasedLow:
if err := read(rel.Offset, 2); err != nil {
return err
}
o.PutUint16(b[0:2], uint16(uint64(o.Uint16(b[:2]))+(delta>>0)))
if _, err := m.Write(b[0:2]); err != nil {
return err
}
break
case ImageRelBasedHighLow:
if err := read(rel.Offset, 4); err != nil {
return err
}
o.PutUint32(b[0:4], uint32(uint64(o.Uint32(b[:4]))+delta))
if _, err := m.Write(b[0:4]); err != nil {
return err
}
break
case ImageRelBasedDir64:
if err := read(rel.Offset, 8); err != nil {
return err
}
o.PutUint64(b[0:8], uint64(o.Uint64(b[:8]))+delta)
if _, err := m.Write(b[0:8]); err != nil {
return err
}
break
// Could use some help for ensuring we have proper support for
// machine-specific relocations. If you are interested in this
// use case for some reason, feel free to send PRs. -- john
case ImageRelBasedMachineSpecific5:
switch machine {
// MIPS: JMP reloc
case ImageFileMachineR3000, ImageFileMachineR3000BE,
ImageFileMachineR4000, ImageFileMachineR10000,
ImageFileMachineWCEMIPSv2, ImageFileMachineMIPS16,
ImageFileMachineMIPSFPU, ImageFileMachineMIPSFPU16:
return errors.New("MIPS JMP reloc not implemented")
// ARM: MOV32 reloc
case ImageFileMachineARM, ImageFileMachineTHUMB,
ImageFileMachineARMNT:
return errors.New("ARM MOV32 reloc not implemented")
// RISC-V: HI20 reloc
case ImageFileMachineRISCV32, ImageFileMachineRISCV64,
ImageFileMachineRISCV128:
return errors.New("RISC-V HI20 reloc not implemented")
default:
return fmt.Errorf("unknown machine-specific relocation type %d on machine type %04x", rel.Type, machine)
}
case ImageRelBasedMachineSpecific7:
switch machine {
// THUMB: MOV32 reloc
case ImageFileMachineARM, ImageFileMachineTHUMB,
ImageFileMachineARMNT:
return errors.New("THUMB MOV32 reloc not implemented")
// RISC-V: LOW12I reloc
case ImageFileMachineRISCV32, ImageFileMachineRISCV64,
ImageFileMachineRISCV128:
return errors.New("RISC-V LOW12I reloc not implemented")
default:
return fmt.Errorf("unknown machine-specific relocation type %d on machine type %04x", rel.Type, machine)
}
case ImageRelBasedMachineSpecific8:
switch machine {
// RISC-V: LOW12S reloc
case ImageFileMachineRISCV32, ImageFileMachineRISCV64,
ImageFileMachineRISCV128:
return errors.New("RISC-V LOW12S reloc not implemented")
default:
return fmt.Errorf("unknown machine-specific relocation type %d on machine type %04x", rel.Type, machine)
}
case ImageRelBasedMachineSpecific9:
switch machine {
// MIPS: JMP16 reloc
case ImageFileMachineR3000, ImageFileMachineR3000BE,
ImageFileMachineR4000, ImageFileMachineR10000,
ImageFileMachineWCEMIPSv2, ImageFileMachineMIPS16,
ImageFileMachineMIPSFPU, ImageFileMachineMIPSFPU16:
return errors.New("MIPS JMP16 reloc not implemented")
// Itanium: Imm64 reloc
case ImageFileMachineIA64:
return errors.New("Itanium Imm64 reloc not implemented")
default:
return fmt.Errorf("unknown machine-specific relocation type %d on machine type %04x", rel.Type, machine)
}
default:
return fmt.Errorf("unknown relocation type %d", rel.Type)
}
}
return nil
}

View File

@@ -0,0 +1,40 @@
package pe
import "io"
func readfully(r io.Reader, p []byte) error {
n, err := r.Read(p)
if n < 0 || n > len(p) {
panic("invalid read length")
}
if err != nil && err != io.EOF {
return err
}
for n < len(p) {
m, err := r.Read(p[n:])
if m < 0 || m > len(p[n:]) {
panic("invalid read length")
}
n += m
if n < len(p) && err != nil {
return err
}
if n >= len(p) && err != nil && err != io.EOF {
return err
}
}
return nil
}
func readsz(r io.Reader) string {
name := []byte{}
for {
b := [1]byte{}
r.Read(b[:])
if b[0] == 0 {
break
}
name = append(name, b[0])
}
return string(name)
}

View File

@@ -0,0 +1,55 @@
package vmem
const (
memDecommit = 0x4000
memRelease = 0x8000
)
type systemInfo struct {
wProcessorArchitecture uint16
wReserved uint16
dwPageSize uint32
lpMinimumApplicationAddress uintptr
lpMaximumApplicationAddress uintptr
dwActiveProcessorMask uintptr
dwNumberOfProcessors uint32
dwProcessorType uint32
dwAllocationGranularity uint32
wProcessorLevel uint16
wProcessorRevision uint16
}
// Enumeration of allocation type values.
const (
MemCommit = 0x00001000
MemReserve = 0x00002000
MemReset = 0x00080000
MemResetUndo = 0x10000000
)
// Enumeration of protection levels.
const (
PageNoAccess = 0x01
PageReadOnly = 0x02
PageReadWrite = 0x04
PageWriteCopy = 0x08
PageExecute = 0x10
PageExecuteRead = 0x20
PageExecuteReadWrite = 0x40
PageExecuteWriteCopy = 0x80
)
// RoundDown rounds an address up to a given multiple of size. Size must be a
// power of two.
func RoundDown(addr uint64, size uint64) uint64 {
if size&(size-1) != 0 {
panic("alignment size is not a power of two")
}
return addr &^ (size - 1)
}
// RoundUp rounds an address up to a given multiple of size. Size must be a
// power of two.
func RoundUp(addr uint64, size uint64) uint64 {
return RoundDown(addr+size-1, size)
}

View File

@@ -0,0 +1,70 @@
// +build !windows
package vmem
// Memory represents a raw block of memory.
type Memory struct {
data []byte
}
// GetPageSize returns the size of a memory page.
func GetPageSize() uint64 {
panic("not implemented")
}
// Alloc allocates memory at addr of size with allocType and protect.
// It returns nil if it fails.
func Alloc(addr, size uint64, allocType, protect int) *Memory {
panic("not implemented")
}
// Get returns a range of existing memory. If the range is not a block of
// allocated memory, the returned memory will pagefault when accessed.
func Get(addr, size uint64) *Memory {
panic("not implemented")
}
// Free frees the block of memory.
func (m *Memory) Free() {
panic("not implemented")
}
// Addr returns the actual address of the memory.
func (m *Memory) Addr() uint64 {
panic("not implemented")
}
// Read implements the io.Reader interface.
func (m *Memory) Read(b []byte) (n int, err error) {
panic("not implemented")
}
// ReadAt implements the io.ReaderAt interface.
func (m *Memory) ReadAt(b []byte, off int64) (n int, err error) {
panic("not implemented")
}
// Write implements the io.Writer interface.
func (m *Memory) Write(b []byte) (n int, err error) {
panic("not implemented")
}
// WriteAt implements the io.WriterAt interface.
func (m *Memory) WriteAt(b []byte, off int64) (n int, err error) {
panic("not implemented")
}
// Seek implements the io.Seeker interface.
func (m *Memory) Seek(offset int64, whence int) (int64, error) {
panic("not implemented")
}
// Protect changes the memory protection for a range of memory.
func (m *Memory) Protect(addr, size uint64, protect int) error {
panic("not implemented")
}
// Clear sets all bytes in the memory block to zero.
func (m *Memory) Clear() {
panic("not implemented")
}

View File

@@ -0,0 +1,178 @@
// +build windows
package vmem
import (
"errors"
"io"
"reflect"
"sync"
"unsafe"
"golang.org/x/sys/windows"
)
var (
kernel32 = windows.NewLazySystemDLL("kernel32")
kernel32VirtualAlloc = kernel32.NewProc("VirtualAlloc")
kernel32VirtualFree = kernel32.NewProc("VirtualFree")
kernel32VirtualProtect = kernel32.NewProc("VirtualProtect")
kernel32GetNativeSystemInfo = kernel32.NewProc("GetNativeSystemInfo")
kernel32FlushInstructionCache = kernel32.NewProc("FlushInstructionCache")
kernel32GetCurrentProcess = kernel32.NewProc("GetCurrentProcess")
pageSize uint64
pageSizeOnce sync.Once
)
// getCurrentProcess returns the current process handle.
func getCurrentProcess() uintptr {
r, _, _ := kernel32GetCurrentProcess.Call()
return uintptr(r)
}
// GetPageSize returns the size of a memory page.
func GetPageSize() uint64 {
pageSizeOnce.Do(func() {
if kernel32GetNativeSystemInfo.Find() != nil {
pageSize = 0x1000
}
info := systemInfo{}
kernel32GetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&info)))
pageSize = uint64(info.dwPageSize)
})
return pageSize
}
// Memory represents a raw block of memory.
type Memory struct {
data []byte
i int64
}
// Alloc allocates memory at addr of size with allocType and protect.
// It returns nil if it fails.
func Alloc(addr, size uint64, allocType, protect int) *Memory {
r, _, _ := kernel32VirtualAlloc.Call(uintptr(addr), uintptr(size), uintptr(allocType), uintptr(protect))
if r == 0 {
return nil
}
return Get(uint64(r), size)
}
// Get returns a range of existing memory. If the range is not a block of
// allocated memory, the returned memory will pagefault when accessed.
func Get(addr, size uint64) *Memory {
m := &Memory{}
sh := (*reflect.SliceHeader)(unsafe.Pointer(&m.data))
sh.Data = uintptr(addr)
sh.Len = int(size)
sh.Cap = int(size)
return m
}
// Free frees the block of memory.
func (m *Memory) Free() {
sh := (*reflect.SliceHeader)(unsafe.Pointer(&m.data))
kernel32VirtualFree.Call(sh.Data, 0, memRelease)
m.data = nil
}
// Addr returns the actual address of the memory.
func (m *Memory) Addr() uint64 {
return uint64((*reflect.SliceHeader)(unsafe.Pointer(&m.data)).Data)
}
// Read implements the io.Reader interface.
func (m *Memory) Read(b []byte) (n int, err error) {
if m.i >= int64(len(m.data)) {
return 0, io.EOF
}
n = copy(b, m.data[m.i:])
m.i += int64(n)
return n, nil
}
// ReadAt implements the io.ReaderAt interface.
func (m *Memory) ReadAt(b []byte, off int64) (n int, err error) {
if off < 0 {
return 0, errors.New("negative offset")
}
if off >= int64(len(m.data)) {
return 0, io.EOF
}
n = copy(b, m.data[off:])
if n < len(b) {
return n, io.EOF
}
return n, nil
}
// Write implements the io.Writer interface.
func (m *Memory) Write(b []byte) (n int, err error) {
if m.i >= int64(len(m.data)) {
return 0, io.ErrShortWrite
}
n = copy(m.data[m.i:], b)
if kernel32FlushInstructionCache.Find() == nil {
kernel32FlushInstructionCache.Call(getCurrentProcess(), uintptr(unsafe.Pointer(&m.data[m.i])), uintptr(n))
}
m.i += int64(n)
return n, nil
}
// WriteAt implements the io.WriterAt interface.
func (m *Memory) WriteAt(b []byte, off int64) (n int, err error) {
if off < 0 {
return 0, errors.New("negative offset")
}
if off >= int64(len(m.data)) {
return 0, io.ErrShortWrite
}
n = copy(m.data[off:], b)
if kernel32FlushInstructionCache.Find() == nil {
kernel32FlushInstructionCache.Call(getCurrentProcess(), uintptr(unsafe.Pointer(&m.data[off])), uintptr(n))
}
if n < len(b) {
return n, io.ErrShortWrite
}
return n, nil
}
// Seek implements the io.Seeker interface.
func (m *Memory) Seek(offset int64, whence int) (int64, error) {
var n int64
switch whence {
case io.SeekStart:
n = offset
case io.SeekCurrent:
n = m.i + offset
case io.SeekEnd:
n = int64(len(m.data)) + offset
default:
return 0, errors.New("invalid whence")
}
if n < 0 {
return 0, errors.New("negative position")
}
m.i = n
return n, nil
}
// Clear sets all bytes in the memory block to zero.
func (m *Memory) Clear() {
for i := range m.data {
m.data[i] = 0
}
if kernel32FlushInstructionCache.Find() == nil {
kernel32FlushInstructionCache.Call(getCurrentProcess(), uintptr(unsafe.Pointer(&m.data[0])), uintptr(len(m.data)))
}
}
// Protect changes the memory protection for a range of memory.
func (m *Memory) Protect(addr, size uint64, protect int) error {
// TODO: error handling
oldProtect := uint32(0)
kernel32VirtualProtect.Call(uintptr(m.Addr()+addr), uintptr(size), uintptr(protect), uintptr(unsafe.Pointer(&oldProtect)))
return nil
}

View File

@@ -0,0 +1,16 @@
// +build !windows
package winloader
import "errors"
// MakePEBEntryForModule is a hack that inserts an entry for the loaded module
// into the PEB loader data to make it appear to Windows functions.
func MakePEBEntryForModule() error {
return errors.New("platform not supported")
}
// GetProcessHInstance gets the HINSTANCE for the current process.
func GetProcessHInstance() (uintptr, error) {
return errors.New("platform not supported")
}

View File

@@ -0,0 +1,73 @@
package winloader
import (
"errors"
"fmt"
"unsafe"
"golang.org/x/sys/windows"
)
var (
ntdllModule = windows.NewLazySystemDLL("ntdll")
ntdllNtQueryInformationProcess = ntdllModule.NewProc("NtQueryInformationProcess")
)
type _ProcessBasicInformation struct {
Reserved1 uintptr
PebBaseAddress uintptr
Reserved2 [2]uintptr
UniqueProcessID uintptr
Reserved3 uintptr
}
type _PEB struct {
Reserved1 [2]byte
BeingDebugged byte
Reserved2 [1]byte
Reserved3 [2]uintptr
Ldr uintptr
}
type _List struct {
Front uintptr
Back uintptr
}
type _PEBLoaderData struct {
Length uint32
Initialized uint8
Padding [3]uint8
SsHandle uintptr
InLoadOrderModuleList _List
InMemoryOrderModuleList _List
InInitializationOrderModuleList _List
}
// MakePEBEntryForModule is a hack that inserts an entry for the loaded module
// into the PEB loader data to make it appear to Windows functions.
func MakePEBEntryForModule() error {
process := -1
sizeNeeded := uint32(0)
pbi := _ProcessBasicInformation{}
status, _, _ := ntdllNtQueryInformationProcess.Call(
uintptr(process),
0,
uintptr(unsafe.Pointer(&pbi)),
unsafe.Sizeof(pbi),
uintptr(unsafe.Pointer(&sizeNeeded)),
)
if status != 0 {
return fmt.Errorf("NtQueryInformationProcess failed: %08x", status)
}
// TODO: Implement attempt to insert module entry.
return errors.New("unimplemented")
}
// GetProcessHInstance gets the HINSTANCE for the current process.
func GetProcessHInstance() (uintptr, error) {
var hinstance windows.Handle
windows.GetModuleHandleEx(0, nil, &hinstance)
return uintptr(hinstance), nil
}

View File

@@ -0,0 +1,96 @@
package winloader
import (
"syscall"
"github.com/jchv/go-winloader/internal/loader"
"golang.org/x/sys/windows"
)
// Proc is a Proc implementation using a native procedure.
type Proc uintptr
// Call calls a native procedure.
func (p Proc) Call(a ...uint64) (r1, r2 uint64, lastErr error) {
var n1, n2 uintptr
switch len(a) {
case 0:
n1, n2, lastErr = syscall.Syscall(uintptr(p), 0, 0, 0, 0)
case 1:
n1, n2, lastErr = syscall.Syscall(uintptr(p), 1, uintptr(a[0]), 0, 0)
case 2:
n1, n2, lastErr = syscall.Syscall(uintptr(p), 2, uintptr(a[0]), uintptr(a[1]), 0)
case 3:
n1, n2, lastErr = syscall.Syscall(uintptr(p), 3, uintptr(a[0]), uintptr(a[1]), uintptr(a[2]))
case 4:
n1, n2, lastErr = syscall.Syscall6(uintptr(p), 4, uintptr(a[0]), uintptr(a[1]), uintptr(a[2]), uintptr(a[3]), 0, 0)
case 5:
n1, n2, lastErr = syscall.Syscall6(uintptr(p), 5, uintptr(a[0]), uintptr(a[1]), uintptr(a[2]), uintptr(a[3]), uintptr(a[4]), 0)
case 6:
n1, n2, lastErr = syscall.Syscall6(uintptr(p), 6, uintptr(a[0]), uintptr(a[1]), uintptr(a[2]), uintptr(a[3]), uintptr(a[4]), uintptr(a[5]))
case 7:
n1, n2, lastErr = syscall.Syscall9(uintptr(p), 7, uintptr(a[0]), uintptr(a[1]), uintptr(a[2]), uintptr(a[3]), uintptr(a[4]), uintptr(a[5]), uintptr(a[6]), 0, 0)
case 8:
n1, n2, lastErr = syscall.Syscall9(uintptr(p), 8, uintptr(a[0]), uintptr(a[1]), uintptr(a[2]), uintptr(a[3]), uintptr(a[4]), uintptr(a[5]), uintptr(a[6]), uintptr(a[7]), 0)
case 9:
n1, n2, lastErr = syscall.Syscall9(uintptr(p), 9, uintptr(a[0]), uintptr(a[1]), uintptr(a[2]), uintptr(a[3]), uintptr(a[4]), uintptr(a[5]), uintptr(a[6]), uintptr(a[7]), uintptr(a[8]))
case 10:
n1, n2, lastErr = syscall.Syscall12(uintptr(p), 10, uintptr(a[0]), uintptr(a[1]), uintptr(a[2]), uintptr(a[3]), uintptr(a[4]), uintptr(a[5]), uintptr(a[6]), uintptr(a[7]), uintptr(a[8]), uintptr(a[9]), 0, 0)
case 11:
n1, n2, lastErr = syscall.Syscall12(uintptr(p), 11, uintptr(a[0]), uintptr(a[1]), uintptr(a[2]), uintptr(a[3]), uintptr(a[4]), uintptr(a[5]), uintptr(a[6]), uintptr(a[7]), uintptr(a[8]), uintptr(a[9]), uintptr(a[10]), 0)
case 12:
n1, n2, lastErr = syscall.Syscall12(uintptr(p), 12, uintptr(a[0]), uintptr(a[1]), uintptr(a[2]), uintptr(a[3]), uintptr(a[4]), uintptr(a[5]), uintptr(a[6]), uintptr(a[7]), uintptr(a[8]), uintptr(a[9]), uintptr(a[10]), uintptr(a[11]))
case 13:
n1, n2, lastErr = syscall.Syscall15(uintptr(p), 13, uintptr(a[0]), uintptr(a[1]), uintptr(a[2]), uintptr(a[3]), uintptr(a[4]), uintptr(a[5]), uintptr(a[6]), uintptr(a[7]), uintptr(a[8]), uintptr(a[9]), uintptr(a[10]), uintptr(a[11]), uintptr(a[12]), 0, 0)
case 14:
n1, n2, lastErr = syscall.Syscall15(uintptr(p), 14, uintptr(a[0]), uintptr(a[1]), uintptr(a[2]), uintptr(a[3]), uintptr(a[4]), uintptr(a[5]), uintptr(a[6]), uintptr(a[7]), uintptr(a[8]), uintptr(a[9]), uintptr(a[10]), uintptr(a[11]), uintptr(a[12]), uintptr(a[13]), 0)
case 15:
n1, n2, lastErr = syscall.Syscall15(uintptr(p), 15, uintptr(a[0]), uintptr(a[1]), uintptr(a[2]), uintptr(a[3]), uintptr(a[4]), uintptr(a[5]), uintptr(a[6]), uintptr(a[7]), uintptr(a[8]), uintptr(a[9]), uintptr(a[10]), uintptr(a[11]), uintptr(a[12]), uintptr(a[13]), uintptr(a[14]))
default:
panic("too many arguments")
}
return uint64(n1), uint64(n2), lastErr
}
// Addr gets the native address of the procedure.
func (p Proc) Addr() uint64 {
return uint64(p)
}
// Library is a module implementation using the native Windows loader.
type Library windows.Handle
// Proc implements loader.Module
func (l Library) Proc(name string) loader.Proc {
proc, _ := windows.GetProcAddress(windows.Handle(l), name)
if proc == 0 {
return nil
}
return Proc(proc)
}
// Ordinal implements loader.Module
func (l Library) Ordinal(ordinal uint64) loader.Proc {
proc, _ := windows.GetProcAddressByOrdinal(windows.Handle(l), uintptr(ordinal))
if proc == 0 {
return nil
}
return Proc(proc)
}
// Free implements loader.Module
func (l Library) Free() error {
return windows.FreeLibrary(windows.Handle(l))
}
// Loader is a loader that uses the native Windows library loader.
type Loader struct{}
// Load loads a module into memory.
func (Loader) Load(libname string) (loader.Module, error) {
handle, err := windows.LoadLibrary(libname)
if err != nil {
return nil, err
}
return Library(handle), nil
}

View File

@@ -0,0 +1,32 @@
package winloader
import (
"github.com/jchv/go-winloader/internal/loader"
"github.com/jchv/go-winloader/internal/vmem"
)
// NativeMachine is a loader.Machine implementation that uses the native
// machine the binary is running on.
type NativeMachine struct{}
// IsArchitectureSupported implements loader.Machine.
func (NativeMachine) IsArchitectureSupported(machine int) bool {
return machine == NativeArch
}
func (NativeMachine) GetPageSize() uint64 {
return vmem.GetPageSize()
}
// Alloc implements loader.Machine.
func (NativeMachine) Alloc(addr, size uint64, allocType, protect int) loader.Memory {
if mem := vmem.Alloc(addr, size, allocType, protect); mem != nil {
return mem
}
return nil
}
// MemProc implements loader.MemProc.
func (NativeMachine) MemProc(addr uint64) loader.Proc {
return Proc(addr)
}

View File

@@ -0,0 +1,9 @@
package winloader
import (
"github.com/jchv/go-winloader/internal/pe"
)
// NativeArch is a constant that will be equal to the PE machine type
// enumeration value that corresponds to the arch the binary is running as.
const NativeArch = pe.ImageFileMachinei386

View File

@@ -0,0 +1,9 @@
package winloader
import (
"github.com/jchv/go-winloader/internal/pe"
)
// NativeArch is a constant that will be equal to the PE machine type
// enumeration value that corresponds to the arch the binary is running as.
const NativeArch = pe.ImageFileMachineAMD64

View File

@@ -0,0 +1,9 @@
package winloader
import (
"github.com/jchv/go-winloader/internal/pe"
)
// NativeArch is a constant that will be equal to the PE machine type
// enumeration value that corresponds to the arch the binary is running as.
const NativeArch = pe.ImageFileMachineARM64