Antivirus
unknown
powershell
5 days ago
24 kB
7
No Index
# Antivirus.ps1 - Minimal Antivirus
# Author: Gorstak (rewrite)
# Usage: Run as Administrator. Scans all drives, blocks malware execution via ACL.
# Memory scan included. No YARA, no bloat.
#Requires -RunAsAdministrator
param(
[switch]$Uninstall
)
$ErrorActionPreference = "SilentlyContinue"
$script:LogFile = "$env:ProgramData\Antivirus\antivirus.log"
$script:CachePath = "$env:ProgramData\Antivirus\hashcache.json"
$script:CacheHMACPath = "$env:ProgramData\Antivirus\hashcache.hmac"
$script:FileIndexPath = "$env:ProgramData\Antivirus\fileindex.json"
$script:FileIndexHMACPath = "$env:ProgramData\Antivirus\fileindex.hmac"
$script:BlockedListPath = "$env:ProgramData\Antivirus\blocked.txt"
$script:ScanExtensions = @("*.exe", "*.dll", "*.sys", "*.scr", "*.bat", "*.cmd", "*.ps1", "*.vbs", "*.js")
$script:HashCache = @{}
$script:FileIndex = @{}
# --- Setup ---
if (!(Test-Path "$env:ProgramData\Antivirus")) {
New-Item -ItemType Directory -Path "$env:ProgramData\Antivirus" -Force | Out-Null
}
# --- Hash Cache (JSON + HMAC integrity) ---
# HMAC prevents tampering - if cache is modified externally, it's discarded.
function Get-CacheHMACKey {
try {
$machineSid = (Get-CimInstance Win32_UserAccount -Filter "LocalAccount=True" -ErrorAction Stop | Select-Object -First 1).SID
$raw = "$machineSid|$($PSCommandPath)|Antivirus2025"
$sha = [System.Security.Cryptography.SHA256]::Create()
return $sha.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($raw))
} catch {
$raw = "$env:COMPUTERNAME|$env:USERNAME|AntivirusFallback"
$sha = [System.Security.Cryptography.SHA256]::Create()
return $sha.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($raw))
}
}
function Get-CacheHMAC {
param([string]$Content)
$hmac = New-Object System.Security.Cryptography.HMACSHA256
$hmac.Key = Get-CacheHMACKey
$bytes = $hmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($Content))
return [System.BitConverter]::ToString($bytes).Replace("-", "").ToLower()
}
function Test-CacheIntegrity {
if (!(Test-Path $script:CachePath) -or !(Test-Path $script:CacheHMACPath)) { return $false }
try {
$content = Get-Content $script:CachePath -Raw -ErrorAction Stop
$storedHmac = (Get-Content $script:CacheHMACPath -Raw -ErrorAction Stop).Trim()
$computedHmac = Get-CacheHMAC -Content $content
return ($storedHmac -eq $computedHmac)
} catch { return $false }
}
function Load-HashCache {
if (Test-Path $script:CachePath) {
if (Test-CacheIntegrity) {
try {
$json = Get-Content $script:CachePath -Raw -ErrorAction Stop | ConvertFrom-Json
$json.PSObject.Properties | ForEach-Object {
$script:HashCache[$_.Name] = $_.Value
}
Write-Log "Loaded hash cache: $($script:HashCache.Count) entries (HMAC valid)"
} catch {
Write-Log "Cache parse error, starting fresh." "WARN"
$script:HashCache = @{}
}
} else {
Write-Log "Cache HMAC mismatch - possible tampering. Discarding cache." "WARN"
Remove-Item $script:CachePath -Force -ErrorAction SilentlyContinue
Remove-Item $script:CacheHMACPath -Force -ErrorAction SilentlyContinue
$script:HashCache = @{}
}
}
}
function Save-HashCache {
try {
$json = $script:HashCache | ConvertTo-Json -Depth 3 -Compress
Set-Content -Path $script:CachePath -Value $json -Encoding UTF8 -Force
$hmac = Get-CacheHMAC -Content $json
Set-Content -Path $script:CacheHMACPath -Value $hmac -Encoding UTF8 -Force
} catch {}
}
function Get-CachedResult {
param([string]$Hash)
if ($script:HashCache.ContainsKey($Hash)) {
return $script:HashCache[$Hash]
}
return $null
}
function Set-CachedResult {
param([string]$Hash, [bool]$Malicious, [string]$Source, [string]$Detail)
$script:HashCache[$Hash] = @{
Malicious = $Malicious
Source = $Source
Detail = $Detail
CheckedAt = (Get-Date).ToString("o")
}
}
# --- File Index (path -> LastWriteTime + Size) ---
# Tracks which files have been processed. If unchanged, skip entirely - no hash, no API.
function Load-FileIndex {
if ((Test-Path $script:FileIndexPath) -and (Test-Path $script:FileIndexHMACPath)) {
try {
$content = Get-Content $script:FileIndexPath -Raw -ErrorAction Stop
$storedHmac = (Get-Content $script:FileIndexHMACPath -Raw -ErrorAction Stop).Trim()
$computedHmac = Get-CacheHMAC -Content $content
if ($storedHmac -ne $computedHmac) {
Write-Log "File index HMAC mismatch - full rescan required." "WARN"
Remove-Item $script:FileIndexPath -Force -ErrorAction SilentlyContinue
Remove-Item $script:FileIndexHMACPath -Force -ErrorAction SilentlyContinue
return
}
$json = $content | ConvertFrom-Json
$json.PSObject.Properties | ForEach-Object {
$script:FileIndex[$_.Name] = $_.Value
}
Write-Log "Loaded file index: $($script:FileIndex.Count) entries"
} catch {
Write-Log "File index load error, full rescan." "WARN"
$script:FileIndex = @{}
}
}
}
function Save-FileIndex {
try {
$json = $script:FileIndex | ConvertTo-Json -Depth 2 -Compress
Set-Content -Path $script:FileIndexPath -Value $json -Encoding UTF8 -Force
$hmac = Get-CacheHMAC -Content $json
Set-Content -Path $script:FileIndexHMACPath -Value $hmac -Encoding UTF8 -Force
} catch {}
}
function Test-FileNeedsScan {
param([System.IO.FileInfo]$File)
$key = $File.FullName.ToLower()
if ($script:FileIndex.ContainsKey($key)) {
$stored = $script:FileIndex[$key]
if ($stored.LastWrite -eq $File.LastWriteTime.ToString("o") -and $stored.Size -eq $File.Length) {
return $false
}
}
return $true
}
function Set-FileIndexed {
param([System.IO.FileInfo]$File, [string]$Hash)
$key = $File.FullName.ToLower()
$script:FileIndex[$key] = @{
LastWrite = $File.LastWriteTime.ToString("o")
Size = $File.Length
Hash = $Hash
}
}
Load-HashCache
Load-FileIndex
function Write-Log {
param([string]$Msg, [string]$Level = "INFO")
$entry = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] [$Level] $Msg"
Write-Host $entry -ForegroundColor $(switch($Level) { "THREAT" {"Red"} "WARN" {"Yellow"} "OK" {"Green"} default {"Gray"} })
$entry | Out-File -FilePath $script:LogFile -Append -Encoding UTF8
}
# --- API Checks ---
function Test-HashMalicious {
param([string]$Hash)
# 1. CIRCL Hashlookup - known good check
try {
$r = Invoke-RestMethod -Uri "https://hashlookup.circl.lu/lookup/sha256/$Hash" -Method Get -TimeoutSec 8 -ErrorAction Stop
if ($r.'hashlookup:trust' -and [int]$r.'hashlookup:trust' -gt 50) {
return @{ Malicious = $false; Source = "CIRCL"; Detail = "Trust=$($r.'hashlookup:trust')" }
}
} catch {}
# 2. MalwareBazaar - known bad check
try {
$body = @{ query = "get_info"; hash = $Hash } | ConvertTo-Json -Compress
$r = Invoke-RestMethod -Uri "https://mb-api.abuse.ch/api/v1/" -Method Post -Body $body -ContentType "application/json" -TimeoutSec 10 -ErrorAction Stop
if ($r.query_status -eq "hash_not_found") {
# not in malware DB
} elseif ($r.query_status -eq "ok" -or $r.query_status -eq "hash_found") {
return @{ Malicious = $true; Source = "MalwareBazaar"; Detail = "Known malware sample" }
}
} catch {}
# 3. Team Cymru MHR (DNS-based, uses SHA1/MD5 - skip for SHA256)
# Future: add MD5 hash and query via DNS TXT
return @{ Malicious = $false; Source = "None"; Detail = "Not found in threat databases" }
}
# --- File Scanner ---
function Invoke-FileScan {
param([string[]]$Paths)
Write-Log "Starting file scan across: $($Paths -join ', ')"
$scanned = 0; $threats = 0; $skipped = 0
$selfPath = $PSCommandPath.ToLower()
$dataPath = "$env:ProgramData\Antivirus".ToLower()
foreach ($scanPath in $Paths) {
if (!(Test-Path $scanPath)) { continue }
$files = Get-ChildItem -Path $scanPath -Include $script:ScanExtensions -Recurse -File -ErrorAction SilentlyContinue |
Where-Object { $_.Length -gt 0 -and $_.Length -lt 100MB -and
$_.FullName.ToLower() -ne $selfPath -and
!$_.FullName.ToLower().StartsWith($dataPath) }
foreach ($file in $files) {
# Tier 1: File index - skip if path+LastWrite+Size unchanged (no disk read)
if (!(Test-FileNeedsScan -File $file)) {
$skipped++
continue
}
# Tier 2: Hash the file, check hash cache (avoids API call)
try {
$hash = (Get-FileHash -Path $file.FullName -Algorithm SHA256 -ErrorAction Stop).Hash
} catch { continue }
$cached = Get-CachedResult -Hash $hash
if ($cached) {
# Known hash - apply verdict without API call
if ($cached.Malicious) {
Deny-Execution -FilePath $file.FullName
}
Set-FileIndexed -File $file -Hash $hash
$skipped++
continue
}
# Tier 3: Unknown hash - hit APIs
$scanned++
if ($scanned % 100 -eq 0) {
Write-Log "Progress: $scanned API-checked, $threats threats, $skipped skipped..."
if ($scanned % 500 -eq 0) { Save-HashCache; Save-FileIndex }
}
$result = Test-HashMalicious -Hash $hash
if ($result.Malicious) {
$threats++
Deny-Execution -FilePath $file.FullName
Write-Log "MALWARE: $($file.FullName) [$($result.Source): $($result.Detail)]" "THREAT"
}
Set-CachedResult -Hash $hash -Malicious $result.Malicious -Source $result.Source -Detail $result.Detail
Set-FileIndexed -File $file -Hash $hash
Start-Sleep -Milliseconds 50
}
}
Save-HashCache
Save-FileIndex
Write-Log "Scan complete. API-checked=$scanned, Threats=$threats, Skipped=$skipped" "OK"
}
# --- Execution Denial ---
function Deny-Execution {
param([string]$FilePath)
# Never block ourselves or our data
$selfPath = $PSCommandPath.ToLower()
$dataPath = "$env:ProgramData\Antivirus".ToLower()
$targetLower = $FilePath.ToLower()
if ($targetLower -eq $selfPath -or $targetLower.StartsWith($dataPath)) { return }
try {
$acl = Get-Acl -Path $FilePath
$denyRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
"Everyone", "ExecuteFile", "Deny"
)
$acl.AddAccessRule($denyRule)
Set-Acl -Path $FilePath -AclObject $acl
# Track blocked files for uninstall
$FilePath | Out-File -FilePath $script:BlockedListPath -Append -Encoding UTF8
Write-Log "Blocked execution: $FilePath" "WARN"
} catch {
Write-Log "Failed to block: $FilePath - $_" "WARN"
}
}
# --- Memory Scan ---
# Behavioral heuristics - detects injected/reflective/hollowed code regardless of naming
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public class MemoryScanner {
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(int access, bool inherit, int pid);
[DllImport("kernel32.dll")]
public static extern bool CloseHandle(IntPtr handle);
[DllImport("kernel32.dll")]
public static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, int dwLength);
[StructLayout(LayoutKind.Sequential)]
public struct MEMORY_BASIC_INFORMATION {
public IntPtr BaseAddress;
public IntPtr AllocationBase;
public uint AllocationProtect;
public IntPtr RegionSize;
public uint State;
public uint Protect;
public uint Type;
}
public const uint MEM_COMMIT = 0x1000;
public const uint MEM_PRIVATE = 0x20000;
public const uint PAGE_EXECUTE_READWRITE = 0x40;
public const uint PAGE_EXECUTE_WRITECOPY = 0x80;
public const int PROCESS_QUERY_INFORMATION = 0x0400;
public const int PROCESS_VM_READ = 0x0010;
public static int CountRWXRegions(int pid) {
int count = 0;
IntPtr handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid);
if (handle == IntPtr.Zero) return -1;
try {
IntPtr address = IntPtr.Zero;
MEMORY_BASIC_INFORMATION mbi;
while (VirtualQueryEx(handle, address, out mbi, Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION))) != 0) {
if (mbi.State == MEM_COMMIT && mbi.Type == MEM_PRIVATE &&
(mbi.Protect == PAGE_EXECUTE_READWRITE || mbi.Protect == PAGE_EXECUTE_WRITECOPY)) {
count++;
}
address = new IntPtr(mbi.BaseAddress.ToInt64() + mbi.RegionSize.ToInt64());
if (address.ToInt64() <= mbi.BaseAddress.ToInt64()) break;
}
} finally {
CloseHandle(handle);
}
return count;
}
}
"@ -ErrorAction SilentlyContinue
$script:SafeProcesses = @(
"system", "idle", "registry", "smss", "csrss", "wininit", "services",
"lsass", "svchost", "winlogon", "dwm", "explorer", "taskmgr",
"conhost", "fontdrvhost", "sihost", "runtimebroker", "taskhostw",
"searchindexer", "spoolsv", "dllhost", "wudfhost", "audiodg",
"powershell", "pwsh", "msedge", "chrome", "firefox", "code"
)
function Invoke-MemoryScan {
Write-Log "Memory scan started..."
$threats = 0
$selfPath = $PSCommandPath.ToLower()
Get-Process | Where-Object { $_.Id -notin @(0, 4, $PID) } | ForEach-Object {
$procName = $_.ProcessName.ToLower()
if ($script:SafeProcesses -contains $procName) { return }
# Skip other instances of this script
try {
$wmi = Get-CimInstance Win32_Process -Filter "ProcessId=$($_.Id)" -ErrorAction Stop
if ($wmi.CommandLine -and $wmi.CommandLine.ToLower().Contains($selfPath)) { return }
} catch {}
$flags = @()
# H1: No backing file on disk
if ([string]::IsNullOrEmpty($_.Path) -or !(Test-Path $_.Path -ErrorAction SilentlyContinue)) {
$flags += "NoBackingFile"
}
# H2: Image path vs main module mismatch (process hollowing)
try {
if ($_.Path -and $_.Modules.Count -gt 0 -and $_.Modules[0].FileName) {
if ($_.Modules[0].FileName.ToLower() -ne $_.Path.ToLower()) {
$flags += "ImageMismatch"
}
}
} catch {}
# H3: RWX private memory regions (shellcode/injection)
try {
$rwx = [MemoryScanner]::CountRWXRegions($_.Id)
if ($rwx -gt 5) { $flags += "RWX($rwx)" }
} catch {}
# H4: High private memory with few modules (injected payload)
if ($_.PrivateMemorySize64 -gt 800MB -and $_.Modules.Count -lt 8) {
$flags += "HighMemLowMods"
}
# H5: Unsigned binary in non-standard location
try {
if ($_.Path -and (Test-Path $_.Path)) {
$p = $_.Path.ToLower()
$standard = $p.StartsWith("c:\windows\") -or $p.StartsWith("c:\program files\") -or $p.StartsWith("c:\program files (x86)\")
if (!$standard) {
$sig = Get-AuthenticodeSignature -FilePath $_.Path -ErrorAction Stop
if ($sig.Status -ne "Valid") { $flags += "UnsignedNonStandard" }
}
}
} catch {}
# Verdict: 2+ flags = kill
if ($flags.Count -ge 2) {
$threats++
Write-Log "MEMORY THREAT: $($_.ProcessName) (PID:$($_.Id)) - $($flags -join ' | ')" "THREAT"
try { Stop-Process -Id $_.Id -Force } catch {}
}
}
Write-Log "Memory scan complete. Threats: $threats" $(if ($threats -gt 0) {"THREAT"} else {"OK"})
}
# --- Real-time Monitor ---
function Start-RealtimeMonitor {
Write-Log "Starting real-time file monitor..."
$drives = Get-CimInstance Win32_LogicalDisk | Where-Object { $_.DriveType -in (2, 3) }
$watchers = @()
foreach ($drive in $drives) {
$root = $drive.DeviceID + "\"
try {
$w = New-Object System.IO.FileSystemWatcher
$w.Path = $root
$w.IncludeSubdirectories = $true
$w.NotifyFilter = [System.IO.NotifyFilters]'FileName, LastWrite'
$w.EnableRaisingEvents = $true
$watchers += $w
$action = {
$path = $Event.SourceEventArgs.FullPath
if ($path -notmatch '\.(exe|dll|sys|scr|bat|cmd|ps1|vbs|js)$') { return }
Start-Sleep -Milliseconds 500
if (!(Test-Path $path)) { return }
try {
$hash = (Get-FileHash -Path $path -Algorithm SHA256 -ErrorAction Stop).Hash
# Check hash cache first
$cached = Get-CachedResult -Hash $hash
if ($cached) {
if ($cached.Malicious) {
Deny-Execution -FilePath $path
Write-Log "REALTIME THREAT (cached): $path" "THREAT"
}
$fi = Get-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue
if ($fi) { Set-FileIndexed -File $fi -Hash $hash; Save-FileIndex }
return
}
$result = Test-HashMalicious -Hash $hash
if ($result.Malicious) {
Deny-Execution -FilePath $path
Write-Log "REALTIME THREAT: $path [$($result.Source)]" "THREAT"
}
Set-CachedResult -Hash $hash -Malicious $result.Malicious -Source $result.Source -Detail $result.Detail
$fi = Get-Item -LiteralPath $path -Force -ErrorAction SilentlyContinue
if ($fi) { Set-FileIndexed -File $fi -Hash $hash }
Save-HashCache
Save-FileIndex
} catch {}
}
Register-ObjectEvent -InputObject $w -EventName Created -Action $action | Out-Null
Register-ObjectEvent -InputObject $w -EventName Changed -Action $action | Out-Null
Write-Log "Watching: $root"
} catch {
Write-Log "Could not watch $root" "WARN"
}
}
Write-Log "Real-time monitor active. Press Ctrl+C to stop." "OK"
try {
while ($true) {
Start-Sleep -Seconds 60
Invoke-MemoryScan
}
} finally {
$watchers | ForEach-Object { $_.Dispose() }
Write-Log "Monitor stopped."
}
}
# --- Persistence ---
function Install-Startup {
$scriptPath = $PSCommandPath
$taskName = "Antivirus"
$existing = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
if ($existing) {
Write-Log "Already installed as scheduled task."
return
}
# Method 1: PowerShell cmdlets
try {
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-ExecutionPolicy Bypass -WindowStyle Hidden -File `"$scriptPath`""
$trigger = New-ScheduledTaskTrigger -AtStartup
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
-StartWhenAvailable -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger `
-Principal $principal -Settings $settings -Description "Antivirus Monitor" -Force -ErrorAction Stop | Out-Null
Write-Log "Installed startup task (PowerShell method)." "OK"
return
} catch {
Write-Log "PowerShell task registration failed: $_ - trying schtasks..." "WARN"
}
# Method 2: schtasks.exe fallback
try {
$cmd = "schtasks /Create /TN `"$taskName`" /TR `"powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -File \`"$scriptPath\`"`" /SC ONSTART /RU SYSTEM /RL HIGHEST /F"
$result = cmd /c $cmd 2>&1
if ($LASTEXITCODE -eq 0) {
Write-Log "Installed startup task (schtasks fallback)." "OK"
} else {
Write-Log "schtasks also failed: $result" "WARN"
}
} catch {
Write-Log "All persistence methods failed: $_" "WARN"
}
}
function Uninstall-Antivirus {
Write-Log "Uninstalling Antivirus..."
$taskName = "Antivirus"
try {
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
Write-Log "Removed scheduled task." "OK"
} catch {}
try { schtasks /Delete /TN "$taskName" /F 2>&1 | Out-Null } catch {}
# Kill other running instances
try {
Get-CimInstance Win32_Process -Filter "Name='powershell.exe' OR Name='pwsh.exe'" -ErrorAction SilentlyContinue |
Where-Object { $_.ProcessId -ne $PID -and $_.CommandLine -like "*Antivirus*" } |
ForEach-Object {
Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue
Write-Log "Stopped running instance (PID:$($_.ProcessId))" "OK"
}
} catch {}
# Restore execution permissions on blocked files
Write-Log "Restoring execution permissions on blocked files..."
if (Test-Path $script:BlockedListPath) {
try {
Get-Content $script:BlockedListPath -ErrorAction Stop | ForEach-Object {
$f = $_.Trim()
if ($f -and (Test-Path $f)) {
try {
$acl = Get-Acl -Path $f
$denyRules = $acl.Access | Where-Object {
$_.AccessControlType -eq "Deny" -and $_.FileSystemRights -match "ExecuteFile"
}
foreach ($rule in $denyRules) { $acl.RemoveAccessRule($rule) | Out-Null }
Set-Acl -Path $f -AclObject $acl
} catch {}
}
}
} catch {}
}
Write-Log "Uninstall complete. Data at $env:ProgramData\Antivirus can be deleted manually." "OK"
exit 0
}
# --- Main ---
Write-Log "=== Antivirus Starting ===" "OK"
if ($Uninstall) { Uninstall-Antivirus }
Install-Startup
$drives = (Get-CimInstance Win32_LogicalDisk | Where-Object { $_.DriveType -eq 3 }).DeviceID | ForEach-Object { "$_\" }
Invoke-FileScan -Paths $drives
Invoke-MemoryScan
Start-RealtimeMonitor
Editor is loading...
Leave a Comment