Skip to content

Commit

Permalink
It's red vs blue
Browse files Browse the repository at this point in the history
  • Loading branch information
lkarlslund committed Sep 26, 2022
1 parent 098ca09 commit 5e62bec
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 49 deletions.
122 changes: 75 additions & 47 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ func main() {
inputname := flag.String("input", "", "File to read usernames from, uses stdin if not supplied")
outputname := flag.String("output", "", "File to write detected usernames to, uses stdout if not supplied")

// evasive maneuvers
throttle := flag.Int("throttle", 0, "Only do a request every N ms, 0 to disable")
maxrequests := flag.Int("maxrequests", 0, "Disconnect and reconnect a connection after n requests, 0 to disable")

maxservers := flag.Int("maxservers", 8, "Maximum amount of servers to run in parallel")
maxstrategy := flag.String("maxstrategy", "fastest", "How to select servers if more are found than wanted (fastest, random)")
parallel := flag.Int("parallel", 8, "How many connections per server to run in parallel")
Expand Down Expand Up @@ -242,68 +246,92 @@ func main() {

var jobs sync.WaitGroup

var throttleTimer *time.Ticker
if *throttle > 0 {
throttleTimer = time.NewTicker(time.Millisecond * time.Duration(*throttle))
}

jobs.Add(*parallel * len(servers))
for _, server := range servers {
for i := 0; i < *parallel; i++ {
go func(server string) {
connectMutex.Lock()

if connectError != nil {
connectMutex.Unlock()
jobs.Done()
return
}
var requests int
reconnectLoop:
for {
connectMutex.Lock()
if connectError != nil {
connectMutex.Unlock()
jobs.Done()
return
}

var conn *ldap.Conn
switch tlsmode {
case NoTLS:
conn, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", server, *port))
case StartTLS:
conn, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", server, *port))
if err == nil {
err = conn.StartTLS(&tls.Config{ServerName: server})
var conn *ldap.Conn
switch tlsmode {
case NoTLS:
conn, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", server, *port))
case StartTLS:
conn, err = ldap.Dial("tcp", fmt.Sprintf("%s:%d", server, *port))
if err == nil {
err = conn.StartTLS(&tls.Config{ServerName: server})
}
case TLS:
config := &tls.Config{
ServerName: server,
InsecureSkipVerify: *ignoreCert,
}
conn, err = ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", server, *port), config)
}
case TLS:
config := &tls.Config{
ServerName: server,
InsecureSkipVerify: *ignoreCert,

if err != nil {
log.Printf("Problem connecting to LDAP %v server: %v", server, err)
connectError = err
jobs.Done()
connectMutex.Unlock()
return
}
conn, err = ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", server, *port), config)
}

if err != nil {
log.Printf("Problem connecting to LDAP %v server: %v", server, err)
connectError = err
jobs.Done()
connectMutex.Unlock()
return
}

connectMutex.Unlock()

for username := range inputqueue {
request := ldap.NewSearchRequest(
"", // The base dn to search
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&(NtVer=\x06\x00\x00\x00)(AAC=\x10\x00\x00\x00)(User="+username+"))"), // The filter to apply
[]string{"NetLogon"}, // A list attributes to retrieve
nil,
)
response, err := conn.Search(request)
if err != nil {
if v, ok := err.(*ldap.Error); ok && v.ResultCode == 201 {
for username := range inputqueue {
// do throttling if needed
if throttleTimer != nil {
<-throttleTimer.C
}

request := ldap.NewSearchRequest(
"", // The base dn to search
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&(NtVer=\x06\x00\x00\x00)(AAC=\x10\x00\x00\x00)(User="+username+"))"), // The filter to apply
[]string{"NetLogon"}, // A list attributes to retrieve
nil,
)
response, err := conn.Search(request)
if err != nil {
if v, ok := err.(*ldap.Error); ok && v.ResultCode == 201 {
continue
}
log.Printf("failed to execute search request: %v", err)
continue
}
log.Printf("failed to execute search request: %v", err)
continue
}

// Did we catch something?
res := response.Entries[0].Attributes[0].ByteValues[0]
if len(res) > 2 && res[0] == 0x17 && res[1] == 00 {
outputqueue <- username
// Did we catch something?
res := response.Entries[0].Attributes[0].ByteValues[0]
if len(res) > 2 && res[0] == 0x17 && res[1] == 00 {
outputqueue <- username
}

// Should we start a new connection to avoid detection
requests++
if *maxrequests != 0 && requests == *maxrequests {
requests = 0
conn.Close()
continue reconnectLoop
}
}
// No more input in channel, bye bye from this worker
break
}

jobs.Done()
}(server)
}
Expand Down
5 changes: 3 additions & 2 deletions readme.MD
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Looks for enabled normal user accounts. No Windows audit logs generated. High sp
- Outputs to stdout (default) or file
- Parallelized, multiple connections to multiple servers (defaults to 8 servers, 8 connections per server)
- Shows progressbar if you're using both input and output files
- Evasive maneuvers: Use --throttle 20 for a 20ms delay between each request (slows everything down to a crawl)
- Evasive maneuvers: Use --maxrequests 1000 to close connection and reconnect after 1000 requests in each connection (try to avoid detection based on traffic volume)

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

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

```bash
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]
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]
```
### Examples
Expand All @@ -42,7 +44,6 @@ Look for username lists to feed into this elsewhere - for instance the 10M list
- No Windows event logs are generated (tested on Windows 2016 / 2019)
- Requires custom network level monitoring (unencrypted LDAP analysis or traffic volume for LDAPS)
- One user has reported sandbox detection by LDAP query volume
## Mitigation
Expand Down

0 comments on commit 5e62bec

Please sign in to comment.