diff --git a/README.md b/README.md index 7e02fbc..e3ca38b 100644 --- a/README.md +++ b/README.md @@ -21,19 +21,21 @@ _**This toolset is proudly the first publicly released Phantom Vault Extractor a ``` ### Decryptor usage example: ``` -./phantom_decryptor.bin -h phantom.txt -w wordlist.txt ----------------------------------------------- | Cyclone's Phantom Vault Decryptor | | https://github.com/cyclone-github/phantom_pwn | ----------------------------------------------- -Vault file: phantom.txt +Vault file: hash.txt Valid Vaults: 1 CPU Threads: 16 Wordlist: wordlist.txt -Working... +2024/11/30 14:11:35 Working... +{"encryptedKey":{"digest":"sha256","encrypted":"5pLvA3bCjNGYBbSjjFY3mdPknwFfp3cz9dCBv6izyyrqEhYCBkKwo3zZUzBP44KtY3","iterations":10000,"kdf":"pbkdf2","nonce":"NZT6kw5Cd5VeZu5yJGJcFcP24tnmg4xsR","salt":"A43vTZnm9c5CiQ6FLTdV9v"},"version":1}:password +2024/11/30 14:11:39 Decrypted: 1/1 6181.36 h/s 00h:00m:03s + +2024/11/30 14:11:39 Finished -Decrypted: 0/1 6360.82 h/s 00h:01m:00s ``` ### Decryptor supported options: ``` diff --git a/phantom_decryptor/go.mod b/phantom_decryptor/go.mod index b670cf5..bff0b15 100644 --- a/phantom_decryptor/go.mod +++ b/phantom_decryptor/go.mod @@ -4,7 +4,7 @@ go 1.22.4 require ( github.com/btcsuite/btcutil v1.0.2 - golang.org/x/crypto v0.26.0 + golang.org/x/crypto v0.29.0 ) -require golang.org/x/sys v0.23.0 // indirect +require golang.org/x/sys v0.27.0 // indirect diff --git a/phantom_decryptor/go.sum b/phantom_decryptor/go.sum index 7bfeb36..80bd0b0 100644 --- a/phantom_decryptor/go.sum +++ b/phantom_decryptor/go.sum @@ -22,16 +22,16 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= diff --git a/phantom_decryptor/main.go b/phantom_decryptor/main.go index 3ad811c..4aa0229 100644 --- a/phantom_decryptor/main.go +++ b/phantom_decryptor/main.go @@ -37,6 +37,11 @@ v0.1.3-2024-07-06-1100; fixed https://github.com/cyclone-github/phantom_pwn/issues/3 v0.1.4-2024-08-31-1630; finished implementing flag -o {output file} +v0.1.5-2024-11-30-1415; + fix https://github.com/cyclone-github/phantom_pwn/issues/6 + swapped crackedCount and lineProcessed channels for atomic int32 for better performance + multiple performance optimizations in process.go + print vault:password when vault is cracked */ // main func @@ -80,11 +85,15 @@ func main() { // set CPU threads numThreads := setNumThreads(*threadFlag) - // channels / variables - crackedCountCh := make(chan int, 10) // buffer of 10 to reduce blocking - linesProcessedCh := make(chan int, 1000) // buffer of 1000 to reduce blocking + // variables + var ( + crackedCount int32 + linesProcessed int32 + wg sync.WaitGroup + ) + + // channels stopChan := make(chan struct{}) - var wg sync.WaitGroup // goroutine to watch for ctrl+c handleGracefulShutdown(stopChan) @@ -102,14 +111,16 @@ func main() { // monitor status of workers wg.Add(1) - go monitorPrintStats(crackedCountCh, linesProcessedCh, stopChan, startTime, validVaultCount, &wg, *statsIntervalFlag) + go monitorPrintStats(&crackedCount, &linesProcessed, stopChan, startTime, validVaultCount, &wg, *statsIntervalFlag) // start the processing logic - startProc(*wordlistFileFlag, *outputFile, numThreads, stopChan, vaults, crackedCountCh, linesProcessedCh) + startProc(*wordlistFileFlag, *outputFile, numThreads, vaults, &crackedCount, &linesProcessed, stopChan) // close stop channel to signal all workers to stop - time.Sleep(10 * time.Millisecond) closeStopChannel(stopChan) + + // wait for monitorPrintStats to finish + wg.Wait() } // end code diff --git a/phantom_decryptor/print_welcome.go b/phantom_decryptor/print_welcome.go index 72fc3ef..5cd703d 100644 --- a/phantom_decryptor/print_welcome.go +++ b/phantom_decryptor/print_welcome.go @@ -8,7 +8,7 @@ import ( // version func func versionFunc() { - fmt.Fprintln(os.Stderr, "Cyclone's Phantom Vault Decryptor v0.1.4-2024-08-31-1630\nhttps://github.com/cyclone-github/phantom_pwn\n") + fmt.Fprintln(os.Stderr, "Cyclone's Phantom Vault Decryptor v0.1.5-2024-11-30-1415\nhttps://github.com/cyclone-github/phantom_pwn\n") } // help func diff --git a/phantom_decryptor/process.go b/phantom_decryptor/process.go index 0f609c0..dac2a67 100644 --- a/phantom_decryptor/process.go +++ b/phantom_decryptor/process.go @@ -2,185 +2,109 @@ package main import ( "bufio" - "bytes" "fmt" - "io" "log" "os" "sync" "sync/atomic" - "time" ) // process logic -func startProc(wordlistFileFlag string, outputPath string, numGoroutines int, stopChan chan struct{}, vaults []Vault, crackedCountCh chan int, linesProcessedCh chan int) { - const readBufferSize = 1024 * 1024 // read buffer - const writeBufferSize = 128 * 1024 // write buffer - - var linesHashed int64 = 0 - var procWg sync.WaitGroup - var readWg sync.WaitGroup - var writeWg sync.WaitGroup - var hexDecodeErrors int64 = 0 // hex error counter - - readChunks := make(chan []byte, 500) // channel for reading chunks of data - writeData := make(chan []byte, 10) // channel for writing processed data - +func startProc(wordlistFileFlag string, outputPath string, numGoroutines int, vaults []Vault, crackedCount *int32, linesProcessed *int32, stopChan chan struct{}) { var file *os.File var err error + if wordlistFileFlag == "" { - file = os.Stdin // default to stdin if no input flag is provided + file = os.Stdin } else { file, err = os.Open(wordlistFileFlag) if err != nil { - log.Printf("Error opening file: %v\n", err) - return + log.Fatalf("Error opening file: %v\n", err) } defer file.Close() } - startTime := time.Now() - - readWg.Add(1) - go func() { - defer readWg.Done() - var remainder []byte - reader := bufio.NewReaderSize(file, readBufferSize) - for { - chunk := make([]byte, readBufferSize) - n, err := reader.Read(chunk) - if err == io.EOF { - break - } - if err != nil { - fmt.Println(os.Stderr, "Error reading chunk:", err) - return - } + var outputFile *os.File + if outputPath != "" { + outputFile, err = os.OpenFile(outputPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + log.Fatalf("Error opening output file: %v", err) + } + defer outputFile.Close() + } - chunk = chunk[:n] - chunk = append(remainder, chunk...) + var writer *bufio.Writer + if outputPath != "" { + writer = bufio.NewWriter(outputFile) + } else { + writer = bufio.NewWriter(os.Stdout) + } + defer writer.Flush() - lastNewline := bytes.LastIndexByte(chunk, '\n') - if lastNewline == -1 { - remainder = chunk - } else { - readChunks <- chunk[:lastNewline+1] - remainder = chunk[lastNewline+1:] - } - } - if len(remainder) > 0 { - readChunks <- remainder - } - close(readChunks) - }() + var ( + writerMu sync.Mutex + wg sync.WaitGroup + ) + // start worker goroutines + linesCh := make(chan []byte, 1000) for i := 0; i < numGoroutines; i++ { - procWg.Add(1) + wg.Add(1) go func() { - defer procWg.Done() - for chunk := range readChunks { - localBuffer := bytes.NewBuffer(nil) - writer := bufio.NewWriterSize(localBuffer, writeBufferSize) - processChunk(chunk, &linesHashed, &hexDecodeErrors, writer, stopChan, vaults, crackedCountCh, linesProcessedCh) - writer.Flush() - if localBuffer.Len() > 0 { - writeData <- localBuffer.Bytes() - } + defer wg.Done() + for password := range linesCh { + processPassword(password, vaults, &writerMu, writer, crackedCount, linesProcessed, stopChan) } }() } - writeWg.Add(1) - go func() { - defer writeWg.Done() - var writer *bufio.Writer - if outputPath != "" { - outFile, err := os.OpenFile(outputPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - fmt.Println(os.Stderr, "Error creating output file:", err) - return - } - defer outFile.Close() - writer = bufio.NewWriterSize(outFile, writeBufferSize) - } else { - writer = bufio.NewWriterSize(os.Stdout, writeBufferSize) - } - - for data := range writeData { - writer.Write(data) - } - writer.Flush() - }() - - procWg.Wait() - readWg.Wait() - close(writeData) - writeWg.Wait() - - elapsedTime := time.Since(startTime) - runTime := float64(elapsedTime.Seconds()) - linesPerSecond := float64(linesHashed) / elapsedTime.Seconds() - if hexDecodeErrors > 0 { - log.Printf("HEX decode errors: %d\n", hexDecodeErrors) + // read lines from file and send them to workers + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Bytes() + password := make([]byte, len(line)) + copy(password, line) + linesCh <- password } - log.Printf("Finished processing %d lines in %.3f sec (%.3f lines/sec)\n", linesHashed, runTime, linesPerSecond) -} + close(linesCh) -// process wordlist chunks -func processChunk(chunk []byte, count *int64, hexErrorCount *int64, writer *bufio.Writer, stopChan chan struct{}, vaults []Vault, crackedCountCh chan int, linesProcessedCh chan int) { - lineStart := 0 - for i := 0; i < len(chunk); i++ { - if chunk[i] == '\n' { - password := chunk[lineStart:i] - decodedBytes, _, hexErrCount := checkForHexBytes(password) - startCracker(stopChan, decodedBytes, vaults, crackedCountCh, linesProcessedCh, writer) - atomic.AddInt64(count, 1) - atomic.AddInt64(hexErrorCount, int64(hexErrCount)) - lineStart = i + 1 // move start index past the newline - } + if err := scanner.Err(); err != nil { + log.Fatalf("Error reading file: %v\n", err) } - // handle cases where there is no newline at the end of the chunk - if lineStart < len(chunk) { - password := chunk[lineStart:] - decodedBytes, _, hexErrCount := checkForHexBytes(password) - startCracker(stopChan, decodedBytes, vaults, crackedCountCh, linesProcessedCh, writer) - atomic.AddInt64(count, 1) - atomic.AddInt64(hexErrorCount, int64(hexErrCount)) - } + wg.Wait() - writer.Flush() + log.Println("Finished") } -// hash cracking worker -func startCracker(stopChan chan struct{}, password []byte, vaults []Vault, crackedCountCh chan int, linesProcessedCh chan int, writer *bufio.Writer) { - allDecrypted := true +func processPassword(password []byte, vaults []Vault, writerMu *sync.Mutex, writer *bufio.Writer, crackedCount *int32, linesProcessed *int32, stopChan chan struct{}) { + atomic.AddInt32(linesProcessed, 1) + // check for hex, ignore hexErrCount + decodedPassword, _, _ := checkForHexBytes(password) for i := range vaults { - if !vaults[i].Decrypted { // check only undecrypted vaults - decryptedData, err := decryptVault(vaults[i].EncryptedData, password, vaults[i].Salt, vaults[i].Nonce, vaults[i].Iterations, vaults[i].Kdf) - if err != nil { - allDecrypted = false - continue // skip to next vault if decryption fails + if atomic.LoadInt32(&vaults[i].Decrypted) == 0 { + decryptedData, err := decryptVault(vaults[i].EncryptedData, decodedPassword, vaults[i].Salt, vaults[i].Nonce, vaults[i].Iterations, vaults[i].Kdf) + if err != nil || !isValid(decryptedData) { + continue } - if isValid(decryptedData) { - crackedCountCh <- 1 - vaults[i].Decrypted = true + + if atomic.CompareAndSwapInt32(&vaults[i].Decrypted, 0, 1) { + output := fmt.Sprintf("%s:%s\n", vaults[i].VaultText, string(decodedPassword)) if writer != nil { - writer.WriteString(fmt.Sprintf("\nPassword: '%s'\n", password)) + writerMu.Lock() + atomic.AddInt32(crackedCount, 1) + writer.WriteString(output) writer.Flush() - } else { - fmt.Printf("\nPassword: '%s'\n", password) + writerMu.Unlock() + } + + // exit if all vaults are cracked + if isAllVaultsCracked(vaults) { + closeStopChannel(stopChan) } - } else { - allDecrypted = false + return } } } - - linesProcessedCh <- 1 - - if allDecrypted { - closeStopChannel(stopChan) - } } diff --git a/phantom_decryptor/stats.go b/phantom_decryptor/stats.go index fa75944..6c83c7d 100644 --- a/phantom_decryptor/stats.go +++ b/phantom_decryptor/stats.go @@ -5,13 +5,13 @@ import ( "log" "os" "sync" + "sync/atomic" "time" ) // monitor status -func monitorPrintStats(crackedCountCh, linesProcessedCh <-chan int, stopChan <-chan struct{}, startTime time.Time, validVaultCount int, wg *sync.WaitGroup, interval int) { - crackedCount := 0 - linesProcessed := 0 +func monitorPrintStats(crackedCount *int32, linesProcessed *int32, stopChan <-chan struct{}, startTime time.Time, validVaultCount int, wg *sync.WaitGroup, interval int) { + var ticker *time.Ticker if interval > 0 { ticker = time.NewTicker(time.Duration(interval) * time.Second) @@ -20,31 +20,26 @@ func monitorPrintStats(crackedCountCh, linesProcessedCh <-chan int, stopChan <-c for { select { - case <-crackedCountCh: - crackedCount++ - case <-linesProcessedCh: - linesProcessed++ case <-stopChan: // print final stats and exit - printStats(time.Since(startTime), crackedCount, validVaultCount, linesProcessed, true) + printStats(time.Since(startTime), int(atomic.LoadInt32(crackedCount)), validVaultCount, int(atomic.LoadInt32(linesProcessed)), true) wg.Done() return case <-func() <-chan time.Time { if ticker != nil { return ticker.C } - // return nil channel if ticker is not used return nil }(): if interval > 0 { - printStats(time.Since(startTime), crackedCount, validVaultCount, linesProcessed, false) + printStats(time.Since(startTime), int(atomic.LoadInt32(crackedCount)), validVaultCount, int(atomic.LoadInt32(linesProcessed)), false) } } } } // printStats -func printStats(elapsedTime time.Duration, crackedCount, validVaultCount, linesProcessed int, exitProgram bool) { +func printStats(elapsedTime time.Duration, crackedCount int, validVaultCount, linesProcessed int, exitProgram bool) { hours := int(elapsedTime.Hours()) minutes := int(elapsedTime.Minutes()) % 60 seconds := int(elapsedTime.Seconds()) % 60 diff --git a/phantom_decryptor/utils.go b/phantom_decryptor/utils.go index 3cf8455..723cc7d 100644 --- a/phantom_decryptor/utils.go +++ b/phantom_decryptor/utils.go @@ -6,6 +6,7 @@ import ( "os/exec" "os/signal" "runtime" + "sync/atomic" "syscall" ) @@ -42,7 +43,6 @@ func handleGracefulShutdown(stopChan chan struct{}) { go func() { <-interruptChan fmt.Fprintln(os.Stderr, "\nCtrl+C pressed. Shutting down...") - //close(stopChan) closeStopChannel(stopChan) }() } @@ -54,3 +54,13 @@ func setNumThreads(userThreads int) int { } return userThreads } + +// check if all vaults are cracked +func isAllVaultsCracked(vaults []Vault) bool { + for i := range vaults { + if atomic.LoadInt32(&vaults[i].Decrypted) == 0 { + return false + } + } + return true +} diff --git a/phantom_decryptor/vault.go b/phantom_decryptor/vault.go index 73b7030..63913b7 100644 --- a/phantom_decryptor/vault.go +++ b/phantom_decryptor/vault.go @@ -20,8 +20,9 @@ type Vault struct { Salt []byte Nonce []byte Iterations int - Decrypted bool + Decrypted int32 Kdf string + VaultText string } // isValid function as placeholder, always returning true @@ -122,6 +123,7 @@ func readVaultData(filePath string) ([]Vault, error) { Nonce: nonce, Iterations: hash.EncryptedKey.Iterations, Kdf: hash.EncryptedKey.Kdf, + VaultText: line, } vaults = append(vaults, vault) }