├── README.md ├── function.go ├── go.mod ├── go.sum ├── mem_getprot_darwin.go ├── mem_getprot_linux.go ├── mem_getprot_other.go ├── mem_getprot_windows.go ├── memory.go ├── memory_other.go ├── memory_windows.go ├── power.gif ├── process.go ├── process_darwin.go ├── process_intel.go ├── process_linux.go ├── process_other.go ├── process_windows.go ├── reflect_value.go ├── run_tests.bat ├── run_tests.sh ├── standalone_test ├── .gitignore ├── README.md ├── go.mod ├── go.sum ├── main.go └── subvert_test_copy.go ├── subvert.go ├── subvert_test.go ├── symbols.go ├── symbols_darwin.go ├── symbols_elf.go └── symbols_windows.go /README.md: -------------------------------------------------------------------------------- 1 | Subvert 2 | ======= 3 | 4 | Package subvert provides functions to subvert go's runtime system, allowing you to: 5 | 6 | * Get addresses of stack-allocated or otherwise protected values 7 | * Access unexported values 8 | * Call unexported functions 9 | * Apply patches to memory (even if it's read-only) 10 | * Make aliases to functions 11 | 12 | ![Now I know what it feels like to be God!](power.gif) 13 | 14 | This is not a power to be taken lightly! It's expected that you're fully 15 | versed in how the go type system works, and why there are protections and 16 | restrictions in the first place. Using this package incorrectly will quickly 17 | lead to undefined behavior and bizarre crashes, even segfaults or nuclear 18 | missile launches. 19 | 20 | **YOU HAVE BEEN WARNED!** 21 | 22 | 23 | 24 | Example 25 | ------- 26 | 27 | ```golang 28 | import ( 29 | "fmt" 30 | "reflect" 31 | "unsafe" 32 | 33 | "github.com/kstenerud/go-subvert" 34 | ) 35 | 36 | type SubvertTester struct { 37 | A int 38 | a int 39 | int 40 | } 41 | 42 | const constString = "testing" 43 | 44 | func Demonstrate() { 45 | v := SubvertTester{1, 2, 3} 46 | 47 | rv := reflect.ValueOf(v) 48 | rv_A := rv.FieldByName("A") 49 | rv_a := rv.FieldByName("a") 50 | rv_int := rv.FieldByName("int") 51 | 52 | fmt.Printf("Interface of A: %v\n", rv_A.Interface()) 53 | 54 | // MakeWritable 55 | 56 | // rv_a.Interface() // This would panic 57 | if err := subvert.MakeWritable(&rv_a); err != nil { 58 | // TODO: Handle this 59 | } 60 | fmt.Printf("Interface of a: %v\n", rv_a.Interface()) 61 | 62 | // rv_int.Interface() // This would panic 63 | if err := subvert.MakeWritable(&rv_int); err != nil { 64 | // TODO: Handle this 65 | } 66 | fmt.Printf("Interface of int: %v\n", rv_int.Interface()) 67 | 68 | // MakeAddressable 69 | 70 | // rv.Addr() // This would panic 71 | if err := subvert.MakeAddressable(&rv); err != nil { 72 | // TODO: Handle this 73 | } 74 | fmt.Printf("Pointer to v: %v\n", rv.Addr()) 75 | 76 | // ExposeFunction 77 | 78 | exposed, err := subvert.ExposeFunction("reflect.methodName", (func() string)(nil)) 79 | if err != nil { 80 | // TODO: Handle this 81 | } 82 | f := exposed.(func() string) 83 | fmt.Printf("Result of reflect.methodName: %v\n", f()) 84 | 85 | // PatchMemory 86 | 87 | rv = reflect.ValueOf(constString) 88 | if err := subvert.MakeAddressable(&rv); err != nil { 89 | // TODO: Handle this 90 | } 91 | strAddr := rv.Addr().Pointer() 92 | strBytes := *((*unsafe.Pointer)(unsafe.Pointer(strAddr))) 93 | if oldMem, err := subvert.PatchMemory(uintptr(strBytes), []byte("XX")); err != nil { 94 | // TODO: Handle this 95 | } 96 | fmt.Printf("constString is now: %v, Oldmem = %v\n", constString, string(oldMem)) 97 | } 98 | ``` 99 | 100 | **Output:** 101 | 102 | ``` 103 | Interface of A: 1 104 | Interface of a: 2 105 | Interface of int: 3 106 | Pointer to v: &{1 2 3} 107 | Result of reflect.methodName: github.com/kstenerud/go-subvert.TestDemonstrate 108 | constString is now: XXsting, Oldmem = te 109 | ``` 110 | 111 | 112 | 113 | License 114 | ------- 115 | 116 | MIT License: 117 | 118 | Copyright 2020 Karl Stenerud 119 | 120 | Permission is hereby granted, free of charge, to any person obtaining a copy of 121 | this software and associated documentation files (the "Software"), to deal in 122 | the Software without restriction, including without limitation the rights to 123 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 124 | the Software, and to permit persons to whom the Software is furnished to do so, 125 | subject to the following conditions: 126 | 127 | The above copyright notice and this permission notice shall be included in all 128 | copies or substantial portions of the Software. 129 | 130 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 131 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 132 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 133 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 134 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 135 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 136 | -------------------------------------------------------------------------------- /function.go: -------------------------------------------------------------------------------- 1 | package subvert 2 | 3 | import ( 4 | "reflect" 5 | "unsafe" 6 | ) 7 | 8 | func getFunctionAddress(function interface{}) (address uintptr, err error) { 9 | rv := reflect.ValueOf(function) 10 | if err = MakeAddressable(&rv); err != nil { 11 | return 12 | } 13 | pFunc := (*unsafe.Pointer)(unsafe.Pointer(rv.UnsafeAddr())) 14 | address = uintptr(*pFunc) 15 | return 16 | } 17 | 18 | func newFunctionWithImplementation(template interface{}, implementationPtr uintptr) (function interface{}, err error) { 19 | rFunc := reflect.MakeFunc(reflect.TypeOf(template), nil) 20 | if err = MakeAddressable(&rFunc); err != nil { 21 | return 22 | } 23 | pFunc := (*unsafe.Pointer)(unsafe.Pointer(rFunc.UnsafeAddr())) 24 | *pFunc = unsafe.Pointer(uintptr(implementationPtr)) 25 | function = rFunc.Interface() 26 | return 27 | } 28 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kstenerud/go-subvert 2 | 3 | go 1.7 4 | 5 | require golang.org/x/arch v0.0.0-20200312215426-ff8b605520f4 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/kstenerud/go-describe v1.2.5 h1:HbPBnrMT2nMnoQGfRe86N/2PvroGJARktzW/2gqUt8Q= 2 | github.com/kstenerud/go-describe v1.2.5/go.mod h1:B8yQFOOLtn5swMynH15MmfeKQAxgw3CyOlHv4ihxq7Y= 3 | golang.org/x/arch v0.0.0-20200312215426-ff8b605520f4 h1:cZG+Ns0n5bdEEsURGnDinFswSebRNMqspbLvxrLZoIc= 4 | golang.org/x/arch v0.0.0-20200312215426-ff8b605520f4/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= 5 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 6 | -------------------------------------------------------------------------------- /mem_getprot_darwin.go: -------------------------------------------------------------------------------- 1 | package subvert 2 | 3 | func osGetMemoryProtection(address uintptr) memProtect { 4 | // TODO 5 | // https://stackoverflow.com/questions/1627998/retrieving-the-memory-map-of-its-own-process-in-os-x-10-5-10-6 6 | // https://stackoverflow.com/questions/9198385/on-os-x-how-do-you-find-out-the-current-memory-protection-level 7 | // https://www.grant.pizza/blog/using-dynamic-libraries-in-static-go-binaries/ 8 | 9 | return memProtectRX 10 | } 11 | -------------------------------------------------------------------------------- /mem_getprot_linux.go: -------------------------------------------------------------------------------- 1 | package subvert 2 | 3 | func osGetMemoryProtection(address uintptr) memProtect { 4 | // TODO: Parse /proc/self/maps: 5 | // 559576822000-559576827000 r-xp 00002000 00:1a 4586 /usr/bin/cat 6 | 7 | return memProtectRX 8 | } 9 | -------------------------------------------------------------------------------- /mem_getprot_other.go: -------------------------------------------------------------------------------- 1 | // +build !windows,!linux,!darwin 2 | 3 | package subvert 4 | 5 | func osGetMemoryProtection(address uintptr) memProtect { 6 | // TODO 7 | return memProtectRX 8 | } 9 | -------------------------------------------------------------------------------- /mem_getprot_windows.go: -------------------------------------------------------------------------------- 1 | package subvert 2 | 3 | func osGetMemoryProtection(address uintptr) memProtect { 4 | // TODO 5 | return memProtectRX 6 | } 7 | -------------------------------------------------------------------------------- /memory.go: -------------------------------------------------------------------------------- 1 | package subvert 2 | 3 | func setMemoryProtection(address uintptr, length uintptr, protection memProtect) (oldProtection memProtect, err error) { 4 | return osSetMemoryProtection(address, length, protection) 5 | } 6 | 7 | // Unprotect a memory region, perform an operation, and then restore the old 8 | // protection. 9 | func applyToProtectedMemory(address uintptr, length uintptr, operation func()) (err error) { 10 | oldProtection, err := setMemoryProtection(address, length, memProtectRWX) 11 | if err != nil { 12 | return 13 | } 14 | 15 | operation() 16 | 17 | _, err = setMemoryProtection(address, length, oldProtection) 18 | return 19 | } 20 | 21 | type memProtect int 22 | 23 | const ( 24 | memProtectNone memProtect = 0 25 | memProtectR = 1 26 | memProtectW = 2 27 | memProtectX = 4 28 | memProtectRW = memProtectR | memProtectW 29 | memProtectRX = memProtectR | memProtectX 30 | memProtectWX = memProtectW | memProtectX 31 | memProtectRWX = memProtectR | memProtectW | memProtectX 32 | ) 33 | -------------------------------------------------------------------------------- /memory_other.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package subvert 4 | 5 | import ( 6 | "syscall" 7 | ) 8 | 9 | var protToOS = []int{ 10 | memProtectNone: 0, 11 | memProtectR: syscall.PROT_READ, 12 | memProtectW: syscall.PROT_WRITE, 13 | memProtectX: syscall.PROT_EXEC, 14 | memProtectRW: syscall.PROT_READ | syscall.PROT_WRITE, 15 | memProtectRX: syscall.PROT_READ | syscall.PROT_EXEC, 16 | memProtectWX: syscall.PROT_WRITE | syscall.PROT_EXEC, 17 | memProtectRWX: syscall.PROT_READ | syscall.PROT_WRITE | syscall.PROT_EXEC, 18 | } 19 | 20 | var osToProt = []memProtect{ 21 | 0: memProtectNone, 22 | syscall.PROT_READ: memProtectR, 23 | syscall.PROT_WRITE: memProtectW, 24 | syscall.PROT_EXEC: memProtectX, 25 | syscall.PROT_READ | syscall.PROT_WRITE: memProtectRW, 26 | syscall.PROT_READ | syscall.PROT_EXEC: memProtectRX, 27 | syscall.PROT_WRITE | syscall.PROT_EXEC: memProtectWX, 28 | syscall.PROT_READ | syscall.PROT_WRITE | syscall.PROT_EXEC: memProtectRWX, 29 | } 30 | 31 | func osSetMemoryProtection(address uintptr, length uintptr, protection memProtect) (old memProtect, err error) { 32 | old = osGetMemoryProtection(address) 33 | end := address + uintptr(length) 34 | for pageStart := address & pageBeginMask; pageStart < end; pageStart += uintptr(pageSize) { 35 | page := SliceAtAddress(address&pageBeginMask, pageSize) 36 | if err = syscall.Mprotect(page, protToOS[protection]); err != nil { 37 | return 38 | } 39 | } 40 | return 41 | } 42 | -------------------------------------------------------------------------------- /memory_windows.go: -------------------------------------------------------------------------------- 1 | package subvert 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | func osSetMemoryProtection(address uintptr, length uintptr, protection memProtect) (old memProtect, err error) { 8 | newProtection := protToOS[protection&0xff] | uintptr(protection&^0xff) 9 | 10 | // https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect 11 | // https://docs.microsoft.com/en-us/windows/win32/memory/memory-protection-constants 12 | var oldProtection uint32 13 | result, _, err := virtualProtect.Call(address, 14 | uintptr(length), 15 | newProtection, 16 | uintptr(unsafe.Pointer(&oldProtection))) 17 | if result != 0 { 18 | err = nil 19 | old = osToProt[int(oldProtection&0xff)] | memProtect(oldProtection&^0xff) 20 | } 21 | return 22 | } 23 | 24 | var protToOS = []uintptr{ 25 | memProtectNone: 0, 26 | memProtectR: 0x02, 27 | memProtectW: 0, 28 | memProtectX: 0x10, 29 | memProtectRW: 0x04, 30 | memProtectRX: 0x20, 31 | memProtectWX: 0, 32 | memProtectRWX: 0x40, 33 | } 34 | 35 | var osToProt = map[int]memProtect{ 36 | 0: memProtectNone, 37 | 0x02: memProtectR, 38 | 0x04: memProtectRW, 39 | 0x10: memProtectX, 40 | 0x20: memProtectRX, 41 | 0x40: memProtectRWX, 42 | } 43 | -------------------------------------------------------------------------------- /power.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kstenerud/go-subvert/996773fcc9ff6b3012adac9937e870a515b1d599/power.gif -------------------------------------------------------------------------------- /process.go: -------------------------------------------------------------------------------- 1 | package subvert 2 | 3 | var ( 4 | pageSize int 5 | pageBeginMask uintptr 6 | processBaseAddress uintptr 7 | processCopy []byte 8 | ) 9 | 10 | func initProcess() { 11 | osInitProcess() 12 | pageSize = osGetPageSize() 13 | pageBeginMask = ^uintptr(pageSize - 1) 14 | processBaseAddress = osGetProcessBaseAddress() 15 | } 16 | -------------------------------------------------------------------------------- /process_darwin.go: -------------------------------------------------------------------------------- 1 | package subvert 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "syscall" 7 | ) 8 | 9 | func osGetProcessBaseAddress() (address uintptr) { 10 | defer func() { 11 | if e := recover(); e != nil { 12 | address = 0 13 | } 14 | }() 15 | 16 | // Do a guess assuming this function's address is close to the base. 17 | addr, err := getFunctionAddress(osGetProcessBaseAddress) 18 | if err != nil { 19 | fmt.Println(err) 20 | return 21 | } 22 | startAddress := addr & ^uintptr(0xffffff) 23 | length := 0x400000 24 | 25 | // Mach-O images start with either FEEDFACE (32-bit) or FEEDFACF (64-bit) 26 | toFind := []byte{0xcf, 0xfa, 0xed, 0xfe} 27 | if !is64BitUintptr { 28 | toFind[0] = 0xce 29 | } 30 | asBytes := SliceAtAddress(startAddress, length) 31 | index := bytes.Index(asBytes, toFind) 32 | if index < 0 { 33 | return 34 | } 35 | address = startAddress + uintptr(index) 36 | return 37 | } 38 | 39 | func osGetPageSize() int { 40 | return syscall.Getpagesize() 41 | } 42 | 43 | func osInitProcess() { 44 | // Nothing to do 45 | } 46 | -------------------------------------------------------------------------------- /process_intel.go: -------------------------------------------------------------------------------- 1 | // +build 396 amd64 amd64p32 2 | 3 | package subvert 4 | 5 | import ( 6 | "encoding/binary" 7 | "fmt" 8 | 9 | "golang.org/x/arch/x86/x86asm" 10 | ) 11 | 12 | const callOpFirstByte = byte(0xe8) 13 | const callOpLength = 5 14 | const callOpArgLength = 4 15 | 16 | // Maps function location to a list of places where it's called from 17 | var callLocations map[uintptr][]uintptr 18 | 19 | func initCallCache() (err error) { 20 | if callLocations != nil { 21 | return 22 | } 23 | 24 | table, err := GetSymbolTable() 25 | if err != nil { 26 | return 27 | } 28 | 29 | addCallLocation := func(callLoc, callDst uintptr) { 30 | if callLoc == 0 || callDst == 0 { 31 | panic(fmt.Errorf("callLoc %x, callDst %x", callLoc, callDst)) 32 | } 33 | locations, ok := callLocations[callDst] 34 | if !ok { 35 | locations = make([]uintptr, 0, 4) 36 | } 37 | locations = append(locations, callLoc) 38 | callLocations[callDst] = locations 39 | } 40 | 41 | callLocations = make(map[uintptr][]uintptr) 42 | 43 | registerSize := 32 44 | if is64BitUintptr { 45 | registerSize = 64 46 | } 47 | 48 | for _, f := range table.Funcs { 49 | bytes := SliceAtAddress(uintptr(f.Entry), int(f.End-f.Entry)) 50 | pc := uintptr(f.Entry) 51 | for len(bytes) >= callOpLength { 52 | inst, _ := x86asm.Decode(bytes, registerSize) 53 | if bytes[0] == callOpFirstByte { 54 | argDst := bytes[1:callOpLength] 55 | callArg := uintptr(int32(binary.LittleEndian.Uint32(argDst))) 56 | callDst := uintptr(pc + callArg + callOpLength) 57 | addCallLocation(pc+1, callDst) 58 | } 59 | pc += uintptr(int64(inst.Len)) 60 | bytes = bytes[inst.Len:] 61 | } 62 | } 63 | 64 | return 65 | } 66 | 67 | func osRedirectCalls(src, dst uintptr) (err error) { 68 | if err = initCallCache(); err != nil { 69 | return 70 | } 71 | 72 | callLocations, ok := callLocations[src] 73 | if !ok { 74 | err = fmt.Errorf("Function is not referenced in this program") 75 | return 76 | } 77 | 78 | for _, loc := range callLocations { 79 | newCallArg := uint32(dst - loc - callOpArgLength) 80 | bytes := SliceAtAddress(loc, callOpArgLength) 81 | 82 | err = applyToProtectedMemory(GetSliceAddr(bytes), callOpArgLength, func() { 83 | binary.LittleEndian.PutUint32(bytes, newCallArg) 84 | }) 85 | if err != nil { 86 | return 87 | } 88 | } 89 | 90 | return 91 | } 92 | -------------------------------------------------------------------------------- /process_linux.go: -------------------------------------------------------------------------------- 1 | package subvert 2 | 3 | import ( 4 | "bufio" 5 | "os" 6 | "strconv" 7 | "syscall" 8 | ) 9 | 10 | func osGetProcessBaseAddress() (address uintptr) { 11 | path := "/proc/self/maps" 12 | 13 | fileReader, err := os.Open(path) 14 | if err != nil { 15 | return 16 | } 17 | reader := bufio.NewReader(fileReader) 18 | addrString, err := reader.ReadBytes('-') 19 | if err != nil { 20 | return 21 | } 22 | 23 | addrString = addrString[:len(addrString)-1] 24 | parsed, err := strconv.ParseUint(string(addrString), 16, 64) 25 | if err != nil { 26 | return 27 | } 28 | address = uintptr(parsed) 29 | return 30 | } 31 | 32 | func osGetPageSize() int { 33 | return syscall.Getpagesize() 34 | } 35 | 36 | func osInitProcess() { 37 | // Nothing to do 38 | } 39 | -------------------------------------------------------------------------------- /process_other.go: -------------------------------------------------------------------------------- 1 | // +build !396,!amd64,!amd64p32 2 | 3 | package subvert 4 | 5 | import ( 6 | "fmt" 7 | ) 8 | 9 | func osRedirectCalls(src, dst uintptr) (err error) { 10 | return fmt.Errorf("Not implemented on this arch") 11 | } 12 | -------------------------------------------------------------------------------- /process_windows.go: -------------------------------------------------------------------------------- 1 | package subvert 2 | 3 | import ( 4 | "log" 5 | "reflect" 6 | "syscall" 7 | ) 8 | 9 | func osGetProcessBaseAddress() (address uintptr) { 10 | // This will be 0x400000 unless ASLR is on. 11 | 12 | // https://docs.microsoft.com/en-us/previous-versions/bb985992(v=msdn.10)?redirectedfrom=MSDN 13 | address, _, _ = getModuleHandle.Call(0) 14 | return 15 | } 16 | 17 | func osGetPageSize() int { 18 | // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info 19 | var memory [20]uint32 20 | ptr := reflect.ValueOf(&memory).Elem().UnsafeAddr() 21 | getSystemInfo.Call(ptr) 22 | pageSize := int(memory[1]) // dwPageSize 23 | if pageSize <= 0 { 24 | log.Printf("go-subvert: Warning: GetSystemInfo returned page size of %v\n", pageSize) 25 | pageSize = 0x1000 26 | } 27 | return pageSize 28 | } 29 | 30 | var kernel32 *syscall.LazyDLL 31 | var virtualProtect *syscall.LazyProc 32 | var getModuleHandle *syscall.LazyProc 33 | var getSystemInfo *syscall.LazyProc 34 | 35 | func osInitProcess() { 36 | kernel32 = syscall.NewLazyDLL("kernel32.dll") 37 | virtualProtect = kernel32.NewProc("VirtualProtect") 38 | virtualProtect.Addr() // Forces a panic if not found 39 | getModuleHandle = kernel32.NewProc("GetModuleHandleA") 40 | getModuleHandle.Addr() 41 | getSystemInfo = kernel32.NewProc("GetSystemInfo") 42 | getSystemInfo.Addr() 43 | } 44 | -------------------------------------------------------------------------------- /reflect_value.go: -------------------------------------------------------------------------------- 1 | package subvert 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "reflect" 7 | "unsafe" 8 | ) 9 | 10 | func getRVFlagPtr(v *reflect.Value) *uintptr { 11 | return (*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + rvFlagOffset)) 12 | } 13 | 14 | var ( 15 | rvFlagAddr uintptr 16 | rvFlagRO uintptr 17 | 18 | rvFlagOffset uintptr 19 | rvFlagsFound bool 20 | rvFlagsError = fmt.Errorf("This function is disabled because the internal " + 21 | "flags structure has changed with this go release. Please open " + 22 | "an issue at https://github.com/kstenerud/go-subvert/issues/new") 23 | ) 24 | 25 | type rvFlagTester struct { 26 | A int // reflect/value.go: flagAddr 27 | a int // reflect/value.go: flagStickyRO 28 | int // reflect/value.go: flagEmbedRO 29 | // Note: flagRO = flagStickyRO | flagEmbedRO as of go 1.5 30 | } 31 | 32 | func initReflectValue() { 33 | initReflectValueFlags() 34 | } 35 | 36 | func initReflectValueFlags() { 37 | fail := func(reason string) { 38 | rvFlagsFound = false 39 | log.Println(fmt.Sprintf("reflect.Value flags could not be determined because %v."+ 40 | "Please open an issue at https://github.com/kstenerud/go-subvert/issues", reason)) 41 | } 42 | getFlag := func(v reflect.Value) uintptr { 43 | return uintptr(reflect.ValueOf(v).FieldByName("flag").Uint()) 44 | } 45 | getFldFlag := func(v reflect.Value, fieldName string) uintptr { 46 | return getFlag(v.FieldByName(fieldName)) 47 | } 48 | 49 | if field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag"); ok { 50 | rvFlagOffset = field.Offset 51 | } else { 52 | fail("reflect.Value no longer has a flag field") 53 | return 54 | } 55 | 56 | v := rvFlagTester{} 57 | rv := reflect.ValueOf(&v).Elem() 58 | rvFlagRO = (getFldFlag(rv, "a") | getFldFlag(rv, "int")) ^ getFldFlag(rv, "A") 59 | if rvFlagRO == 0 { 60 | fail("reflect.Value.flag no longer has flagEmbedRO or flagStickyRO bit") 61 | return 62 | } 63 | 64 | rvFlagAddr = getFlag(reflect.ValueOf(int(1))) ^ getFldFlag(rv, "A") 65 | if rvFlagAddr == 0 { 66 | fail("reflect.Value.flag no longer has a flagAddr bit") 67 | return 68 | } 69 | rvFlagsFound = true 70 | } 71 | -------------------------------------------------------------------------------- /run_tests.bat: -------------------------------------------------------------------------------- 1 | go test ./... 2 | cd standalone_test 3 | go build 4 | standalone_test.exe 5 | cd .. 6 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -eu 4 | 5 | go test ./... 6 | cd standalone_test 7 | go build 8 | ./standalone_test 9 | cd .. 10 | -------------------------------------------------------------------------------- /standalone_test/.gitignore: -------------------------------------------------------------------------------- 1 | standalone_test 2 | standalone_test.exe 3 | -------------------------------------------------------------------------------- /standalone_test/README.md: -------------------------------------------------------------------------------- 1 | Standalone tests for go-subvert 2 | =============================== 3 | 4 | These are just copies of the normal tests, packed into a regular binary. 5 | 6 | This serves two purposes: 7 | 8 | 1. It tests in a normal executable environment 9 | 2. It allows all tests to be run under Windows (the testing environment in Windows has some problems) 10 | 11 | ### To run tests: 12 | 13 | go build 14 | ./standalone_test 15 | -------------------------------------------------------------------------------- /standalone_test/go.mod: -------------------------------------------------------------------------------- 1 | module standalone_test 2 | 3 | go 1.10 4 | 5 | replace github.com/kstenerud/go-subvert => ../ 6 | 7 | require github.com/kstenerud/go-subvert v0.0.0-00010101000000-000000000000 8 | -------------------------------------------------------------------------------- /standalone_test/go.sum: -------------------------------------------------------------------------------- 1 | github.com/kstenerud/go-subvert v1.2.1 h1:vI+WhK84UfMnn45f8c/J8ek12CKSSspzbeahm7atCDc= 2 | github.com/kstenerud/go-subvert v1.2.1/go.mod h1:GIfTzLfEoc6N+W/hE5rYqwJuVBGNML9vL3C2isQi6pw= 3 | golang.org/x/arch v0.0.0-20200312215426-ff8b605520f4 h1:cZG+Ns0n5bdEEsURGnDinFswSebRNMqspbLvxrLZoIc= 4 | golang.org/x/arch v0.0.0-20200312215426-ff8b605520f4/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4= 5 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 6 | -------------------------------------------------------------------------------- /standalone_test/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/kstenerud/go-subvert" 8 | ) 9 | 10 | func runTests(tests []func() error) bool { 11 | fmt.Printf("Running standalone tests...\n") 12 | success := true 13 | for _, test := range tests { 14 | symbol, err := subvert.GetFunctionSymbol(test) 15 | if err != nil { 16 | fmt.Println(err) 17 | return false 18 | } 19 | if err := test(); err != nil { 20 | fmt.Printf("Test %v failed: %v\n", symbol.Name, err) 21 | success = false 22 | } 23 | } 24 | return success 25 | } 26 | 27 | func main() { 28 | if !runTests([]func() error{ 29 | TestAddressable, 30 | TestAliasFunction, 31 | TestExposeFunction, 32 | TestPatchMemory, 33 | TestSliceAddr, 34 | TestWritable, 35 | }) { 36 | fmt.Printf("Tests failed\n") 37 | os.Exit(1) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /standalone_test/subvert_test_copy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "unsafe" 7 | 8 | "github.com/kstenerud/go-subvert" 9 | ) 10 | 11 | type SubvertTester struct { 12 | A int 13 | a int 14 | int 15 | } 16 | 17 | func getPanic(function func()) (result interface{}) { 18 | defer func() { 19 | if e := recover(); e != nil { 20 | result = e 21 | } 22 | }() 23 | 24 | function() 25 | return 26 | } 27 | 28 | func assertPanics(function func()) (err error) { 29 | if getPanic(function) == nil { 30 | return fmt.Errorf("Expected function to panic") 31 | } 32 | return 33 | } 34 | 35 | func assertDoesNotPanic(function func()) (err error) { 36 | if e := getPanic(function); e != nil { 37 | return fmt.Errorf("%v", e) 38 | } 39 | return 40 | } 41 | 42 | func TestAddressable() (err error) { 43 | rv := reflect.ValueOf(1) 44 | 45 | if err = assertPanics(func() { rv.Addr() }); err != nil { 46 | return 47 | } 48 | if err = subvert.MakeAddressable(&rv); err != nil { 49 | return 50 | } 51 | rv.Addr() 52 | return 53 | } 54 | 55 | func TestWritable() (err error) { 56 | v := SubvertTester{} 57 | 58 | rv_A := reflect.ValueOf(v).FieldByName("A") 59 | rv_a := reflect.ValueOf(v).FieldByName("a") 60 | rv_int := reflect.ValueOf(v).FieldByName("int") 61 | 62 | rv_A.Interface() 63 | 64 | if err = assertPanics(func() { rv_a.Interface() }); err != nil { 65 | return 66 | } 67 | if err = subvert.MakeWritable(&rv_a); err != nil { 68 | return 69 | } 70 | rv_a.Interface() 71 | 72 | if err = assertPanics(func() { rv_int.Interface() }); err != nil { 73 | return 74 | } 75 | if err = subvert.MakeWritable(&rv_int); err != nil { 76 | return 77 | } 78 | rv_int.Interface() 79 | return 80 | } 81 | 82 | func TestExposeFunction() (err error) { 83 | return assertDoesNotPanic(func() { 84 | var exposed interface{} 85 | exposed, err = subvert.ExposeFunction("reflect.methodName", (func() string)(nil)) 86 | if err != nil { 87 | return 88 | } 89 | if exposed == nil { 90 | err = fmt.Errorf("exposed should not be nil") 91 | return 92 | } 93 | f := exposed.(func() string) 94 | expected := "github.com/kstenerud/go-subvert.getPanic" 95 | actual := f() 96 | if actual != expected { 97 | err = fmt.Errorf("Expected [%v] but got [%v]", expected, actual) 98 | return 99 | } 100 | }) 101 | } 102 | 103 | func TestPatchMemory() (err error) { 104 | // Note: If two const strings have the same value, they will occupy the same 105 | // location in memory! Thus myStr was made with a different value from 106 | // constString 107 | const myStr = "some test" 108 | rv := reflect.ValueOf(myStr) 109 | if err = subvert.MakeAddressable(&rv); err != nil { 110 | return 111 | } 112 | strAddr := rv.Addr().Pointer() 113 | strBytes := *((*unsafe.Pointer)(unsafe.Pointer(strAddr))) 114 | oldMem, err := subvert.PatchMemory(uintptr(strBytes)+5, []byte("XXXX")) 115 | if err != nil { 116 | return 117 | } 118 | 119 | expectedOldMem := "test" 120 | if string(oldMem) != expectedOldMem { 121 | return fmt.Errorf("Expected oldMem to be %v but got %v", expectedOldMem, string(oldMem)) 122 | } 123 | 124 | expected := "some XXXX" 125 | // Note: Comparing myStr will fail due to cached static data. You must copy it first. 126 | actual := myStr 127 | if actual != expected { 128 | return fmt.Errorf("Expected %v but got %v", expected, actual) 129 | } 130 | return 131 | } 132 | 133 | func TestSliceAddr() (err error) { 134 | expected := "abcd" 135 | addr := subvert.GetSliceAddr([]byte(expected)) 136 | actual := string(subvert.SliceAtAddress(addr, 4)) 137 | if actual != expected { 138 | return fmt.Errorf("Expected %v but got %v", expected, actual) 139 | } 140 | return 141 | } 142 | 143 | //go:noinline 144 | func zFunc() string { 145 | return "z" 146 | } 147 | 148 | func TestAliasFunction() (err error) { 149 | fIntf, err := subvert.AliasFunction(zFunc) 150 | if err != nil { 151 | return 152 | } 153 | f := fIntf.(func() string) 154 | 155 | expected := "z" 156 | actual := f() 157 | if actual != expected { 158 | return fmt.Errorf("Expected %v, but got %v", expected, actual) 159 | } 160 | return 161 | } 162 | -------------------------------------------------------------------------------- /subvert.go: -------------------------------------------------------------------------------- 1 | // Package subvert provides functions to subvert go's type & memory protections, 2 | // and expose unexported values & functions. 3 | // 4 | // This is not a power to be taken lightly! It's expected that you're fully 5 | // versed in how the go type system works, and why there are protections and 6 | // restrictions in the first place. Using this package incorrectly will quickly 7 | // lead to undefined behavior and bizarre crashes, even segfaults or nuclear 8 | // missile launches. 9 | 10 | // YOU HAVE BEEN WARNED! 11 | package subvert 12 | 13 | import ( 14 | "debug/gosym" 15 | "math" 16 | "reflect" 17 | "unsafe" 18 | ) 19 | 20 | // MakeWritable clears a value's RO flags. The RO flags are generally used to 21 | // determine whether a value is exported (and thus accessible) or not. 22 | func MakeWritable(v *reflect.Value) error { 23 | if !rvFlagsFound { 24 | return rvFlagsError 25 | } 26 | *getRVFlagPtr(v) &= ^rvFlagRO 27 | return nil 28 | } 29 | 30 | // MakeAddressable adds the addressable flag to a value, allowing you to take 31 | // its address. The most common reason for making an object non-addressable is 32 | // because it's allocated on the stack or in read-only memory. 33 | // 34 | // Making a pointer to a stack value will cause undefined behavior if you 35 | // attempt to access it outside of the stack-allocated object's scope. 36 | // 37 | // Do not write to an object in read-only memory. It would be bad. 38 | func MakeAddressable(v *reflect.Value) error { 39 | if !rvFlagsFound { 40 | return rvFlagsError 41 | } 42 | *getRVFlagPtr(v) |= rvFlagAddr 43 | return nil 44 | } 45 | 46 | // SliceAtAddress turns a memory range into a go slice. 47 | // 48 | // No checks are made as to whether the memory is writable or even readable. 49 | // 50 | // Do not append to the slice. 51 | func SliceAtAddress(address uintptr, length int) []byte { 52 | return (*[math.MaxInt32]byte)(unsafe.Pointer(address))[:length:length] 53 | } 54 | 55 | // GetSliceAddr gets the address of a slice 56 | func GetSliceAddr(slice []byte) uintptr { 57 | pSlice := (*unsafe.Pointer)((unsafe.Pointer)(&slice)) 58 | return uintptr(*pSlice) 59 | } 60 | 61 | // PatchMemory applies a patch to the specified memory location. If the memory 62 | // is read-only, it will be made temporarily writable while the patch is applied. 63 | func PatchMemory(address uintptr, patch []byte) (oldMemory []byte, err error) { 64 | memory := SliceAtAddress(address, len(patch)) 65 | oldMemory = make([]byte, len(memory)) 66 | copy(oldMemory, memory) 67 | err = applyToProtectedMemory(address, uintptr(len(patch)), func() { 68 | copy(memory, patch) 69 | }) 70 | return 71 | } 72 | 73 | // ExposeFunction exposes a function or method, allowing you to bypass export 74 | // restrictions. It looks for the symbol specified by funcSymName and returns a 75 | // function with its implementation, or nil if the symbol wasn't found. 76 | // 77 | // funcSymName must be the exact symbol name from the binary. Use AllFunctions() 78 | // to find it. If your program doesn't have any references to a function, it 79 | // will be omitted from the binary during compilation. You can prevent this by 80 | // saving a reference to it somewhere, or calling a function that indirectly 81 | // references it. 82 | // 83 | // templateFunc MUST have the correct function type, or else undefined behavior 84 | // will result! 85 | // 86 | // Example: 87 | // exposed := ExposeFunction("reflect.methodName", (func() string)(nil)) 88 | // if exposed != nil { 89 | // f := exposed.(func() string) 90 | // fmt.Printf("Result of reflect.methodName: %v\n", f()) 91 | // } 92 | func ExposeFunction(funcSymName string, templateFunc interface{}) (function interface{}, err error) { 93 | fn, err := getFunctionSymbolByName(funcSymName) 94 | if err != nil { 95 | return 96 | } 97 | return newFunctionWithImplementation(templateFunc, uintptr(fn.Entry)) 98 | } 99 | 100 | // AliasFunction returns a new function object that calls the same underlying 101 | // code as the original function. 102 | func AliasFunction(function interface{}) (aliasedFunction interface{}, err error) { 103 | rFunc := reflect.ValueOf(function) 104 | if err = MakeAddressable(&rFunc); err != nil { 105 | return 106 | } 107 | fAddr := *(*unsafe.Pointer)(unsafe.Pointer(rFunc.UnsafeAddr())) 108 | return newFunctionWithImplementation(function, uintptr(fAddr)) 109 | } 110 | 111 | // GetSymbolTable loads (if necessary) and returns the symbol table for this process 112 | func GetSymbolTable() (*gosym.Table, error) { 113 | if symTable == nil && symTableLoadError == nil { 114 | symTable, symTableLoadError = loadSymbolTable() 115 | } 116 | 117 | return symTable, symTableLoadError 118 | } 119 | 120 | // AllFunctions returns the name of every function that has been compiled 121 | // into the current binary. Use it as a debug helper to see if a function 122 | // has been compiled in or not. 123 | func AllFunctions() (functions map[string]bool, err error) { 124 | var table *gosym.Table 125 | if table, err = GetSymbolTable(); err != nil { 126 | return 127 | } 128 | 129 | functions = make(map[string]bool) 130 | for _, function := range table.Funcs { 131 | functions[function.Name] = true 132 | } 133 | return 134 | } 135 | 136 | func init() { 137 | initReflectValue() 138 | initProcess() 139 | } 140 | 141 | const is64BitUintptr = uint64(^uintptr(0)) == ^uint64(0) 142 | -------------------------------------------------------------------------------- /subvert_test.go: -------------------------------------------------------------------------------- 1 | package subvert 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "runtime" 7 | "testing" 8 | "unsafe" 9 | ) 10 | 11 | type SubvertTester struct { 12 | A int 13 | a int 14 | int 15 | } 16 | 17 | const constString = "testing" 18 | 19 | func Demonstrate() { 20 | v := SubvertTester{1, 2, 3} 21 | 22 | rv := reflect.ValueOf(v) 23 | rv_A := rv.FieldByName("A") 24 | rv_a := rv.FieldByName("a") 25 | rv_int := rv.FieldByName("int") 26 | 27 | fmt.Printf("Interface of A: %v\n", rv_A.Interface()) 28 | 29 | // MakeWritable 30 | 31 | // rv_a.Interface() // This would panic 32 | if err := MakeWritable(&rv_a); err != nil { 33 | // TODO: Handle this 34 | } else { 35 | fmt.Printf("Interface of a: %v\n", rv_a.Interface()) 36 | } 37 | 38 | // rv_int.Interface() // This would panic 39 | if err := MakeWritable(&rv_int); err != nil { 40 | // TODO: Handle this 41 | } else { 42 | fmt.Printf("Interface of int: %v\n", rv_int.Interface()) 43 | } 44 | 45 | // MakeAddressable 46 | 47 | // rv.Addr() // This would panic 48 | if err := MakeAddressable(&rv); err != nil { 49 | // TODO: Handle this 50 | } else { 51 | fmt.Printf("Pointer to v: %v\n", rv.Addr()) 52 | } 53 | 54 | // ExposeFunction 55 | 56 | exposed, err := ExposeFunction("reflect.methodName", (func() string)(nil)) 57 | if err != nil { 58 | // TODO: Handle this 59 | } else { 60 | f := exposed.(func() string) 61 | fmt.Printf("Result of reflect.methodName: %v\n", f()) 62 | } 63 | 64 | // PatchMemory 65 | 66 | rv = reflect.ValueOf(constString) 67 | if err := MakeAddressable(&rv); err != nil { 68 | // TODO: Handle this 69 | } else { 70 | strAddr := rv.Addr().Pointer() 71 | strBytes := *((*unsafe.Pointer)(unsafe.Pointer(strAddr))) 72 | if oldMem, err := PatchMemory(uintptr(strBytes), []byte("XX")); err != nil { 73 | // TODO: Handle this 74 | } else { 75 | fmt.Printf("constString is now: %v, Oldmem = %v\n", constString, string(oldMem)) 76 | } 77 | } 78 | } 79 | 80 | func getPanic(function func()) (result interface{}) { 81 | defer func() { 82 | if e := recover(); e != nil { 83 | result = e 84 | } 85 | }() 86 | 87 | function() 88 | return 89 | } 90 | 91 | func assertPanics(t *testing.T, function func()) { 92 | if getPanic(function) == nil { 93 | t.Errorf("Expected function to panic") 94 | } 95 | } 96 | 97 | func assertDoesNotPanic(t *testing.T, function func()) { 98 | if err := getPanic(function); err != nil { 99 | t.Errorf("Unexpected panic: %v", err) 100 | } 101 | } 102 | 103 | func TestDemonstrate(t *testing.T) { 104 | Demonstrate() 105 | } 106 | 107 | func TestAddressable(t *testing.T) { 108 | rv := reflect.ValueOf(1) 109 | 110 | assertPanics(t, func() { rv.Addr() }) 111 | if err := MakeAddressable(&rv); err != nil { 112 | t.Error(err) 113 | return 114 | } 115 | rv.Addr() 116 | } 117 | 118 | func TestWritable(t *testing.T) { 119 | v := SubvertTester{} 120 | 121 | rv_A := reflect.ValueOf(v).FieldByName("A") 122 | rv_a := reflect.ValueOf(v).FieldByName("a") 123 | rv_int := reflect.ValueOf(v).FieldByName("int") 124 | 125 | rv_A.Interface() 126 | 127 | assertPanics(t, func() { rv_a.Interface() }) 128 | if err := MakeWritable(&rv_a); err != nil { 129 | t.Error(err) 130 | return 131 | } 132 | rv_a.Interface() 133 | 134 | assertPanics(t, func() { rv_int.Interface() }) 135 | if err := MakeWritable(&rv_int); err != nil { 136 | t.Error(err) 137 | return 138 | } 139 | rv_int.Interface() 140 | } 141 | 142 | func TestExposeFunction(t *testing.T) { 143 | if runtime.GOOS == "windows" { 144 | fmt.Printf("Skipping TestExposeFunction because it doesn't work in test binaries on this platform. Please run standalone_test.\n") 145 | return 146 | } 147 | 148 | assertDoesNotPanic(t, func() { 149 | exposed, err := ExposeFunction("reflect.methodName", (func() string)(nil)) 150 | if err != nil { 151 | t.Error(err) 152 | return 153 | } 154 | if exposed == nil { 155 | t.Errorf("exposed should not be nil") 156 | return 157 | } 158 | f := exposed.(func() string) 159 | expected := "github.com/kstenerud/go-subvert.getPanic" 160 | actual := f() 161 | if actual != expected { 162 | t.Errorf("Expected [%v] but got [%v]", expected, actual) 163 | return 164 | } 165 | }) 166 | } 167 | 168 | func TestPatchMemory(t *testing.T) { 169 | // Note: If two const strings have the same value, they will occupy the same 170 | // location in memory! Thus myStr was made with a different value from 171 | // constString 172 | const myStr = "some test" 173 | rv := reflect.ValueOf(myStr) 174 | if err := MakeAddressable(&rv); err != nil { 175 | t.Error(err) 176 | return 177 | } 178 | strAddr := rv.Addr().Pointer() 179 | strBytes := *((*unsafe.Pointer)(unsafe.Pointer(strAddr))) 180 | oldMem, err := PatchMemory(uintptr(strBytes)+5, []byte("XXXX")) 181 | if err != nil { 182 | t.Error(err) 183 | return 184 | } 185 | 186 | expectedOldMem := "test" 187 | if string(oldMem) != expectedOldMem { 188 | t.Errorf("Expected oldMem to be %v but got %v", expectedOldMem, string(oldMem)) 189 | return 190 | } 191 | 192 | expected := "some XXXX" 193 | // Note: Comparing myStr will fail due to cached static data. You must copy it first. 194 | actual := myStr 195 | if actual != expected { 196 | t.Errorf("Expected %v but got %v", expected, actual) 197 | } 198 | } 199 | 200 | func TestSliceAddr(t *testing.T) { 201 | expected := "abcd" 202 | addr := GetSliceAddr([]byte(expected)) 203 | actual := string(SliceAtAddress(addr, 4)) 204 | if actual != expected { 205 | t.Errorf("Expected %v but got %v", expected, actual) 206 | } 207 | } 208 | 209 | //go:noinline 210 | func zFunc() string { 211 | return "z" 212 | } 213 | 214 | func TestAliasFunction(t *testing.T) { 215 | fIntf, err := AliasFunction(zFunc) 216 | if err != nil { 217 | t.Error(err) 218 | return 219 | } 220 | f := fIntf.(func() string) 221 | 222 | expected := "z" 223 | actual := f() 224 | if actual != expected { 225 | t.Errorf("Expected %v, but got %v", expected, actual) 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /symbols.go: -------------------------------------------------------------------------------- 1 | package subvert 2 | 3 | import ( 4 | "debug/gosym" 5 | "fmt" 6 | ) 7 | 8 | var ( 9 | symTable *gosym.Table 10 | symTableLoadError error 11 | ) 12 | 13 | func loadSymbolTable() (table *gosym.Table, err error) { 14 | if err = symTableLoadError; err != nil { 15 | return 16 | } 17 | 18 | if table = symTable; table != nil { 19 | return 20 | } 21 | 22 | table, err = osReadSymbolsFromMemory() 23 | if err == nil && table != nil { 24 | symTable = table 25 | return 26 | } 27 | 28 | table, err = osReadSymbolsFromExeFile() 29 | symTableLoadError = err 30 | if err != nil { 31 | symTable = nil 32 | } else if table == nil { 33 | err = fmt.Errorf("Unknown error: symbol table was nil") 34 | } else { 35 | symTable = table 36 | } 37 | 38 | symTable = table 39 | return 40 | } 41 | 42 | // GetFunctionSymbol returns the symbols for a given function. 43 | func GetFunctionSymbol(function interface{}) (symbol *gosym.Func, err error) { 44 | var table *gosym.Table 45 | if table, err = GetSymbolTable(); err != nil { 46 | return 47 | } 48 | 49 | address, err := getFunctionAddress(function) 50 | if err != nil { 51 | return 52 | } 53 | symbol = table.PCToFunc(uint64(address)) 54 | if symbol == nil { 55 | err = fmt.Errorf("Function symbol at %x not found", address) 56 | } 57 | return 58 | } 59 | 60 | func getFunctionSymbolByName(name string) (symbol *gosym.Func, err error) { 61 | var table *gosym.Table 62 | if table, err = GetSymbolTable(); err != nil { 63 | return 64 | } 65 | 66 | symbol = table.LookupFunc(name) 67 | if symbol == nil { 68 | err = fmt.Errorf("%v: function symbol not found", name) 69 | } 70 | return 71 | } 72 | -------------------------------------------------------------------------------- /symbols_darwin.go: -------------------------------------------------------------------------------- 1 | // +build darwin 2 | 3 | package subvert 4 | 5 | import ( 6 | "bytes" 7 | "debug/gosym" 8 | "debug/macho" 9 | "fmt" 10 | "io" 11 | "os" 12 | ) 13 | 14 | func osReadSymbolsFromMemory() (symTable *gosym.Table, err error) { 15 | if processBaseAddress == 0 { 16 | return nil, fmt.Errorf("Base address not found") 17 | } 18 | reader := bytes.NewReader(SliceAtAddress(processBaseAddress, 0x10000000)) 19 | return osReadSymbols(reader) 20 | } 21 | 22 | func osReadSymbolsFromExeFile() (symTable *gosym.Table, err error) { 23 | var exePath string 24 | if exePath, err = os.Executable(); err != nil { 25 | symTableLoadError = err 26 | return 27 | } 28 | 29 | var reader io.ReaderAt 30 | if reader, err = os.Open(exePath); err != nil { 31 | symTableLoadError = err 32 | return 33 | } 34 | 35 | return osReadSymbols(reader) 36 | } 37 | 38 | func osReadSymbols(reader io.ReaderAt) (symTable *gosym.Table, err error) { 39 | exe, err := macho.NewFile(reader) 40 | if err != nil { 41 | return 42 | } 43 | defer exe.Close() 44 | 45 | var sect *macho.Section 46 | if sect = exe.Section("__text"); sect == nil { 47 | err = fmt.Errorf("Unable to find Mach-O __text section") 48 | return 49 | } 50 | textStart := sect.Addr 51 | 52 | if sect = exe.Section("__gopclntab"); sect == nil { 53 | err = fmt.Errorf("Unable to find Mach-O __gopclntab section") 54 | return 55 | } 56 | lineTableData, err := sect.Data() 57 | if err != nil { 58 | return 59 | } 60 | 61 | lineTable := gosym.NewLineTable(lineTableData, textStart) 62 | return gosym.NewTable([]byte{}, lineTable) 63 | } 64 | -------------------------------------------------------------------------------- /symbols_elf.go: -------------------------------------------------------------------------------- 1 | // +build !windows,!darwin 2 | 3 | package subvert 4 | 5 | import ( 6 | "bytes" 7 | "debug/elf" 8 | "debug/gosym" 9 | "fmt" 10 | "io" 11 | "os" 12 | ) 13 | 14 | func osReadSymbolsFromMemory() (symTable *gosym.Table, err error) { 15 | if processBaseAddress == 0 { 16 | return nil, fmt.Errorf("Base address not found") 17 | } 18 | reader := bytes.NewReader(SliceAtAddress(processBaseAddress, 0x10000000)) 19 | return osReadSymbols(reader) 20 | } 21 | 22 | func osReadSymbolsFromExeFile() (symTable *gosym.Table, err error) { 23 | var exePath string 24 | if exePath, err = os.Executable(); err != nil { 25 | symTableLoadError = err 26 | return 27 | } 28 | 29 | var reader io.ReaderAt 30 | if reader, err = os.Open(exePath); err != nil { 31 | symTableLoadError = err 32 | return 33 | } 34 | 35 | return osReadSymbols(reader) 36 | } 37 | 38 | func osReadSymbols(reader io.ReaderAt) (symTable *gosym.Table, err error) { 39 | exe, err := elf.NewFile(reader) 40 | if err != nil { 41 | return 42 | } 43 | defer exe.Close() 44 | 45 | sect := exe.Section(".text") 46 | if sect == nil { 47 | err = fmt.Errorf("Unable to find ELF .text section") 48 | return 49 | } 50 | textStart := sect.Addr 51 | 52 | sect = exe.Section(".gopclntab") 53 | if sect == nil { 54 | err = fmt.Errorf("Unable to find ELF .gopclntab section") 55 | return 56 | } 57 | lineTableData, err := sect.Data() 58 | if err != nil { 59 | return 60 | } 61 | 62 | lineTable := gosym.NewLineTable(lineTableData, textStart) 63 | return gosym.NewTable([]byte{}, lineTable) 64 | } 65 | -------------------------------------------------------------------------------- /symbols_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package subvert 4 | 5 | import ( 6 | // "bytes" 7 | "debug/gosym" 8 | "debug/pe" 9 | "fmt" 10 | "io" 11 | "os" 12 | ) 13 | 14 | func osReadSymbolsFromMemory() (symTable *gosym.Table, err error) { 15 | if processBaseAddress == 0 { 16 | return nil, fmt.Errorf("Base address not found") 17 | } 18 | 19 | return nil, fmt.Errorf("TODO: Fails with: fail to read string table: unexpected EOF") 20 | 21 | // reader := bytes.NewReader(SliceAtAddress(processBaseAddress, 0x2c4000)) 22 | // return osReadSymbols(reader) 23 | } 24 | 25 | func osReadSymbolsFromExeFile() (symTable *gosym.Table, err error) { 26 | var exePath string 27 | if exePath, err = os.Executable(); err != nil { 28 | symTableLoadError = err 29 | return 30 | } 31 | 32 | var reader io.ReaderAt 33 | if reader, err = os.Open(exePath); err != nil { 34 | symTableLoadError = err 35 | return 36 | } 37 | 38 | return osReadSymbols(reader) 39 | } 40 | 41 | func osReadSymbols(reader io.ReaderAt) (symTable *gosym.Table, err error) { 42 | exe, err := pe.NewFile(reader) 43 | if err != nil { 44 | return 45 | } 46 | defer exe.Close() 47 | 48 | var imageBase uint64 49 | switch oh := exe.OptionalHeader.(type) { 50 | case *pe.OptionalHeader32: 51 | imageBase = uint64(oh.ImageBase) 52 | case *pe.OptionalHeader64: 53 | imageBase = oh.ImageBase 54 | default: 55 | err = fmt.Errorf("Unrecognized PE format") 56 | return 57 | } 58 | 59 | sect := exe.Section(".text") 60 | if sect == nil { 61 | err = fmt.Errorf("Unable to find PE .text section") 62 | return 63 | } 64 | textStart := imageBase + uint64(sect.VirtualAddress) 65 | 66 | findSymbol := func(symbols []*pe.Symbol, name string) *pe.Symbol { 67 | for _, s := range symbols { 68 | if s.Name == name { 69 | return s 70 | } 71 | } 72 | return nil 73 | } 74 | 75 | lineTableStart := findSymbol(exe.Symbols, "runtime.pclntab") 76 | lineTableEnd := findSymbol(exe.Symbols, "runtime.epclntab") 77 | if lineTableStart == nil || lineTableEnd == nil { 78 | err = fmt.Errorf("Could not find PE runtime.pclntab or runtime.epclntab") 79 | return 80 | } 81 | sectionIndex := lineTableStart.SectionNumber - 1 82 | if sectionIndex < 0 || int(sectionIndex) >= len(exe.Sections) { 83 | err = fmt.Errorf("Invalid PE format: invalid section number %v", lineTableStart.SectionNumber) 84 | return 85 | } 86 | lineTableData, err := exe.Sections[sectionIndex].Data() 87 | if err != nil { 88 | return 89 | } 90 | if int(lineTableStart.Value) > len(lineTableData) || 91 | int(lineTableEnd.Value) > len(lineTableData) || 92 | lineTableStart.Value > lineTableEnd.Value { 93 | err = fmt.Errorf("Invalid PE pcln start/end indices: %v, %v", lineTableStart.Value, lineTableEnd.Value) 94 | return 95 | } 96 | lineTableData = lineTableData[lineTableStart.Value:lineTableEnd.Value] 97 | 98 | lineTable := gosym.NewLineTable(lineTableData, textStart) 99 | return gosym.NewTable([]byte{}, lineTable) 100 | } 101 | --------------------------------------------------------------------------------