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 }