254 lines
7.3 KiB
Go
254 lines
7.3 KiB
Go
|
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
|
||
|
}
|