diff --git a/Borealis.ps1 b/Borealis.ps1 index d1b295e..1d85240 100644 --- a/Borealis.ps1 +++ b/Borealis.ps1 @@ -371,12 +371,44 @@ function Ensure-AgentTasks { "@ $bytes = [System.Text.Encoding]::Unicode.GetBytes($inline) $encoded = [Convert]::ToBase64String($bytes) - $argList = @('-NoProfile','-ExecutionPolicy','Bypass','-EncodedCommand', $encoded) + + # Use a temporary VBS shim to elevate and run PowerShell fully hidden (no flashing console) + $tempDir = Join-Path $ScriptRoot 'Temp' + if (-not (Test-Path $tempDir)) { New-Item -ItemType Directory -Path $tempDir -Force | Out-Null } + $vbsPath = Join-Path $tempDir 'RunAgentRegistrarElevatedHidden.vbs' + $vbs = @' +Set sh = CreateObject("Shell.Application") +cmd = "powershell.exe" +args = " -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -EncodedCommand " & "" +sh.ShellExecute cmd, args, "", "runas", 0 +'@ + $vbs = $vbs -replace '', $encoded + Write-AgentLog -FileName 'AgentTaskRegistration.log' -Message 'Preparing elevated hidden registrar VBS shim.' + Set-Content -Path $vbsPath -Value $vbs -Encoding ASCII -Force try { - Start-Process -FilePath 'powershell.exe' -ArgumentList ($argList -join ' ') -Verb RunAs -Wait | Out-Null + Write-AgentLog -FileName 'AgentTaskRegistration.log' -Message 'Invoking wscript.exe to run registrar hidden with elevation.' + Start-Process -FilePath 'wscript.exe' -ArgumentList ('"{0}"' -f $vbsPath) -WindowStyle Hidden -Wait | Out-Null } catch { Write-Host "Failed to elevate for task registration." -ForegroundColor Red + Write-AgentLog -FileName 'AgentTaskRegistration.log' -Message ("Registrar elevation failed: " + $_) + } finally { + Remove-Item $vbsPath -Force -ErrorAction SilentlyContinue } + # Best-effort: wait briefly for tasks to be registered so subsequent steps see them + try { + $maxWaitSec = 30 + $sw = [System.Diagnostics.Stopwatch]::StartNew() + do { + Start-Sleep -Milliseconds 500 + try { $ts = Get-ScheduledTask -TaskName $supName -ErrorAction SilentlyContinue } catch { $ts = $null } + } while (-not $ts -and $sw.Elapsed.TotalSeconds -lt $maxWaitSec) + $sw.Stop() + if ($ts) { + Write-AgentLog -FileName 'AgentTaskRegistration.log' -Message "Supervisor task detected after $([int]$sw.Elapsed.TotalSeconds)s." + } else { + Write-AgentLog -FileName 'AgentTaskRegistration.log' -Message "Supervisor task not detected within timeout; continuing." + } + } catch {} } function InstallOrUpdate-BorealisAgent { Write-Host "Ensuring Agent Dependencies Exist..." -ForegroundColor DarkCyan diff --git a/Data/Agent/Scripts/register_agent_tasks.ps1 b/Data/Agent/Scripts/register_agent_tasks.ps1 index ce422fa..7bb866d 100644 --- a/Data/Agent/Scripts/register_agent_tasks.ps1 +++ b/Data/Agent/Scripts/register_agent_tasks.ps1 @@ -32,7 +32,7 @@ try { # Watchdog task (5-min repetition for 1 year) try { Unregister-ScheduledTask -TaskName $WdName -Confirm:$false -ErrorAction SilentlyContinue } catch {} - $wdArg = ('-NoProfile -ExecutionPolicy Bypass -File "{0}" -SupervisorTaskName "{1}"' -f $wdDest, $SupName) + $wdArg = ('-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "{0}" -SupervisorTaskName "{1}"' -f $wdDest, $SupName) $wdAction = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument $wdArg $wdTrigger = New-ScheduledTaskTrigger -Once -At ([datetime]::Now.AddMinutes(1)) -RepetitionInterval (New-TimeSpan -Minutes 5) -RepetitionDuration (New-TimeSpan -Days 365) $wdSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -Hidden -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit ([TimeSpan]::Zero) diff --git a/Data/Agent/agent_deployment.py b/Data/Agent/agent_deployment.py index 1bd9b2c..79d818a 100644 --- a/Data/Agent/agent_deployment.py +++ b/Data/Agent/agent_deployment.py @@ -84,9 +84,10 @@ def run_elevated_powershell(paths, ps_content, log_name): sei.hwnd = None sei.lpVerb = "runas" sei.lpFile = "powershell.exe" - sei.lpParameters = f"-NoProfile -ExecutionPolicy Bypass -File \"{stub_path}\"" + sei.lpParameters = f"-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File \"{stub_path}\"" sei.lpDirectory = paths["project_root"] - sei.nShow = 1 + # Hide the elevated PowerShell window entirely (UAC prompt may still appear) + sei.nShow = 0 if not ctypes.windll.shell32.ShellExecuteExW(ctypes.byref(sei)): log_write(paths, log_name, "[ERROR] UAC elevation failed (ShellExecuteExW)") return 1 diff --git a/Data/Agent/agent_info.py b/Data/Agent/agent_info.py index 4671b12..2fbecdb 100644 --- a/Data/Agent/agent_info.py +++ b/Data/Agent/agent_info.py @@ -18,6 +18,28 @@ except Exception: import aiohttp import asyncio +# ---------------- Helpers for hidden subprocess on Windows ---------------- +IS_WINDOWS = os.name == 'nt' +CREATE_NO_WINDOW = 0x08000000 if IS_WINDOWS else 0 + + +def _run_hidden(cmd_list, timeout=None): + """Run a subprocess hidden on Windows (no visible console window).""" + kwargs = {"capture_output": True, "text": True} + if timeout is not None: + kwargs["timeout"] = timeout + if IS_WINDOWS: + kwargs["creationflags"] = CREATE_NO_WINDOW + return subprocess.run(cmd_list, **kwargs) + + +def _run_powershell_hidden(ps_cmd: str, timeout: int = 60): + """Run a powershell -NoProfile -Command string fully hidden on Windows.""" + ps = os.path.expandvars(r"%SystemRoot%\\System32\\WindowsPowerShell\\v1.0\\powershell.exe") + if not os.path.isfile(ps): + ps = "powershell.exe" + return _run_hidden([ps, "-NoProfile", "-Command", ps_cmd], timeout=timeout) + def detect_agent_os(): """ @@ -194,12 +216,7 @@ def _detect_virtual_machine() -> bool: "$model = [string]$cs.Model; $manu = [string]$cs.Manufacturer; " "Write-Output ($model + '|' + $manu)" ) - out = subprocess.run( - ["powershell", "-NoProfile", "-Command", ps_cmd], - capture_output=True, - text=True, - timeout=6, - ) + out = _run_powershell_hidden(ps_cmd, timeout=6) s = (out.stdout or "").strip().lower() if any(k in s for k in ("virtual", "vmware", "virtualbox", "kvm", "qemu", "xen", "hyper-v")): return True @@ -257,12 +274,7 @@ def _detect_device_type_non_vm() -> str: "Write-Output ($typeEx.ToString() + '|' + $type.ToString() + '|' + " "([string]::Join(',', $ch)) + '|' + $hasBatt)" ) - out = subprocess.run( - ["powershell", "-NoProfile", "-Command", ps_cmd], - capture_output=True, - text=True, - timeout=6, - ) + out = _run_powershell_hidden(ps_cmd, timeout=6) resp = (out.stdout or "").strip() parts = resp.split("|") type_ex = int(parts[0]) if len(parts) > 0 and parts[0].isdigit() else None @@ -429,12 +441,7 @@ def _get_internal_ip(): "Where-Object { $_.IPAddress -and $_.IPAddress -notmatch '^169\\.254\\.' -and $_.IPAddress -notmatch '^127\\.' } | " "Sort-Object -Property PrefixLength | Select-Object -First 1 -ExpandProperty IPAddress" ) - out = subprocess.run( - ["powershell", "-NoProfile", "-Command", ps_cmd], - capture_output=True, - text=True, - timeout=20, - ) + out = _run_powershell_hidden(ps_cmd, timeout=20) val = (out.stdout or "").strip() if val: return val @@ -489,12 +496,7 @@ def collect_summary(config): # Try WMIC, then robust PowerShell fallback regardless of WMIC presence raw = "" try: - out = subprocess.run( - ["wmic", "os", "get", "lastbootuptime"], - capture_output=True, - text=True, - timeout=20, - ) + out = _run_hidden(["wmic", "os", "get", "lastbootuptime"], timeout=20) raw = "".join(out.stdout.splitlines()[1:]).strip() except Exception: raw = "" @@ -505,12 +507,7 @@ def collect_summary(config): "(Get-CimInstance Win32_OperatingSystem).LastBootUpTime | " "ForEach-Object { (Get-Date -Date $_ -Format 'yyyy-MM-dd HH:mm:ss') }" ) - out = subprocess.run( - ["powershell", "-NoProfile", "-Command", ps_cmd], - capture_output=True, - text=True, - timeout=20, - ) + out = _run_powershell_hidden(ps_cmd, timeout=20) raw = (out.stdout or "").strip() if raw: last_reboot = raw @@ -571,8 +568,7 @@ def collect_software(): try: if plat == "windows": try: - out = subprocess.run(["wmic", "product", "get", "name,version"], - capture_output=True, text=True, timeout=60) + out = _run_hidden(["wmic", "product", "get", "name,version"], timeout=60) for line in out.stdout.splitlines(): if line.strip() and not line.lower().startswith("name"): parts = line.strip().split(" ") @@ -589,12 +585,7 @@ def collect_software(): "| Select-Object DisplayName,DisplayVersion " "| ConvertTo-Json" ) - out = subprocess.run( - ["powershell", "-NoProfile", "-Command", ps_cmd], - capture_output=True, - text=True, - timeout=60, - ) + out = _run_powershell_hidden(ps_cmd, timeout=60) data = json.loads(out.stdout or "[]") if isinstance(data, dict): data = [data] @@ -627,12 +618,7 @@ def collect_memory(): try: if plat == "windows": try: - out = subprocess.run( - ["wmic", "memorychip", "get", "BankLabel,Speed,SerialNumber,Capacity"], - capture_output=True, - text=True, - timeout=60, - ) + out = _run_hidden(["wmic", "memorychip", "get", "BankLabel,Speed,SerialNumber,Capacity"], timeout=60) lines = [l for l in out.stdout.splitlines() if l.strip() and "BankLabel" not in l] for line in lines: parts = [p for p in line.split() if p] @@ -648,12 +634,7 @@ def collect_memory(): "Get-CimInstance Win32_PhysicalMemory | " "Select-Object BankLabel,Speed,SerialNumber,Capacity | ConvertTo-Json" ) - out = subprocess.run( - ["powershell", "-NoProfile", "-Command", ps_cmd], - capture_output=True, - text=True, - timeout=60, - ) + out = _run_powershell_hidden(ps_cmd, timeout=60) data = json.loads(out.stdout or "[]") if isinstance(data, dict): data = [data] @@ -748,12 +729,7 @@ def collect_storage(): found = True if not found: try: - out = subprocess.run( - ["wmic", "logicaldisk", "get", "DeviceID,Size,FreeSpace"], - capture_output=True, - text=True, - timeout=60, - ) + out = _run_hidden(["wmic", "logicaldisk", "get", "DeviceID,Size,FreeSpace"], timeout=60) lines = [l for l in out.stdout.splitlines() if l.strip()][1:] for line in lines: parts = line.split() @@ -820,12 +796,7 @@ def collect_network(): "Select-Object InterfaceAlias,@{Name='IPv4';Expression={$_.IPv4Address.IPAddress}}," "@{Name='MAC';Expression={$_.NetAdapter.MacAddress}} | ConvertTo-Json" ) - out = subprocess.run( - ["powershell", "-NoProfile", "-Command", ps_cmd], - capture_output=True, - text=True, - timeout=60, - ) + out = _run_powershell_hidden(ps_cmd, timeout=60) data = json.loads(out.stdout or "[]") if isinstance(data, dict): data = [data] diff --git a/Data/Agent/borealis-agent.py b/Data/Agent/borealis-agent.py index 559739e..302ac90 100644 --- a/Data/Agent/borealis-agent.py +++ b/Data/Agent/borealis-agent.py @@ -509,7 +509,7 @@ async def _run_powershell_as_system(path: str): st = f"{t.tm_hour:02d}:{t.tm_min:02d}" create_cmd = [ "schtasks", "/Create", "/TN", name, - "/TR", f"\"powershell.exe -ExecutionPolicy Bypass -NoProfile -File \"\"{path}\"\"\"", + "/TR", f"\"powershell.exe -ExecutionPolicy Bypass -NoProfile -WindowStyle Hidden -File \"\"{path}\"\"\"", "/SC", "ONCE", "/ST", st, "/RL", "HIGHEST", "/RU", "SYSTEM", "/F" ] try: @@ -1083,7 +1083,7 @@ $ps = "{ps}" $scr = "{path}" $out = "{out_path}" try {{ Unregister-ScheduledTask -TaskName $task -Confirm:$false -ErrorAction SilentlyContinue }} catch {{}} -$action = New-ScheduledTaskAction -Execute $ps -Argument ('-NoProfile -ExecutionPolicy Bypass -File "' + $scr + '" *> "' + $out + '"') +$action = New-ScheduledTaskAction -Execute $ps -Argument ('-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "' + $scr + '" *> "' + $out + '"') $settings = New-ScheduledTaskSettingsSet -DeleteExpiredTaskAfter (New-TimeSpan -Minutes 5) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries $principal= New-ScheduledTaskPrincipal -UserId ([System.Security.Principal.WindowsIdentity]::GetCurrent().Name) -LogonType Interactive -RunLevel Limited Register-ScheduledTask -TaskName $task -Action $action -Settings $settings -Principal $principal -Force | Out-Null diff --git a/Data/Agent/script_agent.py b/Data/Agent/script_agent.py index 080dc18..b821d73 100644 --- a/Data/Agent/script_agent.py +++ b/Data/Agent/script_agent.py @@ -47,11 +47,13 @@ def run_powershell_script_content(content: str): if not os.path.isfile(ps): ps = "powershell.exe" try: + flags = 0x08000000 if os.name == 'nt' else 0 # CREATE_NO_WINDOW proc = subprocess.run( [ps, "-ExecutionPolicy", "Bypass", "-NoProfile", "-File", path], capture_output=True, text=True, timeout=60*60, + creationflags=flags, ) return proc.returncode, proc.stdout or "", proc.stderr or "" except Exception as e: @@ -183,7 +185,7 @@ $ps = "{ps_exe}" $scr = "{script_path}" $out = "{out_path}" try {{ Unregister-ScheduledTask -TaskName $task -Confirm:$false -ErrorAction SilentlyContinue }} catch {{}} -$action = New-ScheduledTaskAction -Execute $ps -Argument ('-NoProfile -ExecutionPolicy Bypass -File "' + $scr + '" *> "' + $out + '"') +$action = New-ScheduledTaskAction -Execute $ps -Argument ('-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "' + $scr + '" *> "' + $out + '"') $settings = New-ScheduledTaskSettingsSet -DeleteExpiredTaskAfter (New-TimeSpan -Minutes 5) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries $principal= New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest Register-ScheduledTask -TaskName $task -Action $action -Settings $settings -Principal $principal -Force | Out-Null