<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://velkris.red/feed.xml" rel="self" type="application/atom+xml" /><link href="https://velkris.red/" rel="alternate" type="text/html" /><updated>2026-07-28T02:49:52+00:00</updated><id>https://velkris.red/feed.xml</id><title type="html">VELKRIS</title><subtitle>Red team tradecraft, malware analysis, and evasion research.</subtitle><author><name>Velkris</name></author><entry><title type="html">An update, and where this is going</title><link href="https://velkris.red/posts/2026/07/27/an-update-and-where-this-is-going/" rel="alternate" type="text/html" title="An update, and where this is going" /><published>2026-07-27T00:00:00+00:00</published><updated>2026-07-27T00:00:00+00:00</updated><id>https://velkris.red/posts/2026/07/27/an-update-and-where-this-is-going</id><content type="html" xml:base="https://velkris.red/posts/2026/07/27/an-update-and-where-this-is-going/"><![CDATA[<p>When I started this site last October, the plan was to post lab notes and track
a certification journey. That plan changed, for the better.</p>

<p>Republishing course material and lab walkthroughs isn’t something I’m interested
in doing - it isn’t mine to give away, and the internet has enough copied notes
already. What I’d actually found useful, over months of study, was forcing
myself to explain a mechanism from scratch until it held together. So that’s what
this became: original write-ups on how things <em>actually</em> work, built from public
research and specifications, in my own words.</p>

<p>The first batch is up:</p>

<ul>
  <li><strong>Kerberos, end to end</strong> - and the ticket attacks (golden, silver, and
diamond) that fall straight out of the protocol.</li>
  <li><strong>Delegation abuse</strong> and <strong>domain dominance</strong> - the Active Directory paths, and
the persistence that outlives a password reset.</li>
  <li><strong>AMSI internals</strong>, <strong>imphash</strong>, <strong>entropy</strong>, and <strong>TLSH</strong> - the analysis and
detection side I spend most of my time in.</li>
</ul>

<p>Alongside the writing, the <a href="/projects/">Projects</a> page now covers the two tools I
maintain - <strong>OffsetInspect</strong> and <strong>OffsetScan</strong> - both read-only, both built for
detection engineering and malware triage without ever touching endpoint
protection.</p>

<p>Everything here stays defensive and educational. The goal isn’t a wall of
tradecraft; it’s a small, honest set of explanations I’d have wanted when I was
first figuring these out. More to come.</p>

<ul>
  <li>Velkris</li>
</ul>]]></content><author><name>Velkris</name></author><category term="meta" /><summary type="html"><![CDATA[When I started this site last October, the plan was to post lab notes and track a certification journey. That plan changed, for the better.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://velkris.red/assets/img/og.png" /><media:content medium="image" url="https://velkris.red/assets/img/og.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">TLSH: fuzzy hashing for malware clustering</title><link href="https://velkris.red/posts/2026/07/25/tlsh-fuzzy-hashing-for-malware-clustering/" rel="alternate" type="text/html" title="TLSH: fuzzy hashing for malware clustering" /><published>2026-07-25T00:00:00+00:00</published><updated>2026-07-25T00:00:00+00:00</updated><id>https://velkris.red/posts/2026/07/25/tlsh-fuzzy-hashing-for-malware-clustering</id><content type="html" xml:base="https://velkris.red/posts/2026/07/25/tlsh-fuzzy-hashing-for-malware-clustering/"><![CDATA[<p>A cryptographic hash is designed so that flipping one bit scrambles the entire
digest. That’s perfect for integrity and useless for similarity - change a byte
in a sample and its SHA-256 tells you nothing about how close the two files are.
<strong>Fuzzy hashing</strong> inverts that property: similar inputs produce similar digests,
so you can measure <em>distance</em> between files. TLSH (Trend Micro Locality Sensitive
Hash) is the one I reach for at corpus scale.</p>

<h2 id="the-idea">The idea</h2>

<p>TLSH slides a window across the file, populates a table of buckets based on byte
triplets, and derives a compact digest from the statistical distribution of those
buckets - plus a header capturing overall properties like length and quartile
ratios. Two files built from mostly the same bytes populate the buckets
similarly, so their digests land close together.</p>

<p>Comparison isn’t equality; it’s a <strong>distance score</strong>. Zero means effectively
identical; the number climbs as files diverge. You pick a threshold and cluster
everything within it:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>offsetscan cluster ./samples --recurse --threshold 40
# lower threshold  = tighter, fewer clusters
# ClusterId shared = near-duplicates; null = a singleton
</code></pre></div></div>

<h2 id="why-it-matters-for-triage">Why it matters for triage</h2>

<p>Exact-hash dedup collapses only byte-identical files. A repacked or recompiled
variant has a fresh SHA-256 and slips the net - but TLSH still parks it next to
its siblings. Across a few thousand samples that turns an undifferentiated pile
into a handful of families, and a new arrival can be scored against known
clusters instead of analyzed cold.</p>

<p>A few honest limits. TLSH needs a <strong>minimum amount of data and variance</strong> - very
small or very uniform files won’t produce a usable digest. Threshold choice is a
judgment call: too loose and unrelated families merge, too tight and genuine
variants split. And like imphash, similarity is a <em>lead</em>, not attribution - pair
it with import hashing and the build-toolchain fingerprint so agreement across
independent axes carries the weight, not any single score. It shines exactly
where single-file inspection can’t answer the question: <em>which of these thousands
are the same thing wearing different clothes?</em></p>]]></content><author><name>Velkris</name></author><category term="malware-analysis" /><category term="detection" /><summary type="html"><![CDATA[A cryptographic hash is designed so that flipping one bit scrambles the entire digest. That’s perfect for integrity and useless for similarity - change a byte in a sample and its SHA-256 tells you nothing about how close the two files are. Fuzzy hashing inverts that property: similar inputs produce similar digests, so you can measure distance between files. TLSH (Trend Micro Locality Sensitive Hash) is the one I reach for at corpus scale.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://velkris.red/assets/img/og.png" /><media:content medium="image" url="https://velkris.red/assets/img/og.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">How Kerberoasting works</title><link href="https://velkris.red/posts/2026/07/21/how-kerberoasting-works/" rel="alternate" type="text/html" title="How Kerberoasting works" /><published>2026-07-21T00:00:00+00:00</published><updated>2026-07-21T00:00:00+00:00</updated><id>https://velkris.red/posts/2026/07/21/how-kerberoasting-works</id><content type="html" xml:base="https://velkris.red/posts/2026/07/21/how-kerberoasting-works/"><![CDATA[<p>Kerberoasting (MITRE ATT&amp;CK <strong>T1558.003</strong>) is one of those techniques that
survives because it abuses the protocol exactly as designed. Any authenticated
domain user can request a service ticket for an account that has a Service
Principal Name, and part of that ticket is encrypted with a key derived from the
service account’s password. Request it, take it offline, and crack it - no
elevated rights, no touching the target service.</p>

<h2 id="the-protocol-detail-it-leans-on">The protocol detail it leans on</h2>

<p>In Kerberos, when a user wants to reach a service, the KDC issues a <strong>TGS</strong>
(service ticket). A portion of that ticket is encrypted with the <em>service
account’s</em> long-term key. For a machine account that key is random and
effectively uncrackable. But when the SPN is registered to a <strong>regular user
account</strong> - a SQL service running as <code class="language-plaintext highlighter-rouge">svc_sql</code>, say - the key derives from a
human-chosen password. That’s the whole game.</p>

<p>The attacker never needs to authenticate <em>to</em> the service. They just ask the KDC
for the ticket, which the KDC will hand to any valid domain principal:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. Enumerate accounts that have an SPN set (LDAP: servicePrincipalName=*).
2. Request a TGS for each SPN.
3. Extract the encrypted blob and crack it offline against a wordlist.
</code></pre></div></div>

<p>Because step 3 is offline, there’s no lockout, no failed-logon noise on the
target, and no rate limit.</p>

<h2 id="why-rc4-makes-it-worse">Why RC4 makes it worse</h2>

<p>Tickets encrypted with <strong>RC4-HMAC (etype 23)</strong> are far faster to crack than
AES, and a downgrade to RC4 is often possible. Defenders should watch for a spike
in <strong>TGS requests (event 4769)</strong> for RC4 etype, especially many distinct SPNs
from one principal in a short window.</p>

<h2 id="cutting-it-off">Cutting it off</h2>

<ul>
  <li>Give service accounts <strong>long, random passwords</strong> - 25+ characters makes
offline cracking impractical. Group Managed Service Accounts (gMSAs) do this
for you and rotate automatically.</li>
  <li><strong>Retire unnecessary SPNs.</strong> An account with no SPN can’t be roasted.</li>
  <li><strong>Disable RC4</strong> where the environment allows, forcing AES etypes.</li>
  <li>Alert on 4769 patterns rather than trying to block the request itself - the
request is legitimate Kerberos traffic, which is precisely why the technique
endures.</li>
</ul>]]></content><author><name>Velkris</name></author><category term="kerberos" /><category term="active-directory" /><summary type="html"><![CDATA[Kerberoasting (MITRE ATT&amp;CK T1558.003) is one of those techniques that survives because it abuses the protocol exactly as designed. Any authenticated domain user can request a service ticket for an account that has a Service Principal Name, and part of that ticket is encrypted with a key derived from the service account’s password. Request it, take it offline, and crack it - no elevated rights, no touching the target service.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://velkris.red/assets/img/og.png" /><media:content medium="image" url="https://velkris.red/assets/img/og.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">AMSI internals: where the scan actually happens</title><link href="https://velkris.red/posts/2026/07/15/amsi-internals/" rel="alternate" type="text/html" title="AMSI internals: where the scan actually happens" /><published>2026-07-15T00:00:00+00:00</published><updated>2026-07-15T00:00:00+00:00</updated><id>https://velkris.red/posts/2026/07/15/amsi-internals</id><content type="html" xml:base="https://velkris.red/posts/2026/07/15/amsi-internals/"><![CDATA[<p>The Antimalware Scan Interface is the reason a malicious script often gets caught
<em>after</em> it’s been decoded but <em>before</em> it runs. Knowing exactly where AMSI sits
in that pipeline explains both why it’s effective and why it’s brittle.</p>

<h2 id="where-it-sits">Where it sits</h2>

<p>AMSI is a thin, in-process API that scripting hosts call to submit content for
inspection. PowerShell, the Windows Script Host, the .NET runtime, and Office VBA
all wire into it. The important property is <em>timing</em>: the host calls AMSI with the
content it’s <strong>about to execute</strong>, after any obfuscation or decoding the host
itself performs. So a Base64-wrapped, string-concatenated payload is handed to
AMSI in its final, deobfuscated form - which is why “just encode it” rarely
beats AMSI on its own.</p>

<p>The two functions that matter are <code class="language-plaintext highlighter-rouge">AmsiScanBuffer</code> and <code class="language-plaintext highlighter-rouge">AmsiScanString</code>. The host
passes the bytes; AMSI forwards them to the registered antimalware provider
(Microsoft Defender by default, via a COM provider interface) and returns an
<code class="language-plaintext highlighter-rouge">AMSI_RESULT</code>. A value at or above <code class="language-plaintext highlighter-rouge">AMSI_RESULT_DETECTED</code> (32768) means block.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Script host ──(deobfuscated content)──► amsi.dll::AmsiScanBuffer
amsi.dll   ──(COM)──► registered provider (e.g. Defender)
provider   ──► AMSI_RESULT  →  clean / detected
</code></pre></div></div>

<p>Everything is <strong>in memory and in-process</strong>. Nothing is written to disk, which is
what lets a scanner inspect content that only ever exists at runtime.</p>

<h2 id="why-its-brittle---at-a-high-level">Why it’s brittle - at a high level</h2>

<p>Because the scan happens inside the host’s own process, the trust boundary is
weak by design. The publicly documented failure classes are:</p>

<ul>
  <li><strong>In-process tampering</strong> - patching the scan routine in the module loaded into
your own process so it always returns “clean.”</li>
  <li><strong>Provider/registration weaknesses</strong> - interfering with how the provider is
loaded or invoked.</li>
  <li><strong>Content-level evasion</strong> - breaking up the signatured content so the final
buffer no longer matches, which works only against brittle exact-literal
signatures.</li>
</ul>

<p>The defensive reading is the useful one: AMSI is a strong <em>tripwire on decoded
content</em>, but it’s not a security boundary against code already running with your
privileges. Treat an AMSI hit as high-value telemetry, correlate it with the
process and command line that produced it, and test whether your own signatures
survive trivial content mutation - a robust signature keys on structure, not on a
single literal string.</p>]]></content><author><name>Velkris</name></author><category term="detection" /><category term="evasion" /><summary type="html"><![CDATA[The Antimalware Scan Interface is the reason a malicious script often gets caught after it’s been decoded but before it runs. Knowing exactly where AMSI sits in that pipeline explains both why it’s effective and why it’s brittle.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://velkris.red/assets/img/og.png" /><media:content medium="image" url="https://velkris.red/assets/img/og.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Domain dominance: persistence after Domain Admin</title><link href="https://velkris.red/posts/2026/07/09/domain-dominance-persistence-after-domain-admin/" rel="alternate" type="text/html" title="Domain dominance: persistence after Domain Admin" /><published>2026-07-09T00:00:00+00:00</published><updated>2026-07-09T00:00:00+00:00</updated><id>https://velkris.red/posts/2026/07/09/domain-dominance-persistence-after-domain-admin</id><content type="html" xml:base="https://velkris.red/posts/2026/07/09/domain-dominance-persistence-after-domain-admin/"><![CDATA[<p>Reaching Domain Admin is a milestone, not the finish line. <strong>Domain dominance</strong>
is the tradecraft of staying in - surviving password resets, re-imaging, and
incident response - by capturing key material that outlives any single account.
Understanding these is as much a defender’s job as an operator’s, because each
one changes what “contained” has to mean.</p>

<h2 id="the-persistence-primitives">The persistence primitives</h2>

<ul>
  <li><strong>Golden ticket</strong> - forge TGTs with the stolen <strong>krbtgt</strong> key. Resetting user
passwords won’t help; only rotating krbtgt (twice) invalidates it.</li>
  <li><strong>DCSync</strong> - abuse directory replication rights (<code class="language-plaintext highlighter-rouge">DS-Replication-Get-Changes</code>)
to pull any account’s hashes straight from a DC, no code on the box. It looks
like legitimate replication, which is what makes it quiet.</li>
  <li><strong>DPAPI domain backup key</strong> - a domain has one master backup key that can
decrypt <em>any</em> user’s DPAPI-protected secrets (browser passwords, saved
credentials) forever. It effectively never rotates.</li>
  <li><strong>Golden certificate</strong> - steal the enterprise CA’s private key and you can mint
authentication certificates for anyone, indefinitely (the AD CS analog of a
golden ticket).</li>
  <li><strong>AdminSDHolder / SDProp</strong> - plant an ACE on the template that SDProp stamps
back onto privileged groups every hour, re-granting access even after cleanup.</li>
</ul>

<h2 id="why-theyre-hard-to-evict">Why they’re hard to evict</h2>

<p>Every one of these captures something structural - a domain-wide key, a
replication right, a CA - rather than an account. Reset the obvious credentials
and the foothold remains. That’s why real remediation after a DA compromise is
heavy: rotate <strong>krbtgt twice</strong>, audit and revoke <strong>replication rights</strong>, treat
the <strong>CA</strong> as compromised, and in the worst cases rebuild trust from a known-good
state.</p>

<h2 id="what-to-watch">What to watch</h2>

<ul>
  <li>Replication requests (<code class="language-plaintext highlighter-rouge">4662</code> with the replication GUIDs) from anything that
isn’t a DC.</li>
  <li>New or unexpected <strong>certificate templates</strong> and CA enrollment activity.</li>
  <li>Changes to <strong>AdminSDHolder</strong> and to privileged-group membership that reappear
after removal.</li>
  <li>krbtgt password age - an account that <em>never</em> rotates is a standing risk, not a
detection, but it caps how bad a golden ticket can get.</li>
</ul>

<p>Assume the keys, then verify visibility. Dominance is only durable against a
defender who doesn’t know these exist.</p>]]></content><author><name>Velkris</name></author><category term="active-directory" /><category term="persistence" /><summary type="html"><![CDATA[Reaching Domain Admin is a milestone, not the finish line. Domain dominance is the tradecraft of staying in - surviving password resets, re-imaging, and incident response - by capturing key material that outlives any single account. Understanding these is as much a defender’s job as an operator’s, because each one changes what “contained” has to mean.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://velkris.red/assets/img/og.png" /><media:content medium="image" url="https://velkris.red/assets/img/og.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Reading entropy to spot packed code</title><link href="https://velkris.red/posts/2026/07/03/reading-entropy-to-spot-packed-code/" rel="alternate" type="text/html" title="Reading entropy to spot packed code" /><published>2026-07-03T00:00:00+00:00</published><updated>2026-07-03T00:00:00+00:00</updated><id>https://velkris.red/posts/2026/07/03/reading-entropy-to-spot-packed-code</id><content type="html" xml:base="https://velkris.red/posts/2026/07/03/reading-entropy-to-spot-packed-code/"><![CDATA[<p>Shannon entropy measures how unpredictable a byte stream is, on a scale of 0 to
8 bits per byte. Plain English text and normal machine code sit comfortably in
the middle. Compressed or encrypted data pushes hard toward the ceiling, because
by definition it has squeezed out the redundancy an analyst - or a signature -
would key on. That single number is a fast first filter for packed payloads.</p>

<h2 id="the-intuition">The intuition</h2>

<p>Entropy is defined over the byte-value distribution of a window:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>H = -Σ p(b) · log2 p(b)     for each byte value b in 0..255
</code></pre></div></div>

<ul>
  <li><strong>~4.5-6.5 bits/byte</strong> - typical for x86/x64 code and mixed data.</li>
  <li><strong>~7.9+ bits/byte</strong> - near-maximal; the region looks like ciphertext or a
compressed blob. A packed section, an embedded archive, or an encrypted stage.</li>
</ul>

<p>A whole-file average hides the interesting part, so scan in windows and look at
the <em>shape</em>:</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-OffsetEntropy</span><span class="w"> </span><span class="o">.</span><span class="nx">\sample.bin</span><span class="w"> </span><span class="nt">-HighOnly</span><span class="w"> </span><span class="o">|</span><span class="w">
    </span><span class="n">Select-Object</span><span class="w"> </span><span class="nt">-ExpandProperty</span><span class="w"> </span><span class="nx">Windows</span><span class="w">
</span></code></pre></div></div>

<h2 id="reading-the-curve-not-the-number">Reading the curve, not the number</h2>

<p>A packer usually leaves a tell: a low-entropy stub (the unpacking code) sitting
in front of a high-entropy body (the compressed payload). PE section headers
reinforce the story - a <code class="language-plaintext highlighter-rouge">.text</code> section whose raw size dwarfs a tiny virtual
size, or an oddly named section pegged at 7.9, is worth a closer look.</p>

<p>Two honest caveats. High entropy is not malicious on its own: legitimately
compressed resources, embedded PNGs, and TLS certificates all read high. And low
entropy doesn’t clear a file - plenty of malware is plaintext script. Entropy
tells you <em>where to look</em>, not <em>what you found</em>. Cross-reference the flagged
windows with the strings that survive there and with the PE section they fall
in, and the packed regions usually announce themselves.</p>]]></content><author><name>Velkris</name></author><category term="malware-analysis" /><summary type="html"><![CDATA[Shannon entropy measures how unpredictable a byte stream is, on a scale of 0 to 8 bits per byte. Plain English text and normal machine code sit comfortably in the middle. Compressed or encrypted data pushes hard toward the ceiling, because by definition it has squeezed out the redundancy an analyst - or a signature - would key on. That single number is a fast first filter for packed payloads.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://velkris.red/assets/img/og.png" /><media:content medium="image" url="https://velkris.red/assets/img/og.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Kerberos delegation abuse: unconstrained, constrained, RBCD</title><link href="https://velkris.red/posts/2026/06/24/kerberos-delegation-abuse/" rel="alternate" type="text/html" title="Kerberos delegation abuse: unconstrained, constrained, RBCD" /><published>2026-06-24T00:00:00+00:00</published><updated>2026-06-24T00:00:00+00:00</updated><id>https://velkris.red/posts/2026/06/24/kerberos-delegation-abuse</id><content type="html" xml:base="https://velkris.red/posts/2026/06/24/kerberos-delegation-abuse/"><![CDATA[<p>Delegation exists so a front-end service can act on a user’s behalf against a
back-end - a web app reaching a database as <em>you</em>, not as itself. Useful, and
routinely turned into a privilege-escalation path. There are three flavors, each
abused differently.</p>

<h2 id="unconstrained">Unconstrained</h2>

<p>A computer trusted for <strong>unconstrained delegation</strong> caches the TGT of any user
who authenticates to it. So if you control such a host and can convince a
privileged account to authenticate to it - the classic trick is coercing a
domain controller with the <em>printer bug</em> - its TGT lands in your memory, ready to
reuse. One caching service plus one coerced DC equals Domain Admin.</p>

<h2 id="constrained">Constrained</h2>

<p><strong>Constrained delegation</strong> (<code class="language-plaintext highlighter-rouge">msDS-AllowedToDelegateTo</code>) limits a service to named
SPNs, which sounds safer. The catch is <strong>S4U2Self</strong> + <strong>S4U2Proxy</strong>: an account
with constrained delegation and <em>protocol transition</em> can request a service
ticket <strong>to itself as any user</strong>, then forward it to the allowed SPNs - no
password from the victim required. And because delegation targets an SPN, not a
service, an attacker can often swap the service class (<code class="language-plaintext highlighter-rouge">http</code> → <code class="language-plaintext highlighter-rouge">cifs</code>) to reach
more than intended on the same host.</p>

<h2 id="resource-based-rbcd">Resource-based (RBCD)</h2>

<p>RBCD flips the trust: the <em>resource</em> declares who may delegate to it, in
<code class="language-plaintext highlighter-rouge">msDS-AllowedToActOnBehalfOfOtherIdentity</code>. That attribute lives on the target
computer object - so if you have write access to it (a <code class="language-plaintext highlighter-rouge">GenericWrite</code>, or the
right to create a machine account), you can point delegation at an account you
control and impersonate any user to that host.</p>

<h2 id="reining-it-in">Reining it in</h2>

<ul>
  <li>Mark high-value accounts <strong>“Account is sensitive and cannot be delegated”</strong> and
add them to <strong>Protected Users</strong>.</li>
  <li>Retire <strong>unconstrained</strong> delegation entirely where you can; it’s the sharpest
edge.</li>
  <li>Tightly control <strong>who can write</strong> delegation attributes and create machine
accounts (<code class="language-plaintext highlighter-rouge">ms-DS-MachineAccountQuota</code> of 0 closes the easy RBCD path).</li>
  <li>Hunt for <code class="language-plaintext highlighter-rouge">TrustedForDelegation</code> flags and unexpected <code class="language-plaintext highlighter-rouge">msDS-AllowedToActOnBehalf</code>
writes in your directory as configuration drift, not just at attack time.</li>
</ul>]]></content><author><name>Velkris</name></author><category term="kerberos" /><category term="active-directory" /><summary type="html"><![CDATA[Delegation exists so a front-end service can act on a user’s behalf against a back-end - a web app reaching a database as you, not as itself. Useful, and routinely turned into a privilege-escalation path. There are three flavors, each abused differently.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://velkris.red/assets/img/og.png" /><media:content medium="image" url="https://velkris.red/assets/img/og.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Imphash: fingerprinting malware by its imports</title><link href="https://velkris.red/posts/2026/06/12/imphash-fingerprinting-malware-by-its-imports/" rel="alternate" type="text/html" title="Imphash: fingerprinting malware by its imports" /><published>2026-06-12T00:00:00+00:00</published><updated>2026-06-12T00:00:00+00:00</updated><id>https://velkris.red/posts/2026/06/12/imphash-fingerprinting-malware-by-its-imports</id><content type="html" xml:base="https://velkris.red/posts/2026/06/12/imphash-fingerprinting-malware-by-its-imports/"><![CDATA[<p>A Windows PE doesn’t just contain code - it declares, up front, every external
function it intends to call. That list lives in the Import Address Table, and it
turns out to be a surprisingly durable fingerprint. The <strong>import hash</strong>, or
<em>imphash</em>, is simply a hash of that list, and it’s one of the cheapest ways to
group related binaries.</p>

<h2 id="how-its-computed">How it’s computed</h2>

<p>The recipe (originally described by Mandiant) is deterministic:</p>

<ol>
  <li>Walk the import directory in the order the entries appear.</li>
  <li>For each imported symbol, build the string <code class="language-plaintext highlighter-rouge">library.function</code>, lowercased.
The library name has its extension stripped (<code class="language-plaintext highlighter-rouge">kernel32.dll</code> → <code class="language-plaintext highlighter-rouge">kernel32</code>).</li>
  <li>Ordinal-only imports are rendered as <code class="language-plaintext highlighter-rouge">ordNNN</code>, except for a few special
libraries (<code class="language-plaintext highlighter-rouge">ws2_32</code>, <code class="language-plaintext highlighter-rouge">wsock32</code>, <code class="language-plaintext highlighter-rouge">oleaut32</code>) whose well-known ordinals resolve
back to real names.</li>
  <li>Join everything with commas and take the MD5.</li>
</ol>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-OffsetPEInfo</span><span class="w"> </span><span class="o">.</span><span class="nx">\sample.exe</span><span class="w"> </span><span class="o">|</span><span class="w">
    </span><span class="n">Select-Object</span><span class="w"> </span><span class="nx">Machine</span><span class="p">,</span><span class="w"> </span><span class="nx">ImpHash</span><span class="p">,</span><span class="w"> </span><span class="nx">ImportedDllCount</span><span class="w">
</span></code></pre></div></div>

<p>Because the hash is driven by <em>which</em> functions are imported and <em>in what
order</em>, two samples built from the same source with the same toolchain tend to
share an imphash even when their bytes differ - different strings, different
resources, recompiled with a new campaign ID.</p>

<h2 id="why-it-works-and-where-it-breaks">Why it works, and where it breaks</h2>

<p>The import order is largely a property of the linker and the source, not of the
payload, so imphash clusters build lineage rather than behavior. That’s the
strength: it survives cosmetic change. It’s also the weakness - collisions are
common for anything built with the same packer or framework, so a shared imphash
is a <em>lead</em>, not proof. Delay-loaded imports and pure dynamic resolution
(<code class="language-plaintext highlighter-rouge">LoadLibrary</code> + <code class="language-plaintext highlighter-rouge">GetProcAddress</code>) don’t appear in the static table at all, so
malware that resolves APIs at runtime has a nearly empty imphash that says
little.</p>

<p>Treat it as one signal among several: pair it with the Rich-header toolchain
fingerprint (build environment), a fuzzy hash like TLSH (byte-level similarity),
and the Authenticode signer. When three independent axes agree, the attribution
is a lot harder to argue with than any one hash alone.</p>]]></content><author><name>Velkris</name></author><category term="malware-analysis" /><category term="detection" /><summary type="html"><![CDATA[A Windows PE doesn’t just contain code - it declares, up front, every external function it intends to call. That list lives in the Import Address Table, and it turns out to be a surprisingly durable fingerprint. The import hash, or imphash, is simply a hash of that list, and it’s one of the cheapest ways to group related binaries.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://velkris.red/assets/img/og.png" /><media:content medium="image" url="https://velkris.red/assets/img/og.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Golden, silver, and diamond tickets</title><link href="https://velkris.red/posts/2026/05/29/golden-silver-and-diamond-tickets/" rel="alternate" type="text/html" title="Golden, silver, and diamond tickets" /><published>2026-05-29T00:00:00+00:00</published><updated>2026-05-29T00:00:00+00:00</updated><id>https://velkris.red/posts/2026/05/29/golden-silver-and-diamond-tickets</id><content type="html" xml:base="https://velkris.red/posts/2026/05/29/golden-silver-and-diamond-tickets/"><![CDATA[<p>Ticket forgery is what makes Kerberos persistence so hard to shake. Once an
attacker holds the right key material, they don’t request tickets - they <em>mint</em>
or <em>modify</em> them, and the domain accepts the result because it’s
cryptographically valid.</p>

<h2 id="golden-tickets-t1558001">Golden tickets (T1558.001)</h2>

<p>A golden ticket is a forged <strong>TGT</strong>, and it works because every TGT is encrypted
with the <strong>krbtgt</strong> account’s key. Recover that key - the krbtgt NTLM hash or AES
key, typically via DCSync after reaching Domain Admin - and you can fabricate a
TGT for any principal, with any group memberships, and an arbitrary lifetime. The
KDC has no way to tell a forged TGT from one it issued, because it validates the
ticket by decrypting it with the very key you copied.</p>

<p>The uncomfortable part: rotating a compromised user’s password does nothing. The
only cure is rotating <strong>krbtgt twice</strong> (the account keeps its previous key for
compatibility, so one rotation isn’t enough), which invalidates every outstanding
TGT in the domain.</p>

<h2 id="silver-tickets-t1558002">Silver tickets (T1558.002)</h2>

<p>A silver ticket is narrower and quieter. Instead of forging a TGT, you forge a
<strong>service ticket</strong> for one specific service, signed with that <em>service account’s</em>
key. Because the service validates the ticket with its own key and never consults
the KDC, a silver ticket generates <strong>no <code class="language-plaintext highlighter-rouge">4768</code>/<code class="language-plaintext highlighter-rouge">4769</code> events</strong> - there’s no
authentication traffic to log. The tradeoff is scope: the forgery only works for
that one service (and any SPN sharing the account’s key).</p>

<h2 id="diamond-tickets">Diamond tickets</h2>

<p>A diamond ticket is the stealthy evolution of the golden ticket. Rather than
building a TGT from scratch, the attacker requests a <strong>legitimate TGT</strong> from the
KDC, decrypts it with the krbtgt key, <strong>modifies the PAC</strong> (adding privileged
group SIDs, for example), and re-encrypts it. It still needs the krbtgt key - so
the root defense is identical - but because the ticket <em>started</em> as a real,
KDC-issued TGT, it carries genuine, internally consistent fields and a real
preceding AS-REQ/AS-REP.</p>

<p>That’s what makes it evasive: many golden-ticket detections lean on the artifacts
of forging a ticket <em>from nothing</em> - default lifetimes baked in by tooling,
implausible values, or a service ticket that has no matching authentication
event. A diamond ticket inherits the KDC’s own values, so those tells largely
disappear. The forgery hides inside a legitimate shell.</p>

<h2 id="spotting-and-stopping-forgeries">Spotting and stopping forgeries</h2>

<ul>
  <li><strong>Golden:</strong> watch for TGTs with anomalous lifetimes, missing or mismatched
account metadata, and logons that skip a preceding AS exchange.</li>
  <li><strong>Silver:</strong> you won’t see KDC logs, so pivot to the <em>service host</em> - process
creation and service-side logon events that don’t match a normal Kerberos flow.</li>
  <li><strong>Diamond:</strong> the hardest to catch on ticket artifacts alone; lean on the PAC
not matching the account’s real group membership, and on privileged logons that
don’t line up with expected access. Detection shifts toward <em>behavior</em>.</li>
</ul>

<p>All three share one root cause and one root fix: protect the key material.
Rotate <strong>krbtgt twice</strong> on any DA-level compromise, give service accounts long
random keys (gMSAs), and treat the krbtgt key and CA as the crown jewels they
are - because once they leak, the tickets they sign are indistinguishable from
the real thing.</p>]]></content><author><name>Velkris</name></author><category term="kerberos" /><category term="active-directory" /><summary type="html"><![CDATA[Ticket forgery is what makes Kerberos persistence so hard to shake. Once an attacker holds the right key material, they don’t request tickets - they mint or modify them, and the domain accepts the result because it’s cryptographically valid.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://velkris.red/assets/img/og.png" /><media:content medium="image" url="https://velkris.red/assets/img/og.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Kerberos, end to end</title><link href="https://velkris.red/posts/2026/05/20/kerberos-end-to-end/" rel="alternate" type="text/html" title="Kerberos, end to end" /><published>2026-05-20T00:00:00+00:00</published><updated>2026-05-20T00:00:00+00:00</updated><id>https://velkris.red/posts/2026/05/20/kerberos-end-to-end</id><content type="html" xml:base="https://velkris.red/posts/2026/05/20/kerberos-end-to-end/"><![CDATA[<p>Almost every Active Directory attack worth knowing rides on Kerberos, so it pays
to hold the whole exchange in your head. Strip away the acronyms and it’s three
conversations: prove who you are, get a ticket for a service, present it.</p>

<h2 id="the-three-exchanges">The three exchanges</h2>

<p><strong>AS-REQ / AS-REP - authentication.</strong> The client encrypts a timestamp with a key
derived from the user’s password (pre-authentication) and sends it to the KDC.
If the KDC can decrypt it, the user is who they claim. It returns a
<strong>Ticket-Granting Ticket (TGT)</strong>, encrypted with the <strong>krbtgt</strong> account’s key -
so only a domain controller can read it back.</p>

<p><strong>TGS-REQ / TGS-REP - authorization.</strong> To reach a service, the client presents
its TGT and names a <strong>Service Principal Name (SPN)</strong>. The KDC issues a <strong>service
ticket (TGS)</strong>, and here’s the key detail: a portion is encrypted with the
<em>service account’s</em> long-term key.</p>

<p><strong>AP-REQ - access.</strong> The client hands the service ticket to the service, which
decrypts it with its own key. The service never talks to the KDC to validate it -
it trusts anything encrypted with its key.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Client ──AS-REQ──►  KDC        (pre-auth timestamp)
Client ◄─AS-REP───  KDC        (TGT, sealed with krbtgt key)
Client ──TGS-REQ─►  KDC        (TGT + target SPN)
Client ◄─TGS-REP──  KDC        (service ticket, sealed with service key)
Client ──AP-REQ──►  Service    (present the ticket)
</code></pre></div></div>

<h2 id="why-the-attacks-fall-out-of-the-design">Why the attacks fall out of the design</h2>

<p>Every well-known technique keys on one of these facts:</p>

<ul>
  <li>The service-ticket blob is encrypted with the service account’s key - if that
account is a user with a weak password, you can crack it offline. That’s
<strong>Kerberoasting</strong>.</li>
  <li>Accounts without pre-auth leak an AS-REP encrypted with their key -
<strong>AS-REP roasting</strong>.</li>
  <li>The TGT is sealed with the krbtgt key. Steal that key and you can forge a TGT
for anyone - a <strong>golden ticket</strong>.</li>
  <li>The service trusts its own key blindly, so forging a service ticket with the
service account’s hash - a <strong>silver ticket</strong> - never touches the KDC and leaves
no <code class="language-plaintext highlighter-rouge">4768</code>/<code class="language-plaintext highlighter-rouge">4769</code>.</li>
</ul>

<p>Hold this map and the rest of AD attack tradecraft stops being a list of tricks
and starts being consequences of one protocol.</p>]]></content><author><name>Velkris</name></author><category term="kerberos" /><category term="active-directory" /><summary type="html"><![CDATA[Almost every Active Directory attack worth knowing rides on Kerberos, so it pays to hold the whole exchange in your head. Strip away the acronyms and it’s three conversations: prove who you are, get a ticket for a service, present it.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://velkris.red/assets/img/og.png" /><media:content medium="image" url="https://velkris.red/assets/img/og.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>