├── LICENSE.md ├── README.md ├── cmd └── demo │ └── main.go ├── common.go ├── go.mod ├── go.sum ├── internal ├── loader │ ├── loader.go │ └── machine.go ├── memloader │ ├── cache.go │ └── loader.go ├── pe │ ├── export.go │ ├── format.go │ ├── format_test.go │ ├── import.go │ ├── module.go │ ├── module_test.go │ ├── reloc.go │ └── util.go ├── vmem │ ├── common.go │ ├── memory_other.go │ ├── memory_windows.go │ └── memory_windows_test.go └── winloader │ ├── hacks_other.go │ ├── hacks_windows.go │ ├── loader_windows.go │ ├── machine_windows.go │ ├── nativearch_386.go │ ├── nativearch_amd64.go │ └── nativearch_arm64.go ├── loader_other.go ├── loader_windows.go └── tinydll ├── Makefile ├── README.md ├── export.def ├── tiny.c └── tiny.dll /LICENSE.md: -------------------------------------------------------------------------------- 1 | # ISC License 2 | 3 | Copyright © 2021, John Chadwick 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-winloader 2 | 3 | **Note:** This library is still experimental. There are no guarantees of API stability, or runtime stability. Proceed with caution. 4 | 5 | go-winloader is a library that implements the Windows module loader algorithm in pure Go. The actual Windows module loader, accessible via `LoadLibrary`, only supports loading modules from disk, which is sometimes undesirable. With go-winloader, you can load modules directly from memory without needing to write intermediate temporary files to disk. 6 | 7 | This is a bit more versatile than linking directly to object files, since you do not need object files for this approach. As a downside, it is a purely Windows-only approach. 8 | 9 | Note: Unlike native APIs, in place of processor register sized values go-winloader uses `uint64` instead of `uintptr` except when calling native host functions. This allows it to remain neutral to processor architecture at runtime, which in the future may allow for more esoteric use cases. (See TODO for more information on potential future features.) 10 | 11 | ## Example 12 | 13 | ```go 14 | // Load a module off disk (for simplicity; you can pass in any byte slice.) 15 | b, _ := ioutil.ReadFile("my.dll") 16 | mod, err := winloader.LoadFromMemory(b) 17 | if err != nil { 18 | log.Fatalln("error loading module:", err) 19 | } 20 | 21 | // Get a procedure. 22 | addProc := mod.Proc("Add") 23 | if proc == nil { 24 | log.Fatalln("module my.dll is missing required procedure Add") 25 | } 26 | 27 | // Call the procedure! 28 | result, _, _ := addProc.Call(1, 2) 29 | log.Printf("1 + 2 = %d", result) 30 | ``` 31 | 32 | ## TODO 33 | 34 | * WinSXS support 35 | 36 | * Some binaries have manifests requesting a specific version of a library. 37 | There is an undocumented library at sxs.dll. Since it's undocumented, 38 | it might be difficult to work out how to use it. 39 | 40 | * It'd be nice to have a reimplementation of the whole SXS algorithm, just 41 | for completeness sake. (It might even be useful to Wine.) 42 | 43 | * Additional compatibility hacks 44 | 45 | * Because we are not Windows loader, Windows loader's internal structures 46 | do not update when we load binaries into the address space. 47 | 48 | * Because of this, our HINSTANCE value may not always work properly. 49 | 50 | * There's a hack that sends the process HINSTANCE instead. 51 | 52 | * There's a stub for a hack that would inject the library into the PEB 53 | loader data linked lists. 54 | 55 | * This might fail catastrophically or have worse consequences, but it 56 | would be interesting to explore. 57 | 58 | * Another useful hack would be one that can override calls to important 59 | Windows functions and implement their functionality for cases when our 60 | own false HINSTANCE is used. This could be done on a process-wide level 61 | (for best compatibility) or directly on the import table (usually enough, 62 | but tricky modules will bypass this.) 63 | 64 | * Better support for loading executable images. 65 | 66 | * Right now, if you attempt to load an executable, it executes the 67 | entrypoint eagerly when it tries to send the DLL attach message. 68 | 69 | * Threading support. 70 | 71 | * Perhaps have a helper function that can run a function in a new thread, 72 | automatically calling `DLL_THREAD_ATTACH`/`DLL_THREAD_DETACH` as needed 73 | on memory loaded modules. 74 | 75 | * While it may not be necessary for all libraries, it is likely necessary 76 | for libraries that have statically linked the MSVC runtime, and for 77 | libraries that use thread-local storage. Otherwise, calling functions on 78 | threads other than the initial one is likely to crash. 79 | 80 | * Even better: if we can find a place to hook new threads, this would be a 81 | nice hack to support. 82 | 83 | * Versatility 84 | 85 | * Operating systems other than Windows: 86 | 87 | * Need to figure out how to make MSABI calls. Maybe CGo with msabi 88 | function pointers, or maybe we need to write out the asm by hand. 89 | 90 | * Emulator would need the ability to generate stub addresses that call 91 | back into Go code, so we can use them to handle imports and whatnot. 92 | 93 | * Would need a custom loader that lets you emulate calls to other 94 | libraries. 95 | 96 | * CPU emulation 97 | 98 | * Should be possible implement virtual machines with emulated CPUs. 99 | 100 | * Similar to the outside Windows case, we need a custom loader to 101 | emulate library calls. Although we *can* use the host system's 102 | libraries, we need to translate API calls so that they work 103 | correctly, like translating addresses and marshaling/unmarshaling 104 | data as necessary, and of course calling conventions are entirely 105 | different. 106 | 107 | * We should provide shims at least for running 32-bit binaries in 108 | 64-bit processes using emulation. How much of the API to emulate 109 | would be hard to guage, but at least for documented APIs it should 110 | be relatively straightforward work. 111 | 112 | * Legacy formats 113 | 114 | * Currently go-winloader only supports loading PE32 or PE64 binaries. 115 | 116 | * It might be useful to someone to implement loading very old legacy 117 | binaries, like NE or LX. 118 | 119 | * Expose lower level APIs 120 | 121 | * In order to allow this library to be useful while it is still in 122 | heavy flux, a very small surface area is exposed today. As it 123 | matures, more of these internal libraries should be exposed as 124 | public API. 125 | -------------------------------------------------------------------------------- /cmd/demo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | 9 | "github.com/jchv/go-winloader" 10 | ) 11 | 12 | func main() { 13 | flag.Parse() 14 | data, err := ioutil.ReadFile(flag.Arg(0)) 15 | if err != nil { 16 | log.Fatalln(err) 17 | } 18 | module, err := winloader.LoadFromMemory(data) 19 | if err != nil { 20 | log.Fatalln(err) 21 | } 22 | proc := module.Proc("Add") 23 | if proc == nil { 24 | log.Fatalln("could not find proc Add") 25 | } 26 | r, _, _ := proc.Call(1, 2) 27 | fmt.Printf("Add(1, 2) = %d\n", r) 28 | } 29 | -------------------------------------------------------------------------------- /common.go: -------------------------------------------------------------------------------- 1 | package winloader 2 | 3 | import ( 4 | "github.com/jchv/go-winloader/internal/loader" 5 | ) 6 | 7 | // Proc represents a proc of a module. 8 | type Proc = loader.Proc 9 | 10 | // Module represents a loaded module. 11 | type Module = loader.Module 12 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jchv/go-winloader 2 | 3 | go 1.14 4 | 5 | require golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed h1:WBkVNH1zd9jg/dK4HCM4lNANnmd12EHC9z+LmcCG4ns= 2 | golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 3 | golang.org/x/sys v0.0.0-20200812155832-6a926be9bd1d h1:QQrM/CCYEzTs91GZylDCQjGHudbPTxF/1fvXdVh5lMo= 4 | golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061 h1:DQmQoKxQWtyybCtX/3dIuDBcAhFszqq8YiNeS6sNu1c= 5 | -------------------------------------------------------------------------------- /internal/loader/loader.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | // Module represents a loaded Windows module. 4 | type Module interface { 5 | // Proc returns a procedure by symbol name. Returns nil if the symbol is 6 | // not found. 7 | Proc(name string) Proc 8 | 9 | // Ordinal returns a procedure by ordinal. 10 | Ordinal(ordinal uint64) Proc 11 | 12 | // Free closes the module and frees the memory. After this, GetProcAddress 13 | // will stop working and procedures will no longer function. 14 | Free() error 15 | } 16 | 17 | // Loader represents a named module loader implementation. 18 | type Loader interface { 19 | Load(libname string) (Module, error) 20 | } 21 | 22 | // MemLoader represents a memory module loader implementation. 23 | type MemLoader interface { 24 | LoadMem(module []byte) (Module, error) 25 | } 26 | -------------------------------------------------------------------------------- /internal/loader/machine.go: -------------------------------------------------------------------------------- 1 | package loader 2 | 3 | import "io" 4 | 5 | // Proc represents a procedure in memory. 6 | type Proc interface { 7 | // Call calls the procedure. r1 and r2 contain the return value. lastErr 8 | // contains the Windows error value after calling. 9 | Call(a ...uint64) (r1, r2 uint64, lastErr error) 10 | 11 | // Returns the raw address of this function. 12 | Addr() uint64 13 | } 14 | 15 | // Memory is an interface for a block of allocated virtual memory. 16 | type Memory interface { 17 | io.ReadWriteSeeker 18 | io.ReaderAt 19 | io.WriterAt 20 | 21 | // Free frees the virtual memory region. 22 | Free() 23 | 24 | // Addr returns the address of the region of memory in the virtual address 25 | // space. 26 | Addr() uint64 27 | 28 | // Clear zeros the entire region of memory. 29 | Clear() 30 | 31 | // Protect changes the memory protection for a subregion of this allocated 32 | // block. It should match the semantics of the VirtualProtect function on 33 | // Windows. 34 | Protect(addr, size uint64, protect int) error 35 | } 36 | 37 | // Machine is an abstract machine interface. 38 | type Machine interface { 39 | // IsArchitectureSupported returns whether or not an architecture is 40 | // supported by this abstract machine. Machine is a PE machine ID. 41 | IsArchitectureSupported(machine int) bool 42 | 43 | // GetPageSize returns the size of a memory page on this abstract machine. 44 | GetPageSize() uint64 45 | 46 | // Alloc performs virtual memory allocation. It should match the semantics 47 | // of VirtualAlloc/VirtualFree on Windows. 48 | Alloc(addr, size uint64, allocType, protect int) Memory 49 | 50 | // MemProc returns an object for interfacing with a procedure at addr in 51 | // the abstract machine's virtual memory space. 52 | MemProc(addr uint64) Proc 53 | } 54 | -------------------------------------------------------------------------------- /internal/memloader/cache.go: -------------------------------------------------------------------------------- 1 | package memloader 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/jchv/go-winloader/internal/loader" 7 | ) 8 | 9 | // Cache implements a memory cache for PE modules. 10 | type Cache struct { 11 | next loader.Loader 12 | cache map[string]loader.Module 13 | } 14 | 15 | // NewCache creates a new cache with the specified options. 16 | func NewCache(next loader.Loader) *Cache { 17 | return &Cache{ 18 | next: next, 19 | cache: make(map[string]loader.Module), 20 | } 21 | } 22 | 23 | // Load implements loader.Loader by loading from cache or falling back. 24 | func (c *Cache) Load(libname string) (loader.Module, error) { 25 | if m, ok := c.cache[strings.ToLower(libname)]; ok { 26 | return m, nil 27 | } 28 | if m, ok := c.cache[strings.ToLower(libname)+".dll"]; ok { 29 | return m, nil 30 | } 31 | return c.next.Load(libname) 32 | } 33 | 34 | // Add adds a module to the cache. 35 | func (c *Cache) Add(libname string, m loader.Module) error { 36 | libname = strings.ToLower(libname) 37 | if strings.HasSuffix(libname, ".dll") { 38 | libname = libname[0 : len(libname)-4] 39 | } 40 | c.cache[libname] = m 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /internal/memloader/loader.go: -------------------------------------------------------------------------------- 1 | package memloader 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "io" 8 | 9 | "github.com/jchv/go-winloader/internal/loader" 10 | "github.com/jchv/go-winloader/internal/pe" 11 | "github.com/jchv/go-winloader/internal/vmem" 12 | "github.com/jchv/go-winloader/internal/winloader" 13 | ) 14 | 15 | // module implements a module for the memory loader. 16 | type module struct { 17 | machine loader.Machine 18 | memory loader.Memory 19 | pemod *pe.Module 20 | exports *pe.ExportTable 21 | hinstance uint64 22 | } 23 | 24 | // Proc implements loader.Module 25 | func (m *module) Proc(name string) loader.Proc { 26 | addr := m.exports.Proc(name) 27 | if addr == 0 { 28 | return nil 29 | } 30 | return m.machine.MemProc(addr) 31 | } 32 | 33 | // Ordinal implements loader.Module 34 | func (m *module) Ordinal(ordinal uint64) loader.Proc { 35 | addr := m.exports.Ordinal(uint16(ordinal)) 36 | if addr == 0 { 37 | return nil 38 | } 39 | return m.machine.MemProc(addr) 40 | } 41 | 42 | // Free implements loader.Module 43 | func (m *module) Free() error { 44 | // Execute entrypoint for detach. 45 | entry := m.machine.MemProc(m.memory.Addr() + uint64(m.pemod.Header.OptionalHeader.AddressOfEntryPoint)) 46 | entry.Call(uint64(m.memory.Addr()), 0, 0) 47 | 48 | // Free memory. 49 | m.memory.Free() 50 | return nil 51 | } 52 | 53 | // Loader implements a memory loader for PE files. 54 | type Loader struct { 55 | next loader.Loader 56 | machine loader.Machine 57 | pebhacks bool 58 | prochinst bool 59 | } 60 | 61 | // Options contains the options for creating a new memory loader. 62 | type Options struct { 63 | // Next specifies the loader to use for recursing to resolve modules by 64 | // name. 65 | Next loader.Loader 66 | 67 | // Machine specifies the machine the module should be loaded into. 68 | Machine loader.Machine 69 | 70 | // HintAddModuleToPEB specifies that the memory loader should try to add 71 | // the loaded module into the PEB so that certain things function as 72 | // expected. 73 | // NOTE: This is not implemented yet and may not be possible. 74 | HintAddModuleToPEB bool 75 | 76 | // HintUseProcessHInstance specifies that the memory loader should use the 77 | // host process's HINSTANCE value for calling into entrypoints. 78 | HintUseProcessHInstance bool 79 | } 80 | 81 | // New creates a new loader with the specified options. 82 | func New(opts Options) loader.MemLoader { 83 | return &Loader{ 84 | next: opts.Next, 85 | machine: opts.Machine, 86 | } 87 | } 88 | 89 | // LoadMem implements the loader.MemLoader interface. 90 | func (l *Loader) LoadMem(data []byte) (loader.Module, error) { 91 | bin, err := pe.LoadModule(bytes.NewReader(data)) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | if !l.machine.IsArchitectureSupported(int(bin.Header.FileHeader.Machine)) { 97 | return nil, fmt.Errorf("image architecture not %04x not supported by this machine", bin.Header.FileHeader.Machine) 98 | } 99 | 100 | pageSize := l.machine.GetPageSize() 101 | imageSize := vmem.RoundUp(uint64(bin.Header.OptionalHeader.SizeOfImage), pageSize) 102 | 103 | var mem loader.Memory 104 | 105 | // If the image is not movable, allocate it at its preferred address. 106 | if bin.Header.OptionalHeader.DllCharacteristics&pe.ImageDLLCharacteristicsDynamicBase == 0 { 107 | mem = l.machine.Alloc(bin.Header.OptionalHeader.ImageBase, imageSize, vmem.MemCommit|vmem.MemReserve, vmem.PageExecuteReadWrite) 108 | if mem == nil { 109 | return nil, fmt.Errorf("image could not be mapped at preferred base 0x%08x and cannot be relocated", bin.Header.OptionalHeader.ImageBase) 110 | } 111 | } 112 | 113 | // Allocate anywhere, so as long as the image won't span a 4 GiB 114 | // alignment boundary. 115 | failedAllocs := []loader.Memory{} 116 | for mem == nil || mem.Addr()>>32 != (mem.Addr()+imageSize)>>32 { 117 | if mem != nil { 118 | failedAllocs = append(failedAllocs, mem) 119 | } 120 | if mem = l.machine.Alloc(0, imageSize, vmem.MemCommit|vmem.MemReserve, vmem.PageExecuteReadWrite); mem == nil { 121 | return nil, fmt.Errorf("allocation of %d bytes failed", imageSize) 122 | } 123 | } 124 | for _, i := range failedAllocs { 125 | i.Free() 126 | } 127 | 128 | realBase := mem.Addr() 129 | hdrsize := uint64(bin.Header.OptionalHeader.SizeOfHeaders) 130 | vmem.Alloc(realBase, hdrsize, vmem.MemCommit, vmem.PageReadWrite).Write(data[0:hdrsize]) 131 | 132 | // Map sections into memory 133 | for _, section := range bin.Sections { 134 | addr := realBase + uint64(section.VirtualAddress) 135 | if section.SizeOfRawData == 0 { 136 | size := uint64(bin.Header.OptionalHeader.SectionAlignment) 137 | if size != 0 { 138 | vmem.Alloc(addr, size, vmem.MemCommit, vmem.PageReadWrite).Clear() 139 | } 140 | } else { 141 | sectionData := data[section.PointerToRawData : section.PointerToRawData+section.SizeOfRawData] 142 | vmem.Alloc(addr, uint64(section.SizeOfRawData), vmem.MemCommit, vmem.PageReadWrite).Write(sectionData) 143 | } 144 | // TODO: need to set Misc.PhysicalAddress? 145 | } 146 | 147 | // TODO: Detect native byte order for relocations. 148 | order := binary.LittleEndian 149 | machine := int(bin.Header.FileHeader.Machine) 150 | 151 | // Perform relocations 152 | relocs := pe.LoadBaseRelocs(bin, mem) 153 | if err := pe.Relocate(machine, relocs, uint64(realBase), bin.Header.OptionalHeader.ImageBase, mem, order); err != nil { 154 | return nil, err 155 | } 156 | 157 | // Perform runtime linking 158 | if err := pe.LinkModule(bin, mem, l.next); err != nil { 159 | return nil, err 160 | } 161 | 162 | // Set access flags. 163 | for _, section := range bin.Sections { 164 | executable := section.Characteristics&pe.ImageSectionCharacteristicsMemoryExecute != 0 165 | readable := section.Characteristics&pe.ImageSectionCharacteristicsMemoryRead != 0 166 | writable := section.Characteristics&pe.ImageSectionCharacteristicsMemoryWrite != 0 167 | protect := vmem.PageNoAccess 168 | switch { 169 | case !executable && !readable && !writable: 170 | protect = vmem.PageNoAccess 171 | case !executable && !readable && writable: 172 | protect = vmem.PageWriteCopy 173 | case !executable && readable && !writable: 174 | protect = vmem.PageReadOnly 175 | case !executable && readable && writable: 176 | protect = vmem.PageReadWrite 177 | case executable && !readable && !writable: 178 | protect = vmem.PageExecute 179 | case executable && !readable && writable: 180 | protect = vmem.PageExecuteWriteCopy 181 | case executable && readable && !writable: 182 | protect = vmem.PageExecuteRead 183 | case executable && readable && writable: 184 | protect = vmem.PageExecuteReadWrite 185 | } 186 | size := uint64(section.SizeOfRawData) 187 | if size == 0 { 188 | size = uint64(bin.Header.OptionalHeader.SectionAlignment) 189 | } 190 | err := mem.Protect(uint64(section.VirtualAddress), size, protect) 191 | if err != nil { 192 | return nil, err 193 | } 194 | } 195 | 196 | // Handle HINSTANCE setup. 197 | hinstance := realBase 198 | if l.pebhacks { 199 | // TODO: implement PEB loader hacks, see if it works. 200 | } 201 | if l.prochinst { 202 | if prochinst, err := winloader.GetProcessHInstance(); err == nil { 203 | hinstance = uint64(prochinst) 204 | } 205 | } 206 | 207 | m := &module{ 208 | machine: l.machine, 209 | memory: mem, 210 | pemod: bin, 211 | hinstance: hinstance, 212 | } 213 | 214 | // Execute TLS callbacks. 215 | tlsdir := bin.Header.OptionalHeader.DataDirectory[pe.ImageDirectoryEntryTLS] 216 | if tlsdir.Size > 0 { 217 | mem.Seek(int64(tlsdir.VirtualAddress), io.SeekStart) 218 | dir := pe.ImageTLSDirectory64{} 219 | b := [8]byte{} 220 | psize := 4 221 | if bin.IsPE64 { 222 | psize = 8 223 | binary.Read(mem, binary.LittleEndian, &dir) 224 | } else { 225 | dir32 := pe.ImageTLSDirectory32{} 226 | binary.Read(mem, binary.LittleEndian, &dir32) 227 | dir = dir32.To64() 228 | } 229 | mem.Seek(int64(dir.AddressOfCallBacks-realBase), io.SeekStart) 230 | if dir.AddressOfCallBacks != 0 { 231 | for { 232 | mem.Read(b[:psize]) 233 | addr := binary.LittleEndian.Uint64(b[:]) 234 | if addr == 0 { 235 | break 236 | } 237 | cb := l.machine.MemProc(addr) 238 | cb.Call(hinstance, 1, 0) 239 | } 240 | } 241 | } 242 | 243 | // Execute entrypoint for attach. 244 | entry := l.machine.MemProc(realBase + uint64(bin.Header.OptionalHeader.AddressOfEntryPoint)) 245 | entry.Call(hinstance, 1, 0) 246 | 247 | m.exports, err = pe.LoadExports(bin, mem, realBase) 248 | if err != nil { 249 | return nil, err 250 | } 251 | 252 | return m, nil 253 | } 254 | -------------------------------------------------------------------------------- /internal/pe/export.go: -------------------------------------------------------------------------------- 1 | package pe 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | ) 7 | 8 | // ExportTable is a table of module exports. 9 | type ExportTable struct { 10 | symbols map[string]uint64 11 | ordinals map[uint16]uint64 12 | } 13 | 14 | // Proc returns an exported function address by symbol, or 0 if it is not found. 15 | func (t *ExportTable) Proc(symbol string) (addr uint64) { 16 | return t.symbols[symbol] 17 | } 18 | 19 | // Ordinal returns an exported function address by ordinal, or 0 if it is not found. 20 | func (t *ExportTable) Ordinal(ordinal uint16) (addr uint64) { 21 | return t.ordinals[ordinal] 22 | } 23 | 24 | // LoadExports returns a symbol table. 25 | func LoadExports(m *Module, mem io.ReadWriteSeeker, base uint64) (*ExportTable, error) { 26 | table := &ExportTable{ 27 | symbols: map[string]uint64{}, 28 | ordinals: map[uint16]uint64{}, 29 | } 30 | 31 | dir := m.Header.OptionalHeader.DataDirectory[ImageDirectoryEntryExport] 32 | if dir.Size == 0 { 33 | return table, nil 34 | } 35 | 36 | // Load export directory header 37 | header := ImageExportDirectory{} 38 | mem.Seek(int64(dir.VirtualAddress), io.SeekStart) 39 | binary.Read(mem, binary.LittleEndian, &header) 40 | 41 | // Load addresses 42 | addresses := make([]uint32, header.NumberOfFunctions) 43 | mem.Seek(int64(header.AddressOfFunctions), io.SeekStart) 44 | for i := range addresses { 45 | b := [4]byte{} 46 | mem.Read(b[:]) 47 | addresses[i] = binary.LittleEndian.Uint32(b[:]) 48 | table.ordinals[uint16(i)] = base + uint64(addresses[i]) 49 | } 50 | 51 | // Load name ordinals 52 | nameords := make([]uint16, header.NumberOfNames) 53 | mem.Seek(int64(header.AddressOfNameOrdinals), io.SeekStart) 54 | for i := range nameords { 55 | b := [2]byte{} 56 | mem.Read(b[:]) 57 | nameords[i] = binary.LittleEndian.Uint16(b[:]) 58 | } 59 | 60 | // Load name addresses 61 | nameaddrs := make([]uint32, header.NumberOfNames) 62 | mem.Seek(int64(header.AddressOfNames), io.SeekStart) 63 | for i := range nameaddrs { 64 | b := [4]byte{} 65 | mem.Read(b[:]) 66 | nameaddrs[i] = binary.LittleEndian.Uint32(b[:]) 67 | } 68 | 69 | // Load names 70 | for i, nameaddr := range nameaddrs { 71 | mem.Seek(int64(nameaddr), io.SeekStart) 72 | table.symbols[readsz(mem)] = base + uint64(addresses[nameords[i]]) 73 | } 74 | 75 | return table, nil 76 | } 77 | -------------------------------------------------------------------------------- /internal/pe/format.go: -------------------------------------------------------------------------------- 1 | package pe 2 | 3 | // CONSTANTS / MAGIC NUMBERS 4 | 5 | // MZSignature is the signature of the MZ format. This is the value of the 6 | // Signature field in ImageDOSHeader. 7 | var MZSignature = [2]byte{'M', 'Z'} 8 | 9 | // PESignature is the signature of the PE format. This is the value of the 10 | // Signature field in ImageNTHeaders32 and ImageNTHeaders64. 11 | var PESignature = [4]byte{'P', 'E', 0, 0} 12 | 13 | // Enumeration of magic numbers 14 | const ( 15 | // ImageNTOptionalHeader32Magic is the magic number for 32-bit optional 16 | // header (ImageOptionalHeader32) 17 | ImageNTOptionalHeader32Magic = 0x010b 18 | 19 | // ImageNTOptionalHeader64Magic is the magic number for 64-bit optional 20 | // header (ImageOptionalHeader64) 21 | ImageNTOptionalHeader64Magic = 0x020b 22 | ) 23 | 24 | // Enumeration of structure lengths. 25 | const ( 26 | // SizeOfImageDOSHeader is the on-disk size of the ImageDOSHeader 27 | // structure. 28 | SizeOfImageDOSHeader = 64 29 | 30 | // SizeOfImageFileHeader is the on-disk size of the ImageFileHeader 31 | // structure. 32 | SizeOfImageFileHeader = 20 33 | 34 | // SizeOfImageOptionalHeader32 is the on-disk size of the 35 | // ImageOptionalHeader32 structure. 36 | SizeOfImageOptionalHeader32 = 224 37 | 38 | // SizeOfImageOptionalHeader64 is the on-disk size of the 39 | // ImageOptionalHeader64 structure. 40 | SizeOfImageOptionalHeader64 = 240 41 | 42 | // SizeOfImageNTHeaders32 is the on-disk size of the ImageNTHeaders32 43 | // structure. 44 | SizeOfImageNTHeaders32 = 248 45 | 46 | // SizeOfImageNTHeaders64 is the on-disk size of the ImageNTHeaders64 47 | // structure. 48 | SizeOfImageNTHeaders64 = 264 49 | 50 | // SizeOfImageDataDirectory is the on-disk size of the ImageDataDirectory 51 | // structure. 52 | SizeOfImageDataDirectory = 8 53 | ) 54 | 55 | // Enumeration of useful field offsets. 56 | const ( 57 | // OffsetOfOptionalHeaderFromNTHeader is the offset from the start of 58 | // the NT header to the optional header magic value. This is helpful for 59 | // determining if the PE file is PE32 or PE64. 60 | OffsetOfOptionalHeaderFromNTHeader = 0x18 61 | ) 62 | 63 | // Enumeration of fixed-size array lengths in PE 64 | const ( 65 | // NumDirectoryEntries specifies the number of data directory entries. 66 | NumDirectoryEntries = 16 67 | 68 | // SectionNameLength is the size of a section short name. 69 | SectionNameLength = 8 70 | ) 71 | 72 | // Enumeration of known Windows Loader limits. (Some of these may not be 73 | // imposed by the format itself and purely by Windows runtime.) 74 | const ( 75 | // MaxNumSections specifies the maximum number of sections that are 76 | // allowed. This is imposed by Windows Loader. 77 | MaxNumSections = 96 78 | ) 79 | 80 | // ENUMERATION VALUES 81 | 82 | // Enumeration of machine values for the file header. 83 | const ( 84 | ImageFileMachineUnknown = 0x0000 85 | ImageFileMachineTargetHost = 0x0001 86 | ImageFileMachinei386 = 0x014c 87 | ImageFileMachineR3000BE = 0x0160 88 | ImageFileMachineR3000 = 0x0162 89 | ImageFileMachineR4000 = 0x0166 90 | ImageFileMachineR10000 = 0x0168 91 | ImageFileMachineWCEMIPSv2 = 0x0169 92 | ImageFileMachineAlpha = 0x0184 93 | ImageFileMachineSH3 = 0x01a2 94 | ImageFileMachineSH3DSP = 0x01a3 95 | ImageFileMachineSH3E = 0x01a4 96 | ImageFileMachineSH4 = 0x01a6 97 | ImageFileMachineSH5 = 0x01a8 98 | ImageFileMachineARM = 0x01c0 99 | ImageFileMachineTHUMB = 0x01c2 100 | ImageFileMachineARMNT = 0x01c4 101 | ImageFileMachineAM33 = 0x01d3 102 | ImageFileMachinePowerPC = 0x01F0 103 | ImageFileMachinePowerPCFP = 0x01f1 104 | ImageFileMachineIA64 = 0x0200 105 | ImageFileMachineMIPS16 = 0x0266 106 | ImageFileMachineAlpha64 = 0x0284 107 | ImageFileMachineMIPSFPU = 0x0366 108 | ImageFileMachineMIPSFPU16 = 0x0466 109 | ImageFileMachineAXP64 = ImageFileMachineAlpha64 110 | ImageFileMachineTricore = 0x0520 111 | ImageFileMachineCEF = 0x00CE 112 | ImageFileMachineEBC = 0x0EBC 113 | ImageFileMachineAMD64 = 0x8664 114 | ImageFileMachineM32R = 0x9041 115 | ImageFileMachineARM64 = 0xAA64 116 | ImageFileMachineCEE = 0x0C0E 117 | ImageFileMachineRISCV32 = 0x5032 118 | ImageFileMachineRISCV64 = 0x5064 119 | ImageFileMachineRISCV128 = 0x5128 120 | ) 121 | 122 | // Enumeration of charateristics values for the file header. 123 | const ( 124 | ImageFileRelocsStripped = 0x0001 125 | ImageFileExecutableImage = 0x0002 126 | ImageFileLineNumsStripped = 0x0004 127 | ImageFileLocalSymsStripped = 0x0008 128 | ImageFileAggressiveWSTrim = 0x0010 129 | ImageFileLargeAddressAware = 0x0020 130 | ImageFileBytesReversedLo = 0x0080 131 | ImageFile32BitMachine = 0x0100 132 | ImageFileDebugStripped = 0x0200 133 | ImageFileRemovableRunFromSwap = 0x0400 134 | ImageFileNetRunFromSwap = 0x0800 135 | ImageFileSystem = 0x1000 136 | ImageFileDLL = 0x2000 137 | ImageFileUPSystemOnly = 0x4000 138 | ImageFileBytesReversedHi = 0x8000 139 | ) 140 | 141 | // Enumeration of image subsystem values. 142 | const ( 143 | ImageSubsystemUnknown = 0 144 | ImageSubsystemNative = 1 145 | ImageSubsystemWindowsGUI = 2 146 | ImageSubsystemWindowsCUI = 3 147 | ImageSubsystemOS2CUI = 5 148 | ImageSubsystemPOSIXCUI = 7 149 | ImageSubsystemNativeWindows = 8 150 | ImageSubsystemWindowsCEGUI = 9 151 | ImageSubsystemEFIApplication = 10 152 | ImageSubsystemEFIBootServiceDriver = 11 153 | ImageSubsystemEFIRuntimeDriver = 12 154 | ImageSubsystemEFIROM = 13 155 | ImageSubsystemXBox = 14 156 | ImageSubsystemWindowsBootApplication = 16 157 | ImageSubsystemXBoxCodeCatalog = 17 158 | ) 159 | 160 | // Enumeration of DLL characteristics values. 161 | const ( 162 | ImageDLLCharacteristicsHighEntropyVA = 0x0020 163 | ImageDLLCharacteristicsDynamicBase = 0x0040 164 | ImageDLLCharacteristicsForceIntegrity = 0x0080 165 | ImageDLLCharacteristicsNXCompat = 0x0100 166 | ImageDLLCharacteristicsNoIsolation = 0x0200 167 | ImageDLLCharacteristicsNoSEH = 0x0400 168 | ImageDLLCharacteristicsNoBind = 0x0800 169 | ImageDLLCharacteristicsAppContainer = 0x1000 170 | ImageDLLCharacteristicsWDMDriver = 0x2000 171 | ImageDLLCharacteristicsGuardCF = 0x4000 172 | ImageDLLCharacteristicsTerminalServerAware = 0x8000 173 | ) 174 | 175 | // Enumeration of image directory entry indexes. These represent indices into 176 | // the data directory array of the optional header. 177 | const ( 178 | ImageDirectoryEntryExport = 0 179 | ImageDirectoryEntryImport = 1 180 | ImageDirectoryEntryResource = 2 181 | ImageDirectoryEntryException = 3 182 | ImageDirectoryEntrySecurity = 4 183 | ImageDirectoryEntryBaseReloc = 5 184 | ImageDirectoryEntryDebug = 6 185 | ImageDirectoryEntryCopyright = 7 186 | ImageDirectoryEntryArchitecture = 7 187 | ImageDirectoryEntryGlobalPtr = 8 188 | ImageDirectoryEntryTLS = 9 189 | ImageDirectoryEntryLoadConfig = 10 190 | ImageDirectoryEntryBoundImport = 11 191 | ImageDirectoryEntryIAT = 12 192 | ImageDirectoryEntryDelayImport = 13 193 | ImageDirectoryEntryCOMDescriptor = 14 194 | ) 195 | 196 | // Enumeration of image section characteristics. 197 | const ( 198 | ImageSectionCharacteristicsNoPad = 0x00000008 199 | ImageSectionCharacteristicsContainsCode = 0x00000020 200 | ImageSectionCharacteristicsContainsInitializedData = 0x00000040 201 | ImageSectionCharacteristicsContainsUninitailizedData = 0x00000080 202 | ImageSectionCharacteristicsLinkOther = 0x00000100 203 | ImageSectionCharacteristicsLinkInfo = 0x00000200 204 | ImageSectionCharacteristicsLinkRemove = 0x00000800 205 | ImageSectionCharacteristicsLinkCOMDAT = 0x00001000 206 | ImageSectionCharacteristicsNoDeferSpecExc = 0x00004000 207 | ImageSectionCharacteristicsGPRel = 0x00008000 208 | ImageSectionCharacteristicsMemoryFarData = 0x00008000 209 | ImageSectionCharacteristicsMemoryPurgeable = 0x00020000 210 | ImageSectionCharacteristicsMemory16Bit = 0x00020000 211 | ImageSectionCharacteristicsMemoryLocked = 0x00040000 212 | ImageSectionCharacteristicsMemoryPreload = 0x00080000 213 | ImageSectionCharacteristicsAlign1Bytes = 0x00100000 214 | ImageSectionCharacteristicsAlign2Bytes = 0x00200000 215 | ImageSectionCharacteristicsAlign4Bytes = 0x00300000 216 | ImageSectionCharacteristicsAlign8Bytes = 0x00400000 217 | ImageSectionCharacteristicsAlign16Bytes = 0x00500000 218 | ImageSectionCharacteristicsAlign32Bytes = 0x00600000 219 | ImageSectionCharacteristicsAlign64Bytes = 0x00700000 220 | ImageSectionCharacteristicsAlign128Bytes = 0x00800000 221 | ImageSectionCharacteristicsAlign256Bytes = 0x00900000 222 | ImageSectionCharacteristicsAlign512Bytes = 0x00A00000 223 | ImageSectionCharacteristicsAlign1024Bytes = 0x00B00000 224 | ImageSectionCharacteristicsAlign2048Bytes = 0x00C00000 225 | ImageSectionCharacteristicsAlign4096Bytes = 0x00D00000 226 | ImageSectionCharacteristicsAlign8192Bytes = 0x00E00000 227 | ImageSectionCharacteristicsAlignMask = 0x00F00000 228 | ImageSectionCharacteristicsLinkNumRelocOverflow = 0x01000000 229 | ImageSectionCharacteristicsMemoryDiscardable = 0x02000000 230 | ImageSectionCharacteristicsMemoryNotCached = 0x04000000 231 | ImageSectionCharacteristicsMemoryNotPaged = 0x08000000 232 | ImageSectionCharacteristicsMemoryShared = 0x10000000 233 | ImageSectionCharacteristicsMemoryExecute = 0x20000000 234 | ImageSectionCharacteristicsMemoryRead = 0x40000000 235 | ImageSectionCharacteristicsMemoryWrite = 0x80000000 236 | ) 237 | 238 | // Enumeration of TLS characteristics. 239 | const ( 240 | ImageSectionTLSCharacteristicsScaleIndex = 0x00000001 241 | ) 242 | 243 | // Enumeration of relocation types. 244 | const ( 245 | ImageRelBasedAbsolute = 0 246 | ImageRelBasedHigh = 1 247 | ImageRelBasedLow = 2 248 | ImageRelBasedHighLow = 3 249 | ImageRelBasedHighAdj = 4 250 | ImageRelBasedMachineSpecific5 = 5 251 | ImageRelBasedReserved = 6 252 | ImageRelBasedMachineSpecific7 = 7 253 | ImageRelBasedMachineSpecific8 = 8 254 | ImageRelBasedMachineSpecific9 = 9 255 | ImageRelBasedDir64 = 10 256 | ) 257 | 258 | // ImageDOSHeader is the structure of the DOS MZ Executable format. All PE 259 | // files contain at least a valid stub DOS MZ executable at the top; the PE 260 | // format itself starts at the address specified by NewHeaderAddr. 261 | type ImageDOSHeader struct { 262 | Signature [2]byte 263 | LastPageBytes uint16 264 | CountPages uint16 265 | CountRelocs uint16 266 | HeaderLen uint16 267 | MinAlloc uint16 268 | MaxAlloc uint16 269 | InitialSS uint16 270 | InitialSP uint16 271 | Checksum uint16 272 | InitialIP uint16 273 | InitialCS uint16 274 | RelocAddr uint16 275 | OverlayNum uint16 276 | Reserved [4]uint16 277 | OEMID uint16 278 | OEMInfo uint16 279 | Reserved2 [10]uint16 280 | NewHeaderAddr uint32 281 | } 282 | 283 | // ImageFileHeader contains some of the basic attributes about the PE/COFF 284 | // file, including the number of sections and the machine type. 285 | type ImageFileHeader struct { 286 | Machine uint16 287 | NumberOfSections uint16 288 | TimeDateStamp uint32 289 | PointerToSymbolTable uint32 290 | NumberOfSymbols uint32 291 | SizeOfOptionalHeader uint16 292 | Characteristics uint16 293 | } 294 | 295 | // ImageOptionalHeader32 contains the optional header for 32-bit PE images. It 296 | // is only 'optional' in the sense that not all PE/COFF binaries have it, 297 | // however it is required for executables and DLLs. 298 | type ImageOptionalHeader32 struct { 299 | Magic uint16 300 | MajorLinkerVersion uint8 301 | MinorLinkerVersion uint8 302 | SizeOfCode uint32 303 | SizeOfInitializedData uint32 304 | SizeOfUninitializedData uint32 305 | AddressOfEntryPoint uint32 306 | BaseOfCode uint32 307 | BaseOfData uint32 308 | 309 | ImageBase uint32 310 | SectionAlignment uint32 311 | FileAlignment uint32 312 | MajorOperatingSystemVersion uint16 313 | MinorOperatingSystemVersion uint16 314 | MajorImageVersion uint16 315 | MinorImageVersion uint16 316 | MajorSubsystemVersion uint16 317 | MinorSubsystemVersion uint16 318 | Win32VersionValue uint32 319 | SizeOfImage uint32 320 | SizeOfHeaders uint32 321 | CheckSum uint32 322 | Subsystem uint16 323 | DllCharacteristics uint16 324 | SizeOfStackReserve uint32 325 | SizeOfStackCommit uint32 326 | SizeOfHeapReserve uint32 327 | SizeOfHeapCommit uint32 328 | LoaderFlags uint32 329 | NumberOfRvaAndSizes uint32 330 | DataDirectory [NumDirectoryEntries]ImageDataDirectory 331 | } 332 | 333 | // ImageOptionalHeader64 contains the optional header for 64-bit PE images. 334 | type ImageOptionalHeader64 struct { 335 | Magic uint16 336 | MajorLinkerVersion uint8 337 | MinorLinkerVersion uint8 338 | SizeOfCode uint32 339 | SizeOfInitializedData uint32 340 | SizeOfUninitializedData uint32 341 | AddressOfEntryPoint uint32 342 | BaseOfCode uint32 343 | ImageBase uint64 344 | SectionAlignment uint32 345 | FileAlignment uint32 346 | MajorOperatingSystemVersion uint16 347 | MinorOperatingSystemVersion uint16 348 | MajorImageVersion uint16 349 | MinorImageVersion uint16 350 | MajorSubsystemVersion uint16 351 | MinorSubsystemVersion uint16 352 | Win32VersionValue uint32 353 | SizeOfImage uint32 354 | SizeOfHeaders uint32 355 | CheckSum uint32 356 | Subsystem uint16 357 | DllCharacteristics uint16 358 | SizeOfStackReserve uint64 359 | SizeOfStackCommit uint64 360 | SizeOfHeapReserve uint64 361 | SizeOfHeapCommit uint64 362 | LoaderFlags uint32 363 | NumberOfRvaAndSizes uint32 364 | DataDirectory [NumDirectoryEntries]ImageDataDirectory 365 | } 366 | 367 | // To64 converts the ImageOptionalHeader32 to an ImageOptionalHeader64. 368 | func (i ImageOptionalHeader32) To64() ImageOptionalHeader64 { 369 | return ImageOptionalHeader64{ 370 | Magic: i.Magic, 371 | MajorLinkerVersion: i.MajorLinkerVersion, 372 | MinorLinkerVersion: i.MinorLinkerVersion, 373 | SizeOfCode: i.SizeOfCode, 374 | SizeOfInitializedData: i.SizeOfInitializedData, 375 | SizeOfUninitializedData: i.SizeOfUninitializedData, 376 | AddressOfEntryPoint: i.AddressOfEntryPoint, 377 | BaseOfCode: i.BaseOfCode, 378 | ImageBase: uint64(i.ImageBase), 379 | SectionAlignment: i.SectionAlignment, 380 | FileAlignment: i.FileAlignment, 381 | MajorOperatingSystemVersion: i.MajorOperatingSystemVersion, 382 | MinorOperatingSystemVersion: i.MinorOperatingSystemVersion, 383 | MajorImageVersion: i.MajorImageVersion, 384 | MinorImageVersion: i.MinorImageVersion, 385 | MajorSubsystemVersion: i.MajorSubsystemVersion, 386 | MinorSubsystemVersion: i.MinorSubsystemVersion, 387 | Win32VersionValue: i.Win32VersionValue, 388 | SizeOfImage: i.SizeOfImage, 389 | SizeOfHeaders: i.SizeOfHeaders, 390 | CheckSum: i.CheckSum, 391 | Subsystem: i.Subsystem, 392 | DllCharacteristics: i.DllCharacteristics, 393 | SizeOfStackReserve: uint64(i.SizeOfStackReserve), 394 | SizeOfStackCommit: uint64(i.SizeOfStackCommit), 395 | SizeOfHeapReserve: uint64(i.SizeOfHeapReserve), 396 | SizeOfHeapCommit: uint64(i.SizeOfHeapCommit), 397 | LoaderFlags: i.LoaderFlags, 398 | NumberOfRvaAndSizes: i.NumberOfRvaAndSizes, 399 | DataDirectory: i.DataDirectory, 400 | } 401 | } 402 | 403 | // ImageNTHeaders32 contains the PE file headers for 32-bit PE images. 404 | type ImageNTHeaders32 struct { 405 | // Signature identifies the PE format; Always "PE\0\0". 406 | Signature [4]byte 407 | FileHeader ImageFileHeader 408 | OptionalHeader ImageOptionalHeader32 409 | } 410 | 411 | // ImageNTHeaders64 contains the PE file headers for 64-bit PE images. 412 | type ImageNTHeaders64 struct { 413 | // Signature identifies the PE format; Always "PE\0\0". 414 | Signature [4]byte 415 | FileHeader ImageFileHeader 416 | OptionalHeader ImageOptionalHeader64 417 | } 418 | 419 | // To64 converts the ImageNTHeaders32 to an ImageNTHeaders64. 420 | func (i ImageNTHeaders32) To64() ImageNTHeaders64 { 421 | return ImageNTHeaders64{ 422 | Signature: i.Signature, 423 | FileHeader: i.FileHeader, 424 | OptionalHeader: i.OptionalHeader.To64(), 425 | } 426 | } 427 | 428 | // ImageDataDirectory holds a record for the given data directory. Each data 429 | // directory contains information about another section, such as the import 430 | // table. The index of the directory entry determines which section it 431 | // pertains to. 432 | type ImageDataDirectory struct { 433 | VirtualAddress uint32 434 | Size uint32 435 | } 436 | 437 | // ImageSectionHeader is the header for a section. Windows Loader uses these 438 | // entries to configure the memory mapping of the executable. A series of 439 | // these structures immediately follow the headers. 440 | type ImageSectionHeader struct { 441 | Name [SectionNameLength]byte 442 | PhysicalAddressOrVirtualSize uint32 443 | VirtualAddress uint32 444 | SizeOfRawData uint32 445 | PointerToRawData uint32 446 | PointerToRelocations uint32 447 | PointerToLinenumbers uint32 448 | NumberOfRelocations uint16 449 | NumberOfLinenumbers uint16 450 | Characteristics uint32 451 | } 452 | 453 | // ImageBaseRelocation holds the header for a single page of base relocation 454 | // data. The .reloc section of the binary contains a series of blocks of 455 | // base relocation data, each starting with this header and followed by n 456 | // 16-bit values that each represent a single relocation. The 4 most 457 | // significant bits specify the type of relocation, while the 12 least 458 | // significant bits contain the lower bits of the address (which is combined 459 | // with the virtual address of the page.) The SizeOfBlock value specifies the 460 | // size of an entire block, in bytes, including its header. 461 | type ImageBaseRelocation struct { 462 | VirtualAddress uint32 463 | SizeOfBlock uint32 464 | } 465 | 466 | // The ImageImportDescriptor contains information about an imported module. 467 | type ImageImportDescriptor struct { 468 | OriginalFirstThunk uint32 469 | TimeDateStamp uint32 470 | ForwarderChain uint32 471 | Name uint32 472 | FirstThunk uint32 473 | } 474 | 475 | // The ImageExportDirectory contains information about the module's exports. 476 | type ImageExportDirectory struct { 477 | Characteristics uint32 478 | TimeDateStamp uint32 479 | MajorVersion uint16 480 | MinorVersion uint16 481 | Name uint32 482 | Base uint32 483 | NumberOfFunctions uint32 484 | NumberOfNames uint32 485 | AddressOfFunctions uint32 486 | AddressOfNames uint32 487 | AddressOfNameOrdinals uint32 488 | } 489 | 490 | // ImageTLSDirectory32 contains information about the module's thread local 491 | // storage callbacks (in PE32) 492 | type ImageTLSDirectory32 struct { 493 | StartAddressOfRawData uint32 494 | EndAddressOfRawData uint32 495 | AddressOfIndex uint32 496 | AddressOfCallBacks uint32 497 | SizeOfZeroFill uint32 498 | Characteristics uint32 499 | } 500 | 501 | // ImageTLSDirectory64 contains information about the module's thread local 502 | // storage callbacks (in PE64) 503 | type ImageTLSDirectory64 struct { 504 | StartAddressOfRawData uint64 505 | EndAddressOfRawData uint64 506 | AddressOfIndex uint64 507 | AddressOfCallBacks uint64 508 | SizeOfZeroFill uint32 509 | Characteristics uint32 510 | } 511 | 512 | // To64 converts the ImageTLSDirectory32 to an ImageTLSDirectory64. 513 | func (i ImageTLSDirectory32) To64() ImageTLSDirectory64 { 514 | return ImageTLSDirectory64{ 515 | StartAddressOfRawData: uint64(i.StartAddressOfRawData), 516 | EndAddressOfRawData: uint64(i.EndAddressOfRawData), 517 | AddressOfIndex: uint64(i.AddressOfIndex), 518 | AddressOfCallBacks: uint64(i.AddressOfCallBacks), 519 | SizeOfZeroFill: i.SizeOfZeroFill, 520 | Characteristics: i.Characteristics, 521 | } 522 | } 523 | -------------------------------------------------------------------------------- /internal/pe/format_test.go: -------------------------------------------------------------------------------- 1 | package pe 2 | 3 | import ( 4 | "encoding/binary" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | // TestSizeValues ensures the size constants are correct. 10 | func TestSizeValues(t *testing.T) { 11 | tests := []struct { 12 | Struct interface{} 13 | Expected int 14 | }{ 15 | {ImageDOSHeader{}, SizeOfImageDOSHeader}, 16 | {ImageFileHeader{}, SizeOfImageFileHeader}, 17 | {ImageOptionalHeader32{}, SizeOfImageOptionalHeader32}, 18 | {ImageOptionalHeader64{}, SizeOfImageOptionalHeader64}, 19 | {ImageNTHeaders32{}, SizeOfImageNTHeaders32}, 20 | {ImageNTHeaders64{}, SizeOfImageNTHeaders64}, 21 | {ImageDataDirectory{}, SizeOfImageDataDirectory}, 22 | } 23 | 24 | for _, test := range tests { 25 | actual := binary.Size(test.Struct) 26 | if actual != test.Expected { 27 | t.Errorf("Size of failed for %s: expected %d, got %d (Δ %d)", reflect.TypeOf(test.Struct).Name(), test.Expected, actual, test.Expected-actual) 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /internal/pe/import.go: -------------------------------------------------------------------------------- 1 | package pe 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | 8 | "github.com/jchv/go-winloader/internal/loader" 9 | ) 10 | 11 | // LinkModule links a PE module in-memory. 12 | func LinkModule(m *Module, mem io.ReadWriteSeeker, ldr loader.Loader) error { 13 | dir := m.Header.OptionalHeader.DataDirectory[ImageDirectoryEntryImport] 14 | if dir.Size == 0 { 15 | return nil 16 | } 17 | 18 | // Determine pointer size based on whether we're PE32 or PE64. 19 | psize := 4 20 | if m.IsPE64 { 21 | psize = 8 22 | } 23 | 24 | // Load import descriptors 25 | descs := []ImageImportDescriptor{} 26 | mem.Seek(int64(dir.VirtualAddress), io.SeekStart) 27 | for { 28 | desc := ImageImportDescriptor{} 29 | binary.Read(mem, binary.LittleEndian, &desc) 30 | 31 | if desc.Name == 0 { 32 | break 33 | } 34 | 35 | descs = append(descs, desc) 36 | } 37 | 38 | // Load modules. 39 | for _, desc := range descs { 40 | thunk := int64(desc.OriginalFirstThunk) 41 | iat := int64(desc.FirstThunk) 42 | if thunk == 0 { 43 | thunk = iat 44 | } 45 | 46 | // Read module name 47 | mem.Seek(int64(desc.Name), io.SeekStart) 48 | 49 | // Load library 50 | libname := readsz(mem) 51 | lib, err := ldr.Load(libname) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | // Read thunk addrs 57 | b := [8]byte{} 58 | thunks := []uint64{} 59 | mem.Seek(thunk, io.SeekStart) 60 | for { 61 | mem.Read(b[:psize]) 62 | thunk := binary.LittleEndian.Uint64(b[:]) 63 | if thunk == 0 { 64 | break 65 | } 66 | thunks = append(thunks, thunk) 67 | } 68 | 69 | // Resolve thunks 70 | resolved := []uint64{} 71 | for _, thunk := range thunks { 72 | thunkord := int64(-1) 73 | if (m.IsPE64 && thunk&0x8000000000000000 != 0) || (!m.IsPE64 && thunk&0x80000000 != 0) { 74 | thunkord = int64(thunk & 0xFFFF) 75 | } 76 | if thunkord != -1 { 77 | // Import by ordinal 78 | if proc := lib.Ordinal(uint64(thunkord)); proc != nil { 79 | resolved = append(resolved, proc.Addr()) 80 | } else { 81 | return fmt.Errorf("could not resolve ordinal %d in module %q", thunkord, libname) 82 | } 83 | } else { 84 | // Read name 85 | mem.Seek(int64(thunk+2), io.SeekStart) 86 | fnname := readsz(mem) 87 | 88 | // Import by name 89 | if proc := lib.Proc(fnname); proc != nil { 90 | resolved = append(resolved, proc.Addr()) 91 | } else { 92 | return fmt.Errorf("could not resolve symbol %q in module %q", fnname, libname) 93 | } 94 | } 95 | } 96 | 97 | // Write resolved IAT 98 | mem.Seek(iat, io.SeekStart) 99 | for _, fn := range resolved { 100 | binary.LittleEndian.PutUint64(b[:], uint64(fn)) 101 | mem.Write(b[:psize]) 102 | } 103 | } 104 | 105 | return nil 106 | } 107 | -------------------------------------------------------------------------------- /internal/pe/module.go: -------------------------------------------------------------------------------- 1 | package pe 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "io" 7 | ) 8 | 9 | var ( 10 | // ErrBadMZSignature is returned when the MZ signature is invalid. 11 | ErrBadMZSignature = errors.New("mz: bad signature") 12 | 13 | // ErrBadPESignature is returned when the PE signature is invalid. 14 | ErrBadPESignature = errors.New("pe: bad signature") 15 | 16 | // ErrUnknownOptionalHeaderMagic is returned when the optional header 17 | // magic field has an unknown value. 18 | ErrUnknownOptionalHeaderMagic = errors.New("pe: unknown optional header magic") 19 | ) 20 | 21 | // Module contains a parsed and loaded PE file. 22 | type Module struct { 23 | IsPE64 bool 24 | DOSHeader ImageDOSHeader 25 | Header ImageNTHeaders64 26 | Sections []ImageSectionHeader 27 | } 28 | 29 | // LoadModule loads a PE module into memory. 30 | func LoadModule(r io.ReadSeeker) (*Module, error) { 31 | m := &Module{} 32 | 33 | r.Seek(0, io.SeekStart) 34 | dos := ImageDOSHeader{} 35 | if err := binary.Read(r, binary.LittleEndian, &dos); err != nil { 36 | return nil, err 37 | } 38 | if dos.Signature != MZSignature { 39 | return nil, ErrBadMZSignature 40 | } 41 | m.DOSHeader = dos 42 | 43 | r.Seek(int64(dos.NewHeaderAddr), io.SeekStart) 44 | pesig := [4]byte{} 45 | if err := binary.Read(r, binary.LittleEndian, &pesig); err != nil { 46 | return nil, err 47 | } 48 | 49 | if pesig != PESignature { 50 | return nil, ErrBadPESignature 51 | } 52 | 53 | optmagic := uint16(0) 54 | r.Seek(int64(dos.NewHeaderAddr)+OffsetOfOptionalHeaderFromNTHeader, io.SeekStart) 55 | if err := binary.Read(r, binary.LittleEndian, &optmagic); err != nil { 56 | return nil, err 57 | } 58 | 59 | r.Seek(int64(dos.NewHeaderAddr), io.SeekStart) 60 | switch optmagic { 61 | case ImageNTOptionalHeader32Magic: 62 | m.IsPE64 = false 63 | nt := ImageNTHeaders32{} 64 | if err := binary.Read(r, binary.LittleEndian, &nt); err != nil { 65 | return nil, err 66 | } 67 | m.Header = nt.To64() 68 | case ImageNTOptionalHeader64Magic: 69 | m.IsPE64 = true 70 | nt := ImageNTHeaders64{} 71 | if err := binary.Read(r, binary.LittleEndian, &nt); err != nil { 72 | return nil, err 73 | } 74 | m.Header = nt 75 | default: 76 | return nil, ErrUnknownOptionalHeaderMagic 77 | } 78 | 79 | // Seek past end of optional headers. 80 | r.Seek(int64(dos.NewHeaderAddr)+OffsetOfOptionalHeaderFromNTHeader+int64(m.Header.FileHeader.SizeOfOptionalHeader), io.SeekStart) 81 | 82 | for i := uint16(0); i < m.Header.FileHeader.NumberOfSections; i++ { 83 | section := ImageSectionHeader{} 84 | if err := binary.Read(r, binary.LittleEndian, §ion); err != nil { 85 | return nil, err 86 | } 87 | m.Sections = append(m.Sections, section) 88 | } 89 | 90 | return m, nil 91 | } 92 | -------------------------------------------------------------------------------- /internal/pe/module_test.go: -------------------------------------------------------------------------------- 1 | package pe 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestLoadNothing(t *testing.T) { 12 | _, err := LoadModule(bytes.NewReader([]byte{})) 13 | if err != io.EOF { 14 | t.Errorf("expected EOF got %v", err) 15 | } 16 | } 17 | 18 | func TestLoadBadMZ(t *testing.T) { 19 | _, err := LoadModule(strings.NewReader("This string is long enough to be an MZ executable, but it isn't.")) 20 | if err != ErrBadMZSignature { 21 | t.Errorf("expected ErrBadMZSignature got %v", err) 22 | } 23 | } 24 | 25 | func TestLoadBadPE(t *testing.T) { 26 | _, err := LoadModule(strings.NewReader("MZ, is what I'd say if I were an MZ executable, but I'm not!\000\000\000\000")) 27 | if err != ErrBadPESignature { 28 | t.Errorf("expected ErrBadPESignature got %v", err) 29 | } 30 | } 31 | func TestLoadTinyPE32(t *testing.T) { 32 | f, err := os.Open("../../tinydll/tiny.dll") 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | _, err = LoadModule(f) 37 | if err != nil { 38 | t.Fatalf("expected nil error got %v", err) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /internal/pe/reloc.go: -------------------------------------------------------------------------------- 1 | package pe 2 | 3 | import ( 4 | "encoding/binary" 5 | "errors" 6 | "fmt" 7 | "io" 8 | ) 9 | 10 | // BaseRelocation is a parsed PE base relocation. 11 | type BaseRelocation struct { 12 | // Relative virtual address of offset (that is, 0 == first byte of PE.) 13 | Offset uint64 14 | 15 | // Type of base relocation using the ImageRelBased* constants. 16 | Type int 17 | } 18 | 19 | // LoadBaseRelocs loads relocations from memory. 20 | func LoadBaseRelocs(m *Module, mem io.ReadSeeker) []BaseRelocation { 21 | relocs := []BaseRelocation{} 22 | dir := m.Header.OptionalHeader.DataDirectory[ImageDirectoryEntryBaseReloc] 23 | if dir.Size == 0 { 24 | return relocs 25 | } 26 | n := uint32(0) 27 | mem.Seek(int64(dir.VirtualAddress), io.SeekStart) 28 | for n < dir.Size { 29 | hdr := ImageBaseRelocation{} 30 | binary.Read(mem, binary.LittleEndian, &hdr) 31 | data := make([]uint16, hdr.SizeOfBlock/2-4) 32 | binary.Read(mem, binary.LittleEndian, &data) 33 | for _, i := range data { 34 | relocs = append(relocs, BaseRelocation{ 35 | Offset: uint64(hdr.VirtualAddress) + uint64(i&0xFFF), 36 | Type: int(i >> 12), 37 | }) 38 | } 39 | n += hdr.SizeOfBlock 40 | } 41 | return relocs 42 | } 43 | 44 | // Relocate performs a series of relocations on m, where address is the load 45 | // address and original is the original address. The ReadWriteSeeker is 46 | // assumed to have the PE image at offset 0. 47 | func Relocate(machine int, rels []BaseRelocation, address uint64, original uint64, m io.ReadWriteSeeker, o binary.ByteOrder) error { 48 | b := [8]byte{} 49 | delta := address - original 50 | 51 | if delta == 0 { 52 | return nil 53 | } 54 | 55 | read := func(off uint64, cb int) error { 56 | m.Seek(int64(off), io.SeekStart) 57 | err := readfully(m, b[0:cb]) 58 | if err != nil { 59 | return err 60 | } 61 | m.Seek(int64(-cb), io.SeekCurrent) 62 | return nil 63 | } 64 | 65 | for _, rel := range rels { 66 | switch rel.Type { 67 | case ImageRelBasedAbsolute: 68 | break 69 | 70 | case ImageRelBasedHigh: 71 | if err := read(rel.Offset, 2); err != nil { 72 | return err 73 | } 74 | o.PutUint16(b[0:2], uint16(uint64(o.Uint16(b[:2]))+(delta>>16))) 75 | if _, err := m.Write(b[0:2]); err != nil { 76 | return err 77 | } 78 | break 79 | 80 | case ImageRelBasedLow: 81 | if err := read(rel.Offset, 2); err != nil { 82 | return err 83 | } 84 | o.PutUint16(b[0:2], uint16(uint64(o.Uint16(b[:2]))+(delta>>0))) 85 | if _, err := m.Write(b[0:2]); err != nil { 86 | return err 87 | } 88 | break 89 | 90 | case ImageRelBasedHighLow: 91 | if err := read(rel.Offset, 4); err != nil { 92 | return err 93 | } 94 | o.PutUint32(b[0:4], uint32(uint64(o.Uint32(b[:4]))+delta)) 95 | if _, err := m.Write(b[0:4]); err != nil { 96 | return err 97 | } 98 | break 99 | 100 | case ImageRelBasedDir64: 101 | if err := read(rel.Offset, 8); err != nil { 102 | return err 103 | } 104 | o.PutUint64(b[0:8], uint64(o.Uint64(b[:8]))+delta) 105 | if _, err := m.Write(b[0:8]); err != nil { 106 | return err 107 | } 108 | break 109 | 110 | // Could use some help for ensuring we have proper support for 111 | // machine-specific relocations. If you are interested in this 112 | // use case for some reason, feel free to send PRs. -- john 113 | case ImageRelBasedMachineSpecific5: 114 | switch machine { 115 | // MIPS: JMP reloc 116 | case ImageFileMachineR3000, ImageFileMachineR3000BE, 117 | ImageFileMachineR4000, ImageFileMachineR10000, 118 | ImageFileMachineWCEMIPSv2, ImageFileMachineMIPS16, 119 | ImageFileMachineMIPSFPU, ImageFileMachineMIPSFPU16: 120 | return errors.New("MIPS JMP reloc not implemented") 121 | // ARM: MOV32 reloc 122 | case ImageFileMachineARM, ImageFileMachineTHUMB, 123 | ImageFileMachineARMNT: 124 | return errors.New("ARM MOV32 reloc not implemented") 125 | // RISC-V: HI20 reloc 126 | case ImageFileMachineRISCV32, ImageFileMachineRISCV64, 127 | ImageFileMachineRISCV128: 128 | return errors.New("RISC-V HI20 reloc not implemented") 129 | default: 130 | return fmt.Errorf("unknown machine-specific relocation type %d on machine type %04x", rel.Type, machine) 131 | } 132 | 133 | case ImageRelBasedMachineSpecific7: 134 | switch machine { 135 | // THUMB: MOV32 reloc 136 | case ImageFileMachineARM, ImageFileMachineTHUMB, 137 | ImageFileMachineARMNT: 138 | return errors.New("THUMB MOV32 reloc not implemented") 139 | // RISC-V: LOW12I reloc 140 | case ImageFileMachineRISCV32, ImageFileMachineRISCV64, 141 | ImageFileMachineRISCV128: 142 | return errors.New("RISC-V LOW12I reloc not implemented") 143 | default: 144 | return fmt.Errorf("unknown machine-specific relocation type %d on machine type %04x", rel.Type, machine) 145 | } 146 | 147 | case ImageRelBasedMachineSpecific8: 148 | switch machine { 149 | // RISC-V: LOW12S reloc 150 | case ImageFileMachineRISCV32, ImageFileMachineRISCV64, 151 | ImageFileMachineRISCV128: 152 | return errors.New("RISC-V LOW12S reloc not implemented") 153 | default: 154 | return fmt.Errorf("unknown machine-specific relocation type %d on machine type %04x", rel.Type, machine) 155 | } 156 | 157 | case ImageRelBasedMachineSpecific9: 158 | switch machine { 159 | // MIPS: JMP16 reloc 160 | case ImageFileMachineR3000, ImageFileMachineR3000BE, 161 | ImageFileMachineR4000, ImageFileMachineR10000, 162 | ImageFileMachineWCEMIPSv2, ImageFileMachineMIPS16, 163 | ImageFileMachineMIPSFPU, ImageFileMachineMIPSFPU16: 164 | return errors.New("MIPS JMP16 reloc not implemented") 165 | // Itanium: Imm64 reloc 166 | case ImageFileMachineIA64: 167 | return errors.New("Itanium Imm64 reloc not implemented") 168 | default: 169 | return fmt.Errorf("unknown machine-specific relocation type %d on machine type %04x", rel.Type, machine) 170 | } 171 | default: 172 | return fmt.Errorf("unknown relocation type %d", rel.Type) 173 | } 174 | } 175 | 176 | return nil 177 | } 178 | -------------------------------------------------------------------------------- /internal/pe/util.go: -------------------------------------------------------------------------------- 1 | package pe 2 | 3 | import "io" 4 | 5 | func readfully(r io.Reader, p []byte) error { 6 | n, err := r.Read(p) 7 | if n < 0 || n > len(p) { 8 | panic("invalid read length") 9 | } 10 | if err != nil && err != io.EOF { 11 | return err 12 | } 13 | for n < len(p) { 14 | m, err := r.Read(p[n:]) 15 | if m < 0 || m > len(p[n:]) { 16 | panic("invalid read length") 17 | } 18 | n += m 19 | if n < len(p) && err != nil { 20 | return err 21 | } 22 | if n >= len(p) && err != nil && err != io.EOF { 23 | return err 24 | } 25 | } 26 | return nil 27 | } 28 | 29 | func readsz(r io.Reader) string { 30 | name := []byte{} 31 | for { 32 | b := [1]byte{} 33 | r.Read(b[:]) 34 | if b[0] == 0 { 35 | break 36 | } 37 | name = append(name, b[0]) 38 | } 39 | return string(name) 40 | } 41 | -------------------------------------------------------------------------------- /internal/vmem/common.go: -------------------------------------------------------------------------------- 1 | package vmem 2 | 3 | const ( 4 | memDecommit = 0x4000 5 | memRelease = 0x8000 6 | ) 7 | 8 | type systemInfo struct { 9 | wProcessorArchitecture uint16 10 | wReserved uint16 11 | dwPageSize uint32 12 | lpMinimumApplicationAddress uintptr 13 | lpMaximumApplicationAddress uintptr 14 | dwActiveProcessorMask uintptr 15 | dwNumberOfProcessors uint32 16 | dwProcessorType uint32 17 | dwAllocationGranularity uint32 18 | wProcessorLevel uint16 19 | wProcessorRevision uint16 20 | } 21 | 22 | // Enumeration of allocation type values. 23 | const ( 24 | MemCommit = 0x00001000 25 | MemReserve = 0x00002000 26 | MemReset = 0x00080000 27 | MemResetUndo = 0x10000000 28 | ) 29 | 30 | // Enumeration of protection levels. 31 | const ( 32 | PageNoAccess = 0x01 33 | PageReadOnly = 0x02 34 | PageReadWrite = 0x04 35 | PageWriteCopy = 0x08 36 | PageExecute = 0x10 37 | PageExecuteRead = 0x20 38 | PageExecuteReadWrite = 0x40 39 | PageExecuteWriteCopy = 0x80 40 | ) 41 | 42 | // RoundDown rounds an address up to a given multiple of size. Size must be a 43 | // power of two. 44 | func RoundDown(addr uint64, size uint64) uint64 { 45 | if size&(size-1) != 0 { 46 | panic("alignment size is not a power of two") 47 | } 48 | return addr &^ (size - 1) 49 | } 50 | 51 | // RoundUp rounds an address up to a given multiple of size. Size must be a 52 | // power of two. 53 | func RoundUp(addr uint64, size uint64) uint64 { 54 | return RoundDown(addr+size-1, size) 55 | } 56 | -------------------------------------------------------------------------------- /internal/vmem/memory_other.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package vmem 5 | 6 | // Memory represents a raw block of memory. 7 | type Memory struct { 8 | data []byte 9 | } 10 | 11 | // GetPageSize returns the size of a memory page. 12 | func GetPageSize() uint64 { 13 | panic("not implemented") 14 | } 15 | 16 | // Alloc allocates memory at addr of size with allocType and protect. 17 | // It returns nil if it fails. 18 | func Alloc(addr, size uint64, allocType, protect int) *Memory { 19 | panic("not implemented") 20 | } 21 | 22 | // Get returns a range of existing memory. If the range is not a block of 23 | // allocated memory, the returned memory will pagefault when accessed. 24 | func Get(addr, size uint64) *Memory { 25 | panic("not implemented") 26 | } 27 | 28 | // Free frees the block of memory. 29 | func (m *Memory) Free() { 30 | panic("not implemented") 31 | } 32 | 33 | // Addr returns the actual address of the memory. 34 | func (m *Memory) Addr() uint64 { 35 | panic("not implemented") 36 | } 37 | 38 | // Read implements the io.Reader interface. 39 | func (m *Memory) Read(b []byte) (n int, err error) { 40 | panic("not implemented") 41 | } 42 | 43 | // ReadAt implements the io.ReaderAt interface. 44 | func (m *Memory) ReadAt(b []byte, off int64) (n int, err error) { 45 | panic("not implemented") 46 | } 47 | 48 | // Write implements the io.Writer interface. 49 | func (m *Memory) Write(b []byte) (n int, err error) { 50 | panic("not implemented") 51 | } 52 | 53 | // WriteAt implements the io.WriterAt interface. 54 | func (m *Memory) WriteAt(b []byte, off int64) (n int, err error) { 55 | panic("not implemented") 56 | } 57 | 58 | // Seek implements the io.Seeker interface. 59 | func (m *Memory) Seek(offset int64, whence int) (int64, error) { 60 | panic("not implemented") 61 | } 62 | 63 | // Protect changes the memory protection for a range of memory. 64 | func (m *Memory) Protect(addr, size uint64, protect int) error { 65 | panic("not implemented") 66 | } 67 | 68 | // Clear sets all bytes in the memory block to zero. 69 | func (m *Memory) Clear() { 70 | panic("not implemented") 71 | } 72 | -------------------------------------------------------------------------------- /internal/vmem/memory_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package vmem 5 | 6 | import ( 7 | "errors" 8 | "io" 9 | "reflect" 10 | "sync" 11 | "unsafe" 12 | 13 | "golang.org/x/sys/windows" 14 | ) 15 | 16 | var ( 17 | kernel32 = windows.NewLazySystemDLL("kernel32") 18 | kernel32VirtualAlloc = kernel32.NewProc("VirtualAlloc") 19 | kernel32VirtualFree = kernel32.NewProc("VirtualFree") 20 | kernel32VirtualProtect = kernel32.NewProc("VirtualProtect") 21 | kernel32GetNativeSystemInfo = kernel32.NewProc("GetNativeSystemInfo") 22 | kernel32FlushInstructionCache = kernel32.NewProc("FlushInstructionCache") 23 | kernel32GetCurrentProcess = kernel32.NewProc("GetCurrentProcess") 24 | 25 | pageSize uint64 26 | pageSizeOnce sync.Once 27 | ) 28 | 29 | // getCurrentProcess returns the current process handle. 30 | func getCurrentProcess() uintptr { 31 | r, _, _ := kernel32GetCurrentProcess.Call() 32 | return uintptr(r) 33 | } 34 | 35 | // GetPageSize returns the size of a memory page. 36 | func GetPageSize() uint64 { 37 | pageSizeOnce.Do(func() { 38 | if kernel32GetNativeSystemInfo.Find() != nil { 39 | pageSize = 0x1000 40 | } 41 | info := systemInfo{} 42 | kernel32GetNativeSystemInfo.Call(uintptr(unsafe.Pointer(&info))) 43 | pageSize = uint64(info.dwPageSize) 44 | }) 45 | return pageSize 46 | } 47 | 48 | // Memory represents a raw block of memory. 49 | type Memory struct { 50 | data []byte 51 | i int64 52 | } 53 | 54 | // Alloc allocates memory at addr of size with allocType and protect. 55 | // It returns nil if it fails. 56 | func Alloc(addr, size uint64, allocType, protect int) *Memory { 57 | r, _, _ := kernel32VirtualAlloc.Call(uintptr(addr), uintptr(size), uintptr(allocType), uintptr(protect)) 58 | if r == 0 { 59 | return nil 60 | } 61 | return Get(uint64(r), size) 62 | } 63 | 64 | // Get returns a range of existing memory. If the range is not a block of 65 | // allocated memory, the returned memory will pagefault when accessed. 66 | func Get(addr, size uint64) *Memory { 67 | m := &Memory{} 68 | sh := (*reflect.SliceHeader)(unsafe.Pointer(&m.data)) 69 | sh.Data = uintptr(addr) 70 | sh.Len = int(size) 71 | sh.Cap = int(size) 72 | return m 73 | } 74 | 75 | // Free frees the block of memory. 76 | func (m *Memory) Free() { 77 | sh := (*reflect.SliceHeader)(unsafe.Pointer(&m.data)) 78 | kernel32VirtualFree.Call(sh.Data, 0, memRelease) 79 | m.data = nil 80 | } 81 | 82 | // Addr returns the actual address of the memory. 83 | func (m *Memory) Addr() uint64 { 84 | return uint64((*reflect.SliceHeader)(unsafe.Pointer(&m.data)).Data) 85 | } 86 | 87 | // Read implements the io.Reader interface. 88 | func (m *Memory) Read(b []byte) (n int, err error) { 89 | if m.i >= int64(len(m.data)) { 90 | return 0, io.EOF 91 | } 92 | n = copy(b, m.data[m.i:]) 93 | m.i += int64(n) 94 | return n, nil 95 | } 96 | 97 | // ReadAt implements the io.ReaderAt interface. 98 | func (m *Memory) ReadAt(b []byte, off int64) (n int, err error) { 99 | if off < 0 { 100 | return 0, errors.New("negative offset") 101 | } 102 | if off >= int64(len(m.data)) { 103 | return 0, io.EOF 104 | } 105 | n = copy(b, m.data[off:]) 106 | if n < len(b) { 107 | return n, io.EOF 108 | } 109 | return n, nil 110 | } 111 | 112 | // Write implements the io.Writer interface. 113 | func (m *Memory) Write(b []byte) (n int, err error) { 114 | if m.i >= int64(len(m.data)) { 115 | return 0, io.ErrShortWrite 116 | } 117 | n = copy(m.data[m.i:], b) 118 | if kernel32FlushInstructionCache.Find() == nil { 119 | kernel32FlushInstructionCache.Call(getCurrentProcess(), uintptr(unsafe.Pointer(&m.data[m.i])), uintptr(n)) 120 | } 121 | m.i += int64(n) 122 | return n, nil 123 | } 124 | 125 | // WriteAt implements the io.WriterAt interface. 126 | func (m *Memory) WriteAt(b []byte, off int64) (n int, err error) { 127 | if off < 0 { 128 | return 0, errors.New("negative offset") 129 | } 130 | if off >= int64(len(m.data)) { 131 | return 0, io.ErrShortWrite 132 | } 133 | n = copy(m.data[off:], b) 134 | if kernel32FlushInstructionCache.Find() == nil { 135 | kernel32FlushInstructionCache.Call(getCurrentProcess(), uintptr(unsafe.Pointer(&m.data[off])), uintptr(n)) 136 | } 137 | if n < len(b) { 138 | return n, io.ErrShortWrite 139 | } 140 | return n, nil 141 | } 142 | 143 | // Seek implements the io.Seeker interface. 144 | func (m *Memory) Seek(offset int64, whence int) (int64, error) { 145 | var n int64 146 | switch whence { 147 | case io.SeekStart: 148 | n = offset 149 | case io.SeekCurrent: 150 | n = m.i + offset 151 | case io.SeekEnd: 152 | n = int64(len(m.data)) + offset 153 | default: 154 | return 0, errors.New("invalid whence") 155 | } 156 | if n < 0 { 157 | return 0, errors.New("negative position") 158 | } 159 | m.i = n 160 | return n, nil 161 | } 162 | 163 | // Clear sets all bytes in the memory block to zero. 164 | func (m *Memory) Clear() { 165 | for i := range m.data { 166 | m.data[i] = 0 167 | } 168 | if kernel32FlushInstructionCache.Find() == nil { 169 | kernel32FlushInstructionCache.Call(getCurrentProcess(), uintptr(unsafe.Pointer(&m.data[0])), uintptr(len(m.data))) 170 | } 171 | } 172 | 173 | // Protect changes the memory protection for a range of memory. 174 | func (m *Memory) Protect(addr, size uint64, protect int) error { 175 | // TODO: error handling 176 | oldProtect := uint32(0) 177 | kernel32VirtualProtect.Call(uintptr(m.Addr()+addr), uintptr(size), uintptr(protect), uintptr(unsafe.Pointer(&oldProtect))) 178 | return nil 179 | } 180 | -------------------------------------------------------------------------------- /internal/vmem/memory_windows_test.go: -------------------------------------------------------------------------------- 1 | package vmem 2 | 3 | import ( 4 | "io" 5 | "testing" 6 | ) 7 | 8 | func TestBasicAlloc(t *testing.T) { 9 | mem := Alloc(0, 0x1000, MemCommit|MemReserve, PageReadWrite) 10 | mem.Write([]byte("Test!")) 11 | mem.Seek(0, io.SeekStart) 12 | b := make([]byte, 5) 13 | mem.Read(b) 14 | if string(b) != "Test!" { 15 | t.Errorf(`expected "Test!" got %q`, string(b)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /internal/winloader/hacks_other.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package winloader 5 | 6 | import "errors" 7 | 8 | // MakePEBEntryForModule is a hack that inserts an entry for the loaded module 9 | // into the PEB loader data to make it appear to Windows functions. 10 | func MakePEBEntryForModule() error { 11 | return errors.New("platform not supported") 12 | } 13 | 14 | // GetProcessHInstance gets the HINSTANCE for the current process. 15 | func GetProcessHInstance() (uintptr, error) { 16 | return 0, errors.New("platform not supported") 17 | } 18 | -------------------------------------------------------------------------------- /internal/winloader/hacks_windows.go: -------------------------------------------------------------------------------- 1 | package winloader 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "unsafe" 7 | 8 | "golang.org/x/sys/windows" 9 | ) 10 | 11 | var ( 12 | ntdllModule = windows.NewLazySystemDLL("ntdll") 13 | ntdllNtQueryInformationProcess = ntdllModule.NewProc("NtQueryInformationProcess") 14 | ) 15 | 16 | type _ProcessBasicInformation struct { 17 | Reserved1 uintptr 18 | PebBaseAddress uintptr 19 | Reserved2 [2]uintptr 20 | UniqueProcessID uintptr 21 | Reserved3 uintptr 22 | } 23 | 24 | type _PEB struct { 25 | Reserved1 [2]byte 26 | BeingDebugged byte 27 | Reserved2 [1]byte 28 | Reserved3 [2]uintptr 29 | Ldr uintptr 30 | } 31 | 32 | type _List struct { 33 | Front uintptr 34 | Back uintptr 35 | } 36 | 37 | type _PEBLoaderData struct { 38 | Length uint32 39 | Initialized uint8 40 | Padding [3]uint8 41 | SsHandle uintptr 42 | InLoadOrderModuleList _List 43 | InMemoryOrderModuleList _List 44 | InInitializationOrderModuleList _List 45 | } 46 | 47 | // MakePEBEntryForModule is a hack that inserts an entry for the loaded module 48 | // into the PEB loader data to make it appear to Windows functions. 49 | func MakePEBEntryForModule() error { 50 | process := -1 51 | sizeNeeded := uint32(0) 52 | pbi := _ProcessBasicInformation{} 53 | status, _, _ := ntdllNtQueryInformationProcess.Call( 54 | uintptr(process), 55 | 0, 56 | uintptr(unsafe.Pointer(&pbi)), 57 | unsafe.Sizeof(pbi), 58 | uintptr(unsafe.Pointer(&sizeNeeded)), 59 | ) 60 | if status != 0 { 61 | return fmt.Errorf("NtQueryInformationProcess failed: %08x", status) 62 | } 63 | 64 | // TODO: Implement attempt to insert module entry. 65 | return errors.New("unimplemented") 66 | } 67 | 68 | // GetProcessHInstance gets the HINSTANCE for the current process. 69 | func GetProcessHInstance() (uintptr, error) { 70 | var hinstance windows.Handle 71 | windows.GetModuleHandleEx(0, nil, &hinstance) 72 | return uintptr(hinstance), nil 73 | } 74 | -------------------------------------------------------------------------------- /internal/winloader/loader_windows.go: -------------------------------------------------------------------------------- 1 | package winloader 2 | 3 | import ( 4 | "syscall" 5 | 6 | "github.com/jchv/go-winloader/internal/loader" 7 | "golang.org/x/sys/windows" 8 | ) 9 | 10 | // Proc is a Proc implementation using a native procedure. 11 | type Proc uintptr 12 | 13 | // Call calls a native procedure. 14 | func (p Proc) Call(a ...uint64) (r1, r2 uint64, lastErr error) { 15 | var ua []uintptr 16 | for _, e := range a { 17 | ua = append(ua, uintptr(e)) 18 | } 19 | n1, n2, lastErr := syscall.SyscallN(uintptr(p), ua...) 20 | return uint64(n1), uint64(n2), lastErr 21 | } 22 | 23 | // Addr gets the native address of the procedure. 24 | func (p Proc) Addr() uint64 { 25 | return uint64(p) 26 | } 27 | 28 | // Library is a module implementation using the native Windows loader. 29 | type Library windows.Handle 30 | 31 | // Proc implements loader.Module 32 | func (l Library) Proc(name string) loader.Proc { 33 | proc, _ := windows.GetProcAddress(windows.Handle(l), name) 34 | if proc == 0 { 35 | return nil 36 | } 37 | return Proc(proc) 38 | } 39 | 40 | // Ordinal implements loader.Module 41 | func (l Library) Ordinal(ordinal uint64) loader.Proc { 42 | proc, _ := windows.GetProcAddressByOrdinal(windows.Handle(l), uintptr(ordinal)) 43 | if proc == 0 { 44 | return nil 45 | } 46 | return Proc(proc) 47 | } 48 | 49 | // Free implements loader.Module 50 | func (l Library) Free() error { 51 | return windows.FreeLibrary(windows.Handle(l)) 52 | } 53 | 54 | // Loader is a loader that uses the native Windows library loader. 55 | type Loader struct{} 56 | 57 | // Load loads a module into memory. 58 | func (Loader) Load(libname string) (loader.Module, error) { 59 | handle, err := windows.LoadLibrary(libname) 60 | if err != nil { 61 | return nil, err 62 | } 63 | return Library(handle), nil 64 | } 65 | -------------------------------------------------------------------------------- /internal/winloader/machine_windows.go: -------------------------------------------------------------------------------- 1 | package winloader 2 | 3 | import ( 4 | "github.com/jchv/go-winloader/internal/loader" 5 | "github.com/jchv/go-winloader/internal/vmem" 6 | ) 7 | 8 | // NativeMachine is a loader.Machine implementation that uses the native 9 | // machine the binary is running on. 10 | type NativeMachine struct{} 11 | 12 | // IsArchitectureSupported implements loader.Machine. 13 | func (NativeMachine) IsArchitectureSupported(machine int) bool { 14 | return machine == NativeArch 15 | } 16 | 17 | func (NativeMachine) GetPageSize() uint64 { 18 | return vmem.GetPageSize() 19 | } 20 | 21 | // Alloc implements loader.Machine. 22 | func (NativeMachine) Alloc(addr, size uint64, allocType, protect int) loader.Memory { 23 | if mem := vmem.Alloc(addr, size, allocType, protect); mem != nil { 24 | return mem 25 | } 26 | return nil 27 | } 28 | 29 | // MemProc implements loader.MemProc. 30 | func (NativeMachine) MemProc(addr uint64) loader.Proc { 31 | return Proc(addr) 32 | } 33 | -------------------------------------------------------------------------------- /internal/winloader/nativearch_386.go: -------------------------------------------------------------------------------- 1 | package winloader 2 | 3 | import ( 4 | "github.com/jchv/go-winloader/internal/pe" 5 | ) 6 | 7 | // NativeArch is a constant that will be equal to the PE machine type 8 | // enumeration value that corresponds to the arch the binary is running as. 9 | const NativeArch = pe.ImageFileMachinei386 10 | -------------------------------------------------------------------------------- /internal/winloader/nativearch_amd64.go: -------------------------------------------------------------------------------- 1 | package winloader 2 | 3 | import ( 4 | "github.com/jchv/go-winloader/internal/pe" 5 | ) 6 | 7 | // NativeArch is a constant that will be equal to the PE machine type 8 | // enumeration value that corresponds to the arch the binary is running as. 9 | const NativeArch = pe.ImageFileMachineAMD64 10 | -------------------------------------------------------------------------------- /internal/winloader/nativearch_arm64.go: -------------------------------------------------------------------------------- 1 | package winloader 2 | 3 | import ( 4 | "github.com/jchv/go-winloader/internal/pe" 5 | ) 6 | 7 | // NativeArch is a constant that will be equal to the PE machine type 8 | // enumeration value that corresponds to the arch the binary is running as. 9 | const NativeArch = pe.ImageFileMachineARM64 10 | -------------------------------------------------------------------------------- /loader_other.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package winloader 5 | 6 | import "fmt" 7 | 8 | // LoadFromFile loads a Windows module from file using the native Windows 9 | // loader. 10 | func LoadFromFile(name string) (Module, error) { 11 | return nil, fmt.Errorf("unsupported platform") 12 | } 13 | 14 | // LoadFromMemory loads a Windows module from memory. 15 | func LoadFromMemory(data []byte) (Module, error) { 16 | return nil, fmt.Errorf("unsupported platform") 17 | } 18 | 19 | // AddToCache adds a module to the loader cache, allowing in-memory libraries 20 | // to link to it. Note that modules in the cache must exist in the same 21 | // address space. 22 | func AddToCache(name string, module Module) error { 23 | return nil 24 | } 25 | -------------------------------------------------------------------------------- /loader_windows.go: -------------------------------------------------------------------------------- 1 | package winloader 2 | 3 | import ( 4 | "github.com/jchv/go-winloader/internal/memloader" 5 | "github.com/jchv/go-winloader/internal/winloader" 6 | ) 7 | 8 | var native = winloader.Loader{} 9 | 10 | var cache = memloader.NewCache(native) 11 | 12 | var ldr = memloader.New(memloader.Options{ 13 | Next: cache, 14 | Machine: winloader.NativeMachine{}, 15 | }) 16 | 17 | // LoadFromFile loads a Windows module from file using the native Windows 18 | // loader. 19 | func LoadFromFile(name string) (Module, error) { 20 | return native.Load(name) 21 | } 22 | 23 | // LoadFromMemory loads a Windows module from memory. 24 | func LoadFromMemory(data []byte) (Module, error) { 25 | return ldr.LoadMem(data) 26 | } 27 | 28 | // AddToCache adds a module to the loader cache, allowing in-memory libraries 29 | // to link to it. Note that modules in the cache must exist in the same 30 | // address space. 31 | func AddToCache(name string, module Module) error { 32 | return cache.Add(name, module) 33 | } 34 | -------------------------------------------------------------------------------- /tinydll/Makefile: -------------------------------------------------------------------------------- 1 | ifeq ($(OS),Windows_NT) 2 | WATCOM := C:/WATCOM 3 | WCC := $(WATCOM)/binnt64/wcc386 4 | WLINK := $(WATCOM)/binnt64/wlink 5 | RM := del 6 | PATHFIX = $(subst /,\,$1) 7 | else 8 | WATCOM := /opt/watcom 9 | WCC := $(WATCOM)/binl64/wcc386 10 | WLINK := $(WATCOM)/binl64/wlink 11 | RM := rm 12 | PATHFIX = $1 13 | endif 14 | 15 | CFLAGS := \ 16 | '-i$(call PATHFIX,$(WATCOM)/h)' \ 17 | '-i$(call PATHFIX,$(WATCOM)/h/nt)' \ 18 | '-i$(call PATHFIX,$(WATCOM)/h/nt/ddk)' \ 19 | -zl -s -bd -os -d0 -fr= -zq 20 | 21 | LDFLAGS := \ 22 | LIBPATH '$(call PATHFIX,$(WATCOM)/lib386)' \ 23 | LIBPATH '$(call PATHFIX,$(WATCOM)/lib386/nt)' 24 | 25 | all: tiny.dll 26 | 27 | .PHONY: clean 28 | 29 | %.o: %.c 30 | $(WCC) $(CFLAGS) "$<" "-fo=$@" 31 | 32 | %.dll: %.o 33 | $(WLINK) $(LDFLAGS) NAME "$@" @export.def FILE {$<} 34 | 35 | clean: 36 | $(RM) tiny.dll tiny.o 37 | -------------------------------------------------------------------------------- /tinydll/README.md: -------------------------------------------------------------------------------- 1 | This directory contains source code to produce a very small 32-bit PE DLL for testing purposes. It can be built with GNU make on Windows or Linux, and assumes that an Open Watcom v2 snapshot can be found at `C:\WATCOM` on Windows and `/opt/watcom` on Linux, overridden using the `WATCOM` make variable (like `make WATCOM=$HOME/watcom`.) 2 | -------------------------------------------------------------------------------- /tinydll/export.def: -------------------------------------------------------------------------------- 1 | FORMAT windows dll 2 | RUNTIME windows = 4.0 3 | ALIAS __DLLstart_ = '_DllMain@12' 4 | 5 | OPTION START = '_DllMain@12' 6 | OPTION STACK = 8k 7 | OPTION ALIGN = 512 8 | OPTION NODEFAULTLIBS 9 | OPTION ELIMINATE 10 | OPTION QUIET 11 | 12 | EXPORT Add.1 = '_Add@8' 13 | -------------------------------------------------------------------------------- /tinydll/tiny.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern BOOL __stdcall DllMain(HANDLE hInstance, DWORD dwReason, LPVOID reserved) { 4 | return TRUE; 5 | } 6 | 7 | int __stdcall Add(int a, int b) { 8 | return a + b; 9 | } 10 | -------------------------------------------------------------------------------- /tinydll/tiny.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jchv/go-winloader/c1995be93bd1362cba4d3d0ba6bfe0efdd0bcaf8/tinydll/tiny.dll --------------------------------------------------------------------------------