commit ed28447302b3caa4ef18f8d9e17c36a9a05badb0 Author: Unbewohnte Date: Sun Mar 12 14:24:48 2023 +0300 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9dc2b52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin/ +release/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e1a6107 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ + The MIT License (MIT) + +Copyright © 2023 Kasyanov Nikolay Alexeyevich (Unbewohnte) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..53691ea --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +all: server client + mkdir -p bin + mv spolitewareServer* bin + mv spolitewareClient* bin + +server: + cd src/server && go build && mv spolitewareServer ../../ + +client: + cd src/client && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build && mv spolitewareClient ../../spolitewareClient_linux_amd64 + cd src/client && CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build && mv spolitewareClient ../../spolitewareClient_darwin_amd64 + cd src/client && CGO_ENABLED=0 GOOS=windows GOARCH=386 go build && mv spolitewareClient.exe ../../spolitewareClient_windows_x32.exe + cd src/client && CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build && mv spolitewareClient.exe ../../spolitewareClient_windows_amd64.exe + +release: client + mkdir -p release/spoliteware + cp LICENSE release/spoliteware + cd src/server && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build && mv spolitewareServer ../../release/spoliteware + mv spolitewareClient* release/spoliteware + cd release && zip -r spoliteware spoliteware \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..90bf6a9 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# Spoliteware - a polite and considerate spyware + +A spyware that asks permission to collect data beforehand. + +## Features + +### Client +- System information + +### Server +- Collected data is sorted per machine in a corresponding folder (hostname_username_IP) +- TLS support + +## TODO +- Give some sort of "reward" for permitting to scan client's machine +- More green telemetry options +- Be more open on what the program does + +## License +MIT \ No newline at end of file diff --git a/src/client/.gitignore b/src/client/.gitignore new file mode 100644 index 0000000..00117e0 --- /dev/null +++ b/src/client/.gitignore @@ -0,0 +1,2 @@ +spolitewareClient +spolitewareClient.exe \ No newline at end of file diff --git a/src/client/go.mod b/src/client/go.mod new file mode 100644 index 0000000..3f734c2 --- /dev/null +++ b/src/client/go.mod @@ -0,0 +1,3 @@ +module Unbewohnte/spolitewareClient + +go 1.20 diff --git a/src/client/main.go b/src/client/main.go new file mode 100644 index 0000000..5f47ab8 --- /dev/null +++ b/src/client/main.go @@ -0,0 +1,188 @@ +/* + The MIT License (MIT) + +Copyright © 2023 Kasyanov Nikolay Alexeyevich (Unbewohnte) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +package main + +import ( + "Unbewohnte/spolitewareClient/osutil" + "bytes" + "encoding/json" + "flag" + "fmt" + "net/http" + "os" + "path/filepath" + "runtime" + "strings" + "time" +) + +const ( + Version string = "client v0.1.0" +) + +var ( + version *bool = flag.Bool("version", false, "Print version information and exit") + serverAddr *string = flag.String("server", "http://localhost:13370/", "Set scheme://addr:port for receiving server") +) + +type Data struct { + Hostname string `json:"hostname"` + Username string `json:"username"` + System map[string]string `json:"system"` +} + +func greeting() { + fmt.Printf( + `I'm sorry to inform you, but you've (intentionally or not) launched a spyware on your machine ! +But rest assured, I will do no harm to your computer and am to withdraw if you would wish so. +No data will be collected and sent without your permission. + +`) +} + +func askForAllPerms() bool { + fmt.Printf(`Would you grant me a permission to [system information; files lookup; ] +(If no -> (optional) specify separate permissions afterwards) y/N: `) + var input string = "n" + fmt.Scanf("%s", &input) + input = strings.ToLower(input) + + if strings.HasPrefix(input, "y") { + fmt.Printf("Your approval is appreciated; I am to treat your system with care\n") + return true + } else { + return false + } +} + +func askForSystemInfo() bool { + fmt.Printf("\nWould you allow me to look around and collect some information about your computer ? [y/N]: ") + var input string = "n" + fmt.Scanf("%s", &input) + input = strings.ToLower(input) + + if strings.HasPrefix(input, "y") { + fmt.Printf("System information scan allowed\n") + return true + } else { + fmt.Printf("As you wish\n") + return false + } +} + +func localCopyNeeded() bool { + fmt.Printf("\nDo you want to save a local copy as well ? [y/N]: ") + var input string = "n" + fmt.Scanf("%s", &input) + input = strings.ToLower(input) + + if strings.HasPrefix(input, "y") { + return true + } else { + return false + } +} + +func main() { + fmt.Printf( + ` +███████╗██████╗ ██████╗ ██╗ ██╗████████╗███████╗██╗ ██╗ █████╗ ██████╗ ███████╗ +██╔════╝██╔══██╗██╔═══██╗██║ ██║╚══██╔══╝██╔════╝██║ ██║██╔══██╗██╔══██╗██╔════╝ +███████╗██████╔╝██║ ██║██║ ██║ ██║ █████╗ ██║ █╗ ██║███████║██████╔╝█████╗ +╚════██║██╔═══╝ ██║ ██║██║ ██║ ██║ ██╔══╝ ██║███╗██║██╔══██║██╔══██╗██╔══╝ +███████║██║ ╚██████╔╝███████╗██║ ██║ ███████╗╚███╔███╔╝██║ ██║██║ ██║███████╗ +╚══════╝╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ + +`) + flag.Parse() + if *version { + fmt.Printf("by Kasyanov Nikolay Alexeyevich (Unbewohnte) %s\n", Version) + return + } + + var data Data + data.System = nil + + greeting() + if askForAllPerms() { + data.System = osutil.GetSystemInfo() + } else { + if askForSystemInfo() { + data.System = osutil.GetSystemInfo() + } + } + + if data.System == nil { + fmt.Printf("\nNothing to send. Bailing out\n") + return + } + + data.Hostname = osutil.GetHostname() + data.Username = osutil.GetUsername() + + dataJson, err := json.MarshalIndent(&data, "", " ") + if err != nil { + return + } + + postBody := bytes.NewBuffer(dataJson) + var retries uint8 = 0 + for { + if retries == 5 { + fmt.Printf("\nFailed to send data\n") + return + } + + response, err := http.Post(*serverAddr, "application/json", postBody) + if err != nil || response == nil { + // try to resend + time.Sleep(time.Second * 5) + retries++ + continue + } + + if response.StatusCode != http.StatusOK { + // try to resend + time.Sleep(time.Second * 5) + retries++ + continue + } + break + } + + fmt.Printf("\nSuccesfully sent data. Thank you !\n") + + if localCopyNeeded() { + wdir, err := os.Getwd() + if err != nil { + wdir = "./" + } + + copyPath := filepath.Join(wdir, "spoliteware_scan_copy.txt") + f, err := os.Create(copyPath) + if err != nil { + fmt.Printf("Failed to create a local copy: %s\n", err) + } else { + _, err = f.Write(dataJson) + if err != nil { + fmt.Printf("Failed to write to a file to save a local copy: %s\n", err) + } + } + f.Close() + fmt.Printf("Saved to %s\n", copyPath) + } + + if runtime.GOOS == "windows" { + fmt.Scanln() + } +} diff --git a/src/client/osutil/info.go b/src/client/osutil/info.go new file mode 100644 index 0000000..9641214 --- /dev/null +++ b/src/client/osutil/info.go @@ -0,0 +1,85 @@ +/* + The MIT License (MIT) + +Copyright © 2023 Kasyanov Nikolay Alexeyevich (Unbewohnte) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +package osutil + +import ( + "os/exec" + "runtime" + "strings" +) + +func GetSystemInfo() map[string]string { + var sysInfo map[string]string = make(map[string]string) + + switch runtime.GOOS { + case "windows": + { + output, err := exec.Command("systeminfo").Output() + if err == nil { + sysInfo["systeminfo"] = string(output) + } + + output, err = exec.Command("wmic", "partition", "get", "name,size,type").Output() + if err == nil { + sysInfo["wmic"] = string(output) + } + } + default: + { + output, err := exec.Command("lscpu").Output() + if err == nil { + sysInfo["lscpu"] = string(output) + } + + output, err = exec.Command("lsblk").Output() + if err == nil { + sysInfo["lsblk"] = string(output) + } + + output, err = exec.Command("lspci").Output() + if err == nil { + sysInfo["lspci"] = string(output) + } + + output, err = exec.Command("uname", "-a").Output() + if err == nil { + sysInfo["uname"] = string(output) + } + + output, err = exec.Command("hostname").Output() + if err == nil { + sysInfo["hostname"] = string(output) + } + } + } + + return sysInfo +} + +func GetHostname() string { + output, err := exec.Command("hostname").Output() + if err != nil { + return "unknown_hostname" + } + + return strings.TrimSpace(string(output)) +} + +func GetUsername() string { + output, err := exec.Command("whoami").Output() + if err != nil { + return "unknown_username" + } + + return strings.TrimSpace(string(output)) +} diff --git a/src/server/.gitignore b/src/server/.gitignore new file mode 100644 index 0000000..9596dd0 --- /dev/null +++ b/src/server/.gitignore @@ -0,0 +1,2 @@ +collected_data/ +spolitewareServer \ No newline at end of file diff --git a/src/server/go.mod b/src/server/go.mod new file mode 100644 index 0000000..f466672 --- /dev/null +++ b/src/server/go.mod @@ -0,0 +1,3 @@ +module Unbewohnte/spolitewareServer + +go 1.20 diff --git a/src/server/main.go b/src/server/main.go new file mode 100644 index 0000000..10f31ea --- /dev/null +++ b/src/server/main.go @@ -0,0 +1,170 @@ +/* + The MIT License (MIT) + +Copyright © 2023 Kasyanov Nikolay Alexeyevich (Unbewohnte) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +package main + +import ( + "encoding/json" + "flag" + "fmt" + "io" + "net" + "net/http" + "os" + "path/filepath" + "strings" + "time" +) + +type Data struct { + Hostname string `json:"hostname"` + Username string `json:"username"` + System map[string]string `json:"system"` +} + +const Version string = "server v0.1.0" + +var ( + version *bool = flag.Bool("version", false, "Print version information and exit") + port *uint = flag.Uint("port", 13370, "Set port to listen on") + keyFile *string = flag.String("key", "", "SSL private key file path") + certFile *string = flag.String("cert", "", "SSL certificate file path") + verbose *bool = flag.Bool("verbose", false, "Print user data-centric messages or not") +) + +func main() { + fmt.Printf( + ` +███████╗██████╗ ██████╗ ██╗ ██╗████████╗███████╗██╗ ██╗ █████╗ ██████╗ ███████╗ +██╔════╝██╔══██╗██╔═══██╗██║ ██║╚══██╔══╝██╔════╝██║ ██║██╔══██╗██╔══██╗██╔════╝ +███████╗██████╔╝██║ ██║██║ ██║ ██║ █████╗ ██║ █╗ ██║███████║██████╔╝█████╗ +╚════██║██╔═══╝ ██║ ██║██║ ██║ ██║ ██╔══╝ ██║███╗██║██╔══██║██╔══██╗██╔══╝ +███████║██║ ╚██████╔╝███████╗██║ ██║ ███████╗╚███╔███╔╝██║ ██║██║ ██║███████╗ +╚══════╝╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝ ╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝ + +`) + + flag.Parse() + if *version { + fmt.Printf("by Kasyanov Nikolay Alexeyevich (Unbewohnte) %s\n", Version) + return + } + + executablePath, err := os.Executable() + if err != nil { + fmt.Printf("[ERROR] Failed to determine executable's path: %s\n", err) + return + } + dataDirPath := filepath.Join(filepath.Dir(executablePath), "collected_data") + + err = os.MkdirAll(dataDirPath, os.ModePerm) + if err != nil { + fmt.Printf("[ERROR] Failed to create data directory: %s\n", err) + return + } + + mux := http.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { + if *keyFile != "" && *certFile != "" { + proto := req.Header.Get("x-forwarded-proto") + if strings.ToLower(proto) == "http" { + http.Redirect(w, req, fmt.Sprintf("https://%s%s", req.Host, req.URL), http.StatusMovedPermanently) + return + } + } + + if req.Header.Get("Content-Type") != "application/json" { + http.Error(w, "Content is not application/json", http.StatusBadRequest) + return + } + + defer req.Body.Close() + body, err := io.ReadAll(req.Body) + if err != nil { + // Internal error, - no need to tell about it to the other side + if *verbose { + fmt.Printf("[ERROR] Failed to read request's body from %s: %s\n", req.RemoteAddr, err) + } + w.WriteHeader(http.StatusOK) + return + } + + ip, _, err := net.SplitHostPort(req.RemoteAddr) + if err != nil { + if *verbose { + fmt.Printf("[ERROR] Failed to split host and port from %s: %s\n", req.RemoteAddr, err) + } + w.WriteHeader(http.StatusOK) + return + } + + var clientData Data + err = json.Unmarshal(body, &clientData) + if err != nil { + if *verbose { + fmt.Printf("[ERROR] Failed to unmarshal data from %s: %s\n", req.RemoteAddr, err) + } + w.WriteHeader(http.StatusOK) + return + } + + clientDir := filepath.Join(dataDirPath, fmt.Sprintf("%s_%s_%s", clientData.Hostname, clientData.Username, ip)) + err = os.MkdirAll(clientDir, os.ModePerm) + if err != nil { + if *verbose { + fmt.Printf("[ERROR] Failed to create directory for %s: %s\n", ip, err) + } + w.WriteHeader(http.StatusOK) + return + } + + clientFilePath := filepath.Join(clientDir, fmt.Sprintf("data_%d.json", time.Now().Unix())) + clientFile, err := os.OpenFile(clientFilePath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm) + if err != nil { + if *verbose { + fmt.Printf("[ERROR] Failed to create file for %s: %s\n", ip, err) + } + w.WriteHeader(http.StatusOK) + return + } + defer clientFile.Close() + + _, err = clientFile.Write(body) + if err != nil { + if *verbose { + fmt.Printf("[ERROR] Failed to append data to %s: %s\n", clientFilePath, err) + } + w.WriteHeader(http.StatusOK) + return + } + + w.WriteHeader(http.StatusOK) + if *verbose { + fmt.Printf("[INFO] Received data from %s_%s_%s\n", clientData.Hostname, clientData.Username, ip) + } + }) + + server := http.Server{ + Addr: fmt.Sprintf(":%d", *port), + Handler: mux, + } + + if *keyFile != "" && *certFile != "" { + fmt.Printf("[INFO] Using TLS\n") + fmt.Printf("[INFO] Server listening on port %d\n", *port) + server.ListenAndServeTLS(*certFile, *keyFile) + } else { + fmt.Printf("[INFO] Not using TLS\n") + fmt.Printf("[INFO] Server listening on port %d\n", *port) + server.ListenAndServe() + } +}