diff --git a/README.md b/README.md index 3f1728a..8002972 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,10 @@ -[![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=cyclone-github&repo=crackmon&theme=gruvbox)](https://github.com/cyclone-github/) +[![Readme Card](https://github-readme-stats.vercel.app/api/pin/?username=cyclone-github&repo=crackmon&theme=gruvbox)](https://github.com/cyclone-github/crackmon/) + +[![Go Report Card](https://goreportcard.com/badge/github.com/cyclone-github/crackmon)](https://goreportcard.com/report/github.com/cyclone-github/crackmon) +[![GitHub issues](https://img.shields.io/github/issues/cyclone-github/crackmon.svg)](https://github.com/cyclone-github/crackmon/issues) +[![License](https://img.shields.io/github/license/cyclone-github/crackmon.svg)](LICENSE) +[![GitHub release](https://img.shields.io/github/release/cyclone-github/crackmon.svg)](https://github.com/cyclone-github/crackmon/releases) + # crackmon Hashcat wrapper tool to bypass current attack if crack rate drops below threshold. diff --git a/func_linux.go b/func_linux.go index 65f18a9..57763b4 100644 --- a/func_linux.go +++ b/func_linux.go @@ -1,48 +1,48 @@ -//go:build linux -// +build linux - -package main - -import ( - "fmt" - "io" - "os" - "os/exec" - "strings" - - "github.com/creack/pty" // used for sending user keyboard commands to hashcat -) - -/* -v2023-10-07.1520 -v2023-10-13.1445; refactored sendX commands -*/ - -// sendX func -func linuxSendCmd(cmd string, stdin io.Writer) { - io.WriteString(stdin, cmd) -} - -// initialize OS specific logic -func initializeAndExecute(cmdStr string, timeT int, crackT int, debug bool) { - cmdSlice := strings.Fields(cmdStr) - cmdName := cmdSlice[0] - cmdArgs := cmdSlice[1:] - - cmd := exec.Command(cmdName, cmdArgs...) - ptmx, err := pty.Start(cmd) // start hashcat command with pty - if err != nil { - fmt.Fprintln(os.Stderr, "Error starting command with PTY:", err) - return - } - defer func() { _ = ptmx.Close() }() // close pty - - sendB = func(stdin io.Writer) { linuxSendCmd("b", stdin) } - sendQ = func(stdin io.Writer) { linuxSendCmd("q", stdin) } - - // listen for user commands - go ReadUserInput(ptmx) - - // initialize common logic - initializeAndExecuteCommon(cmdStr, timeT, crackT, debug, ptmx, ptmx, checkOS) -} +//go:build linux +// +build linux + +package main + +import ( + "fmt" + "io" + "os" + "os/exec" + "strings" + + "github.com/creack/pty" // used for sending user keyboard commands to hashcat +) + +/* +v2023-10-07.1520 +v2023-10-13.1445; refactored sendX commands +*/ + +// sendX func +func linuxSendCmd(cmd string, stdin io.Writer) { + io.WriteString(stdin, cmd) +} + +// initialize OS specific logic +func initializeAndExecute(cmdStr string, timeT int, crackT int, debug bool) { + cmdSlice := strings.Fields(cmdStr) + cmdName := cmdSlice[0] + cmdArgs := cmdSlice[1:] + + cmd := exec.Command(cmdName, cmdArgs...) + ptmx, err := pty.Start(cmd) // start hashcat command with pty + if err != nil { + fmt.Fprintln(os.Stderr, "Error starting command with PTY:", err) + return + } + defer func() { _ = ptmx.Close() }() // close pty + + sendB = func(stdin io.Writer) { linuxSendCmd("b", stdin) } + sendQ = func(stdin io.Writer) { linuxSendCmd("q", stdin) } + + // listen for user commands + go ReadUserInput(ptmx) + + // initialize common logic + initializeAndExecuteCommon(cmdStr, timeT, crackT, debug, ptmx, ptmx, checkOS) +} diff --git a/func_windows.go b/func_windows.go index d2e3877..bd03cb5 100644 --- a/func_windows.go +++ b/func_windows.go @@ -1,60 +1,60 @@ -//go:build windows -// +build windows - -package main - -import ( - "fmt" - "io" - "os" - "time" - - "github.com/UserExistsError/conpty" -) - -/* -v2023-10-07.1520 -v2023-10-13.1445; refactored sendX commands -*/ - -var cptyInstance *conpty.ConPty - -// conpty func for windows pty support -func initializeConPTY(fullCmd string) error { - var err error - cptyInstance, err = conpty.Start(fullCmd) - if err != nil { - return err - } - return nil -} - -// sendX func -func windowsSendCmd(cmd string, stdin io.Writer) { - _, err := cptyInstance.Write([]byte(cmd + "\n")) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to send '%s' command: %v", cmd, err) - } -} - -// initialize OS specific logic -func initializeAndExecute(cmdStr string, timeT int, crackT int, debug bool) { - err := initializeConPTY(cmdStr) - if err != nil { - fmt.Fprintln(os.Stderr, "Error initializing ConPTY:", err) - return - } - sendB = func(stdin io.Writer) { windowsSendCmd("b", stdin) } - sendQ = func(stdin io.Writer) { - windowsSendCmd("q", stdin) - cptyInstance.Close() - time.Sleep(1 * time.Second) - os.Exit(0) - } - - // listen for user commands - go ReadUserInput(cptyInstance) - - // initialize common logic - initializeAndExecuteCommon(cmdStr, timeT, crackT, debug, cptyInstance, cptyInstance, checkOS) -} +//go:build windows +// +build windows + +package main + +import ( + "fmt" + "io" + "os" + "time" + + "github.com/UserExistsError/conpty" +) + +/* +v2023-10-07.1520 +v2023-10-13.1445; refactored sendX commands +*/ + +var cptyInstance *conpty.ConPty + +// conpty func for windows pty support +func initializeConPTY(fullCmd string) error { + var err error + cptyInstance, err = conpty.Start(fullCmd) + if err != nil { + return err + } + return nil +} + +// sendX func +func windowsSendCmd(cmd string, stdin io.Writer) { + _, err := cptyInstance.Write([]byte(cmd + "\n")) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to send '%s' command: %v", cmd, err) + } +} + +// initialize OS specific logic +func initializeAndExecute(cmdStr string, timeT int, crackT int, debug bool) { + err := initializeConPTY(cmdStr) + if err != nil { + fmt.Fprintln(os.Stderr, "Error initializing ConPTY:", err) + return + } + sendB = func(stdin io.Writer) { windowsSendCmd("b", stdin) } + sendQ = func(stdin io.Writer) { + windowsSendCmd("q", stdin) + cptyInstance.Close() + time.Sleep(1 * time.Second) + os.Exit(0) + } + + // listen for user commands + go ReadUserInput(cptyInstance) + + // initialize common logic + initializeAndExecuteCommon(cmdStr, timeT, crackT, debug, cptyInstance, cptyInstance, checkOS) +} diff --git a/go.mod b/go.mod index fa3efa4..44f2590 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module crackmon -go 1.21.1 +go 1.22.4 require ( - github.com/UserExistsError/conpty v0.1.1 - github.com/creack/pty v1.1.18 + github.com/UserExistsError/conpty v0.1.4 + github.com/creack/pty v1.1.24 ) require golang.org/x/sys v0.8.0 // indirect diff --git a/go.sum b/go.sum index 4a2edf9..9416115 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ -github.com/UserExistsError/conpty v0.1.1 h1:cHDsU/XeoeDAQmVvCTV53SrXLG39YJ4++Pp3iAi1gXE= -github.com/UserExistsError/conpty v0.1.1/go.mod h1:PDglKIkX3O/2xVk0MV9a6bCWxRmPVfxqZoTG/5sSd9I= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +github.com/UserExistsError/conpty v0.1.4 h1:+3FhJhiqhyEJa+K5qaK3/w6w+sN3Nh9O9VbJyBS02to= +github.com/UserExistsError/conpty v0.1.4/go.mod h1:PDglKIkX3O/2xVk0MV9a6bCWxRmPVfxqZoTG/5sSd9I= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/utils.go b/utils.go index cf99336..0dc09dd 100644 --- a/utils.go +++ b/utils.go @@ -1,199 +1,199 @@ -package main - -import ( - "bufio" - "fmt" - "io" - "os" - "os/signal" - "regexp" - "runtime" - "strconv" - "sync" - "syscall" - "time" -) - -/* -v2023-10-07.1520 -v2023-10-08.0930; fixed https://github.com/cyclone-github/crackmon/issues/3 -v2023-10-13.1445; fixed https://github.com/cyclone-github/crackmon/issues/4; refactored sendX commands -*/ - -func ReadUserInput(stdin io.Writer) { - scanner := bufio.NewScanner(os.Stdin) - for scanner.Scan() { - text := scanner.Text() - io.WriteString(stdin, text) - } - if err := scanner.Err(); err != nil { - fmt.Fprintln(os.Stderr, "Error reading from stdin:", err) - } -} - -func checkOS() string { - return runtime.GOOS -} - -// watch for ctrl+c -func catchCtrlC(stdin io.Writer) { - c := make(chan os.Signal, 1) - signal.Notify(c, syscall.SIGINT) // SIGINT = Ctrl+C - - go func() { - <-c - fmt.Fprintln(os.Stderr, "\nCaught Ctrl+C. Shutting down...\n") - // sendQ(stdin) - // time.Sleep(1 * time.Second) - os.Exit(0) - }() -} - -var ( - sendB func(io.Writer) - sendQ func(io.Writer) - wg sync.WaitGroup - done = make(chan struct{}) -) - -func initializeAndExecuteCommon(cmdStr string, timeT int, crackT int, debug bool, stdin io.Writer, stdout io.Reader, osChecker func() string) { - catchCtrlC(stdin) - - scanner := bufio.NewScanner(stdout) - - var ( - hashcatRunning bool - hashcatPaused bool - hashcatStartTime time.Time - missedChecks int - hashcatStatus string - stdoutStatus string - cumulativeAvg float64 - totalCracks int - totalTime float64 - ) - // regex for reading hc stdout - runningRe := regexp.MustCompile(`Status\.+: Running`) - pausedRe := regexp.MustCompile(`Status\.+: Paused`) - stoppedRe := regexp.MustCompile(`Stopped:`) - recoveredRe := regexp.MustCompile(`Recovered[.]+:\s*(\d+)`) - dictCacheRe := regexp.MustCompile(`Dictionary cache building`) - invalidArgRe := regexp.MustCompile(`Invalid argument specified.`) - - // default hc status - hashcatStatus = "Waiting for status" - stdoutStatus = "Waiting for stdout" - hashcatRunning = false - hashcatPaused = false - - wg.Add(2) - - go func() { - time.Sleep(10 * time.Millisecond) - defer wg.Done() - for { - select { - case <-done: - return - default: - if scanner.Scan() { - line := scanner.Text() - fmt.Println(line) - - // detect "hashcat : unknown option" - if invalidArgRe.MatchString(line) { - if debug { - fmt.Fprintln(os.Stderr, "Invalid argument specified. Exiting...") - } - os.Exit(1) - return - // check if hc is running - } else if runningRe.MatchString(line) { - hashcatStatus = "Running" - hashcatRunning = true - hashcatPaused = false - missedChecks = 0 - if hashcatStartTime.IsZero() { // set hc start time - hashcatStartTime = time.Now().Add(-time.Second * 9) - } - // check is hc is paused - } else if pausedRe.MatchString(line) { - hashcatStatus = "Paused" - hashcatPaused = true - hashcatRunning = false - missedChecks = 0 - // check is hc is stopped - } else if stoppedRe.MatchString(line) { - close(done) - time.Sleep(1 * time.Second) - os.Exit(0) - return - // Check for "Dictionary cache building" - // not working due to hashcat stdout uses \r rather than \n during dictionary cache building - } else if !hashcatRunning && dictCacheRe.MatchString(line) { - hashcatStatus = "Building dictionary cache..." - hashcatRunning = true - hashcatPaused = false - stdoutStatus = "OK" - missedChecks = 0 - } - // check founds total - recoveredT := recoveredRe.FindStringSubmatch(line) - if len(recoveredT) >= 2 && hashcatRunning && !hashcatPaused { - totalCracks, _ = strconv.Atoi(recoveredT[1]) - totalTime = time.Since(hashcatStartTime).Seconds() - stdoutStatus = "OK" - - // cumulative average, total cracks / total time (same as hashcat's AVG) - if totalTime > 60 { - cumulativeAvg = float64(totalCracks) / totalTime * 60 - } - - // sendB if -c threshold is not met within -t threshold - if totalTime >= float64(timeT*60) { - if cumulativeAvg < float64(crackT) { - sendB(stdin) - } - } - } - } else { - // sendQ if hc stdout cannot be read - missedChecks++ - hashcatStatus = "Unknown" - stdoutStatus = "Cannot read stdout" - if missedChecks >= 120 { - sendQ(stdin) - time.Sleep(1 * time.Second) - os.Exit(1) - return - } - } - } - } - }() - - // debug output goroutine - go func() { - defer wg.Done() - ticker := time.NewTicker(10500 * time.Millisecond) - for { - select { - case <-done: - return - case <-ticker.C: - if debug { - fmt.Fprintf(os.Stderr, "DEBUG: hashcat status:\t %s\n", hashcatStatus) - fmt.Fprintf(os.Stderr, "DEBUG: hashcat stdout:\t %s\n", stdoutStatus) - fmt.Fprintf(os.Stderr, "DEBUG: Recovered:\t %d\n", totalCracks) - if totalTime < 60 { - fmt.Fprintf(os.Stderr, "DEBUG: Recovered AVG:\t N/A\n\n") - } else { - fmt.Fprintf(os.Stderr, "DEBUG: Recovered AVG:\t %.1f\n\n", cumulativeAvg) - } - } - } - } - }() - wg.Wait() - close(done) -} +package main + +import ( + "bufio" + "fmt" + "io" + "os" + "os/signal" + "regexp" + "runtime" + "strconv" + "sync" + "syscall" + "time" +) + +/* +v2023-10-07.1520 +v2023-10-08.0930; fixed https://github.com/cyclone-github/crackmon/issues/3 +v2023-10-13.1445; fixed https://github.com/cyclone-github/crackmon/issues/4; refactored sendX commands +*/ + +func ReadUserInput(stdin io.Writer) { + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + text := scanner.Text() + io.WriteString(stdin, text) + } + if err := scanner.Err(); err != nil { + fmt.Fprintln(os.Stderr, "Error reading from stdin:", err) + } +} + +func checkOS() string { + return runtime.GOOS +} + +// watch for ctrl+c +func catchCtrlC(stdin io.Writer) { + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGINT) // SIGINT = Ctrl+C + + go func() { + <-c + fmt.Fprintln(os.Stderr, "\nCaught Ctrl+C. Shutting down...\n") + // sendQ(stdin) + // time.Sleep(1 * time.Second) + os.Exit(0) + }() +} + +var ( + sendB func(io.Writer) + sendQ func(io.Writer) + wg sync.WaitGroup + done = make(chan struct{}) +) + +func initializeAndExecuteCommon(cmdStr string, timeT int, crackT int, debug bool, stdin io.Writer, stdout io.Reader, osChecker func() string) { + catchCtrlC(stdin) + + scanner := bufio.NewScanner(stdout) + + var ( + hashcatRunning bool + hashcatPaused bool + hashcatStartTime time.Time + missedChecks int + hashcatStatus string + stdoutStatus string + cumulativeAvg float64 + totalCracks int + totalTime float64 + ) + // regex for reading hc stdout + runningRe := regexp.MustCompile(`Status\.+: Running`) + pausedRe := regexp.MustCompile(`Status\.+: Paused`) + stoppedRe := regexp.MustCompile(`Stopped:`) + recoveredRe := regexp.MustCompile(`Recovered[.]+:\s*(\d+)`) + dictCacheRe := regexp.MustCompile(`Dictionary cache building`) + invalidArgRe := regexp.MustCompile(`Invalid argument specified.`) + + // default hc status + hashcatStatus = "Waiting for status" + stdoutStatus = "Waiting for stdout" + hashcatRunning = false + hashcatPaused = false + + wg.Add(2) + + go func() { + time.Sleep(10 * time.Millisecond) + defer wg.Done() + for { + select { + case <-done: + return + default: + if scanner.Scan() { + line := scanner.Text() + fmt.Println(line) + + // detect "hashcat : unknown option" + if invalidArgRe.MatchString(line) { + if debug { + fmt.Fprintln(os.Stderr, "Invalid argument specified. Exiting...") + } + os.Exit(1) + return + // check if hc is running + } else if runningRe.MatchString(line) { + hashcatStatus = "Running" + hashcatRunning = true + hashcatPaused = false + missedChecks = 0 + if hashcatStartTime.IsZero() { // set hc start time + hashcatStartTime = time.Now().Add(-time.Second * 9) + } + // check is hc is paused + } else if pausedRe.MatchString(line) { + hashcatStatus = "Paused" + hashcatPaused = true + hashcatRunning = false + missedChecks = 0 + // check is hc is stopped + } else if stoppedRe.MatchString(line) { + close(done) + time.Sleep(1 * time.Second) + os.Exit(0) + return + // Check for "Dictionary cache building" + // not working due to hashcat stdout uses \r rather than \n during dictionary cache building + } else if !hashcatRunning && dictCacheRe.MatchString(line) { + hashcatStatus = "Building dictionary cache..." + hashcatRunning = true + hashcatPaused = false + stdoutStatus = "OK" + missedChecks = 0 + } + // check founds total + recoveredT := recoveredRe.FindStringSubmatch(line) + if len(recoveredT) >= 2 && hashcatRunning && !hashcatPaused { + totalCracks, _ = strconv.Atoi(recoveredT[1]) + totalTime = time.Since(hashcatStartTime).Seconds() + stdoutStatus = "OK" + + // cumulative average, total cracks / total time (same as hashcat's AVG) + if totalTime > 60 { + cumulativeAvg = float64(totalCracks) / totalTime * 60 + } + + // sendB if -c threshold is not met within -t threshold + if totalTime >= float64(timeT*60) { + if cumulativeAvg < float64(crackT) { + sendB(stdin) + } + } + } + } else { + // sendQ if hc stdout cannot be read + missedChecks++ + hashcatStatus = "Unknown" + stdoutStatus = "Cannot read stdout" + if missedChecks >= 120 { + sendQ(stdin) + time.Sleep(1 * time.Second) + os.Exit(1) + return + } + } + } + } + }() + + // debug output goroutine + go func() { + defer wg.Done() + ticker := time.NewTicker(10500 * time.Millisecond) + for { + select { + case <-done: + return + case <-ticker.C: + if debug { + fmt.Fprintf(os.Stderr, "DEBUG: hashcat status:\t %s\n", hashcatStatus) + fmt.Fprintf(os.Stderr, "DEBUG: hashcat stdout:\t %s\n", stdoutStatus) + fmt.Fprintf(os.Stderr, "DEBUG: Recovered:\t %d\n", totalCracks) + if totalTime < 60 { + fmt.Fprintf(os.Stderr, "DEBUG: Recovered AVG:\t N/A\n\n") + } else { + fmt.Fprintf(os.Stderr, "DEBUG: Recovered AVG:\t %.1f\n\n", cumulativeAvg) + } + } + } + } + }() + wg.Wait() + close(done) +}