A Go library for domain name permutation and DNS analysis, useful for detecting typosquatting, phishing domains, and corporate espionage attempts.
- Domain Permutation: Generate variations of a domain using multiple fuzzing algorithms
- DNS Analysis: Check DNS records, GeoIP location, and domain registration status
- Multiple Output Formats: Support for JSON, CSV, CLI table, and plain list formats
- Concurrent Processing: Multi-threaded scanning for improved performance
- Banner Detection: HTTP and SMTP service banner grabbing
- Visual Similarity: Web page visual comparison using perceptual hashing
- Filtering Options: Show only registered or unregistered domains
go get github.com/ducksify/godnstwistpackage main
import (
"fmt"
"log"
"github.com/ducksify/godnstwist/pkg/dnstwist"
)
func main() {
// Configure options
options := dnstwist.Options{
Domain: "example.com",
Threads: 10,
Format: "json",
}
// Create engine
engine, err := dnstwist.New(options)
if err != nil {
log.Fatal(err)
}
// Get results
results, err := engine.GetResults()
if err != nil {
log.Fatal(err)
}
// Format and display
output := results.Format("cli")
fmt.Print(output)
}The dnstwist.Options struct provides comprehensive configuration:
type Options struct {
// Required
Domain string // Target domain to analyze
Threads int // Number of concurrent threads (must be > 0)
// Output options
Format string // Output format: "cli", "csv", "json", "list"
Output string // Save output to file (optional)
// Fuzzing options
Fuzzers string // Comma-separated fuzzing algorithms
Dictionary string // Dictionary file for additional permutations
TLD string // TLD list file for domain swapping
// DNS and network options
Nameservers string // Custom DNS servers (comma-separated)
UserAgent string // Custom User-Agent string
All bool // Show all DNS records instead of first ones
// Analysis features
Banners bool // Determine HTTP and SMTP service banners
GeoIP bool // Perform GeoIP location lookup
MXCheck bool // Check MX records for email interception
Whois bool // Look up WHOIS information
PHash bool // Evaluate visual similarity of web pages
LSH string // LSH algorithm for page similarity ("ssdeep", "tlsh")
Screenshots string // Directory to save web page screenshots
// Filtering options
Registered bool // Show only registered domains
Unregistered bool // Show only unregistered domains
// Advanced options
LSHURL string // Override URL for original page comparison
PHashURL string // Override URL for visual comparison
}package main
import (
"fmt"
"log"
"github.com/ducksify/godnstwist/pkg/dnstwist"
)
func main() {
options := dnstwist.Options{
Domain: "github.com",
Threads: 5,
Format: "cli",
}
engine, err := dnstwist.New(options)
if err != nil {
log.Fatal(err)
}
results, err := engine.GetResults()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found %d domain variations\n", len(results))
fmt.Print(results.Format("cli"))
}package main
import (
"fmt"
"log"
"github.com/ducksify/godnstwist/pkg/dnstwist"
)
func main() {
options := dnstwist.Options{
Domain: "example.com",
Threads: 10,
Format: "json",
Fuzzers: "addition,omission,transposition,bitsquatting",
Banners: true,
GeoIP: true,
MXCheck: true,
Whois: true,
Nameservers: "8.8.8.8:53,1.1.1.1:53",
UserAgent: "Mozilla/5.0 Custom Bot",
}
engine, err := dnstwist.New(options)
if err != nil {
log.Fatal(err)
}
results, err := engine.GetResults()
if err != nil {
log.Fatal(err)
}
// Process results
for _, result := range results {
fmt.Printf("Domain: %s (Fuzzer: %s)\n", result.Domain, result.Fuzzer)
if len(result.DNS["A"]) > 0 {
fmt.Printf(" IP: %s\n", result.DNS["A"][0])
}
if result.GeoIP != "" {
fmt.Printf(" Location: %s\n", result.GeoIP)
}
if result.Banner["http"] != "" {
fmt.Printf(" HTTP Banner: %s\n", result.Banner["http"])
}
}
}package main
import (
"fmt"
"log"
"github.com/ducksify/godnstwist/pkg/dnstwist"
)
func main() {
options := dnstwist.Options{
Domain: "google.com",
Threads: 8,
Registered: true, // Only show domains with DNS A records
Fuzzers: "addition,omission,hyphenation",
GeoIP: true,
}
engine, err := dnstwist.New(options)
if err != nil {
log.Fatal(err)
}
results, err := engine.GetResults()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found %d registered domain variations:\n", len(results))
for _, result := range results {
fmt.Printf("- %s (%s) - %s\n",
result.Domain,
result.DNS["A"][0],
result.GeoIP)
}
}package main
import (
"fmt"
"log"
"os"
"github.com/ducksify/godnstwist/pkg/dnstwist"
)
func main() {
options := dnstwist.Options{
Domain: "example.org",
Threads: 4,
Fuzzers: "addition,transposition",
}
engine, err := dnstwist.New(options)
if err != nil {
log.Fatal(err)
}
results, err := engine.GetResults()
if err != nil {
log.Fatal(err)
}
// Save as JSON
jsonOutput := results.Format("json")
err = os.WriteFile("results.json", []byte(jsonOutput), 0644)
if err != nil {
log.Fatal(err)
}
// Save as CSV
csvOutput := results.Format("csv")
err = os.WriteFile("results.csv", []byte(csvOutput), 0644)
if err != nil {
log.Fatal(err)
}
// Display as CLI table
fmt.Print(results.Format("cli"))
}package main
import (
"fmt"
"log"
"github.com/ducksify/godnstwist/pkg/dnstwist"
)
func main() {
options := dnstwist.Options{
Domain: "invalid-domain", // This will cause an error
Threads: 1,
}
engine, err := dnstwist.New(options)
if err != nil {
fmt.Printf("Engine creation failed: %v\n", err)
return
}
results, err := engine.GetResults()
if err != nil {
fmt.Printf("Analysis failed: %v\n", err)
return
}
fmt.Printf("Analysis completed successfully with %d results\n", len(results))
}Available fuzzing algorithms (use with Fuzzers option):
- addition: Add characters to the domain
- bitsquatting: Flip bits in domain characters
- homoglyph: Replace characters with similar-looking ones
- hyphenation: Add hyphens between characters
- insertion: Insert characters between existing ones
- omission: Remove characters from the domain
- repetition: Repeat characters in the domain
- replacement: Replace characters with keyboard-adjacent ones
- subdomain: Split domain into subdomains
- transposition: Swap adjacent characters
- vowel-swap: Replace vowels with other vowels
- tld-swap: Swap the TLD with entries from a dictionary file
Example:
options.Fuzzers = "addition,omission,transposition,bitsquatting,tld-swap"The tld-swap fuzzer requires a TLD dictionary file. You can specify a custom file using the TLD option:
options := dnstwist.Options{
Domain: "example.com",
Fuzzers: "tld-swap",
TLD: "dictionaries/abused_tlds.dict", // Custom TLD file
Threads: 10,
}If no TLD file is specified, the fuzzer will use dictionaries/common_tlds.dict by default. If that file doesn't exist, it will fall back to a basic set of common TLDs.
The TLD dictionary file should contain one TLD per line, with comments starting with //:
// Common TLDs
com
net
org
edu
gov
// Add more TLDs here
original example.com 93.184.216.34 /United States
addition examplea.com -
addition exampleb.com -
omission exampl.com 192.0.2.1
[
{
"fuzzer": "original",
"domain": "example.com",
"dns": {
"A": ["93.184.216.34"]
},
"geoip": "United States"
}
]fuzzer,domain
original,example.com
addition,examplea.comexample.com
examplea.com
exampleb.com
Each domain analysis returns a Result struct:
type Result struct {
Fuzzer string // Fuzzing algorithm used
Domain string // Generated domain name
DNS map[string][]string // DNS records (A, AAAA, MX, etc.)
GeoIP string // Geographic location
Banner map[string]string // Service banners (http, smtp)
Whois map[string]string // WHOIS information
LSH map[string]int // LSH similarity scores
PHash int // Perceptual hash for visual similarity
}
// Convenience methods for accessing DNS records:
func (r *Result) GetARecords() []string // Returns A records
func (r *Result) GetMXRecords() []string // Returns MX records
func (r *Result) GetNSRecords() []string // Returns NS records
func (r *Result) HasARecords() bool // Returns true if domain has A records
func (r *Result) HasMXRecords() bool // Returns true if domain has MX records
func (r *Result) HasNSRecords() bool // Returns true if domain has NS records
// Results filtering methods:
func (r Results) GetDomainsWithARecords() Results // Returns only registered domains
func (r Results) GetDomainsWithoutARecords() Results // Returns only unregistered domains
func (r Results) GetDomainsWithMXRecords() Results // Returns domains with MX records
func (r Results) GetDomainsWithNSRecords() Results // Returns domains with NS records- Threads: Increase for faster scanning (default: 10)
- Fuzzers: Limit algorithms to reduce permutations
- Filtering: Use
Registered/Unregisteredto reduce DNS queries - Custom DNS: Use fast DNS servers with
Nameservers
options := dnstwist.Options{
Domain: "example.com",
Threads: 20, // More threads
Fuzzers: "addition,omission", // Fewer algorithms
Registered: true, // Filter early
Nameservers: "1.1.1.1:53", // Fast DNS
}- Go 1.19 or later
- Internet connection for DNS queries and external services
- Optional: GeoIP database file for location lookup
This project is licensed under the terms specified in the LICENSE file.
Contributions are welcome! Please feel free to submit issues and pull requests.
This library powers the dnstwist command-line tool. Install it with:
go install github.com/ducksify/godnstwist/cmd/dnstwist@latestUse the CLI tool:
# Basic usage
dnstwist -d example.com
# Check for MX and NS records
dnstwist -d example.com --mxcheck --nscheck
# Advanced usage with multiple features
dnstwist -d example.com -f json -t 10 --banners --geoipAccess DNS records and filter results:
package main
import (
"fmt"
"log"
"github.com/ducksify/godnstwist/pkg/dnstwist"
)
func main() {
options := dnstwist.Options{
Domain: "example.com",
Threads: 5,
Fuzzers: "addition,omission",
}
engine, err := dnstwist.New(options)
if err != nil {
log.Fatal(err)
}
results, err := engine.GetResults()
if err != nil {
log.Fatal(err)
}
// Access individual results
for _, result := range results {
fmt.Printf("Domain: %s\n", result.Domain)
// Get A records
if aRecords := result.GetARecords(); len(aRecords) > 0 {
fmt.Printf(" A Records: %v\n", aRecords)
}
// Get MX records
if mxRecords := result.GetMXRecords(); len(mxRecords) > 0 {
fmt.Printf(" MX Records: %v\n", mxRecords)
}
// Get NS records
if nsRecords := result.GetNSRecords(); len(nsRecords) > 0 {
fmt.Printf(" NS Records: %v\n", nsRecords)
}
// Check if domain is registered
if result.HasARecords() {
fmt.Printf(" Status: Registered\n")
} else {
fmt.Printf(" Status: Unregistered\n")
}
}
// Filter results
registered := results.GetDomainsWithARecords()
unregistered := results.GetDomainsWithoutARecords()
withMX := results.GetDomainsWithMXRecords()
withNS := results.GetDomainsWithNSRecords()
fmt.Printf("\nSummary:\n")
fmt.Printf(" Total domains: %d\n", len(results))
fmt.Printf(" Registered: %d\n", len(registered))
fmt.Printf(" Unregistered: %d\n", len(unregistered))
fmt.Printf(" With MX records: %d\n", len(withMX))
fmt.Printf(" With NS records: %d\n", len(withNS))
}package main
import (
"fmt"
"log"
"github.com/ducksify/godnstwist/pkg/dnstwist"
)
func main() {
options := dnstwist.Options{
Domain: "example.com",
Threads: 10,
Format: "json",
Fuzzers: "addition,omission,transposition,bitsquatting",
Banners: true,
GeoIP: true,
MXCheck: true,
NSCheck: true,
Whois: true,
Nameservers: "8.8.8.8:53,1.1.1.1:53",
UserAgent: "Mozilla/5.0 Custom Bot",
}
engine, err := dnstwist.New(options)
if err != nil {
log.Fatal(err)
}
results, err := engine.GetResults()
if err != nil {
log.Fatal(err)
}
// Process results
for _, result := range results {
fmt.Printf("Domain: %s (Fuzzer: %s)\n", result.Domain, result.Fuzzer)
if len(result.DNS["A"]) > 0 {
fmt.Printf(" IP: %s\n", result.DNS["A"][0])
}
if result.GeoIP != "" {
fmt.Printf(" Location: %s\n", result.GeoIP)
}
if result.Banner["http"] != "" {
fmt.Printf(" HTTP Banner: %s\n", result.Banner["http"])
}
}
}