Skip to content

Commit d778a88

Browse files
committed
v0.5.0: PS profile injection, root CA detection, report improvements
New detections: - PowerShell profile injection detection (T1546.013) - Root certificate store anomaly detection (T1553.004) Report improvements: - Category breakdown chart (stacked bars by severity) - Remediation click-to-copy now matches system commands (sfc, netsh, etc.) Docs: version bump across README/SECURITY/CLAUDE.md, CHANGELOG entry Made-with: Cursor
1 parent c849a85 commit d778a88

9 files changed

Lines changed: 194 additions & 8 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ desktop.ini
1717
.claude/
1818
*.swp
1919
*.swo
20+
claude_code_prompt.md

AmIHacked.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ if ($script:NonInteractive) {
8080
$script:RedactMap = @{}
8181
$script:SuppressedCount = 0
8282

83-
$script:Version = "0.4.9"
83+
$script:Version = "0.5.0"
8484

8585
# ── Helpers (loaded first) ───────────────────────────────────────────────────
8686

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

7+
## [0.5.0] - 2026-03-16
8+
9+
### Added
10+
- **PowerShell profile injection detection** -- scans all four `$PROFILE` paths for suspicious patterns (IEX, encoded commands, download cradles, etc.) and emits WARNING + T1546.013
11+
- **Root certificate store anomaly detection** -- compares `Cert:\LocalMachine\Root` against 46 well-known CA name fragments; flags unknown root CAs as WARNING + T1553.004 (catches rogue MITM certs)
12+
- **Category breakdown chart in HTML report** -- stacked horizontal bar chart showing finding counts per module/category, placed between the stats grid and system info
13+
- **Improved remediation click-to-copy** -- regex now also matches system commands (`sfc`, `netsh`, `reg`, `certutil`, `dism`, etc.) and the `Import-` verb prefix
14+
15+
### Changed
16+
- Version bumped to 0.5.0
17+
- SECURITY.md updated to mark 0.5.x as supported, 0.3.x as unsupported
18+
719
## [0.4.9] - 2026-03-15
820

921
### Added

CLAUDE.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,17 @@ $summary = $jsonLine | ConvertFrom-Json
134134
| `Check-Network.ps1` | External connections, reverse-DNS, AbuseIPDB lookups, firewall rules |
135135
| `Check-Accounts.ps1` | Hidden/new accounts, brute-force indicators, RDP history, LSA protection |
136136
| `Check-FileSystem.ps1` | Modified system binaries, temp-dir executables, VirusTotal lookups, ADS, 8 persistence mechanisms |
137-
| `Check-DefenseEvasion.ps1` | Cleared event logs, AMSI tampering, Defender status, ETW tampering |
137+
| `Check-DefenseEvasion.ps1` | Cleared event logs, AMSI tampering, Defender status, ETW tampering, PS profile injection, root CA anomalies |
138+
139+
## Prompt Handoff (Cursor ↔ Claude Code)
140+
141+
`claude_code_prompt.md` (gitignored) is the shared prompt file. Cursor writes implementation prompts to it; Claude Code reads and executes them via:
142+
143+
```
144+
Read claude_code_prompt.md and follow all instructions in it.
145+
```
146+
147+
The file is overwritten for each new prompt. Claude Code prompts must **not** change versioning, documentation, or changelog -- Cursor handles those after reviewing the diff.
138148

139149
## Code Conventions
140150

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
[![PowerShell 5.1+](https://img.shields.io/badge/PowerShell-5.1%2B-0d1117?style=for-the-badge&logo=powershell&logoColor=5391FE)](https://docs.microsoft.com/powershell/)
1313
[![Windows 10/11](https://img.shields.io/badge/Windows-10%20%2F%2011-0d1117?style=for-the-badge&logo=windows&logoColor=white)](https://www.microsoft.com/windows)
1414
[![License: MIT](https://img.shields.io/badge/License-MIT-0d1117?style=for-the-badge&logoColor=white)](LICENSE)
15-
[![Version](https://img.shields.io/badge/Version-0.4.9-FF6B6B?style=for-the-badge)](#changelog)
15+
[![Version](https://img.shields.io/badge/Version-0.5.0-FF6B6B?style=for-the-badge)](#changelog)
1616

1717
[![Zero Dependencies](https://img.shields.io/badge/Dependencies-Zero-0d1117?style=flat-square&labelColor=0d1117)](#)
1818
[![MITRE ATT&CK](https://img.shields.io/badge/MITRE%20ATT%26CK-40%2B%20Techniques-ff3333?style=flat-square&labelColor=0d1117)](#mitre-attck-coverage)
@@ -140,7 +140,7 @@ Baselines enable **change detection** — the most powerful signal for catching
140140

141141
```
142142
---AMIHACKED-SUMMARY-JSON---
143-
{"verdict":"CAUTION","critical":0,"warning":3,"info":12,"suppressed":0,"total":15,"duration":28.4,"reportPath":"...","version":"0.4.9"}
143+
{"verdict":"CAUTION","critical":0,"warning":3,"info":12,"suppressed":0,"total":15,"duration":28.4,"reportPath":"...","version":"0.5.0"}
144144
```
145145

146146
- Exit code reflects findings: **0** = clean, **1** = warnings only, **2** = critical findings detected
@@ -170,7 +170,7 @@ Non-interactive environments (e.g. piped through `powershell.exe -NonInteractive
170170
</tr>
171171
<tr>
172172
<td><strong>Defense Evasion</strong></td>
173-
<td>Cleared event logs, AMSI tampering, Defender real-time protection & tamper protection, ETW autologger tampering, DisableAntiSpyware policy</td>
173+
<td>Cleared event logs, AMSI tampering, Defender real-time protection & tamper protection, ETW autologger tampering, DisableAntiSpyware policy, PowerShell profile injection, root certificate store anomalies</td>
174174
</tr>
175175
</table>
176176

@@ -268,7 +268,7 @@ Every finding is tagged with technique IDs from the [MITRE ATT&CK](https://attac
268268
| **Execution** | T1059.001, T1204.002 |
269269
| **Persistence** | T1053.005, T1136.001, T1197, T1543.003, T1546.003, T1546.010, T1546.012, T1546.015, T1547.001, T1547.004 |
270270
| **Privilege Escalation** | T1574.001, T1574.009 |
271-
| **Defense Evasion** | T1036.001, T1036.005, T1070.001, T1562.001, T1562.002, T1562.004, T1564, T1564.002, T1564.004 |
271+
| **Defense Evasion** | T1036.001, T1036.005, T1070.001, T1546.013, T1553.004, T1562.001, T1562.002, T1562.004, T1564, T1564.002, T1564.004 |
272272
| **Credential Access** | T1003.001, T1003.002, T1003.003, T1110, T1110.001, T1555, T1555.003 |
273273
| **Discovery** | T1078.003 |
274274
| **Lateral Movement** | T1021.001 |

SECURITY.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44

55
| Version | Supported |
66
|---------|-----------|
7+
| 0.5.x | Yes |
78
| 0.4.x | Yes |
8-
| 0.3.x | Yes |
9+
| 0.3.x | No |
910
| < 0.3 | No |
1011

1112
## Scope

_commit_msg.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
v0.5.0: PS profile injection, root CA detection, report improvements
2+
3+
New detections:
4+
- PowerShell profile injection detection (T1546.013)
5+
- Root certificate store anomaly detection (T1553.004)
6+
7+
Report improvements:
8+
- Category breakdown chart (stacked bars by severity)
9+
- Remediation click-to-copy now matches system commands (sfc, netsh, etc.)
10+
11+
Docs: version bump across README/SECURITY/CLAUDE.md, CHANGELOG entry

lib/ReportGenerator.ps1

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,11 @@ function Generate-HtmlReport {
119119
$remediationHtml = ""
120120
if ($finding.Remediation) {
121121
$escapedRemediation = $finding.Remediation -replace '<','&lt;' -replace '>','&gt;'
122-
$remediationWithCopy = $escapedRemediation -replace '((?:Remove-|Set-|Disable-|Enable-|Get-|Stop-|Start-|Update-|New-|Add-|Unregister-)[A-Za-z\-]+(?:\s+[^\r\n]*?)?)(?=\s*$|\.)', '<code class="ps-cmd" onclick="copyCmd(this)">$1</code>'
122+
$remediationWithCopy = $escapedRemediation -replace '(?:^|(?<=\s))((Remove-|Set-|Disable-|Enable-|Get-|Stop-|Start-|Update-|New-|Add-|Unregister-|Import-)[A-Za-z\-]+(?:\s+[^\r\n]*?)?)(?=\s*$|\.(?:\s|$))', '<code class="ps-cmd" onclick="copyCmd(this)">$1</code>'
123+
$sysPattern = @'
124+
(?:^|(?<=[\s'"]))((sfc|netsh|reg|certutil|bitsadmin|wevtutil|bcdedit|sc|icacls|takeown|dism)\s+[^\r\n.]*?)(?=\s*$|\.(?:\s|$))
125+
'@
126+
$remediationWithCopy = $remediationWithCopy -replace $sysPattern, '<code class="ps-cmd" onclick="copyCmd(this)">$1</code>'
123127
$remediationHtml = "<div class='finding-remediation'><span class='remediation-label'>Remediation:</span> $remediationWithCopy</div>"
124128
}
125129

@@ -148,6 +152,39 @@ function Generate-HtmlReport {
148152
"@
149153
}
150154

155+
$categoryChartHtml = ""
156+
if ($categories.Count -gt 0) {
157+
$maxCatCount = ($categories | ForEach-Object { $_.Count } | Measure-Object -Maximum).Maximum
158+
$chartRows = ""
159+
foreach ($cat in $categories) {
160+
$catCritC = ($cat.Group | Where-Object { $_.Severity -eq "CRITICAL" }).Count
161+
$catWarnC = ($cat.Group | Where-Object { $_.Severity -eq "WARNING" }).Count
162+
$catInfoC = ($cat.Group | Where-Object { $_.Severity -eq "INFO" }).Count
163+
$catTotalC = $cat.Count
164+
165+
$catChartName = switch ($cat.Name) {
166+
"Process" { "Process &amp; Service Analysis" }
167+
"Network" { "Network Indicators" }
168+
"Account" { "Account &amp; Authentication" }
169+
"FileSystem" { "File System Red Flags" }
170+
"DefenseEvasion" { "Defense Evasion &amp; Anti-Forensics" }
171+
"Baseline" { "Baseline Comparison" }
172+
default { $cat.Name }
173+
}
174+
175+
$critPct = if ($maxCatCount -gt 0) { [math]::Round(($catCritC / $maxCatCount) * 100, 1) } else { 0 }
176+
$warnPct = if ($maxCatCount -gt 0) { [math]::Round(($catWarnC / $maxCatCount) * 100, 1) } else { 0 }
177+
$infoPct = if ($maxCatCount -gt 0) { [math]::Round(($catInfoC / $maxCatCount) * 100, 1) } else { 0 }
178+
179+
$critSeg = if ($catCritC -gt 0) { "<div class='chart-bar-segment' style='width:${critPct}%;background:var(--critical)'></div>" } else { "" }
180+
$warnSeg = if ($catWarnC -gt 0) { "<div class='chart-bar-segment' style='width:${warnPct}%;background:var(--warning)'></div>" } else { "" }
181+
$infoSeg = if ($catInfoC -gt 0) { "<div class='chart-bar-segment' style='width:${infoPct}%;background:var(--info)'></div>" } else { "" }
182+
183+
$chartRows += "<div class='chart-row'><span class='chart-label'>$catChartName</span><div class='chart-bar-bg'>$critSeg$warnSeg$infoSeg</div><span class='chart-count'>$catTotalC</span></div>"
184+
}
185+
$categoryChartHtml = "<div class='category-chart'>$chartRows</div>"
186+
}
187+
151188
$html = @"
152189
<!DOCTYPE html>
153190
<html lang="en">
@@ -389,6 +426,14 @@ function Generate-HtmlReport {
389426
.stat-total .stat-value { color: var(--text-primary); }
390427
.stat-suppressed .stat-value { color: var(--text-secondary); }
391428
429+
/* -- Category Chart -- */
430+
.category-chart { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 1rem 1.5rem; margin-bottom: 1.5rem; max-height: 200px; overflow-y: auto; }
431+
.chart-row { display: flex; align-items: center; gap: 0.75rem; padding: 0.3rem 0; font-size: 0.8rem; }
432+
.chart-label { flex: 0 0 200px; color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
433+
.chart-bar-bg { flex: 1; height: 14px; background: var(--bg-secondary); border-radius: 4px; display: flex; overflow: hidden; }
434+
.chart-bar-segment { height: 100%; }
435+
.chart-count { flex: 0 0 2rem; text-align: right; color: var(--text-muted); font-size: 0.75rem; }
436+
392437
/* -- System Info -- */
393438
.system-info {
394439
background: var(--bg-card);
@@ -737,6 +782,8 @@ function Generate-HtmlReport {
737782
$(if ($SuppressedCount -gt 0) { "<div class=`"stat-card stat-suppressed`"><div class=`"stat-value`">$SuppressedCount</div><div class=`"stat-label`">Suppressed</div></div>" })
738783
</div>
739784
785+
${categoryChartHtml}
786+
740787
<div class="system-info">
741788
<div><span class="label">Computer</span><span class="value">$($SystemInfo.ComputerName)</span></div>
742789
<div><span class="label">User</span><span class="value">$($SystemInfo.Domain)\$($SystemInfo.UserName)</span></div>

modules/Check-DefenseEvasion.ps1

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,4 +245,108 @@ function Invoke-DefenseEvasionChecks {
245245
Write-Verbose "Could not check Defender TamperProtection: $_"
246246
}
247247

248+
# ── 6. PowerShell Profile Injection ──────────────────────────────────
249+
250+
Write-Status "Checking PowerShell profiles for injection..."
251+
252+
try {
253+
$profilePaths = @(
254+
@{ Name = "AllUsersAllHosts"; Path = $PROFILE.AllUsersAllHosts },
255+
@{ Name = "AllUsersCurrentHost"; Path = $PROFILE.AllUsersCurrentHost },
256+
@{ Name = "CurrentUserAllHosts"; Path = $PROFILE.CurrentUserAllHosts },
257+
@{ Name = "CurrentUserCurrentHost"; Path = $PROFILE.CurrentUserCurrentHost }
258+
)
259+
260+
$suspiciousPatterns = @(
261+
'Download',
262+
'IEX\b',
263+
'Invoke-Expression',
264+
'\-[Ee]nc\b',
265+
'\-EncodedCommand',
266+
'Net\.WebClient',
267+
'Start-Process\s+.*-WindowStyle\s+Hidden',
268+
'[Hh]idden',
269+
'[Bb]ypass',
270+
'New-Object\s+System\.Net',
271+
'DownloadString',
272+
'DownloadFile',
273+
'Invoke-WebRequest',
274+
'Invoke-RestMethod',
275+
'bitstransfer',
276+
'FromBase64String'
277+
)
278+
$combinedPattern = ($suspiciousPatterns -join '|')
279+
280+
foreach ($profile in $profilePaths) {
281+
if (-not $profile.Path -or -not (Test-Path $profile.Path)) { continue }
282+
283+
$content = Get-Content $profile.Path -Raw -ErrorAction SilentlyContinue
284+
if (-not $content) { continue }
285+
286+
$matched = $suspiciousPatterns | Where-Object { $content -match $_ }
287+
if ($matched) {
288+
$fileInfo = Get-Item $profile.Path -ErrorAction SilentlyContinue
289+
Add-Finding -Severity "WARNING" -Category "DefenseEvasion" `
290+
-Title "Suspicious PowerShell Profile: $($profile.Name)" `
291+
-Description "The PowerShell profile '$($profile.Name)' at '$($profile.Path)' contains suspicious patterns. Attackers inject code into PS profiles to execute malicious commands on every PowerShell session. Matched patterns: $($matched -join ', ')." `
292+
-Remediation "Review the profile content: Get-Content '$($profile.Path)'. Remove suspicious lines or rename the file." `
293+
-Details @{
294+
Path = $profile.Path
295+
ProfileName = $profile.Name
296+
FileSize = if ($fileInfo) { $fileInfo.Length } else { 0 }
297+
LastModified = if ($fileInfo) { $fileInfo.LastWriteTime } else { $null }
298+
MatchedPatterns = $matched
299+
} `
300+
-MITRE @("T1546.013")
301+
}
302+
}
303+
} catch {
304+
Write-Verbose "Could not check PowerShell profiles: $_"
305+
}
306+
307+
# ── 7. Certificate Store Anomaly Detection ───────────────────────────
308+
309+
Write-Status "Checking root certificate store for anomalies..."
310+
311+
try {
312+
$knownCAs = @(
313+
'Microsoft', 'DigiCert', 'GlobalSign', 'Comodo', 'Sectigo', 'VeriSign',
314+
'Symantec', 'GeoTrust', 'Thawte', "Let's Encrypt", 'ISRG Root', 'Entrust',
315+
'GoDaddy', 'Starfield', 'Amazon', 'Baltimore CyberTrust', 'Cybertrust',
316+
'QuoVadis', 'USERTrust', 'AAA Certificate Services', 'AddTrust', 'Certum',
317+
'CNNIC', 'D-TRUST', 'eMudhra', 'E-Tugra', 'Hongkong Post', 'SECOM',
318+
'SwissSign', 'T-TeleSec', 'TWCA', 'TeliaSonera', 'Actalis', 'Buypass',
319+
'Certigna', 'IdenTrust', 'NetLock', 'OISTE', 'WISeKey', 'SSL.com',
320+
'Trustwave', 'Staat der Nederlanden', 'Government', 'AC RAIZ', 'ACCVRAIZ',
321+
'CFCA', 'XRamp'
322+
)
323+
324+
$rootCerts = Get-ChildItem "Cert:\LocalMachine\Root" -ErrorAction SilentlyContinue
325+
foreach ($cert in $rootCerts) {
326+
$subjectOrFriendly = if ($cert.Subject) { $cert.Subject } else { $cert.FriendlyName }
327+
$isKnown = $false
328+
foreach ($ca in $knownCAs) {
329+
if ($subjectOrFriendly -like "*$ca*") { $isKnown = $true; break }
330+
}
331+
if (-not $isKnown) {
332+
$displayName = if ($cert.Subject -match 'CN=([^,]+)') { $Matches[1] } else { $cert.Subject.Substring(0, [math]::Min(60, $cert.Subject.Length)) }
333+
Add-Finding -Severity "WARNING" -Category "DefenseEvasion" `
334+
-Title "Unknown Root CA: $displayName" `
335+
-Description "Root certificate '$($cert.Subject)' in Cert:\LocalMachine\Root is not in the well-known CA list. This could be a corporate proxy CA (benign) or a rogue certificate installed by malware to intercept HTTPS traffic via MITM." `
336+
-Remediation "Review the certificate in certlm.msc. If unexpected, remove it: Remove-Item 'Cert:\LocalMachine\Root\$($cert.Thumbprint)'" `
337+
-Details @{
338+
Subject = $cert.Subject
339+
Thumbprint = $cert.Thumbprint
340+
NotBefore = $cert.NotBefore
341+
NotAfter = $cert.NotAfter
342+
FriendlyName = $cert.FriendlyName
343+
Issuer = $cert.Issuer
344+
} `
345+
-MITRE @("T1553.004")
346+
}
347+
}
348+
} catch {
349+
Write-Status "Could not check root certificate store: $_" -Color Yellow
350+
}
351+
248352
}

0 commit comments

Comments
 (0)