GShield
unknown
powershell
a month ago
16 kB
4
No Index
# GShield.ps1
# Author: Gorstak
#Requires -RunAsAdministrator
# Standalone AV scanner
param([string[]]$Path, [int]$IntervalMinutes=60)
# ==================== CONFIG ====================
$InstallDir = "$env:ProgramData\Antivirus"
$LogDir = "$InstallDir\logs"
$QuarDir = "$InstallDir\quarantine"
$YaraDir = "$InstallDir\yara"
$RulesDir = "$InstallDir\rules"
$LogFile = "$LogDir\scanner.log"
$Cache = "$InstallDir\av.csv"
$HashCache = "$InstallDir\hashes.csv"
$YaraUrl = "https://github.com/VirusTotal/yara/releases/download/v4.5.2/yara-4.5.2-2326-win64.zip"
$YaraHash = "40B238E43E22AA5BC8F4E4A0B3E7318BA0C8D0CC32E3F92CF5D5C365A3963D72"
$RulesUrl = "https://github.com/Yara-Rules/rules/archive/refs/heads/master.zip"
$Ext = '*.exe','*.msi','*.dll','*.ocx','*.winmd','*.ps1','*.vbs','*.js','*.bat','*.cmd','*.scr'
$Exclusions = @(
"$env:ProgramFiles", "$env:ProgramFiles(x86)", "$env:windir",
"$InstallDir", "C:\Windows\System32", "C:\Windows\SysWOW64"
)
$SuspiciousAPIs = 'VirtualAlloc|WriteProcessMemory|CreateRemoteThread|NtUnmapViewOfSection|ReadProcessMemory|OpenProcess|VirtualProtect|LoadLibrary|GetProcAddress|WinExec|CreateProcess|ShellExecute|URLDownloadToFile|InternetOpen'
# ===============================================
# Colored output function
function Write-ColorOutput {
param([string]$Message, [string]$Color = 'White')
Write-Host $Message -ForegroundColor $Color
}
# Logging function
function Write-Log {
param([string]$Message, [string]$Level = 'INFO')
$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
$logEntry = "$timestamp [$Level] $Message"
$logEntry | Add-Content $LogFile -Force -ErrorAction SilentlyContinue
Write-Host $logEntry
}
# Helper functions
function Get-SHA256 { param($file); (Get-FileHash $file -Algorithm SHA256).Hash }
function Test-Excluded { param($p); $Exclusions | Where-Object { $p -like "$_*" } }
function Ensure-Setup {
# Ensure all dirs exist
@($InstallDir, $LogDir, $QuarDir, $YaraDir, $RulesDir) | ForEach-Object {
if (!(Test-Path $_)) { New-Item $_ -ItemType Directory -Force | Out-Null }
}
$yaraExe = (Get-ChildItem $YaraDir -Filter yara64.exe -EA 0 | Select -First 1).FullName
if (!$yaraExe) {
Write-Log "Downloading YARA..."
$z = "$env:TEMP\yara.zip"
try {
Invoke-WebRequest $YaraUrl -OutFile $z -UseBasicParsing
$dlHash = (Get-SHA256 $z)
if ($dlHash -ne $YaraHash) {
Write-Log "YARA hash mismatch! Expected: $YaraHash Got: $dlHash" "WARN"
Write-Log "Proceeding anyway - update YaraHash in script if this is a new release" "WARN"
}
Expand-Archive $z -DestinationPath $YaraDir -Force
# Move yara64.exe to root of yara dir
Get-ChildItem $YaraDir -Recurse -Filter yara64.exe | Select -First 1 | ForEach-Object {
Copy-Item $_.FullName "$YaraDir\yara64.exe" -Force
}
Remove-Item $z -Force
Write-Log "YARA installed"
} catch {
Write-Log "Failed to download YARA: $_" "ERROR"
}
}
if (!(Test-Path "$RulesDir\index.yar")) {
Write-Log "Downloading YARA rules..."
$z = "$env:TEMP\rules.zip"
try {
Invoke-WebRequest $RulesUrl -OutFile $z -UseBasicParsing
Expand-Archive $z -DestinationPath $env:TEMP -Force
# Find extracted folder and move rules
$extracted = Get-ChildItem $env:TEMP -Directory | Where-Object { $_.Name -like 'rules-*' } | Select -First 1
if ($extracted) {
Copy-Item "$($extracted.FullName)\*" $RulesDir -Recurse -Force
Remove-Item $extracted.FullName -Recurse -Force
}
Remove-Item $z -Force
Write-Log "YARA rules installed"
} catch {
Write-Log "Failed to download rules: $_" "ERROR"
}
}
}
# AMSI Wrapper
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;}}
'@
function Test-Amsi { param([string]$c); if(!$c) {return 0}; return [Amsi]::Scan($c) -ge 1 }
# Browser unsigned-module unloader
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 bool FreeLibrary(IntPtr module);
[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();
// Skip the main exe and core system modules
if (path.ToLower() == proc.MainModule.FileName.ToLower()) continue;
if (name == "ntdll.dll" || name == "kernel32.dll" || name == "kernelbase.dll") 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, 3000);
CloseHandle(hThread);
unloaded.Add(path);
}
}
} catch { }
}
} finally {
CloseHandle(hProc);
}
return unloaded;
}
}
'@
$BrowserProcessNames = @('chrome','firefox','msedge','opera','brave','vivaldi','iexplore','safari','waterfox','librewolf','thorium')
function Invoke-BrowserModuleGuard {
foreach ($name in $BrowserProcessNames) {
$procs = Get-Process -Name $name -ErrorAction SilentlyContinue
foreach ($proc in $procs) {
try {
$removed = [ModuleGuard]::UnloadUnsignedModules($proc.Id)
foreach ($mod in $removed) {
Write-Log "UNLOADED unsigned module from $name (PID $($proc.Id)): $mod" "ALERT"
}
} catch {
Write-Log "ModuleGuard error on $name (PID $($proc.Id)): $_" "WARN"
}
}
}
}
function Get-Entropy { param([byte[]]$b)
if(!$b.Length) {return 0}
$f=@{}; foreach($c in $b){$f[$c]++}
$e=0; foreach($c in $f.Keys){$p=$f[$c]/$b.Length; $e-=$p*([Math]::Log($p)/[Math]::Log(2))}
return $e
}
function Test-Heuristic {
param([string]$path)
$warn = @()
$ext = [IO.Path]::GetExtension($path).ToLower()
try {
$b = [IO.File]::ReadAllBytes($path)
if ((Get-Entropy $b) -gt 7.2) { $warn += "high-entropy" }
if ($ext -in '.exe','.dll') {
$str = [Text.Encoding]::ASCII.GetString($b)
if ($str -match $SuspiciousAPIs) { $warn += "susp-api" }
}
if ($ext -in '.ps1','.vbs','.js','.bat') {
$c = Get-Content $path -Raw -EA 0
if ($c -match '[A-Za-z0-9+/]{80,}={0,2}') { $warn += "b64" }
if ($c -match 'IEX|Invoke-Expression|FromBase64String|DownloadString|Hidden') { $warn += "obf" }
}
} catch {}
return ($warn -join ',')
}
function Invoke-Scan {
param([string]$p)
$yaraExe = (Get-ChildItem $YaraDir -Filter yara64.exe -EA 0 | Select -First 1).FullName
Get-ChildItem $p -Recurse -Include $Ext -EA 0 | Where-Object { !(Test-Excluded $_.FullName) } | ForEach-Object {
$f = $_
try {
$h = (Get-FileHash $f.FullName -A SHA1).Hash
$heur = Test-Heuristic $f.FullName
if ($heur -match "high-entropy|susp-api|obf") {
Write-Log "QUARANTINED: $($f.FullName) [$heur]" "ALERT"
Move-Item $f.FullName "$QuarDir\$($f.Name)" -Force -EA 0
}
if ($f.Extension -in '.ps1','.vbs','.js' -and (Test-Amsi (Get-Content $f.FullName -Raw))) {
Write-Log "QUARANTINED (AMSI): $($f.FullName)" "ALERT"
Move-Item $f.FullName "$QuarDir\$($f.Name)" -Force -EA 0
}
if ($yaraExe) {
$y = & $yaraExe -r "$RulesDir" $f.FullName 2>$null
if ($y) {
Write-Log "QUARANTINED (YARA): $($f.FullName) - $y" "ALERT"
Move-Item $f.FullName "$QuarDir\$($f.Name)" -Force -EA 0
}
}
} catch {}
}
Write-Log "Scan completed: $p"
}
function Install-Persistence {
param([switch]$Uninstall)
$taskName = "GShield"
$scriptPath = $MyInvocation.PSCommandPath
if (-not $scriptPath) { $scriptPath = $PSCommandPath }
if (-not $scriptPath) { $scriptPath = $MyInvocation.MyCommand.Definition }
if ($Uninstall) {
try {
# Try PowerShell cmdlet first
if (Get-Command Get-ScheduledTask -ErrorAction SilentlyContinue) {
Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue |
Unregister-ScheduledTask -Confirm:$false -ErrorAction SilentlyContinue
} else {
# Fallback to schtasks.exe
schtasks.exe /Delete /TN $taskName /F 2>$null
}
Write-ColorOutput "[+] Scheduled task '$taskName' removed" "Green"
}
catch {
Write-ColorOutput "[!] Failed to remove scheduled task: $_" "Red"
}
return
}
try {
# Remove old task if exists (using schtasks for compatibility)
schtasks.exe /Delete /TN $taskName /F 2>$null
# Get current user
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
# Try PowerShell cmdlets first
$useSchtasks = $false
try {
$principal = New-ScheduledTaskPrincipal -UserId $currentUser -LogonType Interactive -RunLevel Highest -ErrorAction Stop
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $currentUser -ErrorAction Stop
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$scriptPath`"" -ErrorAction Stop
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Hours 0) -ErrorAction Stop
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null
}
catch {
$useSchtasks = $true
}
# Fallback to schtasks.exe (works on all Windows versions)
if ($useSchtasks) {
$taskCmd = "powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$scriptPath`""
$result = schtasks.exe /Create /TN $taskName /TR $taskCmd /SC ONLOGON /RL HIGHEST /F 2>&1
if ($LASTEXITCODE -ne 0) {
throw "schtasks failed: $result"
}
}
Write-ColorOutput "[+] Scheduled task created for automatic startup (runs as: $currentUser)" "Green"
Write-ColorOutput " Script path: $scriptPath" "Gray"
}
catch {
Write-ColorOutput "[!] Failed to create scheduled task: $_" "Red"
}
}
# Install persistence (scheduled task) on first run
Install-Persistence
# Main execution
try {
# Ensure log dir exists
if (!(Test-Path $LogDir)) { New-Item $LogDir -ItemType Directory -Force | Out-Null }
Write-Log "GShield AV starting - PID: $PID"
Write-Log "Install dir: $InstallDir"
Write-Log "Log file: $LogFile"
Write-Log "Quarantine: $QuarDir"
Ensure-Setup
# Resolve scan targets: use provided paths or enumerate all local fixed drives
function Get-ScanTargets {
if ($Path -and $Path.Count -gt 0) { return $Path }
return (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 enabled"
# Real-time Process Guard
Register-WmiEvent -Query "SELECT * FROM Win32_ProcessStartTrace" -SourceIdentifier "ProcGuard" -Action {
$e = $Event.SourceEventArgs.NewEvent
$procId = $e.ProcessID
try {
$proc = Get-CimInstance Win32_Process -Filter "ProcessId=$procId"
$procPath = $proc.ExecutablePath
if (!$procPath -or (Test-Excluded $procPath)) { return }
$heur = Test-Heuristic $procPath
if ($heur -match "high-entropy|susp-api|obf") {
Stop-Process -Id $procId -Force -EA 0
Start-Sleep -Milliseconds 500
Move-Item $procPath "$QuarDir\$([IO.Path]::GetFileName($procPath))" -Force -EA 0
Write-Log "BLOCKED+QUARANTINED: $procPath [$heur]" "ALERT"
}
} catch {}
} | Out-Null
# Main loop - runs periodic scans
Write-Log "Entering main loop"
while ($true) {
$targets = Get-ScanTargets
Write-Log "Periodic scan starting across drives: $($targets -join ', ')"
$targets | ForEach-Object { Invoke-Scan $_ }
Invoke-BrowserModuleGuard
Start-Sleep -Seconds ($IntervalMinutes * 60)
}
} catch {
Write-Log "FATAL ERROR: $_" "ERROR"
exit 1
}
Editor is loading...
Leave a Comment