From 71366168d2188d55903b1e113d3080ad0e2513c6 Mon Sep 17 00:00:00 2001 From: Dark Web Informer <162650054+DarkWebInformer@users.noreply.github.com> Date: Sat, 2 May 2026 03:33:38 +0200 Subject: [PATCH] Add files via upload --- README.txt | 67 ++++++++++ go.mod | 7 ++ go.sum | 4 + main.go | 358 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 436 insertions(+) create mode 100644 README.txt create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..05b6391 --- /dev/null +++ b/README.txt @@ -0,0 +1,67 @@ +This tool decrypts files encrypted by VECT ransomware at no cost. + +REQUIREMENTS (to run recovery) +- Encryptor binary (the malware executable, same strain that locked the files) +- At least one encrypted sample file (.vect1) +- Usually a Windows PC where the infected files live (paths below use Windows-style names) + +REQUIREMENTS (to compile from source) +- Go toolchain (see go.mod for the minimum version) +- Network once, so Go can fetch dependencies when you build + +BUILD +1. Copy or clone this decryptor folder onto your machine. +2. Open a terminal in that folder (on Windows: Command Prompt or PowerShell, cd into the folder). +3. Fetch modules and compile: + + go build -o vect1_decryptor.exe . + + On Linux or macOS the binary name omits ".exe"; use any name you like. + +4. You should now have vect1_decryptor.exe (or vect1_decryptor) in that folder. + + To produce a Windows .exe from another OS when Go cross-build is configured: + + GOOS=windows GOARCH=amd64 go build -o vect1_decryptor.exe . + +USAGE + +The tool is a command-line program. Put the ransomware EXE somewhere you can reference, collect your .vect1 files under one directory, then run ONE of these patterns from a terminal opened in any directory (adjust paths accordingly). + +Easiest mode: recover key from your binary plus one sample under -target and decrypt everything .vect1 under that folder: + + vect1_decryptor.exe auto -binary C:\Recovery\encryptor.exe -target D:\EncryptedFiles [-backup] [-hexdump] + + "-backup" saves a ".vect1.bak" copy before overwriting. + "-hexdump" prints a short byte preview for troubleshooting. + +Step-by-step (same folder shortcut) + +1. Copy vect1_decryptor.exe and the ransomware exe into one folder. + +2. Put your encrypted (.vect1) files in that folder, or anywhere under one parent folder; that parent is what you give as "-target". + +3. Open Command Prompt or PowerShell, change to wherever you stored the tools, run auto with paths you actually use: + + cd C:\Recovery + vect1_decryptor.exe auto -binary name_of_encryptor.exe -target . + +4. The program reads the ransomware binary for the key using an encrypted sample it finds under -target, then decrypts all .vect1 files there and strips the ".vect1" extension from their names. + +Other commands + + Extract key only (prints hex): + + vect1_decryptor.exe extract-key -binary encryptor.exe -sample file.docx.vect1 [-out key.txt] + + Decrypt later if you already have the hex key (64 hex characters = 32 bytes): + + vect1_decryptor.exe decrypt -target D:\EncryptedFiles -key [-backup] [-hexdump] + +Need help? + + vect1_decryptor.exe + +with no arguments prints the brief built-in syntax. + +-- SHHQ Ransomware Response & Recovery Unit (RRU) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8fb1c17 --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module vect1_decryptor + +go 1.25.0 + +require golang.org/x/crypto v0.49.0 + +require golang.org/x/sys v0.42.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..bb3d09b --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= diff --git a/main.go b/main.go new file mode 100644 index 0000000..1bec0df --- /dev/null +++ b/main.go @@ -0,0 +1,358 @@ +package main + +import ( + "encoding/hex" + "flag" + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" + "unicode" + + "golang.org/x/crypto/chacha20poly1305" +) + +const ( + nonceSize = 12 + tagSize = 16 + overhead = nonceSize + tagSize + blockSize = 0x100000 + encExt = ".vect1" + hexRows = 4 +) + +func hexdump(label string, data []byte) { + n := hexRows * 16 + if len(data) < n { + n = len(data) + } + chunk := data[:n] + + fmt.Printf(" %-6s offset 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f │ ASCII\n", label) + fmt.Printf(" ------ ------ ----------------------------------------------- │ ----------------\n") + + for i := 0; i < len(chunk); i += 16 { + j := i + 16 + if j > len(chunk) { + j = len(chunk) + } + row := chunk[i:j] + + fmt.Printf(" %06x ", i) + for k := 0; k < 16; k++ { + if k == 8 { + fmt.Print(" ") + } + if k < len(row) { + fmt.Printf("%02x ", row[k]) + } else { + fmt.Print(" ") + } + } + + fmt.Print(" │ ") + for _, b := range row { + if b < 128 && unicode.IsPrint(rune(b)) { + fmt.Printf("%c", b) + } else { + fmt.Print(".") + } + } + fmt.Println() + } + + if len(data) > hexRows*16 { + fmt.Printf(" ... (%d bytes total)\n", len(data)) + } + fmt.Println() +} + +func decryptBlock(key, nonce, ctAndTag []byte) ([]byte, bool) { + aead, err := chacha20poly1305.New(key) + if err != nil { + return nil, false + } + out, err := aead.Open(nil, nonce, ctAndTag, nil) + if err != nil { + return nil, false + } + return out, true +} + +func readTestBlock(path string) (nonce []byte, ctAndTag []byte, err error) { + f, err := os.Open(path) + if err != nil { + return nil, nil, err + } + defer f.Close() + + buf := make([]byte, nonceSize+blockSize+tagSize) + n, err := f.Read(buf) + if err != nil && n == 0 { + return nil, nil, err + } + buf = buf[:n] + + if len(buf) < overhead+1 { + return nil, nil, fmt.Errorf("file too small to use as oracle") + } + return buf[:nonceSize], buf[nonceSize:], nil +} + +func extractKey(binaryPath, samplePath string) ([]byte, error) { + nonce, ctAndTag, err := readTestBlock(samplePath) + if err != nil { + return nil, fmt.Errorf("reading sample: %w", err) + } + + raw, err := os.ReadFile(binaryPath) + if err != nil { + return nil, fmt.Errorf("reading binary: %w", err) + } + + window := len(raw) - 32 + 1 + if window <= 0 { + return nil, fmt.Errorf("binary too small") + } + + fmt.Printf("[*] Scanning %d bytes (%d candidates)...\n", len(raw), window) + + for at := 0; at < window; at++ { + k := raw[at : at+32] + if _, ok := decryptBlock(k, nonce, ctAndTag); ok { + fmt.Printf("[+] Key found at binary offset 0x%08x\n", at) + out := make([]byte, 32) + copy(out, k) + return out, nil + } + } + return nil, fmt.Errorf("key not found in binary") +} + +func decryptFile(key []byte, encPath string, backup, dump bool) bool { + if !strings.HasSuffix(encPath, encExt) { + return false + } + decPath := strings.TrimSuffix(encPath, encExt) + + st, err := os.Stat(encPath) + if err != nil { + fmt.Printf("[-] Stat failed: %s\n", encPath) + return false + } + size := st.Size() + + if size < overhead { + fmt.Printf("[-] Too small: %s\n", encPath) + return false + } + + body, err := os.ReadFile(encPath) + if err != nil { + fmt.Printf("[-] Read failed: %s\n", encPath) + return false + } + + if backup { + _ = os.WriteFile(encPath+".bak", body, 0644) + } + + fmt.Printf("\n[>] %s\n", filepath.Base(encPath)) + + if dump { + fmt.Printf(" Nonce: %s\n\n", hex.EncodeToString(body[:nonceSize])) + hexdump("BEFORE", body[nonceSize:]) + } + + var plain []byte + + if size <= blockSize+overhead { + p, ok := decryptBlock(key, body[:nonceSize], body[nonceSize:]) + if !ok { + fmt.Printf("[-] Auth failure (wrong key?): %s\n", filepath.Base(encPath)) + return false + } + plain = p + } else { + endFirst := int64(blockSize + overhead) + head, ok := decryptBlock(key, body[:nonceSize], body[nonceSize:endFirst]) + if !ok { + fmt.Printf("[-] Auth failure (wrong key?): %s\n", filepath.Base(encPath)) + return false + } + + tailOff := size - overhead + mid := body[endFirst:tailOff] + tail := body[tailOff:] + z := make([]byte, overhead) + + plain = head + plain = append(plain, z...) + plain = append(plain, mid...) + plain = append(plain, tail...) + + wantLen := size - overhead + if int64(len(plain)) > wantLen { + plain = plain[:wantLen] + } + } + + if dump { + hexdump("AFTER", plain) + } + + if err := os.WriteFile(encPath, plain, 0644); err != nil { + fmt.Printf("[-] Write failed: %s\n", encPath) + return false + } + + if err := os.Rename(encPath, decPath); err != nil { + fmt.Printf("[-] Rename failed (%v): %s\n", err, encPath) + return false + } + + fmt.Printf("[+] %s -> %s (%d bytes)\n", + filepath.Base(encPath), filepath.Base(decPath), len(plain)) + return true +} + +func decryptDirectory(key []byte, dir string, backup, dump bool) (ok, failed int) { + _ = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { + if err != nil || d.IsDir() { + return nil + } + if strings.HasSuffix(path, encExt) { + if decryptFile(key, path, backup, dump) { + ok++ + } else { + failed++ + } + } + return nil + }) + return ok, failed +} + +func usage() { + fmt.Println(`Usage: + vect1_decryptor extract-key -binary locker.exe -sample file.vect1 [-out key.txt] + vect1_decryptor decrypt -target -key [-backup] [-hexdump] + vect1_decryptor auto -binary locker.exe -target [-backup] [-hexdump]`) + os.Exit(1) +} + +func main() { + if len(os.Args) < 2 { + usage() + } + + cmd := os.Args[1] + args := os.Args[2:] + + switch cmd { + case "extract-key": + fl := flag.NewFlagSet("extract-key", flag.ExitOnError) + binary := fl.String("binary", "", "") + sample := fl.String("sample", "", "") + outPath := fl.String("out", "", "") + fl.Parse(args) + + if *binary == "" || *sample == "" { + fmt.Println("[-] -binary and -sample are required.") + fl.Usage() + os.Exit(1) + } + + key, err := extractKey(*binary, *sample) + if err != nil { + fmt.Printf("[-] %v\n", err) + os.Exit(1) + } + keyHex := hex.EncodeToString(key) + fmt.Printf("[+] Key (hex): %s\n", keyHex) + if *outPath != "" { + err := os.WriteFile(*outPath, []byte(keyHex), 0600) + if err != nil { + fmt.Printf("[-] Could not save key: %v\n", err) + } else { + fmt.Printf("[+] Saved to %s\n", *outPath) + } + } + + case "decrypt": + fl := flag.NewFlagSet("decrypt", flag.ExitOnError) + path := fl.String("target", "", "") + keyHex := fl.String("key", "", "") + backup := fl.Bool("backup", false, "") + dump := fl.Bool("hexdump", false, "") + fl.Parse(args) + + if *path == "" || *keyHex == "" { + fmt.Println("[-] -target and -key are required.") + fl.Usage() + os.Exit(1) + } + key, err := hex.DecodeString(*keyHex) + if err != nil || len(key) != 32 { + fmt.Println("[-] Key must be exactly 64 hex characters (32 bytes).") + os.Exit(1) + } + + info, err := os.Stat(*path) + if err != nil { + fmt.Printf("[-] Target not found: %s\n", *path) + os.Exit(1) + } + if info.IsDir() { + ok, bad := decryptDirectory(key, *path, *backup, *dump) + fmt.Printf("\n[*] Done: %d decrypted, %d failed\n", ok, bad) + } else { + decryptFile(key, *path, *backup, *dump) + } + + case "auto": + fl := flag.NewFlagSet("auto", flag.ExitOnError) + binary := fl.String("binary", "", "") + target := fl.String("target", "", "") + backup := fl.Bool("backup", false, "") + dump := fl.Bool("hexdump", false, "") + fl.Parse(args) + + if *binary == "" || *target == "" { + fmt.Println("[-] -binary and -target are required.") + fl.Usage() + os.Exit(1) + } + + var sample string + _ = filepath.WalkDir(*target, func(p string, d fs.DirEntry, walkErr error) error { + if walkErr != nil || d.IsDir() || sample != "" { + return nil + } + if strings.HasSuffix(p, encExt) { + sample = p + } + return nil + }) + if sample == "" { + fmt.Printf("[-] No %s files found in %s\n", encExt, *target) + os.Exit(1) + } + fmt.Printf("[*] Using %s as decryption oracle\n", filepath.Base(sample)) + + key, err := extractKey(*binary, sample) + if err != nil { + fmt.Printf("[-] %v\n", err) + os.Exit(1) + } + fmt.Printf("[+] Key: %s\n", hex.EncodeToString(key)) + + ok, bad := decryptDirectory(key, *target, *backup, *dump) + fmt.Printf("\n[*] Done: %d decrypted, %d failed\n", ok, bad) + + default: + fmt.Printf("[-] Unknown command: %s\n", cmd) + usage() + } +}