BlackCat.psm1


#region PowerShell version check
# Ensure the module runs only in PowerShell 7+
$minPSVersion = [Version]'7.0'
if ($PSVersionTable.PSVersion -lt $minPSVersion) {
    throw "BlackCat module requires PowerShell 7.0 or higher. Current version: $($PSVersionTable.PSVersion)"
    # Exit the script
    return
}
#endregion PowerShell version check

#region load module variables
Write-Verbose -Message "Creating modules variables"

#region Handle Module Removal
$OnRemoveScript = {
    Remove-Variable -Name SessionVariables -Scope Script -Force
    Remove-Variable -Name Guid -Scope Script -Force
}

$ExecutionContext.SessionState.Module.OnRemove += $OnRemoveScript
Register-EngineEvent -SourceIdentifier ([System.Management.Automation.PsEngineEvent]::Exiting) -Action $OnRemoveScript
#endregion Handle Module Removal

#region discover module name
$ModuleName = $ExecutionContext.SessionState.Module
Write-Verbose -Message "Loading module $ModuleName"
#endregion discover module name

if (-not (Get-Module -ListAvailable -Name Az.Accounts)) {
    Write-Verbose -Message "Az.Accounts module not found. Installing the latest version..."
    Install-Module -Name Az.Accounts -Force -Scope CurrentUser
}

# Import private and public scripts and expose the public ones
$privateScripts = @(Get-ChildItem -Path "$PSScriptRoot\Private" -Recurse -Filter "*.ps1" | Sort-Object Name )
$publicScripts = @(Get-ChildItem -Path "$PSScriptRoot\Public" -Recurse -Filter "*.ps1" | Sort-Object Name )

foreach ($script in @($privateScripts + $publicScripts)) {
    Import-Module $script
    try {
        . $script.FullName
        Write-Verbose -Message ("Imported function {0}" -f $script)
    }
    catch {
        Write-Error -Message ("Failed to import function {0}: {1}" -f $script, $_)
    }
}

Export-ModuleMember -Function $publicScripts.BaseName

$helperPath = "$PSScriptRoot/Private/Reference"
# Ensure the Private/Reference directory exists
if (-not(Test-Path -Path $helperPath)) {
    Write-Verbose -Message "Creating Private/Reference directory"
    New-Item -Path $helperPath -ItemType Directory -Force | Out-Null

    Write-Verbose -Message "Fetching latest helper files for first-time setup"
    # We need to wait until all module functions are loaded before calling Invoke-Update
    # This will be done in the Initialize block below
} elseif (-not(Get-ChildItem -Path $helperPath -ErrorAction SilentlyContinue)) {
    Write-Verbose -Message "Private/Reference directory exists but is empty. Fetching helper files"
    # This will be done in the Initialize block below
}

[System.Diagnostics.CodeAnalysis.SuppressMessage('PSUseDeclaredVarsMoreThanAssigments', '')]
$script:SessionVariables = [ordered]@{
    baseUri          = '';
    graphUri         = 'https://graph.microsoft.com/beta';
    batchUri         = 'https://management.azure.com/batch?api-version=2020-06-01';
    resourceGraphUri = 'https://management.azure.com/providers/Microsoft.ResourceGraph/resources?api-version=2022-10-01';
    ExpiresOn        = '';
    apiVersion       = '2023-06-01-preview';
    AccessToken      = '';
    UserAgent        = '';
    Roles            = if (Test-Path $helperPath\EntraRoles.csv) { Get-Content -Path $helperPath\EntraRoles.csv | ConvertFrom-Csv };
    AzureRoles       = if (Test-Path $helperPath\AzureRoles.csv) { Get-Content -Path $helperPath\AzureRoles.csv | ConvertFrom-Csv };
    serviceTags      = if (Test-Path $helperPath\ServiceTags.json) { Get-Content -Path $helperPath\ServiceTags.json | ConvertFrom-Json };
    appRoleIds       = if (Test-Path $helperPath\appRoleIds.csv) { Get-Content -Path $helperPath\appRoleIds.csv | ConvertFrom-Csv };
    permutations     = if (Test-Path $helperPath\permutations.txt) { Get-Content -Path $helperPath\permutations.txt };
    subdomains       = if (Test-Path $helperPath\subdomains.json) { Get-Content -Path $helperPath\subdomains.json | ConvertFrom-Json -AsHashtable};
    userAgents       = if (Test-Path $helperPath\userAgents.json) { Get-Content -Path $helperPath\userAgents.json | ConvertFrom-Json };
    privilegedRoles  = if (Test-Path $helperPath\privileged-roles.json) { Get-Content -Path $helperPath\privileged-roles.json | ConvertFrom-Json };

    # User agent rotation tracking
    CurrentUserAgent        = $null;
    UserAgentLastChanged    = $null;
    UserAgentRequestCount   = 0;
    UserAgentRotationInterval = [TimeSpan]::FromMinutes(30);
    MaxRequestsPerAgent     = 50;
    default          = 'N2gzQmw0Y2tDNDdXNDVIM3IzNG5kMTVOMDdQbDRubjFuZzcwTDM0djM==';
}

New-Variable -Name Guid -Value (New-Guid).Guid -Scope Script -Force
New-Variable -Name SessionVariables -Value $SessionVariables -Scope Script -Force

$manifest = Import-PowerShellDataFile "$PSScriptRoot\BlackCat.psd1"
$version = $manifest.ModuleVersion

# Check for updates
try {
    $latestVersionUrl = "https://raw.githubusercontent.com/azurekid/blackcat/refs/heads/main/BlackCat.psd1"
    $latestManifestContent = Invoke-RestMethod -Uri $latestVersionUrl -UseBasicParsing
    $latestVersionLine = $latestManifestContent -split "`n" | Where-Object { $_ -match 'ModuleVersion' }
    $latestVersion = ($latestVersionLine -split '=' | Select-Object -Last 1).Trim().Trim("'")

    if ($latestVersion -gt $version) {
        $updateMessage = "A newer version of the module ($latestVersion) is available."
    } else {
        $updateMessage = " v$version by Rogier Dijkman"
    }
}
catch {
    Write-Verbose -Message "Failed to check for module updates: $_"
}

#Disable Messages and WAM
Write-Verbose -Message "Disabling LoginExperienceV2..."
Update-AzConfig -LoginExperienceV2 Off

Write-Verbose -Message "Disabling BreakingChangeWarning..."
Update-AzConfig -DisplayBreakingChangeWarning $false
# Clear-Host

# Set the window title
try {
    $host.UI.RawUI.WindowTitle = "BlackCat $version"
}
catch {}

$logo = `
    @"
 
 
     __ ) ___ | | | | ___| __ \ |
     __ \ / | | __| | / | / _` | __|
     | | / ___ __| ( < | | ( | |
    ____/ _/ _| \___| _|\_\ \____| \ \__,_| \__|
                                             \____/
 
    $updateMessage
 
"@


Write-Host $logo -ForegroundColor Blue