package core import ( "archive/zip" "context" "crypto/sha256" "encoding/hex" "encoding/json" "fmt" "io" "io/fs" "log" "net/http" "os" "os/exec" "path/filepath" "runtime" "strings" "time" ) func findGo() string { if p, err := exec.LookPath("go"); err == nil { return p } if goroot := os.Getenv("GOROOT"); goroot != "" { candidate := filepath.Join(goroot, "bin", "go") if fileExists(candidate) { return candidate } } goexe := "go" if runtime.GOOS == "windows" { goexe = "go.exe" } for _, p := range []string{ "/usr/local/go/bin/go", filepath.Join(os.Getenv("HOME"), ".local", "go", "bin", goexe), "/tmp/go/bin/" + goexe, "/usr/lib/go/bin/go", `C:\Go\bin\go.exe`, filepath.Join(os.Getenv("ProgramFiles"), "Go", "bin", "go.exe"), filepath.Join(os.Getenv("LocalAppData"), "go", "bin", goexe), filepath.Join(os.Getenv("HOME"), "sdk", "go", "bin", goexe), filepath.Join(os.TempDir(), "go", "bin", goexe), } { if fileExists(p) { return p } } return goexe } type goFile struct { Filename string `json:"filename"` OS string `json:"os"` Arch string `json:"arch"` SHA256 string `json:"sha256"` } type goVersion struct { Version string `json:"version"` Stable bool `json:"stable"` Files []goFile `json:"files"` } func ensureGo() (string, error) { if p, err := exec.LookPath("go"); err == nil { return p, nil } if goroot := os.Getenv("GOROOT"); goroot != "" { candidate := filepath.Join(goroot, "bin", "go") if fileExists(candidate) { return candidate, nil } } if p := findGo(); fileExists(p) { return p, nil } log.Printf("[*] Go not found, installing...") osName := runtime.GOOS archName := runtime.GOARCH switch archName { case "x86_64": archName = "amd64" case "aarch64": archName = "arm64" } ext := ".tar.gz" if osName == "windows" { ext = ".zip" } ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() req, err := http.NewRequestWithContext(ctx, "GET", "https://go.dev/VERSION?m=text", nil) if err != nil { return "", fmt.Errorf("create version request: %w", err) } resp, err := http.DefaultClient.Do(req) if err != nil { return "", fmt.Errorf("check go version: %w", err) } defer resp.Body.Close() versionBytes, err := io.ReadAll(resp.Body) if err != nil { return "", fmt.Errorf("read go version: %w", err) } version := strings.TrimSpace(strings.Split(string(versionBytes), "\n")[0]) filename := fmt.Sprintf("%s.%s-%s%s", version, osName, archName, ext) url := fmt.Sprintf("https://go.dev/dl/%s", filename) // Fetch checksums from go.dev JSON API checksums, err := fetchGoChecksums() if err != nil { return "", fmt.Errorf("fetch go checksums: %w", err) } expectedHash, ok := checksums[filename] if !ok { return "", fmt.Errorf("no checksum found for %s", filename) } log.Printf("[*] downloading %s ...", url) tarball, err := os.CreateTemp("", "go-*"+ext) if err != nil { return "", fmt.Errorf("create temp file: %w", err) } defer os.Remove(tarball.Name()) dlCtx, dlCancel := context.WithTimeout(context.Background(), 120*time.Second) defer dlCancel() dlReq, err := http.NewRequestWithContext(dlCtx, "GET", url, nil) if err != nil { return "", fmt.Errorf("create download request: %w", err) } dlResp, err := http.DefaultClient.Do(dlReq) if err != nil { return "", fmt.Errorf("download go: %w", err) } defer dlResp.Body.Close() hash := sha256.New() if _, err := io.Copy(io.MultiWriter(tarball, hash), dlResp.Body); err != nil { return "", fmt.Errorf("write go archive: %w", err) } tarball.Close() got := hex.EncodeToString(hash.Sum(nil)) if got != expectedHash { return "", fmt.Errorf("checksum mismatch for %s:\n expected: %s\n got: %s", filename, expectedHash, got) } log.Printf("[*] SHA256 verified: %s", expectedHash) installDir := installGo(tarball.Name(), osName) log.Printf("[+] Go %s installed at %s", version, installDir) return filepath.Join(installDir, "bin", "go"), nil } func fetchGoChecksums() (map[string]string, error) { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() req, err := http.NewRequestWithContext(ctx, "GET", "https://go.dev/dl/?mode=json&include=all", nil) if err != nil { return nil, fmt.Errorf("create metadata request: %w", err) } resp, err := http.DefaultClient.Do(req) if err != nil { return nil, fmt.Errorf("get go metadata: %w", err) } defer resp.Body.Close() var versions []goVersion if err := json.NewDecoder(resp.Body).Decode(&versions); err != nil { return nil, fmt.Errorf("decode go metadata: %w", err) } checksums := make(map[string]string) for _, v := range versions { for _, f := range v.Files { checksums[f.Filename] = f.SHA256 } } return checksums, nil } func installGo(archive, osName string) string { homeDir, _ := os.UserHomeDir() candidates := []string{ "/usr/local/go", filepath.Join(homeDir, ".local", "go"), "/tmp/go", } if osName == "windows" { progFiles := os.Getenv("ProgramFiles") if progFiles == "" { progFiles = `C:\Program Files` } candidates = []string{ filepath.Join(progFiles, "Go"), filepath.Join(homeDir, "sdk", "go"), filepath.Join(os.TempDir(), "go"), } } for _, dst := range candidates { parent := filepath.Dir(dst) os.RemoveAll(dst) os.MkdirAll(parent, 0755) if extractGoArchive(archive, parent, osName) == nil { if _, err := os.Stat(dst); err == nil { log.Printf("[*] Go extracted to %s", dst) return dst } } } // Last resort: always try the root of the temp dir tmpRoot := "/tmp" if osName == "windows" { tmpRoot = os.TempDir() } os.RemoveAll(filepath.Join(tmpRoot, "go")) extractGoArchive(archive, tmpRoot, osName) return filepath.Join(tmpRoot, "go") } func extractGoArchive(archive, dst, osName string) error { if osName == "windows" { return extractZip(archive, dst) } return exec.Command("tar", "-C", dst, "-xzf", archive).Run() } func extractZip(src, dst string) error { r, err := zip.OpenReader(src) if err != nil { return fmt.Errorf("open zip: %w", err) } defer r.Close() for _, f := range r.File { target := filepath.Join(dst, f.Name) // ZipSlip protection if !strings.HasPrefix(filepath.Clean(target), filepath.Clean(dst)+string(os.PathSeparator)) { continue } if f.FileInfo().IsDir() { os.MkdirAll(target, 0755) continue } os.MkdirAll(filepath.Dir(target), 0755) rc, err := f.Open() if err != nil { return fmt.Errorf("open %s in zip: %w", f.Name, err) } out, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, f.Mode()) if err != nil { rc.Close() return fmt.Errorf("create %s: %w", target, err) } _, err = io.Copy(out, rc) rc.Close() out.Close() if err != nil { return fmt.Errorf("extract %s: %w", f.Name, err) } } return nil } func findGit(dir string) string { gitExe := "git" if runtime.GOOS == "windows" { gitExe = "git.exe" } var found string filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { if err != nil { return nil } if !d.IsDir() && strings.EqualFold(d.Name(), gitExe) { found = path return io.EOF } return nil }) return found } // auto-installs MinGit on Windows, tries system package manager on Linux/macOS. // Self-contained implant generation is the feature — defer per-platform packaging. func installGit() (string, error) { homeDir, _ := os.UserHomeDir() installDir := filepath.Join(homeDir, ".necropolis", "mingit") if p := findGit(installDir); p != "" { return p, nil } switch runtime.GOOS { case "windows": log.Printf("[*] downloading MinGit...") url := "https://github.com/git-for-windows/git/releases/download/v2.47.0.windows.1/MinGit-2.47.0-64-bit.zip" archive, err := os.CreateTemp("", "mingit-*.zip") if err != nil { return "", fmt.Errorf("create temp: %w", err) } defer os.Remove(archive.Name()) if err := downloadFile(url, archive); err != nil { return "", fmt.Errorf("download mingit: %w", err) } archive.Close() os.RemoveAll(installDir) os.MkdirAll(installDir, 0755) if err := extractZip(archive.Name(), installDir); err != nil { return "", fmt.Errorf("extract mingit: %w", err) } if p := findGit(installDir); p != "" { log.Printf("[+] MinGit installed at %s", p) return p, nil } return "", fmt.Errorf("MinGit extracted but git executable not found under %s", installDir) case "linux": for _, pm := range [][]string{ {"apk", "add", "git"}, {"apt-get", "install", "-y", "git"}, {"yum", "install", "-y", "git"}, } { if _, err := exec.LookPath(pm[0]); err != nil { continue } cmd := exec.Command(pm[0], pm[1:]...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if cmd.Run() == nil { if p, err := exec.LookPath("git"); err == nil { return p, nil } } } return "", fmt.Errorf("could not install git via package manager; install git manually") case "darwin": for _, pm := range [][]string{ {"xcode-select", "--install"}, {"brew", "install", "git"}, } { if _, err := exec.LookPath(pm[0]); err != nil { continue } cmd := exec.Command(pm[0], pm[1:]...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if cmd.Run() == nil { if p, err := exec.LookPath("git"); err == nil { return p, nil } } } return "", fmt.Errorf("could not install git; install git manually") default: return "", fmt.Errorf("unsupported OS: %s", runtime.GOOS) } } func downloadFile(url string, out *os.File) error { resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("bad status: %s", resp.Status) } _, err = io.Copy(out, resp.Body) return err }