Skip to content

Commit

Permalink
Merge pull request #4 from Mutated1994/master
Browse files Browse the repository at this point in the history
feat: Combine SuperMonkey with Monkey.
  • Loading branch information
cch123 authored Sep 13, 2020
2 parents 28d87e2 + 7f6c61d commit b9d97a5
Show file tree
Hide file tree
Showing 50 changed files with 317 additions and 125 deletions.
13 changes: 13 additions & 0 deletions examples/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package main

import "fmt"

func main() {
patchFunc()
fmt.Println()
patchFuncSymbol()
fmt.Println()
patchInstanceFunc()
fmt.Println()
patchInstanceFuncSymbol()
}
10 changes: 4 additions & 6 deletions examples/patch_func.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,18 @@ import (
sm "github.com/cch123/supermonkey"
)

func main() {
func patchFunc() {
fmt.Println("original function output:")
heyHey()
fmt.Println()

sm.Patch("main", "", "heyHey", func() {
patchGuard := sm.Patch(heyHey, func() {
fmt.Println("please be polite")
})
fmt.Println("after patch, function output:")
heyHey()
fmt.Println()

sm.UnpatchAll()
fmt.Println("unpatch all, then output:")
patchGuard.Unpatch()
fmt.Println("unpatch, then output:")
heyHey()
}

Expand Down
18 changes: 8 additions & 10 deletions examples/patch_func_symbol.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,22 @@ import (
sm "github.com/cch123/supermonkey"
)

func main() {
func patchFuncSymbol() {
fmt.Println("original function output:")
heyHey()
fmt.Println()
heyHeyHey()

sm.PatchByFullSymbolName("main.heyHey", func() {
patchGuard := sm.PatchByFullSymbolName("main.heyHeyHey", func() {
fmt.Println("please be polite")
})
fmt.Println("after patch, function output:")
heyHey()
fmt.Println()
heyHeyHey()

sm.UnpatchAll()
fmt.Println("unpatch all, then output:")
heyHey()
patchGuard.Unpatch()
fmt.Println("unpatch, then output:")
heyHeyHey()
}

//go:noinline
func heyHey() {
func heyHeyHey() {
fmt.Println("fake")
}
13 changes: 6 additions & 7 deletions examples/patch_instance_func.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,23 @@ import (

type person struct{ name string }

//go:noinline
func (p *person) speak() {
fmt.Println("my name is ", p.name)
}

func main() {
var p = person{"Xargin"}
func patchInstanceFunc() {
p := &person{"Lance"}
fmt.Println("original function output:")
p.speak()
fmt.Println()

sm.Patch("main", "*person", "speak", func() {
patchGuard := sm.Patch((*person).speak, func(*person) {
fmt.Println("we are all the same")
})
fmt.Println("after patch, function output:")
p.speak()
fmt.Println()

sm.UnpatchAll()
fmt.Println("unpatch all, then output:")
patchGuard.Unpatch()
fmt.Println("unpatch, then output:")
p.speak()
}
22 changes: 8 additions & 14 deletions examples/patch_instance_func_symbol.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,24 @@ package main

import (
"fmt"
"unsafe"

sm "github.com/cch123/supermonkey"
)

type person struct{ name string }

func (p *person) speak() {
fmt.Println("my name is ", p.name)
}

func main() {
var p = person{"Xargin"}
func patchInstanceFuncSymbol() {
p := &person{"Linda"}
fmt.Println("original function output:")
p.speak()
fmt.Println()

sm.PatchByFullSymbolName("main.(*person).speak", func() {
fmt.Println("we are all the same")
patchGuard := sm.PatchByFullSymbolName("main.(*person).speak", func(ptr uintptr) {
p = (*person)(unsafe.Pointer(ptr))
fmt.Println(p.name, ", we are all the same")
})
fmt.Println("after patch, function output:")
p.speak()
fmt.Println()

sm.UnpatchAll()
fmt.Println("unpatch all, then output:")
patchGuard.Unpatch()
fmt.Println("unpatch, then output:")
p.speak()
}
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ module github.com/cch123/supermonkey

go 1.14

require golang.org/x/arch v0.0.0-20200826200359-b19915210f00
require (
github.com/smartystreets/goconvey v1.6.4
golang.org/x/arch v0.0.0-20200826200359-b19915210f00
)
153 changes: 153 additions & 0 deletions internal/bouk/monkey.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package bouk

import (
"fmt"
"reflect"
"sync"
"unsafe"
)

// patch is an applied patch
// needed to undo a patch
type patch struct {
originalBytes []byte
replacement *reflect.Value
}

var (
lock = sync.Mutex{}

patches = make(map[uintptr]patch)
)

type value struct {
_ uintptr
ptr unsafe.Pointer
}

func getPtr(v reflect.Value) unsafe.Pointer {
return (*value)(unsafe.Pointer(&v)).ptr
}

type PatchGuard struct {
target reflect.Value
replacement reflect.Value
}

func (g *PatchGuard) Unpatch() {
unpatchValue(g.target)
}

func (g *PatchGuard) Restore() {
patchValue(g.target, g.replacement)
}

// Patch replaces a function with another
func Patch(target, replacement interface{}) *PatchGuard {
t := reflect.ValueOf(target)
r := reflect.ValueOf(replacement)
patchValue(t, r)

return &PatchGuard{t, r}
}

func PatchSymbol(target, replacement interface{}) *PatchGuard {
t := reflect.ValueOf(target)
r := reflect.ValueOf(replacement)
patchSymbolValue(t, r)

return &PatchGuard{t, r}
}

// PatchInstanceMethod replaces an instance method methodName for the type target with replacement
// Replacement should expect the receiver (of type target) as the first argument
func PatchInstanceMethod(target reflect.Type, methodName string, replacement interface{}) *PatchGuard {
m, ok := target.MethodByName(methodName)
if !ok {
panic(fmt.Sprintf("unknown method %s", methodName))
}
r := reflect.ValueOf(replacement)
patchValue(m.Func, r)

return &PatchGuard{m.Func, r}
}

func patchValue(target, replacement reflect.Value) {
lock.Lock()
defer lock.Unlock()

if target.Kind() != reflect.Func {
panic("target has to be a Func")
}

if replacement.Kind() != reflect.Func {
panic("replacement has to be a Func")
}

if target.Type() != replacement.Type() {
panic(fmt.Sprintf("target and replacement have to have the same type %s != %s", target.Type(), replacement.Type()))
}

if patch, ok := patches[target.Pointer()]; ok {
unpatch(target.Pointer(), patch)
}

bytes := ReplaceFunction(target.Pointer(), (uintptr)(getPtr(replacement)))
patches[target.Pointer()] = patch{bytes, &replacement}
}

func patchSymbolValue(target, replacement reflect.Value) {
lock.Lock()
defer lock.Unlock()

if patch, ok := patches[target.Pointer()]; ok {
unpatch(target.Pointer(), patch)
}

bytes := ReplaceFunction(target.Pointer(), (uintptr)(getPtr(replacement)))
patches[target.Pointer()] = patch{bytes, &replacement}
}

// Unpatch removes any monkey patches on target
// returns whether target was patched in the first place
func Unpatch(target interface{}) bool {
return unpatchValue(reflect.ValueOf(target))
}

// UnpatchInstanceMethod removes the patch on methodName of the target
// returns whether it was patched in the first place
func UnpatchInstanceMethod(target reflect.Type, methodName string) bool {
m, ok := target.MethodByName(methodName)
if !ok {
panic(fmt.Sprintf("unknown method %s", methodName))
}
return unpatchValue(m.Func)
}

// UnpatchAll removes all applied monkeypatches
func UnpatchAll() {
lock.Lock()
defer lock.Unlock()
for target, p := range patches {
unpatch(target, p)
delete(patches, target)
}
}

// Unpatch removes a monkeypatch from the specified function
// returns whether the function was patched in the first place
func unpatchValue(target reflect.Value) bool {
lock.Lock()
defer lock.Unlock()
patch, ok := patches[target.Pointer()]
if !ok {
return false
}
unpatch(target.Pointer(), patch)
delete(patches, target.Pointer())
return true
}

func unpatch(target uintptr, p patch) {
CopyToLocation(target, p.originalBytes)
}
4 changes: 2 additions & 2 deletions sm_386.go → internal/bouk/monkey_386.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package supermonkey
package bouk

// Assembles a jump to a function value
func jmpToFunctionValue(to uintptr) []byte {
func JmpToFunctionValue(to uintptr) []byte {
return []byte{
0xBA,
byte(to),
Expand Down
4 changes: 2 additions & 2 deletions sm_amd64.go → internal/bouk/monkey_amd64.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package supermonkey
package bouk

// Assembles a jump to a function value
func jmpToFunctionValue(to uintptr) []byte {
func JmpToFunctionValue(to uintptr) []byte {
return []byte{
0x48, 0xBA,
byte(to),
Expand Down
8 changes: 4 additions & 4 deletions replace.go → internal/bouk/replace.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package supermonkey
package bouk

import (
"reflect"
Expand All @@ -20,12 +20,12 @@ func pageStart(ptr uintptr) uintptr {

// from is a pointer to the actual function
// to is a pointer to a go funcvalue
func replaceFunction(from, to uintptr) (original []byte) {
jumpData := jmpToFunctionValue(to)
func ReplaceFunction(from, to uintptr) (original []byte) {
jumpData := JmpToFunctionValue(to)
f := rawMemoryAccess(from, len(jumpData))
original = make([]byte, len(f))
copy(original, f)

copyToLocation(from, jumpData)
CopyToLocation(from, jumpData)
return
}
6 changes: 3 additions & 3 deletions replace_unix.go → internal/bouk/replace_unix.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
//+build !windows

package supermonkey
package bouk

import (
"syscall"
)

func mprotectCrossPage(addr uintptr, length int, prot int) {
pageSize := syscall.Getpagesize()
for p := pageStart(addr); p < addr+uintptr(length); p += uintptr(pageSize) {
for p := pageStart(addr); p < addr + uintptr(length); p += uintptr(pageSize) {
page := rawMemoryAccess(p, pageSize)
err := syscall.Mprotect(page, prot)
if err != nil {
Expand All @@ -20,7 +20,7 @@ func mprotectCrossPage(addr uintptr, length int, prot int) {
// this function is super unsafe
// aww yeah
// It copies a slice to a raw memory location, disabling all memory protection before doing so.
func copyToLocation(location uintptr, data []byte) {
func CopyToLocation(location uintptr, data []byte) {
f := rawMemoryAccess(location, len(data))

mprotectCrossPage(location, len(data), syscall.PROT_READ|syscall.PROT_WRITE|syscall.PROT_EXEC)
Expand Down
4 changes: 2 additions & 2 deletions replace_windows.go → internal/bouk/replace_windows.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package supermonkey
package bouk

import (
"syscall"
Expand All @@ -24,7 +24,7 @@ func virtualProtect(lpAddress uintptr, dwSize int, flNewProtect uint32, lpflOldP
// this function is super unsafe
// aww yeah
// It copies a slice to a raw memory location, disabling all memory protection before doing so.
func copyToLocation(location uintptr, data []byte) {
func CopyToLocation(location uintptr, data []byte) {
f := rawMemoryAccess(location, len(data))

var oldPerms uint32
Expand Down
2 changes: 1 addition & 1 deletion goobj/read.go → internal/goobj/read.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ package goobj
import (
"bufio"
"bytes"
"github.com/cch123/supermonkey/objabi"
"github.com/cch123/supermonkey/internal/objabi"
"errors"
"fmt"
"io"
Expand Down
Loading

0 comments on commit b9d97a5

Please sign in to comment.