Public/Reconnaissance/anonymous/Find-DnsRecords.ps1

using namespace System.Management.Automation

class SubdomainCategories : IValidateSetValuesGenerator {
    [string[]] GetValidValues() {
        $categories = @('all')
        if ($script:SessionVariables -and $script:SessionVariables.subdomains -and $script:SessionVariables.subdomains.default) {
            $categories += $script:SessionVariables.subdomains.default.Keys
        }
        return $categories
    }
}

function Find-DnsRecords {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [ValidateNotNullOrEmpty()]
        [Alias("d", "domain")]
        [string[]]$Domains,

        [Parameter(Mandatory = $false)]
        [ValidateRange(1, 300)]
        [Alias("delay-min", "min-delay")]
        [int]$MinDelay = 1,

        [Parameter(Mandatory = $false)]
        [ValidateRange(1, 600)]
        [Alias("delay-max", "max-delay")]
        [int]$MaxDelay = 3,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [Alias("log-path", "l", "log")]
        [string]$LogPath = ".\recon_results.log",

        [Parameter(Mandatory = $false)]
        [ValidateSet("A", "AAAA", "CNAME", "MX", "NS", "TXT", "SOA", "PTR")]
        [Alias("record-types", "types", "r")]
        [string[]]$RecordTypes = @("A", "AAAA", "CNAME", "MX", "TXT"),

        [Parameter(Mandatory = $false)]
        [Alias("fast", "f")]
        [switch]$FastMode,

        [Parameter(Mandatory = $false)]
        [ValidateSet("Minimal", "Standard", "Detailed")]
        [Alias("info-level", "level")]
        [string]$DNSInfoLevel = "Standard",

        [Parameter(Mandatory = $false)]
        [ValidateSet("Object", "JSON", "CSV", "Table")]
        [Alias("output", "o")]
        [string]$OutputFormat = "Table",

        [Parameter(Mandatory = $false)]
        [Alias("enum-subdomains", "subdomains", "s")]
        [switch]$EnumerateSubdomains,

        [Parameter(Mandatory = $false)]
        [ValidateRange(1, 200)]
        [Alias("subdomain-throttle", "throttle-limit", "t")]
        [int]$SubdomainThrottleLimit = 50,

        [Parameter(Mandatory = $false)]
        [ValidateSet([SubdomainCategories])]
        [Alias("subdomain-cat", "cat", "c")]
        [string]$SubdomainCategory = "common",

        [Parameter(Mandatory = $false)]
        [Alias("deep-search", "deep", "ds")]
        [switch]$DeepSubdomainSearch
    )

    begin {
        Write-Verbose "Starting DNS reconnaissance with enhanced provider support"

        if ($MinDelay -gt $MaxDelay) {
            throw "MinDelay cannot be greater than MaxDelay"
        }
        
        if ($EnumerateSubdomains -and $SubdomainThrottleLimit -gt 200) {
            Write-Warning "High subdomain throttle limit may cause rate limiting. Consider reducing to 100 or less."
        }
        
        if ($DeepSubdomainSearch -and -not $EnumerateSubdomains) {
            Write-Warning "DeepSubdomainSearch requires EnumerateSubdomains to be enabled. Enabling subdomain enumeration."
            $EnumerateSubdomains = $true
        }

        $DNSProviders = @{
            "Cloudflare" = @{ URL = "https://cloudflare-dns.com/dns-query"; Region = "Global"; Type = "Commercial"; Reliability = 99.9 }
            "Google" = @{ URL = "https://dns.google/resolve"; Region = "Global"; Type = "Commercial"; Reliability = 99.8 }
            "NextDNS" = @{ URL = "https://dns.nextdns.io"; Region = "Global"; Type = "Privacy"; Reliability = 99.5 }
            "DNS.SB" = @{ URL = "https://doh.dns.sb/dns-query"; Region = "Global"; Type = "Privacy"; Reliability = 99.3 }
            "DNSPod" = @{ URL = "https://dns.pub/dns-query"; Region = "China/Global"; Type = "Commercial"; Reliability = 99.0 }
        }

        # User agents - integrate with BlackCat module if available (replaces UseRandomUserAgent parameter)
        if ($BlackCatAvailable -and $script:SessionVariables -and $script:SessionVariables.userAgents) {
            Write-Verbose "Using BlackCat module user agents from session variables"
            $UserAgents = $script:SessionVariables.userAgents.agents | ForEach-Object { $_.value }
        } else {
            Write-Verbose "Using built-in user agents (anonymous mode)"
            $UserAgents = @(
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.132 Safari/537.36",
                "Mozilla/5.0 (Macintosh; Intel Mac OS X 13_5_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.5938.132 Safari/537.36",
                "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/119.0"
            )
        }



        # Initialize results and configuration
        $Results = [System.Collections.ArrayList]::new()
        $Stats = @{ TotalQueries = 0; SuccessfulQueries = 0; FailedQueries = 0; StartTime = Get-Date }
        $Config = @{
            MinDelay = if ($FastMode) { 0.5 } else { $MinDelay }
            MaxDelay = if ($FastMode) { 1 } else { $MaxDelay }
            Timeout = if ($FastMode) { 2 } else { 10 }
        }
    }

    process {
        foreach ($Domain in $Domains) {
            Write-Host "🎯 Analyzing domain: $Domain" -ForegroundColor Green
            
            # Rotate through providers for load balancing
            $ProviderNames = $DNSProviders.Keys | Sort-Object { Get-Random }
            
            # Build list of domains to query (root domain + subdomains if enabled)
            $DomainsToQuery = @($Domain)
            
            # Add subdomain enumeration for CNAME discovery
            if ($EnumerateSubdomains -and $RecordTypes -contains "CNAME") {
                Write-Host " 🔍 Enumerating subdomains for CNAME discovery..." -ForegroundColor Cyan
                
                # Determine subdomain type based on DeepSubdomainSearch parameter
                $SubdomainType = if ($DeepSubdomainSearch) { 'deep' } else { 'default' }
                
                # Get subdomain list from session variables (same logic as Find-SubDomain)
                $SubdomainList = if ($script:SessionVariables -and $script:SessionVariables.subdomains) {
                    Write-Verbose "Using session variable subdomain list (type: $SubdomainType, category: $SubdomainCategory)"
                    
                    $subdomains = [System.Collections.Generic.HashSet[string]]::new()
                    
                    if ($SubdomainCategory -eq 'all') {
                        foreach ($cat in $script:SessionVariables.subdomains[$SubdomainType].Keys) {
                            # Skip the 'common' category when 'all' is selected for improved performance
                            if ($cat -ne 'common') {
                                foreach ($sd in $script:SessionVariables.subdomains[$SubdomainType].$cat) {
                                    [void]$subdomains.Add($sd)
                                }
                            }
                        }
                    } else {
                        if ($script:SessionVariables.subdomains[$SubdomainType].ContainsKey($SubdomainCategory)) {
                            foreach ($sd in $script:SessionVariables.subdomains[$SubdomainType].$SubdomainCategory) {
                                [void]$subdomains.Add($sd)
                            }
                        } else {
                            Write-Warning "Category '$SubdomainCategory' not found in session variables. Available categories: $($script:SessionVariables.subdomains[$SubdomainType].Keys -join ', ')"
                            $subdomains = $null
                        }
                    }
                    
                    if ($subdomains) { [array]$subdomains } else { $null }
                } else {
                    Write-Warning "Session variables not available. Cannot enumerate subdomains without BlackCat framework."
                    $null
                }
                
                if (-not $SubdomainList) {
                    Write-Host " ⚠️ Subdomain enumeration skipped - no subdomain data available" -ForegroundColor Yellow
                    continue
                }
                
                # Generate subdomain candidates
                $SubdomainCandidates = [System.Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase)
                foreach ($subdomain in $SubdomainList) {
                    [void]$SubdomainCandidates.Add("$subdomain.$Domain")
                }
                
                $SearchTypeMsg = if ($DeepSubdomainSearch) { "deep search" } else { "standard search" }
                Write-Host " 🎯 Testing $($SubdomainCandidates.Count) subdomain candidates ($SearchTypeMsg, category: $SubdomainCategory)..." -ForegroundColor Yellow
                
                # Test subdomains in parallel for existence first (faster DNS resolution check)
                $ValidSubdomains = $SubdomainCandidates | ForEach-Object -Parallel {
                    try {
                        $null = [System.Net.Dns]::GetHostEntry($_)
                        return $_
                    }
                    catch {
                        # Subdomain doesn't resolve, skip it
                        return $null
                    }
                } -ThrottleLimit $SubdomainThrottleLimit | Where-Object { $_ -ne $null }
                
                $DomainsToQuery += $ValidSubdomains
                Write-Host " ✅ Found $($ValidSubdomains.Count) valid subdomains" -ForegroundColor Green
            }
            
            foreach ($RecordType in $RecordTypes) {
                Write-Host " 🔍 Querying $RecordType records..." -ForegroundColor Yellow
                
                # Query each domain (root + valid subdomains)
                foreach ($QueryDomain in $DomainsToQuery) {
                    # Skip subdomain enumeration for non-CNAME record types on subdomains
                    if ($QueryDomain -ne $Domain -and $RecordType -ne "CNAME") {
                        continue
                    }
                    
                    if ($DNSInfoLevel -eq "Detailed" -and $QueryDomain -ne $Domain) {
                        Write-Host " 🔍 Querying $RecordType for subdomain: $QueryDomain" -ForegroundColor Gray
                    }
                    
                    foreach ($ProviderName in $ProviderNames) {
                        $Provider = $DNSProviders[$ProviderName]
                        
                        if ($DNSInfoLevel -eq "Detailed") {
                            Write-Host " 📡 Using $ProviderName ($($Provider.Reliability)%)" -ForegroundColor Cyan
                        }
                        
                        try {
                            $Stats.TotalQueries++
                            
                            # JSON DNS query
                            $QueryUrl = "$($Provider.URL)?name=$QueryDomain&type=$RecordType"
                            $Headers = @{ 
                                "Accept" = "application/dns-json"
                                "User-Agent" = ($UserAgents | Get-Random)
                            }
                            
                            $Response = Invoke-RestMethod -Uri $QueryUrl -Headers $Headers -TimeoutSec $Config.Timeout -ErrorAction Stop
                            
                            # Debug output for CNAME queries
                            if ($DNSInfoLevel -eq "Detailed" -and $RecordType -eq "CNAME") {
                                Write-Host " 🔍 Query URL: $QueryUrl" -ForegroundColor Gray
                                Write-Host " 🔍 Response has $($Response.Answer.Count) answers" -ForegroundColor Gray
                            }
                            
                            # ... existing code for processing Response.Answer ...
                            
                            # For CNAME queries, also try with DO (DNSSEC OK) bit to get unfiltered responses
                            if ($RecordType -eq "CNAME" -and -not $Response.Answer) {
                                try {
                                    $DnssecQueryUrl = "$($Provider.URL)?name=$QueryDomain&type=$RecordType&do=true"
                                    $DnssecResponse = Invoke-RestMethod -Uri $DnssecQueryUrl -Headers $Headers -TimeoutSec $Config.Timeout -ErrorAction Stop
                                    
                                    if ($DnssecResponse.Answer) {
                                        $Response = $DnssecResponse
                                        if ($DNSInfoLevel -eq "Detailed") {
                                            Write-Host " 🔍 DNSSEC query returned $($Response.Answer.Count) answers" -ForegroundColor Gray
                                        }
                                    }
                                } catch {
                                    # DNSSEC query failed, continue with original response
                                    if ($DNSInfoLevel -eq "Detailed") {
                                        Write-Host " 🔍 DNSSEC query failed: $($_.Exception.Message)" -ForegroundColor Gray
                                    }
                                }
                                
                                # If still no CNAME found, try querying for A records to detect proxied CNAMEs
                                if (-not $Response.Answer) {
                                    try {
                                        $AQueryUrl = "$($Provider.URL)?name=$QueryDomain&type=A"
                                        $AResponse = Invoke-RestMethod -Uri $AQueryUrl -Headers $Headers -TimeoutSec $Config.Timeout -ErrorAction Stop
                                        
                                        if ($AResponse.Answer) {
                                            # Check if the A records point to CDN IPs (indicating CNAME flattening/proxying)
                                            $CDNRecords = $AResponse.Answer | Where-Object { 
                                                $_.type -eq 1 -and (
                                                    # Cloudflare IP ranges (more comprehensive)
                                                    ($_.data -match '^(104\.1[6-9]|104\.2[0-7]|104\.21|104\.22|104\.23|104\.24|104\.25|104\.26|104\.27|104\.28|104\.29|104\.30|104\.31|108\.162|141\.101|162\.15[8-9]|172\.6[4-7]|172\.64|172\.65|172\.66|172\.67|172\.68|172\.69|172\.70|172\.71|173\.245|188\.114|190\.93|197\.234|198\.41|131\.0|203\.28)\.') -or
                                                    # AWS CloudFront
                                                    ($_.data -match '^(13\.32|13\.35|52\.8[4-5]|54\.23[0-9]|99\.8[4-6]|143\.204|205\.251)\.') -or
                                                    # Fastly
                                                    ($_.data -match '^(23\.235|151\.101)\.') -or
                                                    # KeyCDN
                                                    ($_.data -match '^(95\.85|104\.16)\.') -or
                                                    # MaxCDN/StackPath
                                                    ($_.data -match '^(66\.254|68\.232)\.')
                                                )
                                            }
                                            
                                            if ($CDNRecords) {
                                                $Response = $AResponse
                                                if ($DNSInfoLevel -eq "Detailed") {
                                                    Write-Host " 🔍 A record query returned $($CDNRecords.Count) CDN records (likely flattened CNAME)" -ForegroundColor Gray
                                                }
                                            }
                                        }
                                    } catch {
                                        if ($DNSInfoLevel -eq "Detailed") {
                                            Write-Host " 🔍 A record fallback query failed: $($_.Exception.Message)" -ForegroundColor Gray
                                        }
                                    }
                                }
                            }
                            
                            if ($Response.Answer) {
                                $FoundRecord = $false
                                
                                foreach ($Answer in $Response.Answer) {
                                    # More flexible record type matching - check both by type number and handle edge cases
                                    $ExpectedType = (@{"A"=1;"AAAA"=28;"CNAME"=5;"MX"=15;"NS"=2;"TXT"=16;"SOA"=6;"PTR"=12}[$RecordType])
                                    
                                    # Debug output for CNAME queries when in detailed mode
                                    if ($DNSInfoLevel -eq "Detailed" -and $RecordType -eq "CNAME") {
                                        Write-Host " 🔍 Answer type: $($Answer.type), Expected: $ExpectedType, Data: $($Answer.data)" -ForegroundColor Gray
                                    }
                                    
                                    # For CNAME queries, check if this is actually a CNAME record
                                    if ($RecordType -eq "CNAME" -and $Answer.type -eq 5) {
                                        $null = $Results.Add([PSCustomObject]@{
                                            Domain = $QueryDomain
                                            RecordType = $RecordType
                                            Data = $Answer.data
                                            TTL = $Answer.TTL
                                            DNSProvider = $ProviderName
                                            ProviderType = $Provider.Type
                                            ProviderRegion = $Provider.Region
                                            Reliability = $Provider.Reliability
                                            Timestamp = Get-Date
                                            QueryMethod = "DoH-JSON"
                                            IsSubdomain = ($QueryDomain -ne $Domain)
                                        })
                                        
                                        $Stats.SuccessfulQueries++
                                        $DisplayDomain = if ($QueryDomain -ne $Domain) { $QueryDomain } else { $QueryDomain }
                                        Write-Host " ✅ $DisplayDomain -> $($Answer.data) (TTL: $($Answer.TTL))" -ForegroundColor Green
                                        $FoundRecord = $true
                                    }
                                    # For non-CNAME queries or exact type matches
                                    elseif ($Answer.type -eq $ExpectedType) {
                                        $null = $Results.Add([PSCustomObject]@{
                                            Domain = $QueryDomain
                                            RecordType = $RecordType
                                            Data = $Answer.data
                                            TTL = $Answer.TTL
                                            DNSProvider = $ProviderName
                                            ProviderType = $Provider.Type
                                            ProviderRegion = $Provider.Region
                                            Reliability = $Provider.Reliability
                                            Timestamp = Get-Date
                                            QueryMethod = "DoH-JSON"
                                            IsSubdomain = ($QueryDomain -ne $Domain)
                                        })
                                        
                                        $Stats.SuccessfulQueries++
                                        $DisplayDomain = if ($QueryDomain -ne $Domain) { $QueryDomain } else { $QueryDomain }
                                        Write-Host " ✅ $DisplayDomain -> $($Answer.data) (TTL: $($Answer.TTL))" -ForegroundColor Green
                                        $FoundRecord = $true
                                    }
                                }
                                
                                # Special handling for CNAME queries that might be resolved to final A/AAAA records
                                if ($RecordType -eq "CNAME" -and -not $FoundRecord) {
                                    # Check if we got A or AAAA records when querying for CNAME (indicates proxied/resolved CNAME)
                                    $ProxiedRecords = $Response.Answer | Where-Object { $_.type -eq 1 -or $_.type -eq 28 }
                                    if ($ProxiedRecords) {
                                        foreach ($ProxiedRecord in $ProxiedRecords) {
                                            # Try to detect if this is likely a proxied CNAME by checking common CDN patterns
                                            $IsLikelyProxied = $false
                                            $ProxyService = "Unknown"
                                            
                                            # Comprehensive Cloudflare IP ranges
                                            if ($ProxiedRecord.data -match '^(104\.1[6-9]|104\.2[0-7]|104\.21|104\.22|104\.23|104\.24|104\.25|104\.26|104\.27|104\.28|104\.29|104\.30|104\.31|108\.162|141\.101|162\.15[8-9]|172\.6[4-7]|172\.64|172\.65|172\.66|172\.67|172\.68|172\.69|172\.70|172\.71|173\.245|188\.114|190\.93|197\.234|198\.41|131\.0|203\.28)\.') {
                                                $IsLikelyProxied = $true
                                                $ProxyService = "Cloudflare"
                                            }
                                            # AWS CloudFront patterns
                                            elseif ($ProxiedRecord.data -match '^(13\.32|13\.35|52\.8[4-5]|54\.23[0-9]|99\.8[4-6]|143\.204|205\.251)\.') {
                                                $IsLikelyProxied = $true
                                                $ProxyService = "AWS CloudFront"
                                            }
                                            # Fastly CDN
                                            elseif ($ProxiedRecord.data -match '^(23\.235|151\.101)\.') {
                                                $IsLikelyProxied = $true
                                                $ProxyService = "Fastly"
                                            }
                                            # Common other CDN patterns can be added here
                                            
                                            if ($IsLikelyProxied) {
                                                $null = $Results.Add([PSCustomObject]@{
                                                    Domain = $QueryDomain
                                                    RecordType = "CNAME (Flattened)"
                                                    Data = "$($ProxiedRecord.data) [$ProxyService Flattened/Proxied]"
                                                    TTL = $ProxiedRecord.TTL
                                                    DNSProvider = $ProviderName
                                                    ProviderType = $Provider.Type
                                                    ProviderRegion = $Provider.Region
                                                    Reliability = $Provider.Reliability
                                                    Timestamp = Get-Date
                                                    QueryMethod = "DoH-JSON"
                                                    IsSubdomain = ($QueryDomain -ne $Domain)
                                                })
                                                
                                                $Stats.SuccessfulQueries++
                                                $DisplayDomain = if ($QueryDomain -ne $Domain) { $QueryDomain } else { $QueryDomain }
                                                Write-Host " 🔶 $DisplayDomain -> $($ProxiedRecord.data) [$ProxyService Flattened CNAME] (TTL: $($ProxiedRecord.TTL))" -ForegroundColor DarkYellow
                                                $FoundRecord = $true
                                            }
                                        }
                                    }
                                }
                            } else {
                                # Handle case where no records are found
                                if ($DNSInfoLevel -ne "Minimal" -and $QueryDomain -eq $Domain) {
                                        Write-Host " ℹ️ No $RecordType records found" -ForegroundColor Gray
                                }
                            }
                            
                            # Add delay between requests
                            if ($Config.MaxDelay -gt 0) {
                                $Delay = Get-Random -Minimum $Config.MinDelay -Maximum $Config.MaxDelay
                                Start-Sleep -Seconds $Delay
                            }
                            
                            # Break after successful response (whether records found or authoritative no-records)
                            # Only continue to next provider on actual HTTP/network failures
                            break  # Success - got authoritative response from DNS provider
                        }
                        catch {
                            $Stats.FailedQueries++
                            
                            if ($DNSInfoLevel -eq "Detailed") {
                                Write-Host " ❌ Query failed: $($_.Exception.Message)" -ForegroundColor Red
                            }
                        }
                    }
                }
            }
        }
    }

    end {
        $Duration = (Get-Date) - $Stats.StartTime
        
        Write-Host "`n📊 Reconnaissance Summary:" -ForegroundColor Magenta
        Write-Host " Total Queries: $($Stats.TotalQueries)" -ForegroundColor White
        Write-Host " Successful: $($Stats.SuccessfulQueries)" -ForegroundColor Green
        Write-Host " Failed: $($Stats.FailedQueries)" -ForegroundColor Red
        Write-Host " Duration: $($Duration.TotalSeconds.ToString('F2')) seconds" -ForegroundColor White
        Write-Host " Records Found: $($Results.Count)" -ForegroundColor Yellow
        if ($EnumerateSubdomains) {
            $SubdomainResults = $Results | Where-Object { $_.IsSubdomain -eq $true }
            Write-Host " Subdomain Records: $($SubdomainResults.Count)" -ForegroundColor Cyan
        }

        # Return results in requested format
        switch ($OutputFormat) {
            "JSON" { return $Results | ConvertTo-Json -Depth 3 }
            "CSV" { return $Results | ConvertTo-CSV }
            "Object" { return $Results }
            "Table"  { return $Results | Format-Table -AutoSize }
        }
    }
}