github.com/iDigitalFlame/xmt@v0.5.4/cmd/script/smonkey/monkey.go_ (about)

     1  // Copyright (C) 2020 - 2023 iDigitalFlame
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU General Public License as published by
     5  // the Free Software Foundation, either version 3 of the License, or
     6  // any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU General Public License
    14  // along with this program.  If not, see <https://www.gnu.org/licenses/>.
    15  //
    16  
    17  // Package smonkey is a mapping for the Monkey (github.com/skx/monkey) Scripting
    18  // engine.
    19  package smonkey
    20  
    21  import (
    22  	"context"
    23  	"fmt"
    24  	"strings"
    25  	"sync"
    26  	"time"
    27  	"unsafe"
    28  
    29  	"github.com/iDigitalFlame/xmt/c2/task"
    30  	"github.com/iDigitalFlame/xmt/cmd"
    31  	"github.com/iDigitalFlame/xmt/util"
    32  	"github.com/iDigitalFlame/xmt/util/xerr"
    33  	"github.com/skx/monkey/evaluator"
    34  	"github.com/skx/monkey/lexer"
    35  	"github.com/skx/monkey/object"
    36  	"github.com/skx/monkey/parser"
    37  )
    38  
    39  // Monkey is a mapping for the Monkey (github.com/skx/monkey) Scripting engine.
    40  // This can be used to run Monkey scripts directly and can be registered by the
    41  // 'task.RegisterScript' function to include the engine in the XMT task runtime.
    42  const Monkey monkeyEngine = 0xE1
    43  
    44  var (
    45  	monkeyPool = sync.Pool{
    46  		New: func() any {
    47  			return &monkeyScript{Environment: object.NewEnvironment()}
    48  		},
    49  	}
    50  
    51  	// Idea from the great read of https://tailscale.com/blog/netaddr-new-ip-type-for-go/
    52  	// Source of the code for this is at https://pkg.go.dev/go4.org/intern
    53  	monkeyLock    sync.RWMutex
    54  	monkeyTracker = make(map[uintptr]*monkeyScript)
    55  )
    56  
    57  type monkeyEngine uint8
    58  type monkeyScript struct {
    59  	*object.Environment
    60  	c util.Builder
    61  }
    62  
    63  func init() {
    64  	evaluator.RegisterBuiltin("exec", exec)
    65  	evaluator.RegisterBuiltin("puts", print)
    66  	evaluator.RegisterBuiltin("sleep", sleep)
    67  	evaluator.RegisterBuiltin("print", print)
    68  	evaluator.RegisterBuiltin("printf", printf)
    69  	evaluator.RegisterBuiltin("println", println)
    70  	evaluator.RegisterBuiltin("exit", func(_ *object.Environment, _ ...object.Object) object.Object { return evaluator.NULL })
    71  }
    72  
    73  // Register is a simple shortcut for 'task.RegisterEngine(uint8(Monkey), Monkey)'.
    74  func Register() error {
    75  	return task.RegisterEngine(uint8(Monkey), Monkey)
    76  }
    77  
    78  // Invoke will use the Monkey (github.com/skx/monkey) Scripting engine. This can
    79  // be used to run code not built in at compile time. The only argument is the script
    80  // that is to be run. The results are the output of the console (all print* together)
    81  // and any errors that may occur or syntax errors.
    82  //
    83  // This will capture the output of all the console writes and adds a 'print*'
    84  // statement as a shortcut to be used.
    85  //
    86  // Another additional function 'exec' can be used to run commands natively. This
    87  // function can take a vardict of strings to be the command line arguments.
    88  func Invoke(s string) (string, error) {
    89  	return InvokeEx(context.Background(), nil, s)
    90  }
    91  
    92  // InvokeContext will use the Monkey (github.com/skx/monkey) Scripting engine.
    93  // This can be used to run code not built in at compile time. A context is required
    94  // to timeout the script execution and the script to be run. The results are the
    95  // output of the console (all print* together) and any errors that may occur or
    96  // syntax errors.
    97  //
    98  // This will capture the output of all the console writes and adds a 'print*'
    99  // statement as a shortcut to be used.
   100  //
   101  // Another additional function 'exec' can be used to run commands natively. This
   102  // function can take a vardict of strings to be the command line arguments.
   103  func InvokeContext(x context.Context, s string) (string, error) {
   104  	return InvokeEx(x, nil, s)
   105  }
   106  func exec(_ *object.Environment, a ...object.Object) object.Object {
   107  	var p cmd.Process
   108  	if len(a) == 1 {
   109  		p.Args = cmd.Split(a[0].Inspect())
   110  	} else {
   111  		for i := range a {
   112  			p.Args = append(p.Args, a[i].Inspect())
   113  		}
   114  	}
   115  	b, err := p.CombinedOutput()
   116  	if err != nil {
   117  		return &object.Error{Message: err.Error()}
   118  	}
   119  	if len(b) > 0 && b[len(b)-1] == 10 {
   120  		b = b[:len(b)-1]
   121  	}
   122  	return &object.String{Value: string(b)}
   123  }
   124  func sleep(_ *object.Environment, a ...object.Object) object.Object {
   125  	if len(a) == 0 {
   126  		return evaluator.NULL
   127  	}
   128  	var n float64
   129  	switch a[0].Type() {
   130  	case object.FLOAT_OBJ:
   131  		if v, ok := a[0].(*object.Float); ok {
   132  			n = v.Value
   133  		}
   134  	case object.INTEGER_OBJ:
   135  		if v, ok := a[0].(*object.Integer); ok && v.Value > 0 {
   136  			n = float64(v.Value)
   137  		}
   138  	}
   139  	if n > 0 {
   140  		time.Sleep(time.Duration(n * float64(time.Second)))
   141  	}
   142  	return evaluator.NULL
   143  }
   144  func print(e *object.Environment, a ...object.Object) object.Object {
   145  	monkeyLock.RLock()
   146  	m, ok := monkeyTracker[uintptr(unsafe.Pointer(e))]
   147  	if monkeyLock.RUnlock(); !ok {
   148  		return evaluator.NULL
   149  	}
   150  	for i := range a {
   151  		m.c.WriteString(a[i].Inspect())
   152  	}
   153  	return evaluator.NULL
   154  }
   155  func printf(e *object.Environment, a ...object.Object) object.Object {
   156  	if len(a) < 1 || a[0].Type() != object.STRING_OBJ {
   157  		return evaluator.NULL
   158  	}
   159  	monkeyLock.RLock()
   160  	var (
   161  		d     = make([]any, len(a)-1)
   162  		m, ok = monkeyTracker[uintptr(unsafe.Pointer(e))]
   163  	)
   164  	if monkeyLock.RUnlock(); !ok {
   165  		return evaluator.NULL
   166  	}
   167  	for i := 1; i < len(a); i++ {
   168  		d[i-1] = a[i].ToInterface()
   169  	}
   170  	m.c.WriteString(fmt.Sprintf(a[0].Inspect(), d...))
   171  	return evaluator.NULL
   172  }
   173  func println(e *object.Environment, a ...object.Object) object.Object {
   174  	return print(e, append(a, &object.String{Value: "\n"})...)
   175  }
   176  
   177  // InvokeEx will use the Monkey (github.com/skx/monkey) Scripting engine.
   178  // This can be used to run code not built in at compile time. A context is required
   179  // to timeout the script execution and the script to be run. The results are the
   180  // output of the console (all print* together) and any errors that may occur or
   181  // syntax errors.
   182  //
   183  // This will capture the output of all the console writes and adds a 'print*'
   184  // statement as a shortcut to be used.
   185  //
   186  // Another additional function 'exec' can be used to run commands natively. This
   187  // function can take a vardict of strings to be the command line arguments.
   188  //
   189  // This Ex function allows to specify a map that contains any starting variables
   190  // to be supplied at runtime.
   191  func InvokeEx(x context.Context, m map[string]any, s string) (string, error) {
   192  	p := parser.New(lexer.New(s))
   193  	if len(p.Errors()) != 0 {
   194  		return "", xerr.Sub(strings.Join(p.Errors(), ";"), 0x1)
   195  	}
   196  	var (
   197  		d = p.ParseProgram()
   198  		e = monkeyPool.Get().(*monkeyScript)
   199  	)
   200  	for k, v := range m {
   201  		if v == nil {
   202  			e.SetConst(k, evaluator.NULL)
   203  			continue
   204  		}
   205  		switch t := v.(type) {
   206  		case bool:
   207  			if t {
   208  				e.SetConst(k, evaluator.TRUE)
   209  				continue
   210  			}
   211  			e.SetConst(k, evaluator.FALSE)
   212  		case []byte:
   213  			a := make([]object.Object, len(t))
   214  			for i := range t {
   215  				a[i] = &object.Integer{Value: int64(t[i])}
   216  			}
   217  			e.SetConst(k, &object.Array{Elements: a})
   218  		case string:
   219  			e.SetConst(k, &object.String{Value: t})
   220  		case int:
   221  			e.SetConst(k, &object.Integer{Value: int64(t)})
   222  		case int8:
   223  			e.SetConst(k, &object.Integer{Value: int64(t)})
   224  		case int16:
   225  			e.SetConst(k, &object.Integer{Value: int64(t)})
   226  		case int32:
   227  			e.SetConst(k, &object.Integer{Value: int64(t)})
   228  		case int64:
   229  			e.SetConst(k, &object.Integer{Value: t})
   230  		case uint:
   231  			e.SetConst(k, &object.Integer{Value: int64(t)})
   232  		case uint8:
   233  			e.SetConst(k, &object.Integer{Value: int64(t)})
   234  		case uint16:
   235  			e.SetConst(k, &object.Integer{Value: int64(t)})
   236  		case uint32:
   237  			e.SetConst(k, &object.Integer{Value: int64(t)})
   238  		case uint64:
   239  			e.SetConst(k, &object.Integer{Value: int64(t)})
   240  		case float32:
   241  			e.SetConst(k, &object.Float{Value: float64(t)})
   242  		case float64:
   243  			e.SetConst(k, &object.Float{Value: t})
   244  		}
   245  	}
   246  	monkeyLock.Lock()
   247  	var (
   248  		u     = uintptr(unsafe.Pointer(e.Environment))
   249  		_, ok = monkeyTracker[u]
   250  	)
   251  	if !ok {
   252  		monkeyTracker[u] = e
   253  	}
   254  	monkeyLock.Unlock()
   255  	var (
   256  		o   = evaluator.EvalContext(x, d, e.Environment)
   257  		r   string
   258  		err error
   259  	)
   260  	if o.Type() == object.ERROR_OBJ {
   261  		err = xerr.Sub(o.Inspect(), 0x1)
   262  	} else {
   263  		r = o.Inspect()
   264  	}
   265  	r = e.c.String() + r
   266  	e.c.Reset()
   267  	for k := range m {
   268  		e.SetConst(k, nil)
   269  	}
   270  	monkeyPool.Put(e)
   271  	return r, err
   272  }
   273  func (monkeyEngine) Invoke(x context.Context, m map[string]any, s string) (string, error) {
   274  	return InvokeEx(x, m, s)
   275  }