mirror of
https://github.com/DarkWebInformer/vect-ransomware-decryptor.git
synced 2026-06-10 20:21:23 +00:00
Add files via upload
This commit is contained in:
committed by
GitHub
parent
984d3b3318
commit
71366168d2
+67
@@ -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 <hex64> [-backup] [-hexdump]
|
||||||
|
|
||||||
|
Need help?
|
||||||
|
|
||||||
|
vect1_decryptor.exe
|
||||||
|
|
||||||
|
with no arguments prints the brief built-in syntax.
|
||||||
|
|
||||||
|
-- SHHQ Ransomware Response & Recovery Unit (RRU)
|
||||||
@@ -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
|
||||||
@@ -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=
|
||||||
@@ -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 <path> -key <hex64> [-backup] [-hexdump]
|
||||||
|
vect1_decryptor auto -binary locker.exe -target <dir> [-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()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user