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 }