From 40727063ec4d7701c8d643c81e463c1cafe701e1 Mon Sep 17 00:00:00 2001 From: Simone Margaritelli Date: Sun, 4 Apr 2021 15:15:32 +0200 Subject: [PATCH] new: new -script allows to run JS code to instrument session --- .gitignore | 1 + core/options.go | 2 + example.js | 70 +++++++ js/data.go | 86 +++++++++ js/fs.go | 70 +++++++ js/http.go | 155 +++++++++++++++ js/init.go | 38 ++++ js/log.go | 48 +++++ modules/utils/script_builtins.go | 311 ------------------------------ session/script.go | 21 ++ session/script_builtin.go | 33 ++++ session/script_builtin_runtime.go | 67 +++++++ session/session.go | 20 +- 13 files changed, 610 insertions(+), 312 deletions(-) create mode 100644 example.js create mode 100644 js/data.go create mode 100644 js/fs.go create mode 100644 js/http.go create mode 100644 js/init.go create mode 100644 js/log.go delete mode 100644 modules/utils/script_builtins.go create mode 100644 session/script.go create mode 100644 session/script_builtin.go create mode 100644 session/script_builtin_runtime.go diff --git a/.gitignore b/.gitignore index cca5b812..b5b43555 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.sw* *.tar.gz *.prof* +betterbot.js pcaps build bettercap diff --git a/core/options.go b/core/options.go index 30b0af1e..c596a6bd 100644 --- a/core/options.go +++ b/core/options.go @@ -17,6 +17,7 @@ type Options struct { CpuProfile *string MemProfile *string CapletsPath *string + Script *string } func ParseOptions() (Options, error) { @@ -35,6 +36,7 @@ func ParseOptions() (Options, error) { CpuProfile: flag.String("cpu-profile", "", "Write cpu profile `file`."), MemProfile: flag.String("mem-profile", "", "Write memory profile to `file`."), CapletsPath: flag.String("caplets-path", "", "Specify an alternative base path for caplets."), + Script: flag.String("script", "", "Load a session script."), } flag.Parse() diff --git a/example.js b/example.js new file mode 100644 index 00000000..a8d55615 --- /dev/null +++ b/example.js @@ -0,0 +1,70 @@ +const telegramToken = 'put your telegram bot token here'; +const telegramChatId = 'put your telegram chat id here'; + +function sendMessage(message) { + var url = 'https://api.telegram.org/bot' + telegramToken + + '/sendMessage?chat_id=' + telegramChatId + + '&text=' + http.Encode(message); + + var resp = http.Get(url, {}); + if( resp.Error ) { + log("error while running sending telegram message: " + resp.Error.Error()); + } +} + +log("session script loaded"); + +// enable recon and probing of new hosts +run('net.recon on'); +run('net.probe on'); + +// enable wifi scanning +run('set wifi.interface wlx00c0ca916886'); +run('wifi.recon on'); + +// register for wifi.deauthentication events +onEvent('wifi.deauthentication', function(event){ + var data = event.Data; + var message = '🚨 Detected deauthentication frame:\n\n' + + 'Time: ' + event.Time.String() + "\n" + + 'GPS: lat=' + session.GPS.Latitude + " lon=" + session.GPS.Longitude + " updated_at=" + session.GPS.Updated.String() + "\n\n" + + 'RSSI: ' + data.RSSI + "\n" + + 'Reason: ' + data.Reason + "\n" + + 'Address1: ' + data.Address1 + "\n" + + 'Address2: ' + data.Address2 + "\n" + + 'Address3: ' + data.Address3; + + // send to telegram bot + sendMessage(message); +}); + +// register for wifi.client.handshake events +onEvent('wifi.client.handshake', function(event){ + var data = event.Data; + var what = 'handshake'; + + if(data.PMKID != null) { + what = "RSN PMKID"; + } else if(data.Full) { + what += " (full)"; + } else if(hand.Half) { + what += " (half)"; + } + + var message = '💰 Captured ' + what + ':\n\n' + + 'Time: ' + event.Time.String() + "\n" + + 'GPS: lat=' + session.GPS.Latitude + " lon=" + session.GPS.Longitude + " updated_at=" + session.GPS.Updated.String() + "\n\n" + + 'Station: ' + data.Station + "\n" + + 'AP: ' + data.AP; + + // send to telegram bot + sendMessage(message); +}); + +// register for any event +onEvent(function(event){ + // if endpoint.new or endpoint.lost, clear the screen and show hosts + if( event.Tag.indexOf('endpoint.') === 0 ) { + run('clear; net.show'); + } +}); diff --git a/js/data.go b/js/data.go new file mode 100644 index 00000000..d6975c3a --- /dev/null +++ b/js/data.go @@ -0,0 +1,86 @@ +package js + +import ( + "bytes" + "compress/gzip" + "encoding/base64" + "github.com/robertkrimen/otto" +) + +func btoa(call otto.FunctionCall) otto.Value { + varValue := base64.StdEncoding.EncodeToString([]byte(call.Argument(0).String())) + v, err := otto.ToValue(varValue) + if err != nil { + return ReportError("Could not convert to string: %s", varValue) + } + return v +} + +func atob(call otto.FunctionCall) otto.Value { + varValue, err := base64.StdEncoding.DecodeString(call.Argument(0).String()) + if err != nil { + return ReportError("Could not decode string: %s", call.Argument(0).String()) + } + v, err := otto.ToValue(string(varValue)) + if err != nil { + return ReportError("Could not convert to string: %s", varValue) + } + return v +} + +func gzipCompress(call otto.FunctionCall) otto.Value { + argv := call.ArgumentList + argc := len(argv) + if argc != 1 { + return ReportError("gzipCompress: expected 1 argument, %d given instead.", argc) + } + + uncompressedBytes := []byte(argv[0].String()) + + var writerBuffer bytes.Buffer + gzipWriter := gzip.NewWriter(&writerBuffer) + _, err := gzipWriter.Write(uncompressedBytes) + if err != nil { + return ReportError("gzipCompress: could not compress data: %s", err.Error()) + } + gzipWriter.Close() + + compressedBytes := writerBuffer.Bytes() + + v, err := otto.ToValue(string(compressedBytes)) + if err != nil { + return ReportError("Could not convert to string: %s", err.Error()) + } + + return v +} + +func gzipDecompress(call otto.FunctionCall) otto.Value { + argv := call.ArgumentList + argc := len(argv) + if argc != 1 { + return ReportError("gzipDecompress: expected 1 argument, %d given instead.", argc) + } + + compressedBytes := []byte(argv[0].String()) + readerBuffer := bytes.NewBuffer(compressedBytes) + + gzipReader, err := gzip.NewReader(readerBuffer) + if err != nil { + return ReportError("gzipDecompress: could not create gzip reader: %s", err.Error()) + } + + var decompressedBuffer bytes.Buffer + _, err = decompressedBuffer.ReadFrom(gzipReader) + if err != nil { + return ReportError("gzipDecompress: could not decompress data: %s", err.Error()) + } + + decompressedBytes := decompressedBuffer.Bytes() + v, err := otto.ToValue(string(decompressedBytes)) + if err != nil { + return ReportError("Could not convert to string: %s", err.Error()) + } + + return v +} diff --git a/js/fs.go b/js/fs.go new file mode 100644 index 00000000..fbe02ccf --- /dev/null +++ b/js/fs.go @@ -0,0 +1,70 @@ +package js + +import ( + "github.com/robertkrimen/otto" + "io/ioutil" +) + +func readDir(call otto.FunctionCall) otto.Value { + argv := call.ArgumentList + argc := len(argv) + if argc != 1 { + return ReportError("readDir: expected 1 argument, %d given instead.", argc) + } + + path := argv[0].String() + dir, err := ioutil.ReadDir(path) + if err != nil { + return ReportError("Could not read directory %s: %s", path, err) + } + + entry_list := []string{} + for _, file := range dir { + entry_list = append(entry_list, file.Name()) + } + + v, err := otto.Otto.ToValue(*call.Otto, entry_list) + if err != nil { + return ReportError("Could not convert to array: %s", err) + } + + return v +} + +func readFile(call otto.FunctionCall) otto.Value { + argv := call.ArgumentList + argc := len(argv) + if argc != 1 { + return ReportError("readFile: expected 1 argument, %d given instead.", argc) + } + + filename := argv[0].String() + raw, err := ioutil.ReadFile(filename) + if err != nil { + return ReportError("Could not read file %s: %s", filename, err) + } + + v, err := otto.ToValue(string(raw)) + if err != nil { + return ReportError("Could not convert to string: %s", err) + } + return v +} + +func writeFile(call otto.FunctionCall) otto.Value { + argv := call.ArgumentList + argc := len(argv) + if argc != 2 { + return ReportError("writeFile: expected 2 arguments, %d given instead.", argc) + } + + filename := argv[0].String() + data := argv[1].String() + + err := ioutil.WriteFile(filename, []byte(data), 0644) + if err != nil { + return ReportError("Could not write %d bytes to %s: %s", len(data), filename, err) + } + + return otto.NullValue() +} \ No newline at end of file diff --git a/js/http.go b/js/http.go new file mode 100644 index 00000000..16d95f42 --- /dev/null +++ b/js/http.go @@ -0,0 +1,155 @@ +package js + +import ( + "bytes" + "fmt" + "github.com/robertkrimen/otto" + "io" + "io/ioutil" + "net/http" + "net/url" + "strings" +) + +type httpPackage struct { +} + +type httpResponse struct { + Error error + Response *http.Response + Raw []byte + Body string + JSON interface{} +} + +func (c httpPackage) Encode(s string) string { + return url.QueryEscape(s) +} + +func (c httpPackage) Request(method string, uri string, headers map[string]string, form map[string]string, json string) httpResponse { + var reader io.Reader + + if form != nil { + data := url.Values{} + for k, v := range form { + data.Set(k, v) + } + reader = bytes.NewBufferString(data.Encode()) + } else if json != "" { + reader = strings.NewReader(json) + } + + req, err := http.NewRequest(method, uri, reader) + if err != nil { + return httpResponse{Error: err} + } + + if form != nil { + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + } else if json != "" { + req.Header.Set("Content-Type", "application/json") + } + + for name, value := range headers { + req.Header.Add(name, value) + } + + transport := &http.Transport{} + client := &http.Client{Transport: transport} + + resp, err := client.Do(req) + if err != nil { + return httpResponse{Error: err} + } + defer resp.Body.Close() + + raw, err := ioutil.ReadAll(resp.Body) + if err != nil { + return httpResponse{Error: err} + } + + res := httpResponse{ + Response: resp, + Raw: raw, + Body: string(raw), + } + + if resp.StatusCode != http.StatusOK { + res.Error = fmt.Errorf("%s", resp.Status) + } + + return res +} + +func (c httpPackage) Get(url string, headers map[string]string) httpResponse { + return c.Request("GET", url, headers, nil, "") +} + +func (c httpPackage) PostForm(url string, headers map[string]string, form map[string]string) httpResponse { + return c.Request("POST", url, headers, form, "") +} + +func (c httpPackage) PostJSON(url string, headers map[string]string, json string) httpResponse { + return c.Request("POST", url, headers, nil, json) +} + +func httpRequest(call otto.FunctionCall) otto.Value { + argv := call.ArgumentList + argc := len(argv) + if argc < 2 { + return ReportError("httpRequest: expected 2 or more, %d given instead.", argc) + } + + method := argv[0].String() + url := argv[1].String() + + client := &http.Client{} + req, err := http.NewRequest(method, url, nil) + if argc >= 3 { + data := argv[2].String() + req, err = http.NewRequest(method, url, bytes.NewBuffer([]byte(data))) + if err != nil { + return ReportError("Could create request to url %s: %s", url, err) + } + + if argc > 3 { + headers := argv[3].Object() + for _, key := range headers.Keys() { + v, err := headers.Get(key) + if err != nil { + return ReportError("Could add header %s to request: %s", key, err) + } + req.Header.Add(key, v.String()) + } + } + } else if err != nil { + return ReportError("Could create request to url %s: %s", url, err) + } + + resp, err := client.Do(req) + if err != nil { + return ReportError("Could not request url %s: %s", url, err) + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return ReportError("Could not read response: %s", err) + } + + object, err := otto.New().Object("({})") + if err != nil { + return ReportError("Could not create response object: %s", err) + } + + err = object.Set("body", string(body)) + if err != nil { + return ReportError("Could not populate response object: %s", err) + } + + v, err := otto.ToValue(object) + if err != nil { + return ReportError("Could not convert to object: %s", err) + } + return v +} diff --git a/js/init.go b/js/init.go new file mode 100644 index 00000000..0a21fab4 --- /dev/null +++ b/js/init.go @@ -0,0 +1,38 @@ +package js + +import ( + "github.com/evilsocket/islazy/log" + "github.com/evilsocket/islazy/plugin" + + "github.com/robertkrimen/otto" +) + +var NullValue = otto.Value{} + +func ReportError(format string, args ...interface{}) otto.Value { + log.Error(format, args...) + return NullValue +} + +func init() { + // TODO: refactor this in packages + + plugin.Defines["readDir"] = readDir + plugin.Defines["readFile"] = readFile + plugin.Defines["writeFile"] = writeFile + + plugin.Defines["log"] = flog + plugin.Defines["log_debug"] = log_debug + plugin.Defines["log_info"] = log_info + plugin.Defines["log_warn"] = log_warn + plugin.Defines["log_error"] = log_error + plugin.Defines["log_fatal"] = log_fatal + + plugin.Defines["btoa"] = btoa + plugin.Defines["atob"] = atob + plugin.Defines["gzipCompress"] = gzipCompress + plugin.Defines["gzipDecompress"] = gzipDecompress + + plugin.Defines["httpRequest"] = httpRequest + plugin.Defines["http"] = httpPackage{} +} diff --git a/js/log.go b/js/log.go new file mode 100644 index 00000000..5d8b85f5 --- /dev/null +++ b/js/log.go @@ -0,0 +1,48 @@ +package js + +import ( + "github.com/evilsocket/islazy/log" + "github.com/robertkrimen/otto" +) + +func flog(call otto.FunctionCall) otto.Value { + for _, v := range call.ArgumentList { + log.Info("%s", v.String()) + } + return otto.Value{} +} + +func log_debug(call otto.FunctionCall) otto.Value { + for _, v := range call.ArgumentList { + log.Debug("%s", v.String()) + } + return otto.Value{} +} + +func log_info(call otto.FunctionCall) otto.Value { + for _, v := range call.ArgumentList { + log.Info("%s", v.String()) + } + return otto.Value{} +} + +func log_warn(call otto.FunctionCall) otto.Value { + for _, v := range call.ArgumentList { + log.Warning("%s", v.String()) + } + return otto.Value{} +} + +func log_error(call otto.FunctionCall) otto.Value { + for _, v := range call.ArgumentList { + log.Error("%s", v.String()) + } + return otto.Value{} +} + +func log_fatal(call otto.FunctionCall) otto.Value { + for _, v := range call.ArgumentList { + log.Fatal("%s", v.String()) + } + return otto.Value{} +} diff --git a/modules/utils/script_builtins.go b/modules/utils/script_builtins.go deleted file mode 100644 index b197e9a9..00000000 --- a/modules/utils/script_builtins.go +++ /dev/null @@ -1,311 +0,0 @@ -package utils - -import ( - "bytes" - "compress/gzip" - "encoding/base64" - "io/ioutil" - "net/http" - - "github.com/bettercap/bettercap/log" - "github.com/bettercap/bettercap/session" - - "github.com/evilsocket/islazy/plugin" - - "github.com/robertkrimen/otto" -) - -var nullOtto = otto.Value{} - -func errOtto(format string, args ...interface{}) otto.Value { - log.Error(format, args...) - return nullOtto -} - -func init() { - // used to read a directory (returns string array) - plugin.Defines["readDir"] = func(call otto.FunctionCall) otto.Value { - argv := call.ArgumentList - argc := len(argv) - if argc != 1 { - return errOtto("readDir: expected 1 argument, %d given instead.", argc) - } - - path := argv[0].String() - dir, err := ioutil.ReadDir(path) - if err != nil { - return errOtto("Could not read directory %s: %s", path, err) - } - - entry_list := []string{} - for _, file := range dir { - entry_list = append(entry_list, file.Name()) - } - - v, err := otto.Otto.ToValue(*call.Otto, entry_list) - if err != nil { - return errOtto("Could not convert to array: %s", err) - } - - return v - } - - // used to read a file ... doh - plugin.Defines["readFile"] = func(call otto.FunctionCall) otto.Value { - argv := call.ArgumentList - argc := len(argv) - if argc != 1 { - return errOtto("readFile: expected 1 argument, %d given instead.", argc) - } - - filename := argv[0].String() - raw, err := ioutil.ReadFile(filename) - if err != nil { - return errOtto("Could not read file %s: %s", filename, err) - } - - v, err := otto.ToValue(string(raw)) - if err != nil { - return errOtto("Could not convert to string: %s", err) - } - return v - } - - plugin.Defines["writeFile"] = func(call otto.FunctionCall) otto.Value { - argv := call.ArgumentList - argc := len(argv) - if argc != 2 { - return errOtto("writeFile: expected 2 arguments, %d given instead.", argc) - } - - filename := argv[0].String() - data := argv[1].String() - - err := ioutil.WriteFile(filename, []byte(data), 0644) - if err != nil { - return errOtto("Could not write %d bytes to %s: %s", len(data), filename, err) - } - - return otto.NullValue() - } - - // log something - plugin.Defines["log"] = func(call otto.FunctionCall) otto.Value { - for _, v := range call.ArgumentList { - log.Info("%s", v.String()) - } - return otto.Value{} - } - - // log debug - plugin.Defines["log_debug"] = func(call otto.FunctionCall) otto.Value { - for _, v := range call.ArgumentList { - log.Debug("%s", v.String()) - } - return otto.Value{} - } - - // log info - plugin.Defines["log_info"] = func(call otto.FunctionCall) otto.Value { - for _, v := range call.ArgumentList { - log.Info("%s", v.String()) - } - return otto.Value{} - } - - // log warning - plugin.Defines["log_warn"] = func(call otto.FunctionCall) otto.Value { - for _, v := range call.ArgumentList { - log.Warning("%s", v.String()) - } - return otto.Value{} - } - - // log error - plugin.Defines["log_error"] = func(call otto.FunctionCall) otto.Value { - for _, v := range call.ArgumentList { - log.Error("%s", v.String()) - } - return otto.Value{} - } - - // log fatal - plugin.Defines["log_fatal"] = func(call otto.FunctionCall) otto.Value { - for _, v := range call.ArgumentList { - log.Fatal("%s", v.String()) - } - return otto.Value{} - } - - // javascript btoa function - plugin.Defines["btoa"] = func(call otto.FunctionCall) otto.Value { - varValue := base64.StdEncoding.EncodeToString([]byte(call.Argument(0).String())) - v, err := otto.ToValue(varValue) - if err != nil { - return errOtto("Could not convert to string: %s", varValue) - } - return v - } - - // javascript atob function - plugin.Defines["atob"] = func(call otto.FunctionCall) otto.Value { - varValue, err := base64.StdEncoding.DecodeString(call.Argument(0).String()) - if err != nil { - return errOtto("Could not decode string: %s", call.Argument(0).String()) - } - v, err := otto.ToValue(string(varValue)) - if err != nil { - return errOtto("Could not convert to string: %s", varValue) - } - return v - } - - // compress data with gzip - plugin.Defines["gzipCompress"] = func(call otto.FunctionCall) otto.Value { - argv := call.ArgumentList - argc := len(argv) - if argc != 1 { - return errOtto("gzipCompress: expected 1 argument, %d given instead.", argc) - } - - uncompressed_bytes := []byte(argv[0].String()) - - var writer_buffer bytes.Buffer - gzip_writer := gzip.NewWriter(&writer_buffer) - _, err := gzip_writer.Write(uncompressed_bytes) - if err != nil { - return errOtto("gzipCompress: could not compress data: %s", err.Error()) - } - gzip_writer.Close() - - compressed_bytes := writer_buffer.Bytes() - - v, err := otto.ToValue(string(compressed_bytes)) - if err != nil { - return errOtto("Could not convert to string: %s", err.Error()) - } - - return v - } - - // decompress data with gzip - plugin.Defines["gzipDecompress"] = func(call otto.FunctionCall) otto.Value { - argv := call.ArgumentList - argc := len(argv) - if argc != 1 { - return errOtto("gzipDecompress: expected 1 argument, %d given instead.", argc) - } - - compressed_bytes := []byte(argv[0].String()) - reader_buffer := bytes.NewBuffer(compressed_bytes) - - gzip_reader, err := gzip.NewReader(reader_buffer) - if err != nil { - return errOtto("gzipDecompress: could not create gzip reader: %s", err.Error()) - } - - var decompressed_buffer bytes.Buffer - _, err = decompressed_buffer.ReadFrom(gzip_reader) - if err != nil { - return errOtto("gzipDecompress: could not decompress data: %s", err.Error()) - } - - decompressed_bytes := decompressed_buffer.Bytes() - v, err := otto.ToValue(string(decompressed_bytes)) - if err != nil { - return errOtto("Could not convert to string: %s", err.Error()) - } - - return v - } - - // read or write environment variable - plugin.Defines["env"] = func(call otto.FunctionCall) otto.Value { - argv := call.ArgumentList - argc := len(argv) - - if argc == 1 { - // get - varName := call.Argument(0).String() - if found, varValue := session.I.Env.Get(varName); found { - v, err := otto.ToValue(varValue) - if err != nil { - return errOtto("Could not convert to string: %s", varValue) - } - return v - } - - } else if argc == 2 { - // set - varName := call.Argument(0).String() - varValue := call.Argument(1).String() - session.I.Env.Set(varName, varValue) - } else { - return errOtto("env: expected 1 or 2 arguments, %d given instead.", argc) - } - - return nullOtto - } - - // send http request - plugin.Defines["httpRequest"] = func(call otto.FunctionCall) otto.Value { - argv := call.ArgumentList - argc := len(argv) - if argc < 2 { - return errOtto("httpRequest: expected 2 or more, %d given instead.", argc) - } - - method := argv[0].String() - url := argv[1].String() - - client := &http.Client{} - req, err := http.NewRequest(method, url, nil) - if argc >= 3 { - data := argv[2].String() - req, err = http.NewRequest(method, url, bytes.NewBuffer([]byte(data))) - if err != nil { - return errOtto("Could create request to url %s: %s", url, err) - } - - if argc > 3 { - headers := argv[3].Object() - for _, key := range headers.Keys() { - v, err := headers.Get(key) - if err != nil { - return errOtto("Could add header %s to request: %s", key, err) - } - req.Header.Add(key, v.String()) - } - } - } else if err != nil { - return errOtto("Could create request to url %s: %s", url, err) - } - - resp, err := client.Do(req) - if err != nil { - return errOtto("Could not request url %s: %s", url, err) - } - defer resp.Body.Close() - - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return errOtto("Could not read response: %s", err) - } - - object, err := otto.New().Object("({})") - if err != nil { - return errOtto("Could not create response object: %s", err) - } - - err = object.Set("body", string(body)) - if err != nil { - return errOtto("Could not populate response object: %s", err) - } - - v, err := otto.ToValue(object) - if err != nil { - return errOtto("Could not convert to object: %s", err) - } - return v - } -} diff --git a/session/script.go b/session/script.go new file mode 100644 index 00000000..45c76618 --- /dev/null +++ b/session/script.go @@ -0,0 +1,21 @@ +package session + +import ( + "github.com/evilsocket/islazy/plugin" + + _ "github.com/bettercap/bettercap/js" +) + +type Script struct { + *plugin.Plugin +} + +func LoadScript(fileName string, ses *Session) (*Script, error) { + if p, err := plugin.Load(fileName); err != nil { + return nil, err + } else { + return &Script{ + Plugin: p, + }, nil + } +} \ No newline at end of file diff --git a/session/script_builtin.go b/session/script_builtin.go new file mode 100644 index 00000000..22424eb7 --- /dev/null +++ b/session/script_builtin.go @@ -0,0 +1,33 @@ +package session + +import ( + "github.com/bettercap/bettercap/js" + "github.com/robertkrimen/otto" +) + +func jsEnvFunc(call otto.FunctionCall) otto.Value { + argv := call.ArgumentList + argc := len(argv) + + if argc == 1 { + // get + varName := call.Argument(0).String() + if found, varValue := I.Env.Get(varName); found { + v, err := otto.ToValue(varValue) + if err != nil { + return js.ReportError("could not convert to string: %s", varValue) + } + return v + } + + } else if argc == 2 { + // set + varName := call.Argument(0).String() + varValue := call.Argument(1).String() + I.Env.Set(varName, varValue) + } else { + return js.ReportError("env: expected 1 or 2 arguments, %d given instead.", argc) + } + return js.NullValue +} + diff --git a/session/script_builtin_runtime.go b/session/script_builtin_runtime.go new file mode 100644 index 00000000..f180e196 --- /dev/null +++ b/session/script_builtin_runtime.go @@ -0,0 +1,67 @@ +package session + +import ( + "github.com/bettercap/bettercap/js" + "github.com/evilsocket/islazy/log" + "github.com/robertkrimen/otto" +) + +func jsRunFunc(call otto.FunctionCall) otto.Value { + argv := call.ArgumentList + argc := len(argv) + if argc != 1 { + return js.ReportError("run accepts one string argument") + } else if argv[0].IsString() == false { + return js.ReportError("run accepts one string argument") + } + + for _, cmd := range ParseCommands(argv[0].String()) { + if err := I.Run(cmd); err != nil { + return js.ReportError("error running '%s': %v", cmd, err) + } + } + + return js.NullValue +} + +func jsOnEventFunc(call otto.FunctionCall) otto.Value { + argv := call.ArgumentList + argc := len(argv) + cb := otto.NullValue() + filterExpr := "" + + // just one argument, a function to receive all events + if argc == 1 { + if argv[0].IsFunction() == false { + return js.ReportError("the single argument must be a function") + } + cb = argv[0] + } else { + if argc != 2 { + return js.ReportError("expected two arguments (event_name, callback), got %d", argc) + } else if argv[0].IsString() == false { + return js.ReportError("first argument must be a string") + } else if argv[1].IsFunction() == false { + return js.ReportError("second argument must be a function") + } + + filterExpr = argv[0].String() + cb = argv[1] + } + + // start a go routine for this event listener + go func(expr string, cb otto.Value) { + listener := I.Events.Listen() + defer I.Events.Unlisten(listener) + + for event := range listener { + if expr == "" || event.Tag == expr { + if _, err := cb.Call(otto.NullValue(), event); err != nil { + I.Events.Log(log.ERROR, "error dispatching event %s: %v", event.Tag, err) + } + } + } + }(filterExpr, cb) + + return js.NullValue +} diff --git a/session/session.go b/session/session.go index f5097788..7d38d4ab 100644 --- a/session/session.go +++ b/session/session.go @@ -3,6 +3,7 @@ package session import ( "errors" "fmt" + "github.com/evilsocket/islazy/plugin" "net" "os" "regexp" @@ -89,6 +90,8 @@ type Session struct { EventsIgnoreList *EventsIgnoreList UnkCmdCallback UnknownCommandCallback Firewall firewall.FirewallManager + + script *Script } func New() (*Session, error) { @@ -299,13 +302,28 @@ func (s *Session) Start() error { s.Events.Add("session.started", nil) } + // register js functions here to avoid cyclic dependency between + // js and session + plugin.Defines["env"] = jsEnvFunc + plugin.Defines["run"] = jsRunFunc + plugin.Defines["onEvent"] = jsOnEventFunc + plugin.Defines["session"] = s + + // load the script here so the session and its internal objects are ready + if *s.Options.Script != "" { + if s.script, err = LoadScript(*s.Options.Script, s); err != nil { + return fmt.Errorf("error loading %s: %v", *s.Options.Script, err) + } + log.Debug("session script %s loaded", *s.Options.Script) + } + return nil } func (s *Session) Skip(ip net.IP) bool { if ip.IsLoopback() { return true - } else if ip.Equal(s.Interface.IP) || ip.Equal(s.Interface.IPv6){ + } else if ip.Equal(s.Interface.IP) || ip.Equal(s.Interface.IPv6) { return true } else if ip.Equal(s.Gateway.IP) { return true