mirror of
https://github.com/8damon/Blackbird-Platform
synced 2026-06-21 13:41:12 +00:00
774 lines
28 KiB
PowerShell
774 lines
28 KiB
PowerShell
param(
|
|
[string]$DriverName = "blackbird",
|
|
[string]$ControllerName = "BlackbirdController",
|
|
[string]$DriverSys = "..\blackbird.sys",
|
|
[string]$ControllerExe = "..\BlackbirdController.exe",
|
|
[string]$SensorCoreDll = "..\J58.dll",
|
|
[string]$InstanceName = "Blackbird Default",
|
|
[string]$InstanceAltitude = "385000.424244",
|
|
[uint32]$InstanceFlags = 0,
|
|
[Alias("av")]
|
|
[switch]$EnableAntiVirtualization,
|
|
[Alias("hide")]
|
|
[switch]$EnableControllerHiding
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$scriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
$repoRoot = (Resolve-Path (Join-Path $scriptRoot "..")).Path
|
|
$osVersion = [Environment]::OSVersion.Version
|
|
$osBuild = $osVersion.Build
|
|
|
|
if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
|
|
[Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
|
throw "Run this script as Administrator."
|
|
}
|
|
|
|
if ($osBuild -lt 19041) {
|
|
Write-Warning "OS build $osBuild detected. This driver toolchain commonly requires Windows 10 2004+ (build 19041+) for kernel exports."
|
|
}
|
|
|
|
function Write-Stage {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[int]$Index,
|
|
[Parameter(Mandatory = $true)]
|
|
[int]$Total,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Activity,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Status
|
|
)
|
|
|
|
$percent = [Math]::Floor(($Index / $Total) * 100)
|
|
Write-Progress -Id 1 -Activity $Activity -Status $Status -PercentComplete $percent
|
|
Write-Host ""
|
|
Write-Host ("[{0}/{1}] {2}" -f $Index, $Total, $Status) -ForegroundColor Cyan
|
|
}
|
|
|
|
function Write-VerboseLog {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Message
|
|
)
|
|
|
|
Write-Host $Message -ForegroundColor DarkGray
|
|
}
|
|
|
|
function Write-InfoLog {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Message
|
|
)
|
|
|
|
Write-Host ("[info] {0}" -f $Message) -ForegroundColor Gray
|
|
}
|
|
|
|
function Format-CommandArgs {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string[]]$Arguments
|
|
)
|
|
|
|
return ($Arguments | ForEach-Object {
|
|
if ($_ -match '\s') { ('"{0}"' -f $_) } else { $_ }
|
|
}) -join ' '
|
|
}
|
|
|
|
function Resolve-ArtifactPath {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$PreferredPath,
|
|
[Parameter(Mandatory = $true)]
|
|
[string[]]$FallbackPaths,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Label
|
|
)
|
|
|
|
$candidates = @($PreferredPath) + $FallbackPaths
|
|
Write-VerboseLog "Resolving $Label. Candidates: $($candidates -join ', ')"
|
|
foreach ($candidate in $candidates) {
|
|
if ([string]::IsNullOrWhiteSpace($candidate)) {
|
|
continue
|
|
}
|
|
|
|
$pathToTry = $candidate
|
|
if (-not [System.IO.Path]::IsPathRooted($pathToTry)) {
|
|
$pathToTry = Join-Path $repoRoot $pathToTry
|
|
}
|
|
|
|
Write-VerboseLog "Trying $Label candidate: $pathToTry"
|
|
$resolved = Resolve-Path -LiteralPath $pathToTry -ErrorAction SilentlyContinue
|
|
if ($null -ne $resolved) {
|
|
Write-InfoLog "Resolved $Label to $($resolved.Path)"
|
|
return $resolved.Path
|
|
}
|
|
}
|
|
|
|
throw "$Label not found. Tried: $($candidates -join ', ')"
|
|
}
|
|
|
|
function Invoke-Sc {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string[]]$Arguments,
|
|
[int[]]$AllowedExitCodes = @(0)
|
|
)
|
|
|
|
$cmdText = Format-CommandArgs -Arguments $Arguments
|
|
Write-VerboseLog "sc.exe $cmdText"
|
|
$output = & sc.exe @Arguments 2>&1
|
|
$exitCode = $LASTEXITCODE
|
|
if ($output) {
|
|
foreach ($line in $output) {
|
|
Write-VerboseLog "sc.exe> $line"
|
|
}
|
|
}
|
|
Write-VerboseLog "sc.exe exit=$exitCode allowed=$($AllowedExitCodes -join ',')"
|
|
if ($AllowedExitCodes -notcontains $exitCode) {
|
|
$detail = (($output | ForEach-Object { "$_" }) -join [Environment]::NewLine).Trim()
|
|
if ([string]::IsNullOrWhiteSpace($detail)) {
|
|
$detail = "(no output)"
|
|
}
|
|
throw "sc.exe $($Arguments -join ' ') failed with exit code $exitCode`n$detail"
|
|
}
|
|
}
|
|
|
|
function Test-ServiceExists {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ServiceName
|
|
)
|
|
|
|
Write-VerboseLog "Checking service existence: $ServiceName"
|
|
& sc.exe query $ServiceName 2>&1 | Out-Null
|
|
Write-VerboseLog "Service $ServiceName exists=$($LASTEXITCODE -eq 0)"
|
|
return $LASTEXITCODE -eq 0
|
|
}
|
|
|
|
function Wait-ServiceNameAvailable {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ServiceName,
|
|
[int]$TimeoutSeconds = 30
|
|
)
|
|
|
|
Write-VerboseLog "Waiting for service name availability: $ServiceName (timeout ${TimeoutSeconds}s)"
|
|
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
|
$attempt = 0
|
|
|
|
while ((Get-Date) -lt $deadline) {
|
|
$attempt++
|
|
& sc.exe query $ServiceName 2>&1 | Out-Null
|
|
$queryExit = $LASTEXITCODE
|
|
|
|
if ($queryExit -eq 1060) {
|
|
Write-VerboseLog "Service name available after $attempt attempt(s): $ServiceName"
|
|
return $true
|
|
}
|
|
|
|
if ($attempt -eq 1 -or ($attempt % 5) -eq 0) {
|
|
Write-VerboseLog "Service name still unavailable on attempt ${attempt}: $ServiceName (sc exit $queryExit)"
|
|
}
|
|
|
|
Start-Sleep -Milliseconds 500
|
|
}
|
|
|
|
Write-VerboseLog "Service name did not become available in time: $ServiceName"
|
|
return $false
|
|
}
|
|
|
|
function New-ServiceWithRetry {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ServiceName,
|
|
[Parameter(Mandatory = $true)]
|
|
[string[]]$CreateArguments,
|
|
[int]$TimeoutSeconds = 30
|
|
)
|
|
|
|
if (-not (Wait-ServiceNameAvailable -ServiceName $ServiceName -TimeoutSeconds $TimeoutSeconds)) {
|
|
throw "Service '$ServiceName' is still present or marked for deletion. Close Services.msc or other tools holding service handles, then rerun installer."
|
|
}
|
|
|
|
Invoke-Sc -Arguments $CreateArguments
|
|
}
|
|
|
|
function Wait-UntilFileUnlocked {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Path,
|
|
[int]$TimeoutSeconds = 20
|
|
)
|
|
|
|
if (-not (Test-Path -LiteralPath $Path)) {
|
|
Write-VerboseLog "Unlock check skipped; file not present: $Path"
|
|
return $true
|
|
}
|
|
|
|
Write-VerboseLog "Waiting for file unlock: $Path (timeout ${TimeoutSeconds}s)"
|
|
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
|
$attempt = 0
|
|
while ((Get-Date) -lt $deadline) {
|
|
$attempt++
|
|
try {
|
|
$stream = [System.IO.File]::Open($Path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
|
|
$stream.Close()
|
|
Write-VerboseLog "File unlocked after $attempt attempt(s): $Path"
|
|
return $true
|
|
}
|
|
catch {
|
|
if ($attempt -eq 1 -or ($attempt % 5) -eq 0) {
|
|
Write-VerboseLog "File still locked on attempt ${attempt}: $Path"
|
|
}
|
|
Start-Sleep -Milliseconds 300
|
|
}
|
|
}
|
|
|
|
Write-VerboseLog "Unlock wait timed out: $Path"
|
|
return $false
|
|
}
|
|
|
|
function Assert-PathExists {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Path,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Label
|
|
)
|
|
|
|
if (-not (Test-Path -LiteralPath $Path)) {
|
|
throw "$Label missing: $Path"
|
|
}
|
|
}
|
|
|
|
function Ensure-MinifilterRegistry {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ServiceName,
|
|
[string]$InstanceName = "Blackbird Default",
|
|
[string]$Altitude = "385000.424244",
|
|
[uint32]$InstanceFlags = 0
|
|
)
|
|
|
|
$serviceKey = "HKLM:\SYSTEM\CurrentControlSet\Services\$ServiceName"
|
|
$instancesKey = Join-Path $serviceKey "Instances"
|
|
$instanceKey = Join-Path $instancesKey $InstanceName
|
|
|
|
Write-VerboseLog "Ensuring minifilter registry for $ServiceName at altitude $Altitude"
|
|
if (-not (Test-Path -LiteralPath $serviceKey)) {
|
|
throw "Service key '$serviceKey' is missing after sc.exe create. Aborting to avoid partial minifilter setup."
|
|
}
|
|
if (-not (Test-Path -LiteralPath $instancesKey)) {
|
|
New-Item -Path $instancesKey | Out-Null
|
|
}
|
|
if (-not (Test-Path -LiteralPath $instanceKey)) {
|
|
New-Item -Path $instanceKey | Out-Null
|
|
}
|
|
|
|
New-ItemProperty -Path $serviceKey -Name "DebugFlags" -PropertyType DWord -Value 0 -Force | Out-Null
|
|
New-ItemProperty -Path $instancesKey -Name "DefaultInstance" -PropertyType String -Value $InstanceName -Force | Out-Null
|
|
New-ItemProperty -Path $instanceKey -Name "Altitude" -PropertyType String -Value $Altitude -Force | Out-Null
|
|
New-ItemProperty -Path $instanceKey -Name "Flags" -PropertyType DWord -Value $InstanceFlags -Force | Out-Null
|
|
}
|
|
|
|
function Set-DriverRuntimeDefaults {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ServiceName,
|
|
[Parameter(Mandatory = $true)]
|
|
[bool]$EnableAntiVirtualization,
|
|
[Parameter(Mandatory = $true)]
|
|
[bool]$EnableSelfHide
|
|
)
|
|
|
|
$serviceKey = "HKLM:\SYSTEM\CurrentControlSet\Services\$ServiceName"
|
|
$parametersKey = Join-Path $serviceKey "Parameters"
|
|
|
|
if (-not (Test-Path -LiteralPath $serviceKey)) {
|
|
throw "Service key '$serviceKey' is missing after driver creation."
|
|
}
|
|
if (-not (Test-Path -LiteralPath $parametersKey)) {
|
|
New-Item -Path $parametersKey | Out-Null
|
|
}
|
|
|
|
New-ItemProperty -Path $parametersKey -Name "EnableAntiVirtualization" -PropertyType DWord -Value ([int]$EnableAntiVirtualization) -Force | Out-Null
|
|
New-ItemProperty -Path $parametersKey -Name "EnableSelfHide" -PropertyType DWord -Value ([int]$EnableSelfHide) -Force | Out-Null
|
|
Write-InfoLog ("Driver runtime defaults: av={0} hide={1}" -f ([int]$EnableAntiVirtualization), ([int]$EnableSelfHide))
|
|
}
|
|
function Get-ServicesUsingMinifilterAltitude {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Altitude,
|
|
[string]$ExcludeServiceName = ""
|
|
)
|
|
|
|
$servicesRoot = "HKLM:\SYSTEM\CurrentControlSet\Services"
|
|
$matches = New-Object System.Collections.Generic.List[string]
|
|
|
|
Write-VerboseLog "Scanning services for altitude conflicts at $Altitude"
|
|
foreach ($serviceKey in (Get-ChildItem -Path $servicesRoot -ErrorAction SilentlyContinue)) {
|
|
$serviceName = $serviceKey.PSChildName
|
|
if ([string]::IsNullOrWhiteSpace($serviceName)) {
|
|
continue
|
|
}
|
|
if (-not [string]::IsNullOrWhiteSpace($ExcludeServiceName) -and
|
|
$serviceName.Equals($ExcludeServiceName, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
continue
|
|
}
|
|
|
|
$instancesPath = Join-Path $serviceKey.PSPath "Instances"
|
|
if (-not (Test-Path -LiteralPath $instancesPath)) {
|
|
continue
|
|
}
|
|
|
|
$instanceKeys = Get-ChildItem -Path $instancesPath -ErrorAction SilentlyContinue
|
|
foreach ($instanceKey in $instanceKeys) {
|
|
$props = Get-ItemProperty -LiteralPath $instanceKey.PSPath -ErrorAction SilentlyContinue
|
|
if ($null -eq $props) {
|
|
continue
|
|
}
|
|
if (-not ($props.PSObject.Properties.Name -contains "Altitude")) {
|
|
continue
|
|
}
|
|
if ([string]$props.Altitude -eq $Altitude) {
|
|
[void]$matches.Add($serviceName)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return $matches.ToArray() | Sort-Object -Unique
|
|
}
|
|
|
|
function Remove-MinifilterServiceArtifacts {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ServiceName
|
|
)
|
|
|
|
if ([string]::IsNullOrWhiteSpace($ServiceName)) {
|
|
return
|
|
}
|
|
|
|
Write-InfoLog "Removing stale minifilter artifacts for $ServiceName"
|
|
Invoke-Sc -Arguments @("stop", $ServiceName) -AllowedExitCodes @(0, 5, 1060, 1062)
|
|
Write-VerboseLog "fltmc.exe unload $ServiceName"
|
|
$fltOut = & fltmc.exe unload $ServiceName 2>&1
|
|
if ($fltOut) {
|
|
foreach ($line in $fltOut) {
|
|
Write-VerboseLog "fltmc> $line"
|
|
}
|
|
}
|
|
Write-VerboseLog "fltmc unload exit=$LASTEXITCODE"
|
|
if ($LASTEXITCODE -ne 0) {
|
|
# ignore unload failures; service delete + registry cleanup is enough for stale entries
|
|
$null = $fltOut
|
|
}
|
|
Invoke-Sc -Arguments @("delete", $ServiceName) -AllowedExitCodes @(0, 1060, 1072)
|
|
|
|
$serviceKey = "HKLM:\SYSTEM\CurrentControlSet\Services\$ServiceName"
|
|
if (Test-Path -LiteralPath $serviceKey) {
|
|
Remove-Item -LiteralPath $serviceKey -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
function Remove-ProjectAltitudeConflicts {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Altitude,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$CurrentServiceName,
|
|
[Parameter(Mandatory = $true)]
|
|
[string[]]$ProjectServiceNames
|
|
)
|
|
|
|
$conflicts = @(Get-ServicesUsingMinifilterAltitude -Altitude $Altitude -ExcludeServiceName $CurrentServiceName)
|
|
if ($conflicts.Count -eq 0) {
|
|
return @()
|
|
}
|
|
|
|
$projectLookup = @{}
|
|
foreach ($name in $ProjectServiceNames) {
|
|
if (-not [string]::IsNullOrWhiteSpace($name)) {
|
|
$projectLookup[$name.ToLowerInvariant()] = $true
|
|
}
|
|
}
|
|
|
|
$unknown = New-Object System.Collections.Generic.List[string]
|
|
foreach ($svc in $conflicts) {
|
|
if ($projectLookup.ContainsKey($svc.ToLowerInvariant())) {
|
|
Write-Host " Removing stale project minifilter service '$svc' with conflicting altitude $Altitude..." -ForegroundColor Yellow
|
|
Remove-MinifilterServiceArtifacts -ServiceName $svc
|
|
}
|
|
else {
|
|
[void]$unknown.Add($svc)
|
|
}
|
|
}
|
|
|
|
return $unknown.ToArray() | Sort-Object -Unique
|
|
}
|
|
|
|
function Get-RecentServiceEvents {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ServiceName,
|
|
[int]$MaxEvents = 5
|
|
)
|
|
|
|
try {
|
|
$events = Get-WinEvent -FilterHashtable @{
|
|
LogName = "System"
|
|
ProviderName = "Service Control Manager"
|
|
} -MaxEvents 80 | Where-Object {
|
|
$_.Message -match [Regex]::Escape($ServiceName)
|
|
} | Select-Object -First $MaxEvents
|
|
|
|
if ($null -eq $events -or $events.Count -eq 0) {
|
|
return "No recent Service Control Manager events matched '$ServiceName'."
|
|
}
|
|
|
|
$lines = @()
|
|
foreach ($evt in $events) {
|
|
$msg = ($evt.Message -replace "\r?\n", " ").Trim()
|
|
if ($msg.Length -gt 220) {
|
|
$msg = $msg.Substring(0, 220) + "..."
|
|
}
|
|
$lines += "[{0}] EventID {1}: {2}" -f $evt.TimeCreated, $evt.Id, $msg
|
|
}
|
|
return ($lines -join [Environment]::NewLine)
|
|
}
|
|
catch {
|
|
return "Failed to query Service Control Manager events: $($_.Exception.Message)"
|
|
}
|
|
}
|
|
|
|
function Get-MinifilterRegistryDump {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ServiceName,
|
|
[string]$InstanceName = "Blackbird Default"
|
|
)
|
|
|
|
$base = "HKLM\SYSTEM\CurrentControlSet\Services\$ServiceName"
|
|
$commands = @(
|
|
@("query", $base),
|
|
@("query", "$base\Instances"),
|
|
@("query", "$base\Instances\$InstanceName")
|
|
)
|
|
|
|
$lines = @()
|
|
foreach ($cmd in $commands) {
|
|
$target = $cmd[1]
|
|
$lines += "reg.exe $($cmd -join ' ')"
|
|
$out = & reg.exe @cmd 2>&1
|
|
if ($LASTEXITCODE -eq 0) {
|
|
$lines += ($out | ForEach-Object { " $_" })
|
|
}
|
|
else {
|
|
$lines += " (query failed: exit $LASTEXITCODE)"
|
|
$lines += ($out | ForEach-Object { " $_" })
|
|
}
|
|
}
|
|
|
|
return ($lines -join [Environment]::NewLine)
|
|
}
|
|
|
|
function Assert-ServiceRunning {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ServiceName,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ExpectedStatePattern,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$FailureLabel,
|
|
[int]$TimeoutSeconds = 45
|
|
)
|
|
|
|
$deadline = [DateTime]::UtcNow.AddSeconds([Math]::Max(5, $TimeoutSeconds))
|
|
$lastText = ""
|
|
|
|
while ([DateTime]::UtcNow -lt $deadline) {
|
|
$output = & sc.exe query $ServiceName 2>&1
|
|
$text = (($output | ForEach-Object { "$_" }) -join [Environment]::NewLine)
|
|
$lastText = $text
|
|
|
|
if ($LASTEXITCODE -eq 0 -and $text -match $ExpectedStatePattern) {
|
|
return
|
|
}
|
|
|
|
$sleepMs = 500
|
|
$isPendingState = $LASTEXITCODE -eq 0 -and $text -match "STATE\s*:\s*\d+\s+(START_PENDING|STOP_PENDING|PAUSE_PENDING|CONTINUE_PENDING)"
|
|
if ($isPendingState) {
|
|
if ($text -match "WAIT_HINT\s*:\s*0x([0-9A-Fa-f]+)") {
|
|
$waitHintMs = [Convert]::ToInt32($matches[1], 16)
|
|
$sleepMs = [Math]::Min([Math]::Max([int]($waitHintMs / 4), 250), 2000)
|
|
}
|
|
elseif ($text -match "WAIT_HINT\s*:\s*(\d+)") {
|
|
$waitHintMs = [int]$matches[1]
|
|
$sleepMs = [Math]::Min([Math]::Max([int]($waitHintMs / 4), 250), 2000)
|
|
}
|
|
}
|
|
|
|
Start-Sleep -Milliseconds $sleepMs
|
|
}
|
|
|
|
throw "$FailureLabel`n$lastText"
|
|
}
|
|
|
|
function Ensure-FirewallRulePresent {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$DisplayName,
|
|
[Parameter(Mandatory = $true)]
|
|
[ValidateSet("TCP", "UDP", "ICMPv4")]
|
|
[string]$Protocol,
|
|
[string]$LocalPort = "",
|
|
[string]$IcmpType = ""
|
|
)
|
|
|
|
$existing = Get-NetFirewallRule -DisplayName $DisplayName -ErrorAction SilentlyContinue
|
|
if ($null -ne $existing) {
|
|
return
|
|
}
|
|
|
|
$params = @{
|
|
DisplayName = $DisplayName
|
|
Direction = "Inbound"
|
|
Protocol = $Protocol
|
|
Action = "Allow"
|
|
Profile = "Any"
|
|
RemoteAddress = "LocalSubnet"
|
|
}
|
|
|
|
if (-not [string]::IsNullOrWhiteSpace($LocalPort)) {
|
|
$params.LocalPort = $LocalPort
|
|
}
|
|
if (-not [string]::IsNullOrWhiteSpace($IcmpType)) {
|
|
$params.IcmpType = $IcmpType
|
|
}
|
|
|
|
New-NetFirewallRule @params | Out-Null
|
|
}
|
|
|
|
$totalStages = 8
|
|
$activity = "Blackbird installation"
|
|
|
|
Write-Host ""
|
|
Write-Host "Blackbird Installer"
|
|
Write-Host "-------------------"
|
|
|
|
Write-Stage -Index 1 -Total $totalStages -Activity $activity -Status "Removing previous installation"
|
|
$removerScript = Join-Path $scriptRoot "remover.ps1"
|
|
if (Test-Path -LiteralPath $removerScript) {
|
|
& $removerScript -DriverName $DriverName -ControllerName $ControllerName
|
|
}
|
|
|
|
Write-Stage -Index 2 -Total $totalStages -Activity $activity -Status "Resolving build artifacts"
|
|
$driverSrc = Resolve-ArtifactPath `
|
|
-PreferredPath $DriverSys `
|
|
-FallbackPaths @(
|
|
"vcxproj\x64\Debug\blackbird.sys",
|
|
"vcxproj\x64\Release\blackbird.sys",
|
|
"x64\Debug\blackbird.sys",
|
|
"x64\Release\blackbird.sys",
|
|
"blackbird.sys"
|
|
) `
|
|
-Label "Driver .sys"
|
|
|
|
$controllerSrc = Resolve-ArtifactPath `
|
|
-PreferredPath $ControllerExe `
|
|
-FallbackPaths @(
|
|
"vcxproj\x64\Debug\BlackbirdController.exe",
|
|
"vcxproj\x64\Release\BlackbirdController.exe",
|
|
"x64\Debug\BlackbirdController.exe",
|
|
"x64\Release\BlackbirdController.exe",
|
|
"BlackbirdController.exe"
|
|
) `
|
|
-Label "Controller .exe"
|
|
|
|
$sensorCoreSrc = Resolve-ArtifactPath `
|
|
-PreferredPath $SensorCoreDll `
|
|
-FallbackPaths @(
|
|
"vcxproj\x64\Debug\J58.dll",
|
|
"vcxproj\x64\Release\J58.dll",
|
|
"x64\Debug\J58.dll",
|
|
"x64\Release\J58.dll",
|
|
"J58.dll"
|
|
) `
|
|
-Label "SensorCore .dll"
|
|
|
|
$driverDst = Join-Path $env:windir "System32\drivers\blackbird.sys"
|
|
$controllerDir = Join-Path $env:ProgramFiles "Blackbird"
|
|
$controllerDst = Join-Path $controllerDir "BlackbirdController.exe"
|
|
$sensorCoreDst = Join-Path $controllerDir "J58.dll"
|
|
|
|
Write-InfoLog "Driver source: $driverSrc"
|
|
Write-InfoLog "Controller source: $controllerSrc"
|
|
Write-InfoLog "SensorCore source: $sensorCoreSrc"
|
|
|
|
Write-Stage -Index 3 -Total $totalStages -Activity $activity -Status "Stopping existing services and copying binaries"
|
|
Invoke-Sc -Arguments @("stop", $DriverName) -AllowedExitCodes @(0, 1060, 1062, 1243)
|
|
Write-VerboseLog "fltmc.exe unload $DriverName"
|
|
$fltUnloadOut = & fltmc.exe unload $DriverName 2>&1
|
|
if ($fltUnloadOut) {
|
|
foreach ($line in $fltUnloadOut) {
|
|
Write-VerboseLog "fltmc> $line"
|
|
}
|
|
}
|
|
Write-VerboseLog "fltmc unload exit=$LASTEXITCODE"
|
|
Invoke-Sc -Arguments @("stop", $ControllerName) -AllowedExitCodes @(0, 1060, 1062)
|
|
if (-not (Wait-UntilFileUnlocked -Path $controllerDst -TimeoutSeconds 20)) {
|
|
throw "$controllerDst is still locked after controller service stop. Reboot then rerun installer."
|
|
}
|
|
if (-not (Wait-UntilFileUnlocked -Path $sensorCoreDst -TimeoutSeconds 20)) {
|
|
throw "$sensorCoreDst is still locked after controller service stop. Reboot then rerun installer."
|
|
}
|
|
if (-not (Wait-UntilFileUnlocked -Path $driverDst -TimeoutSeconds 20)) {
|
|
throw "$driverDst is still locked after driver stop/unload. Reboot then rerun installer."
|
|
}
|
|
Write-VerboseLog "Ensuring controller directory exists: $controllerDir"
|
|
New-Item -ItemType Directory -Path $controllerDir -Force | Out-Null
|
|
Write-InfoLog "Copying driver: $driverSrc -> $driverDst"
|
|
Copy-Item -LiteralPath $driverSrc -Destination $driverDst -Force
|
|
Write-InfoLog "Copying controller: $controllerSrc -> $controllerDst"
|
|
Copy-Item -LiteralPath $controllerSrc -Destination $controllerDst -Force
|
|
Write-InfoLog "Copying SensorCore: $sensorCoreSrc -> $sensorCoreDst"
|
|
Copy-Item -LiteralPath $sensorCoreSrc -Destination $sensorCoreDst -Force
|
|
|
|
Assert-PathExists -Path $driverDst -Label "Installed driver"
|
|
Assert-PathExists -Path $controllerDst -Label "Installed controller"
|
|
Assert-PathExists -Path $sensorCoreDst -Label "Installed SensorCore"
|
|
|
|
Write-Stage -Index 4 -Total $totalStages -Activity $activity -Status "Checking minifilter altitude conflicts"
|
|
$unknownPreCreateConflicts = Remove-ProjectAltitudeConflicts `
|
|
-Altitude $InstanceAltitude `
|
|
-CurrentServiceName $DriverName `
|
|
-ProjectServiceNames @($DriverName)
|
|
if ($unknownPreCreateConflicts.Count -gt 0) {
|
|
$list = $unknownPreCreateConflicts -join ", "
|
|
throw "Altitude $InstanceAltitude is already used by non-project minifilter service(s): $list. Change -InstanceAltitude or remove the conflicting filter(s)."
|
|
}
|
|
|
|
Write-Stage -Index 5 -Total $totalStages -Activity $activity -Status "Registering services"
|
|
Invoke-Sc -Arguments @("stop", $DriverName) -AllowedExitCodes @(0, 1060, 1062)
|
|
Invoke-Sc -Arguments @("delete", $DriverName) -AllowedExitCodes @(0, 1060)
|
|
New-ServiceWithRetry -ServiceName $DriverName -CreateArguments @(
|
|
"create", $DriverName,
|
|
"type=", "filesys",
|
|
"start=", "demand",
|
|
"error=", "normal",
|
|
"group=", "FSFilter Activity Monitor",
|
|
"depend=", "FltMgr",
|
|
"binPath=", $driverDst,
|
|
"DisplayName=", "Blackbird Driver"
|
|
)
|
|
Invoke-Sc -Arguments @("qc", $DriverName)
|
|
Ensure-MinifilterRegistry -ServiceName $DriverName -InstanceName $InstanceName -Altitude $InstanceAltitude -InstanceFlags $InstanceFlags
|
|
Set-DriverRuntimeDefaults -ServiceName $DriverName -EnableAntiVirtualization $EnableAntiVirtualization.IsPresent -EnableSelfHide $EnableControllerHiding.IsPresent
|
|
|
|
Invoke-Sc -Arguments @("stop", $ControllerName) -AllowedExitCodes @(0, 1060, 1062)
|
|
Invoke-Sc -Arguments @("delete", $ControllerName) -AllowedExitCodes @(0, 1060)
|
|
New-ServiceWithRetry -ServiceName $ControllerName -CreateArguments @("create", $ControllerName, "type=", "own", "start=", "auto", "obj=", "LocalSystem", "binPath=", $controllerDst, "DisplayName=", "Blackbird Controller Service")
|
|
Invoke-Sc -Arguments @("failure", $ControllerName, "reset=", "60", "actions=", "restart/5000/restart/5000/restart/5000")
|
|
|
|
if (-not (Test-ServiceExists -ServiceName $DriverName)) {
|
|
throw "Driver service '$DriverName' was not created."
|
|
}
|
|
if (-not (Test-ServiceExists -ServiceName $ControllerName)) {
|
|
throw "Controller service '$ControllerName' was not created."
|
|
}
|
|
|
|
Write-Stage -Index 6 -Total $totalStages -Activity $activity -Status "Configuring firewall rules"
|
|
Ensure-FirewallRulePresent -DisplayName "Blackbird Operator UDP Discovery" -Protocol UDP -LocalPort "49371"
|
|
Ensure-FirewallRulePresent -DisplayName "Blackbird Operator TCP Status" -Protocol TCP -LocalPort "49372"
|
|
Ensure-FirewallRulePresent -DisplayName "Blackbird Operator TCP Command" -Protocol TCP -LocalPort "49373"
|
|
Ensure-FirewallRulePresent -DisplayName "Blackbird Operator ICMPv4" -Protocol ICMPv4 -IcmpType "8"
|
|
|
|
Write-Stage -Index 7 -Total $totalStages -Activity $activity -Status "Starting driver"
|
|
$driverRecovered = $false
|
|
try {
|
|
Invoke-Sc -Arguments @("start", $DriverName)
|
|
}
|
|
catch {
|
|
$driverStartError = $_.Exception.Message
|
|
if ($driverStartError -match "exit code 1243") {
|
|
Write-Host " sc start returned 1243. Attempting minifilter load via fltmc..." -ForegroundColor Yellow
|
|
$fltOut = & fltmc.exe load $DriverName 2>&1
|
|
$fltCode = $LASTEXITCODE
|
|
if ($fltCode -eq 0) {
|
|
Write-Host " fltmc load succeeded." -ForegroundColor Green
|
|
}
|
|
else {
|
|
Write-Host " fltmc load failed (exit $fltCode):" -ForegroundColor Yellow
|
|
$fltOut | ForEach-Object { Write-Host " $_" }
|
|
}
|
|
}
|
|
|
|
if ($driverStartError -match "already exists at this altitude|2149515281") {
|
|
Write-Host " Detected minifilter altitude collision. Attempting conflict cleanup + retry..." -ForegroundColor Yellow
|
|
$unknownRetryConflicts = Remove-ProjectAltitudeConflicts `
|
|
-Altitude $InstanceAltitude `
|
|
-CurrentServiceName $DriverName `
|
|
-ProjectServiceNames @($DriverName)
|
|
|
|
if ($unknownRetryConflicts.Count -eq 0) {
|
|
try {
|
|
Ensure-MinifilterRegistry -ServiceName $DriverName -InstanceName $InstanceName -Altitude $InstanceAltitude -InstanceFlags $InstanceFlags
|
|
Set-DriverRuntimeDefaults -ServiceName $DriverName -EnableAntiVirtualization $EnableAntiVirtualization.IsPresent -EnableSelfHide $EnableControllerHiding.IsPresent
|
|
Invoke-Sc -Arguments @("start", $DriverName)
|
|
Write-Host " Driver start succeeded after altitude conflict remediation." -ForegroundColor Green
|
|
$driverRecovered = $true
|
|
}
|
|
catch {
|
|
$driverStartError = $_.Exception.Message
|
|
}
|
|
}
|
|
else {
|
|
$unknownList = $unknownRetryConflicts -join ", "
|
|
Write-Host " unresolved altitude conflicts (non-project services): $unknownList" -ForegroundColor Yellow
|
|
}
|
|
}
|
|
|
|
if ($driverRecovered) {
|
|
# continue installation flow
|
|
}
|
|
else {
|
|
Write-Host "[!] Failed to start driver service '$DriverName'." -ForegroundColor Red
|
|
Write-Host " sc query output:" -ForegroundColor Yellow
|
|
& sc.exe query $DriverName 2>&1 | ForEach-Object { Write-Host " $_" }
|
|
Write-Host " sc qc output:" -ForegroundColor Yellow
|
|
& sc.exe qc $DriverName 2>&1 | ForEach-Object { Write-Host " $_" }
|
|
Write-Host " fltmgr status:" -ForegroundColor Yellow
|
|
& sc.exe query fltmgr 2>&1 | ForEach-Object { Write-Host " $_" }
|
|
Write-Host " minifilter registry keys:" -ForegroundColor Yellow
|
|
(Get-MinifilterRegistryDump -ServiceName $DriverName -InstanceName $InstanceName) -split "`r?`n" | ForEach-Object { Write-Host " $_" }
|
|
Write-Host " recent SCM events:" -ForegroundColor Yellow
|
|
(Get-RecentServiceEvents -ServiceName $DriverName) -split "`r?`n" | ForEach-Object { Write-Host " $_" }
|
|
Write-Host " conflicting altitude services:" -ForegroundColor Yellow
|
|
$altitudeConflicts = Get-ServicesUsingMinifilterAltitude -Altitude $InstanceAltitude -ExcludeServiceName ""
|
|
if ($altitudeConflicts.Count -eq 0) {
|
|
Write-Host " (none detected in Services\\*\\Instances)"
|
|
}
|
|
else {
|
|
$altitudeConflicts | ForEach-Object { Write-Host " $_" }
|
|
}
|
|
Write-Host " hint: exit code 127 usually means a missing kernel export (driver built for newer OS APIs)." -ForegroundColor Yellow
|
|
throw
|
|
}
|
|
}
|
|
|
|
Assert-ServiceRunning -ServiceName $DriverName -ExpectedStatePattern "STATE\s*:\s*\d+\s+RUNNING" -FailureLabel "Driver service failed to reach RUNNING state."
|
|
|
|
Write-Stage -Index 8 -Total $totalStages -Activity $activity -Status "Starting controller"
|
|
Invoke-Sc -Arguments @("start", $ControllerName)
|
|
Assert-ServiceRunning -ServiceName $ControllerName -ExpectedStatePattern "STATE\s*:\s*\d+\s+RUNNING" -FailureLabel "Controller service failed to reach RUNNING state."
|
|
|
|
Write-Progress -Id 1 -Activity $activity -Completed
|
|
|
|
Write-Host "[*] Installed and started $DriverName + $ControllerName"
|
|
Write-Host " Driver: $driverDst"
|
|
Write-Host " Controller: $controllerDst"
|
|
Write-Host " SensorCore: $sensorCoreDst"
|
|
|