diff --git a/README.md b/README.md index 83ab0f6..b108617 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ ![Version](https://img.shields.io/badge/version-2.0.2-00a2ed?style=flat) ![License](https://img.shields.io/badge/license-MIT-green?style=flat) +**[Project site](https://devcop95.github.io/pullgoscript/)** — the same information, laid out as a browsable reference: every route with the flag that unlocks it, the twelve audit controls, how to verify a release, and the security model with its two documented limits. The download table reads the newest release from the GitHub API, so it does not go stale between releases. + **OBLITERATUS** answers one question — *would Mimikatz work against this host?* — and answers it with evidence: registry state, token privileges, service configuration and browser store exposure, each mapped to the exact Mimikatz module and MITRE technique it corresponds to. It ships as a single binary with two modes: diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/docs/audit.html b/docs/audit.html new file mode 100644 index 0000000..6f94794 --- /dev/null +++ b/docs/audit.html @@ -0,0 +1,196 @@ + + + + + +Audit checks — OBLITERATUS + + + + + +
+
+ OBLITERATUS + +
+
+ +
+ +

The attack-surface audit

+

+ Twelve controls, each one a condition Mimikatz depends on, each carrying a stable ID, + a severity, the evidence behind the verdict, a remediation and the exact module it + corresponds to. This is the part that runs without any flag — it is read-only and it is + the reason the tool exists. +

+ +
obliteratus.exe --audit          # human-readable, exit 1 when a CRITICAL/HIGH control fails
+obliteratus.exe --audit --json   # machine-readable
+obliteratus.exe --audit --csv    # spreadsheet
+ +
+ Checks degrade to unknown, never to a false pass. + If a privilege or an asset is missing, the control reports that it could not see, because an + audit that guesses is worse than one that admits a gap. +
+ +

The twelve controls

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Verified against a live report on a fully patched Windows 11 host (elevated context).
IDControlMimikatzTechniqueSeverity
MK-WDIGESTWDigest does not cache plaintext credentialssekurlsa::wdigestT1003.001CRITICAL
MK-AUTOLOGON-PASSNo cleartext password stored in Winlogonwinlogon\DefaultPasswordT1552.001CRITICAL
MK-LSASS-PPLLSASS runs as a protected process (PPL)sekurlsa::logonpasswordsT1003.001HIGH
MK-CREDGUARDCredential Guard / virtualization-based security enabledsekurlsa::logonpasswordsT1003.001HIGH
MK-SEDEBUGSeDebugPrivilege not enabled in the current tokenprivilege::debugT1134.001HIGH
MK-AVEndpoint protection installed and enabled—T1562.001HIGH
MK-PSLOGPowerShell Script Block Logging enabled—T1059.001MEDIUM
MK-LAPSManaged local admin password (LAPS) in use—T1078.002MEDIUM
MK-AUTOLOGONAutomatic logon disabledlsadump::secretsT1003.004MEDIUM
MK-SYSMONSysmon or equivalent EDR telemetry installed——LOW
MK-CACHEDLOGONSCached domain logon count reviewedlsadump::cacheT1003.005INFO
MK-CONTEXTAudit execution context — elevated or not——INFO
+
+ +

Exit-code contract

+ +
+ + + + + + +
Worst severity foundExit codeMeaning for a pipeline
CRITICAL or HIGH failing1Gate the merge / fail the hardening review
MEDIUM, LOW, INFO only0Report, do not fail
+
+ +
+ Expect exit 1 on a healthy workstation, and read the report before you call it a + false alarm. On a fully patched, fully elevated Windows 11 host the live report shows + MK-SEDEBUG failing as HIGH: the token carries + SeDebugPrivilege but has not enabled it, and a high-integrity admin token can + enable it whenever it wants — which is precisely the point the control is making. The + contract is about the worst severity, so that one HIGH is what makes a scripted run exit + non-zero. +
+ +

Report shape

+ +

+ Findings carry id, title, severity, + status, evidence, remediation, + technique and module, alongside + tool, version, hostname, + generated_at, elevated, modules and a + summary count per severity. Stable IDs mean you can diff two runs across a + remediation window and see exactly which control moved. +

+ +

+ Adding a check is one function plus one line in RunChecks — see + CONTRIBUTING.md. + The audit package never imports server; progress is reported through + an Emitter callback so the checks stay unit-testable. +

+ +
+ + + + + diff --git a/docs/capabilities.html b/docs/capabilities.html new file mode 100644 index 0000000..b3aac6a --- /dev/null +++ b/docs/capabilities.html @@ -0,0 +1,208 @@ + + + + + +Capabilities & gates — OBLITERATUS + + + + + +
+
+ OBLITERATUS + +
+
+ +
+ +

Capabilities & gates

+

+ The complete API surface — all 29 routes — with the flag that unlocks each one. Nothing + here is hidden behind a flag you cannot see: a locked route answers + 403 and returns the exact flag that would open it. +

+ +
+ Every request below is a loopback request to 127.0.0.1:8080 and carries + X-Signal-Token: <per-run token>. The token is shown in the dashboard page + and printed (short form) at startup; it is regenerated on every launch. + See Security model for what that token does and does not protect. +
+ +

Page and channel routes

+ +
+ + + + + + + + +
No token: these cannot carry one on first load. The loopback Host check is what makes them safe to serve.
RouteGuardWhat it serves
GET /loopback HostThe dashboard (token injected into the page, same-origin only)
GET /healthzloopback HostLiveness, ok
GET /wsloopback Host + loopback Origin + ?t=WebSocket console channel; browsers cannot attach headers to a WS handshake, so the token travels in the query string
+
+ +

Always available (token only)

+ +
+ + + + + + + + + + + + +
Compiled in, reachable with the per-run token, no flag required.
RouteWhat it doesTechnique
GET /v1/sys/stExecution context: reports USER or ADMIN for the process token—
GET /v1/sys/b2Live process table (JSON) for PID targeting—
GET /v1/sys/hexFirst 256 bytes of the payload file, hex-encoded — payload integrity inspection—
POST /v1/sys/cmdCommand console: executes a shell command as the token holder—
POST /v1/sys/optTelemetry/privacy hardening: writes HKLM policy (AllowTelemetry, WER) and stops + disables DiagTrack. No flag, and it takes no body.—
POST /v1/audit/reportThe audit checklist as JSON—
POST /v1/audit/csvThe audit checklist as CSV—
+
+ +

Credential collection — --enable-collect

+ +
+ + + + + + + + + + + + + + + + +
Auto-elevates via UAC at startup: the SAM hives, the LSASS minidump and the browser App-Bound key unwrap cannot work with a filtered token.
RouteWhat it doesTechnique
GET /v1/sys/a7Browser credential inventoryT1555.003
GET /v1/sys/cookiesBrowser cookie inventoryT1555.003
GET /v1/sys/nexusIdentity Nexus: the identity picture assembled from local browser storesT1555.003
POST /v1/op/a7Streamed credential collectionT1555.003
POST /v1/op/cookiesStreamed cookie collectionT1555.003
POST /v1/sys/samExports the local SAM and SYSTEM hives with RegSaveKeyEx and walks the [MS-SAMR] hash chainT1003.002
POST /v1/op/lsassLSASS minidump — the sekurlsa::logonpasswords pathT1003.001
POST /v1/op/lsass-wipeShreds the collected LSASS materialT1003.001
POST /v1/op/roast-asrepAS-REP roasting against accounts without pre-authT1558.004
POST /v1/op/roast-tgsKerberoasting: requests service tickets for SPNsT1558.003
POST /v1/op/spn-enumSPN enumeration, the recon step before roastingT1558.003
+
+ +

Persistence — --enable-persist

+ +
+ + + + + + + + +
Four backends behind one manager, each mapped to its technique.
RouteWhat it doesTechnique
GET /v1/sys/persistEnumerates all four backends — Run keys, Startup folder, scheduled tasks, WMI subscriptions—
POST /v1/op/persist-addCreates one foothold: registry Run key, Startup folder file, schtasks entry, or a CommandLineEventConsumerT1547.001 · T1547.009 · T1053.005 · T1546.003
POST /v1/op/persist-delRemoves one; a missing entry is not an errorsame four
+
+ +

Self-evasion — --enable-evasion

+ +
+ + + + + + + +
Affects this process only. Both patches are logged with the resolved address, never applied silently.
RouteWhat it doesTechnique
POST /v1/op/etwNeuters ntdll!EtwEventWrite in the tool's own process (W^X-safe transition)T1562.006
POST /v1/op/amsiPatches amsi!AmsiScanBuffer in the tool's own processT1562.001
+
+ +

+ Repatching is refused with 422 already patched rather than applied twice — a + blind second patch would corrupt the prologue it already rewrote. +

+ +

Lateral movement — --enable-lateral

+ +
+ + + + + + + +
Credentials come from the operator in the request body; nothing is stored.
RouteWhat it doesTechnique
POST /v1/op/lateral-winrmRuns a command on a remote host over WS-Management (SOAP, Basic auth)T1021.006
POST /v1/op/lateral-scmRemote execution through the Service Control Manager: create, start, deleteT1021.002 · T1569.002
+
+ +

Process injection — --enable-inject

+ +
+ + + + + +
RouteWhat it doesTechnique
POST /v1/op/d4Halo's Gate indirect-syscall injection into an operator-selected local PID. amd64 only. RW → RX transition: no RWX page ever exists.T1055
+
+ +

+ The payload is unpacked in memory right before the injection. Pack it with + oblpack.exe -in payloads\research_signal.bin -key <passphrase> and start the + console with the same --payload-key. The envelope is + magic || nonce || AES-256-GCM, so the on-disk bytes match no public signature. + The research note on SSN recovery and the W^X design is in + docs/HALOS_GATE.md. +

+ +

Examples

+ +

Request shapes as the handlers enforce them; a wrong shape answers 400 with the expected body.

+ +
# execution context and process table
+curl -s -H "X-Signal-Token: $TOK" http://127.0.0.1:8080/v1/sys/st
+curl -s -H "X-Signal-Token: $TOK" http://127.0.0.1:8080/v1/sys/b2
+
+# command console (runs as the token holder)
+curl -s -X POST -H "X-Signal-Token: $TOK" \
+     -d '{"c":"whoami"}' http://127.0.0.1:8080/v1/sys/cmd
+
+# persistence: list, then add a Run key
+curl -s -H "X-Signal-Token: $TOK" http://127.0.0.1:8080/v1/sys/persist
+curl -s -X POST -H "X-Signal-Token: $TOK" \
+     -d '{"type":"run","hive":"HKCU","name":"x","cmd":"C:\\path\\to\\binary.exe"}' \
+     http://127.0.0.1:8080/v1/op/persist-add
+
+# lateral movement over WinRM
+curl -s -X POST -H "X-Signal-Token: $TOK" \
+     -d '{"host":"10.0.0.5","user":"CORP\\svc","password":"…","command":"whoami"}' \
+     http://127.0.0.1:8080/v1/op/lateral-winrm
+
+# injection into a selected PID (amd64)
+curl -s -X POST -H "X-Signal-Token: $TOK" \
+     -d '{"pid":1234}' http://127.0.0.1:8080/v1/op/d4
+ +
+ Two things worth knowing before you unlock anything. + POST /v1/sys/opt modifies the machine — HKLM policy and the + DiagTrack service — and unlike every other state-changing capability it requires + no flag, only the token. And the token itself is handed to any process that can reach + loopback and fetch the dashboard page, so the console is a single-user tool by design: + the boundary is the machine, not the user account. Both are documented under + Security model. +
+ +
+ + + + + diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..8b5d936 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,216 @@ + + + + + +OBLITERATUS — Windows credential exposure auditor + + + + + +
+
+ OBLITERATUS + +
+
+ +
+ +

Windows Credential Exposure Auditor

+

The Mimikatz playbook, run as a defensive checklist.

+ +
+ Windows 10/11 · amd64 & arm64 + Go 1.26.9+ + MIT + v2.0.2 + cosign keyless signature + not Authenticode-signed +
+ + OBLITERATUS local dashboard + +

+ OBLITERATUS answers one question — would Mimikatz work against this host? — + and answers it with evidence: registry state, token privileges, service configuration + and browser store exposure, each mapped to the exact Mimikatz module and MITRE ATT&CK + technique it corresponds to. +

+ +

+ It ships as a single binary with two modes. The headless audit is safe to run in CI and + exits non-zero when a CRITICAL or HIGH control is failing. The dashboard is a local + console on 127.0.0.1 with a per-run 256-bit token. +

+ +
+ + + + + + + +
Two modes, one binary
ModeCommandAudience
Headless auditobliteratus.exe --auditCI, scripted hardening reviews, blue-team baselines
Local dashboardobliteratus.exeOperators who want the interactive console on loopback
+
+ +
+ Every offensive capability is compiled in but locked. + A locked route answers 403 and names the flag that would unlock it, until the + operator passes that flag explicitly. Unlocking prints a + [!] ... UNLOCKED banner at startup — the effect is never silent. + The full route-by-route gate is in Capabilities. +
+ +

Download

+ +

+ Pinned list for v2.0.2. With JavaScript available this is replaced by the live + release from the GitHub API. +

+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
v2.0.2 — SHA-256 as published in checksums.txt
FileSizeSHA-256
obliteratus_2.0.2_windows_amd64.zip5.8 MiB90b2e6e5794ed17892ccded42df81ea74840ca40b50370bd9fc7e117d9cff3c8
obliteratus_2.0.2_windows_arm64.zip5.4 MiBae47be1d2bbce867b6e5f08de35be2e3d90649ffa8bf9c7bdecc1930cb077160
checksums.txt428 B—
checksums.txt.sig · checksums.txt.pem—cosign keyless signature + certificate
+
+

+ All releases on GitHub → +

+
+ +

Each archive contains obliteratus.exe, oblpack.exe, the README, SECURITY.md and the license.

+ +

Verify before you run

+ +
    +
  1. + Integrity. +
    sha256sum -c checksums.txt
    +# PowerShell: Get-FileHash -Algorithm SHA256 .\obliteratus_2.0.2_windows_amd64.zip
    +
  2. +
  3. + Provenance. The checksum file is signed keyless with cosign, bound to the + workflow run and logged in the Sigstore transparency log. Pin the identity — a bare + Verified OK only says someone signed it: +
    cosign verify-blob \
    +  --certificate checksums.txt.pem \
    +  --signature   checksums.txt.sig \
    +  --certificate-identity-regexp "^https://github.com/DevCop95/pullgoscript/" \
    +  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
    +  checksums.txt
    +
  4. +
  5. + Dependencies. An SPDX SBOM ships per archive: +
    syft convert obliteratus_2.0.2_windows_amd64.sbom.json -o table
    +
  6. +
+ +
+ Windows will still complain, and that is not a build failure. + The .exe files are not Authenticode-signed: + Get-AuthenticodeSignature reports NotSigned, SmartScreen shows + "Windows protected your PC", and Defender may quarantine on sight of the injection or + credential-collection strings. A trusted Authenticode signature needs an EV certificate or + an Azure Trusted Signing account — a paid, identity-verified credential this repository + cannot hold. Until one exists, the cosign signature is the honest guarantee: + this binary came from this repository's release workflow, even though Windows + will not validate it natively. Details and the operator/local signing path are in + Verify. +
+ +

Build from source

+ +
git clone https://github.com/DevCop95/pullgoscript
+cd pullgoscript
+go build -o bin\obliteratus.exe .\cmd\obliteratus
+ +

+ Prefer this to the archives if you want to read the code you are about to run. The + toolchain floor is Go 1.26.9 — the standard library ships inside the binary, so an old + toolchain is a vulnerable dependency. +

+ +

Where to look next

+ +
+ +

Capabilities & gates

+

All 29 routes: what each one does, the flag that unlocks it, and its MITRE technique.

+
+ +

Audit checks

+

The 12 controls, their severity, the Mimikatz module each maps to, and the exit-code contract.

+
+ +

Verify a release

+

What the pipeline produces, the exact cosign command, and the Authenticode gap.

+
+ +

Security model

+

What v1 got wrong, what v2 does instead, and where the trust boundary sits.

+
+
+ +

Authorized use only

+ +

+ OBLITERATUS is for authorized security assessment, research and education on systems you + own or are contracted to test. The credential-collection, persistence, evasion, + lateral-movement and injection modules are deliberately locked; unlocking them is an + explicit acknowledgement that your engagement permits their use. Unauthorized access to + computer systems is illegal. +

+ +

+ MIT © DevCop95 (Yared Henríquez). Source, issues and releases: + github.com/DevCop95/pullgoscript. +

+ +
+ + + + + + diff --git a/docs/security.html b/docs/security.html new file mode 100644 index 0000000..d3b3e5f --- /dev/null +++ b/docs/security.html @@ -0,0 +1,209 @@ + + + + + +Security model — OBLITERATUS + + + + + +
+
+ OBLITERATUS + +
+
+ +
+ +

Security model

+

+ A tool that demonstrates credential theft has to survive the question "what if it is pointed + at me?". v1 did not; v2 was rebuilt around the answer, and the test suite pins each control + closed. +

+ +

What v1 got wrong

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Severity as assessed against the v1 code.
Sevv1 problemv2 control
CRITListened on :8080 — the console was LAN-reachableBinds 127.0.0.1 only; ListenAndServe refuses a non-loopback address
CRITToken derived from the PID — visible to any process — and echoed to unauthenticated GET /256-bit crypto/rand token, regenerated per run, compared in constant time
CRITCheckOrigin: return true — any web page could open the WebSocket and drive the consoleOrigin must be loopback and the ?t= token must match
CRITNo Host validation — DNS rebinding could read the token out of the pageEvery route rejects a non-loopback Host
HIGHMixed GET mutations, no method enforcementServeMux method patterns: automatic 405 on the wrong verb
HIGHToken in HTML readable cross-origin (no CSP, no headers)Same-origin only, plus CSP, nosniff, DENY framing, no-store
HIGHDOM XSS: database-derived URLs rendered through innerHTMLAll server data escaped through window.esc() before it reaches the DOM
MEDAuto-UAC on every startElevation only when asked for: --elevate, or a module that cannot work unelevated
MEDCredentialed collectors reachable by defaultLocked behind --enable-*, failing closed with an actionable 403
MEDPrivileged modules failed opaquely — SAM errors looked like "no SAM material", LSASS failures hung the UIThe real cause is streamed to the dashboard (sam_error / lsass_error), UAC is requested up front, and a relaunch that yields no elevated token fails loudly instead of looping
+
+ +

Where the boundary sits

+ +

+ Anyone who can read the dashboard page — loopback, same origin — is the + operator. That is the design: the console answers to the token, and the token is a lock on the + door, not on the room. +

+ +

Against a hostile web page, the boundary is airtight, and each layer is independent:

+ +
+
+

It can reach the port

+

But a cross-origin fetch() cannot read the response — same-origin policy.

+
+
+

It cannot set the header

+

The custom X-Signal-Token header forces a CORS preflight, which is never approved, so the request is never sent. This is what protects state-changing routes from CSRF.

+
+
+

It cannot guess the token

+

256 bits from crypto/rand, regenerated per run, compared in constant time.

+
+
+

Rebinding gets nothing

+

A rebinding page reaches the port with its own hostname in Host, which is refused before any handler runs.

+
+
+ +

Two limits, documented rather than hidden

+ +
+ 1. The token defends against remote pages, not against local processes. + GET / serves the dashboard with the token inside it, and that route is guarded only + by the loopback Host check. Any process that can reach loopback — including one + running as a different local user, since loopback is machine-scoped — can read the token + and then use POST /v1/sys/cmd to execute commands as the user running the console. + The practical consequence is a deployment rule: run the console on a single-user + workstation. On a shared or RDP host it is a cross-user escalation path. +
+ +
+ 2. POST /v1/sys/opt changes the machine with no flag. + It writes HKLM policy and stops + disables the DiagTrack service, and unlike every + other state-changing capability it requires only the token — there is no + --enable-* gate, because the action is privacy hardening rather than credential + access. It is listed on the Capabilities page so nobody + discovers it by accident. +
+ +

+ Both are recorded as open design questions in + issue #6, with the options and + their trade-offs, rather than left to be discovered by whoever reads the code next. +

+ +

Intentional capabilities

+ +

+ These are features, not bugs — they are the reason the tool exists: +

+ + + +

+ They are reachable only over loopback, only with the per-run token, and the credential, + persistence, evasion, lateral and injection modules additionally require their flag at startup. + Hardening regressions in that chain are in scope for vulnerability reports. +

+ +

Out of scope

+ +

+ Physical access, kernel-level attackers, and the operator console itself once the token holder + is trusted. Reports about the intentional capabilities above are answered with a pointer to this + page unless they show the gate failing — a route reachable without its flag, a + non-loopback bind, a missing Host or Origin check, a token comparison that leaks, or a + state-changing route that a hostile page can reach. +

+ +

Reporting a vulnerability

+ +

+ Report privately, never in a public issue with exploit details: GitHub → + Security → Report a vulnerability on + DevCop95/pullgoscript, + or open a minimal issue titled [SECURITY] asking for a private channel. Include the + affected version or commit, impact, reproduction steps and any suggested fix. Expect an + acknowledgement within 72 hours and a status update within 10 business days; fixes are credited + in the release notes unless you prefer otherwise. The full policy is in + SECURITY.md. +

+ +
+ + + + + diff --git a/docs/site.css b/docs/site.css new file mode 100644 index 0000000..20c1a40 --- /dev/null +++ b/docs/site.css @@ -0,0 +1,231 @@ +/* OBLITERATUS docs — hand-written, no build step, no external requests. */ + +:root { + --bg: #0c0e12; + --bg-1: #12151b; + --bg-2: #171b23; + --fg: #dfe4ec; + --fg-dim: #939cab; + --line: #262c37; + --accent: #e2544a; + --accent-2: #f0a04b; + --ok: #4fb477; + --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; + --sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; +} + +* { box-sizing: border-box; } + +html { -webkit-text-size-adjust: 100%; } + +body { + margin: 0; + background: var(--bg); + color: var(--fg); + font: 16px/1.65 var(--sans); +} + +a { color: var(--accent-2); text-decoration: none; } +a:hover { text-decoration: underline; } + +code, kbd, pre, .mono { font-family: var(--mono); } + +code { + background: var(--bg-2); + border: 1px solid var(--line); + border-radius: 4px; + padding: 1px 5px; + font-size: 0.88em; +} + +pre { + background: var(--bg-1); + border: 1px solid var(--line); + border-left: 3px solid var(--accent); + border-radius: 6px; + padding: 14px 16px; + overflow-x: auto; + font-size: 13.5px; + line-height: 1.6; +} +pre code { background: none; border: 0; padding: 0; font-size: inherit; } + +/* ------------------------------------------------------------------ layout */ + +.wrap { max-width: 1080px; margin: 0 auto; padding: 0 22px; } + +header.top { + border-bottom: 1px solid var(--line); + background: rgba(12, 14, 18, 0.92); + position: sticky; + top: 0; + backdrop-filter: blur(6px); + z-index: 10; +} + +header.top .wrap { + display: flex; + align-items: center; + justify-content: space-between; + gap: 18px; + flex-wrap: wrap; + padding-top: 12px; + padding-bottom: 12px; +} + +.brand { + font-family: var(--mono); + font-weight: 700; + letter-spacing: 0.14em; + color: var(--fg); + font-size: 15px; +} +.brand span { color: var(--accent); } + +nav.main { display: flex; gap: 16px; flex-wrap: wrap; font-size: 14.5px; } +nav.main a { color: var(--fg-dim); } +nav.main a:hover, nav.main a[aria-current="page"] { color: var(--fg); } + +main { padding: 34px 0 60px; } + +footer { + border-top: 1px solid var(--line); + color: var(--fg-dim); + font-size: 13.5px; + padding: 22px 0 40px; +} +footer .wrap { display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; } + +/* --------------------------------------------------------------- typography */ + +h1 { + font-size: 34px; + line-height: 1.15; + margin: 0 0 6px; + letter-spacing: -0.01em; +} +h2 { + font-size: 22px; + margin: 40px 0 12px; + padding-top: 14px; + border-top: 1px solid var(--line); +} +h3 { font-size: 17px; margin: 26px 0 8px; } +p { margin: 12px 0; } + +.lede { font-size: 18px; color: var(--fg-dim); margin: 0 0 18px; } +.tagline { color: var(--accent-2); font-family: var(--mono); font-size: 14px; letter-spacing: 0.02em; } +.note { color: var(--fg-dim); font-size: 14px; } + +/* -------------------------------------------------------------------- pills */ + +.pills { display: flex; gap: 8px; flex-wrap: wrap; margin: 14px 0 0; } +.pill { + font-family: var(--mono); + font-size: 12px; + color: var(--fg-dim); + border: 1px solid var(--line); + background: var(--bg-1); + border-radius: 999px; + padding: 3px 10px; + white-space: nowrap; +} +.pill.hot { color: var(--accent); border-color: #40221f; } +.pill.warm { color: var(--accent-2); border-color: #3d2c19; } +.pill.ok { color: var(--ok); border-color: #1e3a2a; } + +/* ------------------------------------------------------------------- tables */ + +.tw { overflow-x: auto; margin: 16px 0; border: 1px solid var(--line); border-radius: 6px; } + +table { border-collapse: collapse; width: 100%; font-size: 14.5px; } + +caption { + caption-side: top; + text-align: left; + color: var(--fg-dim); + font-size: 13px; + padding: 10px 12px 0; +} + +th, td { text-align: left; padding: 9px 12px; border-bottom: 1px solid var(--line); vertical-align: top; } + +thead th { + background: var(--bg-2); + font-size: 12.5px; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--fg-dim); + font-weight: 600; + white-space: nowrap; +} + +tbody tr:last-child td { border-bottom: 0; } +tbody tr:hover { background: var(--bg-1); } + +td.rt, th.rt { font-family: var(--mono); font-size: 13px; white-space: nowrap; } +td.sev { font-family: var(--mono); font-size: 12.5px; white-space: nowrap; } +.sev-crit { color: var(--accent); font-weight: 600; } +.sev-high { color: var(--accent-2); font-weight: 600; } +.sev-med { color: #d9c04a; } +.sev-low { color: var(--fg-dim); } +.sev-info { color: #5b8bbf; } + +.gate-none { color: var(--fg-dim); } +.gate-flag { color: var(--accent-2); font-family: var(--mono); font-size: 12.5px; white-space: nowrap; } + +/* --------------------------------------------------------------- components */ + +.shot { width: 100%; border: 1px solid var(--line); border-radius: 8px; margin: 20px 0 6px; } + +.callout { + border: 1px solid var(--line); + border-left: 3px solid var(--accent-2); + background: var(--bg-1); + border-radius: 6px; + padding: 14px 16px; + margin: 18px 0; + font-size: 14.5px; +} +.callout.warn { border-left-color: var(--accent); } +.callout strong { color: var(--fg); } + +.cardgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 14px; margin: 18px 0; } +.card { border: 1px solid var(--line); background: var(--bg-1); border-radius: 6px; padding: 14px 16px; } +.card h3 { margin: 0 0 6px; font-size: 15px; } +.card p { margin: 0; font-size: 14px; color: var(--fg-dim); } + +.steps { counter-reset: s; list-style: none; padding: 0; margin: 16px 0; } +.steps > li { position: relative; padding-left: 34px; margin-bottom: 14px; } +.steps > li::before { + counter-increment: s; + content: counter(s); + position: absolute; + left: 0; + top: 2px; + width: 22px; + height: 22px; + border-radius: 50%; + border: 1px solid var(--line); + background: var(--bg-2); + color: var(--fg-dim); + font-family: var(--mono); + font-size: 12px; + display: flex; + align-items: center; + justify-content: center; +} + +dl.entry { margin: 0; } +dl.entry dt { + font-family: var(--mono); + font-size: 12.5px; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--fg-dim); + margin-top: 10px; +} +dl.entry dd { margin: 2px 0 0; } + +.hash { font-family: var(--mono); font-size: 12.5px; color: var(--fg-dim); word-break: break-all; } +noscript .callout { border-left-color: var(--accent); } diff --git a/docs/site.js b/docs/site.js new file mode 100644 index 0000000..9a13ea4 --- /dev/null +++ b/docs/site.js @@ -0,0 +1,109 @@ +// Reads the newest release from the GitHub API so the download table never +// goes stale between releases. +// +// Every string that comes from the API is written with textContent, never +// innerHTML, and every href is checked against this repo's own asset prefix +// before it is assigned. The console's v1 had a DOM XSS from rendering +// server data through innerHTML; a docs page fed by an API deserves the same +// discipline. +(function () { + "use strict"; + + var REPO = "DevCop95/pullgoscript"; + var API = "https://api.github.com/repos/" + REPO + "/releases/latest"; + var PREFIX = "https://github.com/" + REPO + "/releases/download/"; + + var mount = document.getElementById("dl-live"); + var fallback = document.getElementById("dl-fallback"); + var status = document.getElementById("dl-status"); + if (!mount || !status) return; + + function el(tag, text, cls) { + var n = document.createElement(tag); + if (text !== null && text !== undefined) n.textContent = text; + if (cls) n.className = cls; + return n; + } + + function safeURL(u) { + return typeof u === "string" && u.indexOf(PREFIX) === 0 ? u : null; + } + + function human(bytes) { + if (typeof bytes !== "number") return ""; + if (bytes < 1024) return bytes + " B"; + if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + " KiB"; + return (bytes / (1024 * 1024)).toFixed(1) + " MiB"; + } + + function parseSums(text) { + var out = {}; + String(text || "").split("\n").forEach(function (line) { + var m = line.trim().split(/\s+/); + if (m.length >= 2) out[m[1]] = m[0]; + }); + return out; + } + + function render(release, sums) { + var assets = (release.assets || []).filter(function (a) { + return safeURL(a.browser_download_url); + }); + if (!assets.length) throw new Error("the latest release has no assets"); + + var table = el("table"); + table.appendChild(el("caption", "Latest release: " + release.tag_name + + ", published " + String(release.published_at || "").slice(0, 10))); + + var thead = el("thead"); + var htr = el("tr"); + ["File", "Size", "SHA-256"].forEach(function (h) { htr.appendChild(el("th", h)); }); + thead.appendChild(htr); + table.appendChild(thead); + + var tbody = el("tbody"); + assets.forEach(function (a) { + var tr = el("tr"); + + var td = el("td"); + var link = el("a", a.name); + link.href = safeURL(a.browser_download_url); + td.appendChild(link); + tr.appendChild(td); + + tr.appendChild(el("td", human(a.size))); + tr.appendChild(el("td", sums[a.name] || "\u2014", "hash")); + tbody.appendChild(tr); + }); + table.appendChild(tbody); + + while (mount.firstChild) mount.removeChild(mount.firstChild); + mount.appendChild(table); + if (fallback) fallback.hidden = true; + status.textContent = "Read live from the GitHub API, so this table follows " + + "the newest release without a rebuild."; + } + + fetch(API, { headers: { Accept: "application/vnd.github+json" } }) + .then(function (r) { + if (!r.ok) throw new Error("the API answered " + r.status); + return r.json(); + }) + .then(function (release) { + var sums = (release.assets || []).filter(function (a) { + return a.name === "checksums.txt"; + })[0]; + var sumsURL = sums ? safeURL(sums.browser_download_url) : null; + if (!sumsURL) return render(release, {}); + return fetch(sumsURL) + .then(function (r) { return r.ok ? r.text() : ""; }) + .then(function (t) { render(release, parseSums(t)); }) + .catch(function () { render(release, {}); }); + }) + .catch(function (err) { + status.textContent = "Could not read the release list automatically (" + + err.message + "). The pinned list below is the v2.0.2 snapshot; the " + + "Releases page is always current."; + if (fallback) fallback.hidden = false; + }); +})(); diff --git a/docs/verify.html b/docs/verify.html new file mode 100644 index 0000000..ef7c4c3 --- /dev/null +++ b/docs/verify.html @@ -0,0 +1,190 @@ + + + + + +Verify a release — OBLITERATUS + + + + + +
+
+ OBLITERATUS + +
+
+ +
+ +

Verify a release

+

+ OBLITERATUS ships offensive modules, so the artifacts will trip SmartScreen and Defender no + matter what. This page is about the guarantees that can be made cryptographically, + and the one that cannot be made yet. +

+ +

What the pipeline produces

+ +

+ .github/workflows/release.yml runs on every v* tag and emits, + per release: +

+ +
+ + + + + + + + + +
ArtifactWhat it proves
obliteratus_<ver>_windows_<arch>.zipThe binaries plus README, SECURITY.md and LICENSE
checksums.txtSHA-256 of every archive
checksums.txt.sigcosign keyless signature over checksums.txt
checksums.txt.pemThe short-lived certificate binding that signature to the workflow run
*.sbom.jsonSPDX SBOM (syft) — full dependency inventory per archive
+
+ +
+ There is no long-lived signing key in this repository. The keyless signature + is bound to the GitHub Actions OIDC identity of the workflow run and recorded in the Sigstore + Rekor transparency log, so it cannot be forged without compromising GitHub's + OIDC issuer — and any use of that identity is publicly logged. +
+ +

Verify a download

+ +
    +
  1. + Install cosign — docs.sigstore.dev +
  2. +
  3. + Checksum integrity. +
    sha256sum -c checksums.txt
    +# PowerShell: Get-FileHash -Algorithm SHA256 .\obliteratus_2.0.2_windows_amd64.zip
    +
  4. +
  5. + Signature, pinned to the exact workflow identity. +
    cosign verify-blob \
    +  --certificate checksums.txt.pem \
    +  --signature   checksums.txt.sig \
    +  --certificate-identity-regexp "^https://github.com/DevCop95/pullgoscript/" \
    +  --certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
    +  checksums.txt
    +# → "Verified OK"
    +
  6. +
  7. + Transparency log entry — proves it was logged, not merely signed. +
    rekor-cli get --artifact checksums.txt
    +
  8. +
+ +
+ The identity flag is the crux. A bare Verified OK only says + someone signed the file. Pinning the identity to DevCop95/pullgoscript is + what stops a signature from any other repository being accepted. For v2.0.2 the certificate + subject is exactly: +
https://github.com/DevCop95/pullgoscript/.github/workflows/release.yml@refs/tags/v2.0.2
+ Use --certificate-identity (singular, exact match) to pin one tag instead of the + repository. Note that the subject embeds the repository name at the time of the release, + so a later rename invalidates an exact pin — which is why the release workflow builds its + self-check from ${GITHUB_REPOSITORY} and the command above uses the repo-prefix + regexp. +
+ +

+ cosign v3 prints Flag --certificate has been deprecated… for the two flags above. + That is expected: the release ships the detached .sig + .pem pair and + the warning refers to the newer single-file --bundle form. The verification result + is unaffected. +

+ +

Reviewing the dependency inventory

+ +
syft convert obliteratus_2.0.2_windows_amd64.sbom.json -o table
+# or grep for the modules you actually care about
+ +

Authenticode: not applied

+ +

The .exe files are not Authenticode-signed. Concretely:

+ + + +

+ This is expected for an unsigned offensive tool and is not a build failure. + A signature Windows actually trusts needs an EV certificate or an + Azure Trusted Signing account — a paid, identity-verified credential that must + live in a secret. No placeholder certificate is checked into CI, because a placeholder would + prove nothing while looking like it did. +

+ +

+ Until that credential exists, the cosign signature is the honest guarantee: + this binary came from this repository's release workflow, even though Windows will not + validate it natively. The gap is tracked publicly in + issue #5. +

+ +

If you hold a certificate

+ +

You can sign a locally built binary today with a cert in CurrentUser\My:

+ +
# $cert = (Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert)[0]
+signtool sign `
+  /fd SHA256 `
+  /tr http://timestamp.digicert.com /td SHA256 `
+  /n "Your Company Name" `
+  .\obliteratus.exe .\oblpack.exe
+
+Get-AuthenticodeSignature .\obliteratus.exe   # → Valid
+ +

+ Timestamping with /tr is not optional: without it the signature dies the moment the + certificate expires. +

+ +
+ + + + + + + + + +
Supply-chain status
StepState
cosign keyless signature over checksums.txtlive · v2.0.2 verified
SPDX SBOM per archivelive
SHA-256 checksumslive
Authenticode on the .exe filesblocked on a certificate
+
+ +

What provenance is not

+ +

+ The cosign guarantee is about provenance — which repository and workflow built + this — not about safety. OBLITERATUS is dual-use by design. Provenance, a + readable SBOM and a pinned toolchain are the whole point; reading the code you are about to run + is still the stronger move, and building from source is documented on the + front page. +

+ +
+ + + + +