Files
psycep ce5ca7f159 module: rename to github.com/mandiant/gopacket
Fixes #6. The module was declared as `module gopacket` in go.mod,
which is not a canonical import path. External projects could not
`go get github.com/mandiant/gopacket` to use any of the 24
protocol packages as a library; the only workaround was to clone
the repo and add a `replace` directive to their own go.mod.

This commit:
- Sets `module github.com/mandiant/gopacket` in go.mod.
- Rewrites 434 import statements across 157 files from the bare
  `gopacket/...` prefix to `github.com/mandiant/gopacket/...`.

Pure mechanical change, no behavior difference. Makes the README's
library story (pkg/ directory, 24 reusable packages) actually
usable from external code.
2026-04-22 10:27:18 -05:00

188 lines
5.1 KiB
Go

// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"flag"
"fmt"
"log"
"net"
"os"
"strings"
"text/tabwriter"
"time"
"github.com/mandiant/gopacket/pkg/flags"
"github.com/mandiant/gopacket/pkg/ldap"
"github.com/mandiant/gopacket/pkg/session"
"github.com/mandiant/gopacket/pkg/transport"
)
var (
resolveIP bool
userFilter string
)
func main() {
// Register tool-specific flags before Parse()
flag.BoolVar(&resolveIP, "resolveIP", false, "Tries to resolve the IP address of computer objects")
flag.StringVar(&userFilter, "user", "", "Requests data for specific computer")
opts := flags.Parse()
if opts.TargetStr == "" {
flag.Usage()
os.Exit(1)
}
target, creds, err := session.ParseTargetString(opts.TargetStr)
if err != nil {
log.Fatalf("[-] Error parsing target string: %v", err)
}
opts.ApplyToSession(&target, &creds)
if !opts.NoPass {
if err := session.EnsurePassword(&creds); err != nil {
log.Fatal(err)
}
}
// Set default LDAP port
if target.Port == 0 {
target.Port = 389
}
// Initialize LDAP Client
client := ldap.NewClient(target, &creds)
defer client.Close()
// Connect
if err := client.Connect(false); err != nil {
log.Fatalf("[-] Connection failed: %v", err)
}
// Login - convert to UPN format for simple bind if not using hash/Kerberos
if creds.Domain != "" && creds.Hash == "" && !creds.UseKerberos && os.Getenv("GOPACKET_NO_UPN") == "" {
creds.Username = fmt.Sprintf("%s@%s", creds.Username, creds.Domain)
creds.Domain = ""
}
if err := client.Login(); err != nil {
log.Fatalf("[-] Bind failed: %v", err)
}
// Get Domain Context
baseDN, err := client.GetDefaultNamingContext()
if err != nil {
log.Fatalf("[-] Failed to get Naming Context: %v", err)
}
// Search for Computer objects
filter := "(&(objectCategory=computer)(objectClass=computer))"
if userFilter != "" {
// Filter for specific computer - add $ if not present
computerName := userFilter
if !strings.HasSuffix(computerName, "$") {
computerName += "$"
}
filter = fmt.Sprintf("(&(objectCategory=computer)(objectClass=computer)(sAMAccountName=%s))", computerName)
}
attributes := []string{
"sAMAccountName",
"dNSHostName",
"operatingSystem",
"operatingSystemVersion",
}
fmt.Printf("[*] Querying %s for information about domain.\n", target.Host)
results, err := client.SearchWithPaging(baseDN, filter, attributes, 100)
if err != nil {
log.Fatalf("[-] Search failed: %v", err)
}
// Determine DNS server for IP resolution
var dnsServer string
if resolveIP {
// Use the target as DNS server (assumes target is DC)
if target.IP != "" {
dnsServer = target.IP
} else {
dnsServer = target.Host
}
// Override with -dc-ip if provided
if creds.DCIP != "" {
dnsServer = creds.DCIP
}
}
// Print results in tabular format (matching Python's format)
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
if resolveIP {
fmt.Fprintln(w, "SAM AcctName\tDNS Hostname\tOS Version\tOS\tIPAddress")
fmt.Fprintln(w, "---------------\t-----------------------------------\t---------------\t-----------------------------------\t--------------------")
} else {
fmt.Fprintln(w, "SAM AcctName\tDNS Hostname\tOS Version\tOS")
fmt.Fprintln(w, "---------------\t-----------------------------------\t---------------\t--------------------")
}
for _, entry := range results.Entries {
name := entry.GetAttributeValue("sAMAccountName")
dnsName := entry.GetAttributeValue("dNSHostName")
osName := entry.GetAttributeValue("operatingSystem")
osVer := entry.GetAttributeValue("operatingSystemVersion")
if resolveIP {
ip := resolveHostname(dnsName, dnsServer)
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", name, dnsName, osVer, osName, ip)
} else {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", name, dnsName, osVer, osName)
}
}
w.Flush()
}
// resolveHostname resolves a hostname to an IP address using the specified DNS server
func resolveHostname(hostname, dnsServer string) string {
if hostname == "" {
return ""
}
// Custom resolver using the DC's DNS. Under -proxy, UDP DNS cannot be
// tunneled via SOCKS5; callers should pass an IP directly in that case.
resolver := &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
return transport.DialContext(ctx, network, dnsServer+":53")
},
}
// Resolve using the custom resolver with timeout
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
addrs, err := resolver.LookupHost(ctx, hostname)
if err != nil {
return ""
}
if len(addrs) > 0 {
return addrs[0]
}
return ""
}