public-functions.ps1

# import other modules
. $PSScriptRoot\gundoghunt-alertpresentation.ps1
. $PSScriptRoot\hunting-functions.ps1

# main function to start gundog
function Start-Gundog {

    if($config.allTenants[0].tenantID -eq "tenantID")
    {
        Read-HostCustom -prompt "Your tenant configuration is empty like the desserts on Tatooine, Boba. Press enter to get to main menu, then choose 'Configuration' >>> "
    }
    displayHeader -menuText "1 All alerts by Tenant `n2 Gundog Hunting by AlertId `n3 Vulnerabilities `n4 All Alerts (med/high) last 24h `n5 All Alerts (med/high) last 24h (Refresh) `n6 Plain KQL Hunting `n7 Hunt from GitHub Repo `n8 Configuration" 
    #$menuChoice = Read-Host "Hunt for"
    $menuChoice = Read-HostCustom -Prompt "Hunt for >>> "

    if($menuChoice -eq 1)
    {
        Get-AllAlertsByTenant
    }
    # gundog hunt
    if($menuChoice -eq 2)
    {
        Get-GunDogHunt
    }
    # all tenants gundog hunt
    if($menuChoice -eq 3)
    {
        Get-Vulnerabilities
    }
    # all tenants all medium & high alerts MDE
    if($menuChoice -eq 4)
    {
        Get-AllAlertsAllTenants
    }
    # all tenants all medium & high alerts MDE - reload every
    if($menuChoice -eq 5)
    {
        Get-AllAlertsAllTenantsRefresh
    }
    # plain kql hunting
    if($menuChoice -eq 6)
    {
        Get-MultiTenantHunting
    }
    # hunt from repo
    if($menuChoice -eq 7)
    {
        Get-MultiTenantHunting -kqlFktInput "github"
    }
    # set config
    if($menuChoice -eq 8)
    {
        Set-GundogConfig
    }

    $menuChoice = ""
}
# fire up notepad to configure gundog.config
function Set-GundogConfig {
    $configFileAndPath = "$PSScriptRoot\gundog.config"
    Start-Process "notepad.exe" $configFileAndPath
}
# get vulnerabilities for one, multiple or all tenants
function Get-Vulnerabilities {

    Get-MultiTenantHunting -headline "Critical vulnerabilities from the last 30 days" -kqlFktInput "
    let newVuln =
    DeviceTvmSoftwareVulnerabilitiesKB
    | where VulnerabilitySeverityLevel == 'Critical'
    | where LastModifiedTime >ago(30day);
    DeviceTvmSoftwareVulnerabilities
    | join newVuln on CveId
    | summarize dcount(DeviceId) by DeviceName, DeviceId, Timestamp=LastModifiedTime, SoftwareName, SoftwareVendor, SoftwareVersion, VulnerabilitySeverityLevel, CvssScore, IsExploitAvailable, CveId
    | project Timestamp, CveId, SoftwareName, SoftwareVendor, SoftwareVersion, VulnerabilitySeverityLevel, CvssScore, IsExploitAvailable, DeviceId
    "

}
# all (med/high) alerts from all tenants refresh
function Get-AllAlertsAllTenantsRefresh {
        $allTenants = get-TenantList
        write-host
        write-host Hunting ... -ForegroundColor green
        write-host
        do
        {
            $global:allTenantResults = New-Object System.Collections.ArrayList 
            $kql = "AlertInfo | where Timestamp > ago(1d) | where Severity == 'Medium' or Severity == 'High' | where ServiceSource == 'Microsoft Defender for Endpoint' | join AlertEvidence on AlertId | summarize count() by AlertId, Timestamp, Title, Severity | order by Timestamp | project-away count_"
            foreach($Tenant in $allTenants)
            {
                $singleTenantResult = get-huntingResultMTP -tenantId $Tenant.TenantId -clientID $clientID -clientSecret $clientSecret -kql $kql 
                if($null -ne $singleTenantResult)
                {
                    $temp = $singleTenantResult | ft @{Name="Timestamp";expression={if($_.timestamp -gt (get-date).addhours(-2)){$color="33"}else{if($_.Severity -eq "High"){$color="31"}};$e = [char]27;"$e[${color}m$($_.Timestamp)${e}[0m"}}, @{Name="Tenant";expression={$Tenant.name.substring(0,3).toupper()}}, Timestamp, Title, AlertId, Severity
                    $allTenantResults.add($temp) | Out-Null
                }
            }
            if(!$config.globalVars.debugOn)
            {
                Clear-Host
            }
            Write-Host ------------------------------------------------------------------------------------------------------------------------- -ForegroundColor green
            write-host "All high & medium alerts from all Tenants last 24h " -ForegroundColor green
            Write-Host ------------------------------------------------------------------------------------------------------------------------- -ForegroundColor green
            Write-Host 
            $date = get-date
            Write-Host Last Update: $date -ForegroundColor green
            $allTenantResults
            Start-Sleep -Seconds 3600
        } #dont stop me now
        while (1 -eq 1) {
            
        }
}
# kql hunting from github
function Get-KQLFromGitHub {

        $gitHubRawUrl = Read-HostCustom -Prompt "GitHub Repo >>> "
        $repoName = $gitHubRawUrl.Replace("https://github.com/","")
        $gitDir = Invoke-WebRequest $gitHubRawUrl                                                                                               
        $global:gitRules = ($gitDir.Links.outerhtml | ?{$_ -like "*$repoName/blob/master*"} | %{[regex]::match($_,'master.*"').Value}).Replace('"',"") | %{if($_ -ne ""){"https://raw.githubusercontent.com/$repoName/" + $_}}
        Write-Host
        write-host "Found those queries:" -ForegroundColor Green
        write-host
        $counter = 0

        foreach($rawLink in $gitRules)
        {
            $counter += 1
            $tempLine = "https://raw.githubusercontent.com/$repoName" + "/master/"
            write-host $counter $rawLink.Replace($tempLine,"").Replace("%20"," ")
        }
        write-host
        [int]$queryNumber= Read-HostCustom -Prompt "Run query >>> "
        $queryNumberIndex = $queryNumber - 1
        $kql = Invoke-WebRequest $gitRules[$queryNumberIndex]
        return $kql
}
# all alerts (med/high) from all tenants (once)
function Get-AllAlertsAllTenants {

    $allTenantResults = New-Object System.Collections.ArrayList 
    $allTenants = get-TenantList
    write-host
    write-host Hunting ... -ForegroundColor green
    write-host
    $kql = "AlertInfo | where Timestamp > ago(1d) | where Severity == 'Medium' or Severity == 'High' | where ServiceSource == 'Microsoft Defender for Endpoint' | join AlertEvidence on AlertId | summarize count() by AlertId, Timestamp, Title, Severity | order by Timestamp | project-away count_"
    foreach($tenant in $allTenants)
    {
        $singleTenantResult = get-huntingResultMTP -tenantId $tenant.TenantId -clientID $clientID -clientSecret $clientSecret -kql $kql
        if($null -ne $singleTenantResult)
        {
            $temp = $singleTenantResult | ft @{Name="Tenant";expression={$tenant.name.substring(0,3).toupper()}}, Timestamp, Title, AlertId, Severity
            $allTenantResults.add($temp) | Out-Null
        }
    }
    if(!$config.globalVars.debugOn)
    {
        Clear-Host
    }
    Write-Host ------------------------------------------------------------------------------------------------------------------------- -ForegroundColor green
    write-host "All high & medium alerts from all Tenants last 24h " -ForegroundColor green
    Write-Host ------------------------------------------------------------------------------------------------------------------------- -ForegroundColor green
    Write-Host
    Write-Host
    $allTenantResults
    $allIncidents = $null
}
# receives list of tenant numbers then does the kql hunting against those tenants
function Get-MultiTenantHunting {
    param (
        [string]$kqlFktInput,
        [string]$headline,
        [string]$tenantNumbers
    )
    if($headline -eq "")
    {
        $headline = "Hunting Results from your custom KQL Query:"
    }
    if($tenantNumbers -eq "")
    {
        $allTenants = get-TenantList
        $menuTextTenants = get-menuTextTenants
        displayHeader -menuText $menuTextTenants 
        [string]$tenantNumbers = Read-HostCustom -Prompt "Hunt in those tenants (Tenant Numbers Comma Seperated, * for all) >>> "
    }
    if($kqlFktInput -eq "")
    {
        $kqlInput = Read-HostCustom -Prompt "Type your query here or copy it to clipboard, then press Enter >>> "
        if($kqlInput -eq "")
        {
            $kql = Get-Clipboard
        }
        else {
            $kql = $kqlInput
        }
    }
    else {
        if($kqlFktInput -eq 'github')
        {
            $kql = Get-KQLFromGitHub
        }
        else {
            $kql = $kqlFktInput   
        }
    }
    write-host Hunting ... -ForegroundColor red
    if(!$config.globalVars.debugOn)
    {
        Clear-Host
    }
    
    if($tenantNumbers -eq "*")
    {
        $allTenantResults = allTenantAction -allTenants $allTenants -kql $kql

        $x = ($allTenantResults | Get-Member | Where-Object{$_.MemberType -eq 'NoteProperty'}).name
        $global:results = $x | ForEach-Object{$t=$_;$allTenantResults.($_)} | Format-Table @{Name="Tenant";expression={$t}},*

        Write-Host ------------------------------------------------------------------------------------------------------------------------- -ForegroundColor green
        write-host "$headline (more via: `$results)" -ForegroundColor green
        Write-Host ------------------------------------------------------------------------------------------------------------------------- -ForegroundColor green

        $results
    }
    else {
            if($tenantNumbers.Contains(","))
            {
                $global:results = multiTenantAction -allTenants $allTenants -tenantNumbers $tenantNumbers -kql $kql

                Write-Host ------------------------------------------------------------------------------------------------------------------------- -ForegroundColor green
                write-host "$headline (more via: `$results)" -ForegroundColor green
                write-host ------------------------------------------------------------------------------------------------------------------------- -ForegroundColor green

                $results | Format-Table Tenant, *
            }
            else {
                $singleTenantResult = singleTenantAction -allTenants $allTenants -tenantNumber $tenantNumbers -kql $kql  
                if($null -ne $singleTenantResult)
                {
                    Write-Host ------------------------------------------------------------------------------------------------------------------------- -ForegroundColor green
                    write-host "$headline (more via: `$results)" -ForegroundColor green
                    write-host ------------------------------------------------------------------------------------------------------------------------- -ForegroundColor green
                    
                    $singleTenantResult | Format-Table
                    $global:results=$singleTenantResult
                }
                else {
                    write-host Sorry Boba, your query results are as empty as the deserts on tatooine.
                }
            }
    }
    # in case of vulnerability hunting, ask for further hunting for devices per CVE
    if($headline -eq "Critical vulnerabilities from the last 30 days")
    {
        $cveid = Read-HostCustom -Prompt "Type CveId for a list of affected devices or press enter to exit >>> "
        if($cveid -ne "")
        {
            Get-MultiTenantHunting -tenantNumbers $tenantNumbers -kqlFktInput "DeviceTvmSoftwareVulnerabilities | where CveId == '$cveid'" -headline "CVE Hunting: $cveid affected devices"
        }
    }
}
# v1 functionality - gundog hunting against alertID
function Get-GunDogHunt {
    $menuTextTenants=get-menuTextTenants
    $allTenants=get-TenantList
    displayHeader -menuText $menuTextTenants
    $tenantNumber = Read-HostCustom -Prompt "Tenant Number >>> "
    $tenantTenantNumber = $tenantNumber - 1 
    $tenantId=$allTenants[$tenantTenantNumber].TenantId
    $AlertId = Read-HostCustom -Prompt "Type AlertID >>> "
    write-host Hunting ... -ForegroundColor red
    get-alertData -AlertId $AlertId -tenantId $tenantId -clientID $clientID -clientSecret $clientSecret
    $CurrentTenant=$allTenants[$tenantTenantNumber].name
    get-alertDataResults

    if($null -eq $alert)
    {
        write-host "We couldn't get any response for the Alert ID you provided:" $AlertId  -ForegroundColor red
    }
}
# all alerts from one tenant (not only med/high)
function Get-AllAlertsByTenant {
    $menuTextTenants=get-menuTextTenants
    $allTenants=get-TenantList
    displayHeader -menuText $menuTextTenants
    $tenantNumber = Read-HostCustom -Prompt "Tenant Number >>> "
    $tenantTenantNumber = ([int]$tenantNumber - 1).ToString()
    $tenantId=$allTenants[$tenantTenantNumber].TenantId
    if($config.globalVars.debugon)
    {
        Write-Host tenantID $tenantId
    }
    $duration=Read-HostCustom -Prompt "Display alerts from last X days (default = 30) >>> "  
    $global:results = huntAllAlerts -TenantId $tenantId -searchDurationLastXDays $duration

    $CurrentTenant=$allTenants[$tenantTenantNumber].name
    if(!$config.globalVars.debugOn)
    {
        Clear-Host
    }
    if($null -ne $results)
    {
        Write-Host ------------------------------------------------------------------------------------------------------------------------- -ForegroundColor green
        write-host "All Alerts from: $CurrentTenant (more info via `$results)" -ForegroundColor green
        Write-Host ------------------------------------------------------------------------------------------------------------------------- -ForegroundColor green
        $results | Select-Object -ErrorAction SilentlyContinue | Sort-Object createdDateTime -Descending | Format-Table @{Name="Time";expression={get-date($_.createdDateTime)}}, id, title, severity,@{Name="source";expression={$_.vendorinformation.provider}}, status, description  
    }
}
#this gives back a psobject of all tenants form config
function get-TenantList {
    $gundogConfig = get-content $PSScriptRoot\gundog.config 
    $global:config = $gundogConfig | ConvertFrom-Json # add $gist.content for IWR

    $allTenants = @()
    $allTenants.Clear()
    $numberOfTenants=0

    $menuTextTenants = ""
    foreach($c in $config.allTenants)
    {
        $PSObjectAllTenants = new-object psobject
        $PSObjectAllTenants | add-member Noteproperty TenantId $c.tenantID
        $PSObjectAllTenants | add-member Noteproperty name $c.name
        $allTenants += $PSObjectAllTenants
        $numberOfTenants = $numberOfTenants + 1
        $menuTextTenants = $menuTextTenants + $numberOfTenants.ToString() + " " + $PSObjectAllTenants.name.substring(0,3).toupper() + " "
    }
    $allTenants
}
# this gives back the TEXT (string) of all tenants from config
function get-menuTextTenants {
    $gundogConfig = get-content $PSScriptRoot\gundog.config 
    $global:config = $gundogConfig | ConvertFrom-Json # add $gist.content for IWR

    $allTenants = @()
    $allTenants.Clear()
    $numberOfTenants=0

    $numberOfTenants = $null
    $menuTextTenants = ""
    foreach($c in $config.allTenants)
    {
        $PSObjectAllTenants = new-object psobject
        $PSObjectAllTenants | add-member Noteproperty TenantId $c.tenantID
        $PSObjectAllTenants | add-member Noteproperty name $c.name
        $allTenants += $PSObjectAllTenants
        $numberOfTenants = $numberOfTenants + 1
        $menuTextTenants = $menuTextTenants + $numberOfTenants.ToString() + " " + $PSObjectAllTenants.name.substring(0,3).toupper() + " "
        if($numberOfTenants % 12 -eq 0)
        {
            $menuTextTenants = $menuTextTenants + "`n`n"
        }
    }
    $menuTextTenants
}