<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Kernelzera]]></title><description><![CDATA[Kernelzera]]></description><link>https://kernelzera.hashnode.dev</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1593680282896/kNC7E8IR4.png</url><title>Kernelzera</title><link>https://kernelzera.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Wed, 16 Sep 2026 08:38:14 GMT</lastBuildDate><atom:link href="https://kernelzera.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Malware Write-up: Analyzing a PDF-Disguised Multi-Stage .NET Loader]]></title><description><![CDATA[Disclaimer: This post describes malware analysis in an isolated lab. Do not run samples on a daily-use machine. Hashes and IOCs are shared for research and defense.

Introduction
I am an IT profession]]></description><link>https://kernelzera.hashnode.dev/malware-write-up-analyzing-a-pdf-disguised-multi-stage-net-loader</link><guid isPermaLink="true">https://kernelzera.hashnode.dev/malware-write-up-analyzing-a-pdf-disguised-multi-stage-net-loader</guid><category><![CDATA[reverse engineering]]></category><category><![CDATA[malware analysis]]></category><category><![CDATA[low level programming]]></category><category><![CDATA[hacking]]></category><dc:creator><![CDATA[Kernelzera]]></dc:creator><pubDate>Sun, 06 Sep 2026 22:56:32 GMT</pubDate><content:encoded><![CDATA[<blockquote>
<p><strong>Disclaimer:</strong> This post describes malware analysis in an isolated lab. Do not run samples on a daily-use machine. Hashes and IOCs are shared for research and defense.</p>
</blockquote>
<h2>Introduction</h2>
<p>I am an IT professional with 15 years of experience across software development — mobile, backend, front-end — and I currently work as a Software Architect. Still, since I was a kid I have been drawn to hacking, and for years I have studied low-level topics focused on security. I decided to start something like a research logbook of those investigations. This article was written by me; because I am Brazilian, I used AI to help rewrite it in English.</p>
<p>In this entry I walk through a sample from <a href="https://bazaar.abuse.ch/">MalwareBazaar</a>. It is obviously malware — that is why it is a <em>sample</em>. What made it interesting was the <strong>social-engineering packaging</strong>: a <strong>PDF-looking icon</strong> and bait naming (<code>pi 2.exe</code>), while the file itself is a <strong>.NET executable</strong> that unpacks through several stages, persists on the host, and tries to talk to a list of domains.</p>
<hr />
<h2>Lab</h2>
<ul>
<li><p><strong>Guest:</strong> FlareVM (Windows)</p>
</li>
<li><p><strong>Network:</strong> host-only + <strong>FakeNet-NG</strong> (no real internet)</p>
</li>
<li><p><strong>Tools:</strong> PEStudio, dnSpy, ExtremeDumper, Procmon, Wireshark / FakeNet pcaps, x32dbg, Ghidra, FLOSS / strings</p>
</li>
<li><p><strong>Sample (SHA-256):</strong></p>
</li>
</ul>
<pre><code class="language-text">af86d6a54fc8fb39239878a6b6e5a8f558ca0bba1bc85af7d5e076a59be18f67
</code></pre>
<p><em>(Internal bait name:</em> <code>pi 2.exe</code><em>, module name like</em> <code>Rm4cgtB</code><em>.)</em></p>
<hr />
<h2>1. First look: PE metadata and the PDF disguise</h2>
<p>In PEStudio / DIE:</p>
<ul>
<li><p>PE / <strong>.NET</strong> assembly (PDF icon as disguise; internal name <code>pi 2.exe</code>, module <code>Rm4cgtB</code>)</p>
</li>
<li><p>High entropy (<strong>7.189</strong>) — packed / encrypted / obfuscated territory</p>
</li>
<li><p>Early hypothesis: <strong>obfuscated loader</strong>, not the final payload</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/6a64e9450fe8019fa8e19870/033e458a-3d4a-4a68-9da0-7eb0539046df.png" alt="PEStudio indicators: size 1203712 bytes, entropy 7.189, .NET module Rm4cgtB" style="display:block;margin:0 auto" />

<p>I opened it in <strong>dnSpy</strong>. In the entry point I first hit a <strong>string reference</strong> to an embedded path — built as <code>string.Join("/", "resources", "nasode.pptx")</code>, then read, reversed, decrypted, and loaded via <code>"L o a d"</code>:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a64e9450fe8019fa8e19870/717ec231-25dc-442d-acb9-3de551bb4772.png" alt="dnSpy entry point: Capnogramat gate, resources/nasode.pptx, Array.Reverse, R_c3g6zT4Bt, Assembly.Load" style="display:block;margin:0 auto" />

<p>That was the breadcrumb — not yet the file itself. Searching for <code>nasode</code> led to the assembly resources (<code>Rm4cgtB.g.resources</code> → <code>resources/nasode.pptx</code>, ~509 KB). I dumped that stream. The “PPTX” is only a disguise; the bytes are the encrypted stage-2 blob:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a64e9450fe8019fa8e19870/ff51254b-7027-4875-b152-e437c306b511.png" alt="dnSpy: Rm4cgtB.g.resources → resources/nasode.pptx" style="display:block;margin:0 auto" />

<p>Static flow of the loader after that:</p>
<ol>
<li><p>Anti-analysis gate (<code>"Capnogramat"</code>) → silent <code>Environment.Exit(0)</code> on some paths</p>
</li>
<li><p>Reads embedded resource <code>resources/nasode.pptx</code> (<strong>fake PPTX</strong>)</p>
</li>
<li><p><code>Array.Reverse</code> on the bytes</p>
</li>
<li><p>Decrypt via an obfuscated method (<code>R_c3g6zT4Bt</code>) → <strong>AES-128-CBC</strong>, with <strong>Key = IV</strong></p>
</li>
<li><p><code>Assembly.Load</code> on the result (name split as <code>"L o a d"</code> to dodge simple string search)</p>
</li>
</ol>
<h3>AES (stage-2)</h3>
<p>Following <code>R_c3g6zT4Bt</code> in dnSpy showed plain AES with <strong>Key = IV</strong>:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a64e9450fe8019fa8e19870/04775fdd-48a8-416d-ba4a-16e2f06fb78e.png" alt="dnSpy: Aes.Create, Key = IV, TransformFinalBlock" style="display:block;margin:0 auto" />

<p>The key material lives in a static string array (hex nibbles):</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a64e9450fe8019fa8e19870/34135c3b-ba33-424e-bdcc-84c66f48490c.png" alt="dnSpy: Key/IV bytes 8C 38 48 1E … F4" style="display:block;margin:0 auto" />

<pre><code class="language-text">8C 38 48 1E BC 29 E9 BE EE 46 05 0B 09 F2 63 F4
</code></pre>
<p>Result: <code>Foliferi.dll</code> (.NET, ~521 KB).</p>
<pre><code class="language-text">SHA-256 (stage2_dec / Foliferi):
E7982DA1E11D310CBD2D168784CB6E1D6A4CC84D70F83F2D430C4F920BF9B07E
</code></pre>
<hr />
<h2>2. Stage-2: Foliferi + .NET Reactor</h2>
<p>Opening the decrypted stage-2 in PEStudio: <code>Foliferi.dll</code>, .NET 32-bit DLL, still high entropy (<strong>7.629</strong>), suspicious future compile stamp:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a64e9450fe8019fa8e19870/53c71c0e-500f-4109-b4eb-be107cf5a42a.png" alt="PEStudio: Foliferi.dll, entropy 7.629" style="display:block;margin:0 auto" />

<p><code>Foliferi.dll</code> was protected with <strong>Eziriz .NET Reactor</strong> (trial / unregistered):</p>
<ul>
<li><p>anti-debug (<code>Debugger.IsAttached</code>)</p>
</li>
<li><p>init in <code>.cctor</code></p>
</li>
<li><p>heavy obfuscation (empty-looking methods, control-flow flattening)</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/6a64e9450fe8019fa8e19870/e7d92a6d-b1d4-44f0-a511-7c1b44553c9b.png" alt="dnSpy: Debugger.IsAttached → Debugger Detected" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/6a64e9450fe8019fa8e19870/abf40cb2-49ad-4474-94da-684c452c4699.png" alt="dnSpy: Eziriz .NET Reactor unregistered / trial string" style="display:block;margin:0 auto" />

<p>I tried <strong>NETReactorSlayer</strong> → unpack was <strong>incomplete</strong>. So I switched strategy: <strong>dynamic dumping</strong> (ExtremeDumper) while the process was running — Foliferi shows up as a loaded module:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a64e9450fe8019fa8e19870/b44334be-1970-4486-8875-d433110387e9.png" alt="ExtremeDumper: Foliferi modules in the live process" style="display:block;margin:0 auto" />

<p><strong>Takeaway:</strong> static unpack of the protector does not always close the case; a <strong>runtime dump</strong> often yields more readable modules.</p>
<hr />
<h2>3. Host behavior: what the malware does on the box</h2>
<p>With FakeNet + Procmon (and later a fuller CSV of the original loader):</p>
<h3>Drop + persistence</h3>
<p>The loader does not only “write the Run key” from its own process — it spawns <code>cmd.exe</code> with a delay via <code>ping</code>:</p>
<ol>
<li><strong>Persistence</strong></li>
</ol>
<pre><code class="language-text">cmd /c ping 127.0.0.1 -n 35 &gt; nul &amp;&amp; REG ADD "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /f /v "PI-0023455" /t REG_SZ /d "...\Desktop\PI-0023455.exe"
</code></pre>
<ol>
<li><strong>Copy + execute</strong></li>
</ol>
<pre><code class="language-text">cmd /c ping 127.0.0.1 -n 48 &gt; nul &amp;&amp; copy "&lt;sample&gt;.exe" "...\Desktop\PI-0023455.exe" &amp;&amp; ping ... &amp;&amp; "...\Desktop\PI-0023455.exe"
</code></pre>
<p>So:</p>
<ul>
<li><p>Drop: <code>Desktop\PI-0023455.exe</code> (same hash as the sample in this family)</p>
</li>
<li><p>Persistence: <code>HKCU\...\Run\PI-0023455</code></p>
</li>
</ul>
<p>In System Informer the living process is already the Desktop copy (PDF icon, parent often <em>Non-existent</em> because the original loader exited):</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a64e9450fe8019fa8e19870/b4834d74-9069-4bf6-83f2-5ab11791f6aa.png" alt="System Informer: PI-0023455.exe on Desktop" style="display:block;margin:0 auto" />

<p>Procmon / registry confirmation of the Run key:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a64e9450fe8019fa8e19870/21f0eb95-7147-402e-8e9c-2ec4b85f87c4.png" alt="Procmon: traffic around HKCU Run / PI-0023455" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/6a64e9450fe8019fa8e19870/efecdd32-e4d8-409f-989f-6c955f29bfcf.png" alt="PowerShell: HKCU Run PI-0023455 → DesktopI-0023455.exe" style="display:block;margin:0 auto" />

<p>If you filter Procmon only on <code>PI-0023455.exe</code>, it is easy to <strong>miss</strong> the <code>REG ADD</code>. The key is written by <code>cmd</code>.</p>
<h3>The Reactor trial date trap</h3>
<p>After a few days, the process would start and die immediately. Going back to notes: the Reactor trial checks a date window (~`2026-08-10` ± 14 days). Outside that window, the sample aborts early.</p>
<p>Lab fix:</p>
<ul>
<li><p>Disable VirtualBox guest time sync (<code>GetHostTimeDisabled</code>)</p>
</li>
<li><p>Pin the guest date (e.g. <code>2026-08-15</code>)</p>
</li>
</ul>
<p><strong>Lesson:</strong> if the sample “does nothing”, check date, packer trial windows, and <code>Process Exit</code> / CPU time in Procmon — “five minutes of capture” ≠ “five minutes of living malware”.</p>
<hr />
<h2>4. Network (FakeNet / pcap)</h2>
<p>With the correct date and a valid capture:</p>
<ul>
<li><p>Many <strong>DNS</strong> queries for <code>www.*</code> hosts (C2 / panel candidates)</p>
</li>
<li><p><strong>TCP 80/443</strong> attempts → under FakeNet often only <strong>ICMP unreachable</strong></p>
</li>
<li><p><strong>No useful HTTP</strong> in that session (no full handshake → no request body)</p>
</li>
</ul>
<p>Wireshark on the FakeNet pcap — early C2 candidates resolving via <code>169.254.0.33</code>, then failed connects:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a64e9450fe8019fa8e19870/9f94b143-c743-4f68-9087-cdd26e6df553.png" alt="Wireshark: DNS www.aravaeducraft.com / www.bierhenkel-immo.com" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/6a64e9450fe8019fa8e19870/cb9968ad-b6d9-4fd7-b33c-9f32abcc6a69.png" alt="Wireshark: DNS www.mmsp532.top (and related www.* queries)" style="display:block;margin:0 auto" />

<h3>Domain IOCs (candidates)</h3>
<p>Defanged for publishing:</p>
<pre><code class="language-text">www[.]aravaeducraft[.]com
www[.]auninternational[.]com
www[.]bangaranga[.]fashion
www[.]bierhenkel-immo[.]com
www[.]brswigacademy[.]com
www[.]dogivia[.]com
www[.]genki-healthcare[.]com
www[.]klynker[.]com
www[.]lovinej[.]info
www[.]mmsp532[.]top
www[.]novamed-beauty[.]ru
www[.]pv16[.]xyz
www[.]slot-oyunu[.]top
www[.]uvgullasedu[.]com
www[.]vcfxzm[.]com
www[.]wakomatsumoto[.]com
</code></pre>
<p>Possible noise / other context: <code>www[.]bradesco[.]com[.]br</code>, <code>www[.]vrbo[.]com</code>, <code>update[.]googleapis[.]com</code>.</p>
<p>Public reputation (examples):</p>
<ul>
<li><p><code>slot-oyunu[.]top</code> — vendor detections (e.g. Kaspersky/Fortinet as malware on VT)</p>
</li>
<li><p><code>mmsp*.top</code> cluster — disposable infra / suspicious redirects in OSINT</p>
</li>
</ul>
<p>Those domains were <strong>not</strong> in cleartext in the stage-1 loader (not even simple XOR) — they fit a later stage / runtime list.</p>
<hr />
<h2>5. Stage-3: <code>bgK2rzW…dll</code> and in-memory execution</h2>
<p>ExtremeDumper produced a more readable module, roughly:</p>
<pre><code class="language-text">bgK2rzW57itpC1szi9tD7KX13LJ.dll
</code></pre>
<p>In dnSpy it shows up as a <strong>dynamic / in-memory</strong> assembly:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a64e9450fe8019fa8e19870/bd759734-e34d-4b60-9977-d001de287b28.png" alt="dnSpy: bgK2… module with Dynamic/InMemory = Yes" style="display:block;margin:0 auto" />

<p>Typical init: <code>ChainResponder</code> <code>.cctor</code> → <code>ResponderBridge.RespondMonoResponder</code> (huge method with <strong>control-flow flattening</strong>).</p>
<h3>Chain until native execution</h3>
<p>Inside the unpacker (obfuscated names → real APIs):</p>
<table>
<thead>
<tr>
<th>Step</th>
<th>API / idea</th>
</tr>
</thead>
<tbody><tr>
<td>Decrypt</td>
<td>AES / <code>CryptoStream</code></td>
</tr>
<tr>
<td>Allocate</td>
<td><code>VirtualAlloc</code> RWX (<code>MEM_COMMIT</code> + <code>PAGE_EXECUTE_READWRITE</code> / <code>0x40</code>)</td>
</tr>
<tr>
<td>Copy</td>
<td><code>Marshal.Copy</code> → RWX buffer</td>
</tr>
<tr>
<td>Prepare</td>
<td><code>GetDelegateForFunctionPointer</code></td>
</tr>
<tr>
<td>Run</td>
<td>static delegate (<code>_ResponderThread</code>)</td>
</tr>
</tbody></table>
<p>Broken API strings (<code>"Virtual " + "Alloc"</code>) = classic anti-<code>strings</code>.</p>
<h3>Native debugging: trampolines and the JIT trap</h3>
<p>With dnSpy + x32dbg + Ghidra:</p>
<ol>
<li><p>Dump of an RWX stub → prologue <code>55 8B EC</code></p>
</li>
<li><p>Not the “final malware”: a <strong>trampoline/selector</strong> (<code>CMP</code> + <code>JMP EAX</code>)</p>
</li>
<li><p>Hop chain → larger functions in the same RWX region</p>
</li>
<li><p>Many <code>CALL</code>s landed in <code>clr</code> / <code>mscorlib.ni</code> / <code>clrjit</code></p>
</li>
</ol>
<p>Breakpoint on <code>VirtualAlloc</code> in x32dbg (process <code>PI-0023455</code>), including RWX (<code>flProtect = 0x40</code>):</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a64e9450fe8019fa8e19870/13e7fc2f-85bb-4f84-b4ee-3333ecf97e6c.png" alt="x32dbg: breakpoint on kernel32.VirtualAlloc" style="display:block;margin:0 auto" />

<img src="https://cdn.hashnode.com/uploads/covers/6a64e9450fe8019fa8e19870/a978c786-d878-41c5-807a-1c4031bf89a6.png" alt="x32dbg: VirtualAlloc with PAGE_EXECUTE_READWRITE (0x40)" style="display:block;margin:0 auto" />

<p>Dump taken to Ghidra — listing of the native stub / hops:</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a64e9450fe8019fa8e19870/baa9f77a-d3b1-4d37-b695-d4a60e388526.png" alt="Ghidra: listing of dumped native stage" style="display:block;margin:0 auto" />

<p>Conclusion of the native phase: a lot of the “interesting” RWX code was <strong>JIT-compiled .NET</strong>, not a classic third PE hidden on disk. Endless hopping there has low ROI.</p>
<p><strong>Key lesson:</strong> when disassembly mostly calls into the CLR and uses tokens like <code>0x2000…</code>, you are probably in JIT — go back to behavior, strings from the unpacked managed stage, or network APIs.</p>
<hr />
<h2>6. VirusTotal: validation</h2>
<p>On VT the same hash shows up as <code>pi 2.exe</code>, with something like <strong>53/71</strong> engines and Behavior from sandboxes (CAPE, Zenbox, etc.):</p>
<img src="https://cdn.hashnode.com/uploads/covers/6a64e9450fe8019fa8e19870/464f1e38-c67c-4ccc-8482-a5c4b222ebb3.png" alt="VirusTotal: pi 2.exe — 53/71, community score −11" style="display:block;margin:0 auto" />

<p>Behavior confirmed what the lab already had:</p>
<ul>
<li><p><code>cmd</code> + <code>ping</code> + <code>REG ADD</code> Run / <code>PI-0023455</code></p>
</li>
<li><p>copy to Desktop + execution</p>
</li>
<li><p>AES / .NET resources</p>
</li>
<li><p>tags like <code>long-sleeps</code>, obfuscation, anti-debug</p>
</li>
</ul>
<p>Expected differences:</p>
<ul>
<li><p>sandbox DNS ≠ full FakeNet domain list</p>
</li>
<li><p>VT environment noise (Google Update, font/decoy “memory patterns”)</p>
</li>
</ul>
<p>A <strong>negative Community Score</strong> (e.g. −11) means the community voted the <strong>file as malicious</strong>, not that the report quality is “bad”.</p>
<hr />
<h2>7. Classification (what we can claim)</h2>
<p><strong>Observed</strong></p>
<ul>
<li><p>Multi-stage .NET loader (AES resource → Foliferi/Reactor → in-memory unpacker)</p>
</li>
<li><p>Persistence (Desktop + Run via <code>cmd</code>)</p>
</li>
<li><p>DNS beaconing + web connect attempts (incomplete C2 under FakeNet)</p>
</li>
</ul>
<p><strong>Not observed in this analysis</strong></p>
<ul>
<li><p>Stable extra-stage download with full HTTP</p>
</li>
<li><p>Clear exfil/POST</p>
</li>
<li><p>Ransomware / mass file destruction</p>
</li>
<li><p>A 100% family attribution from VT alone (treat engine family names as hypotheses)</p>
</li>
</ul>
<p><strong>Working classification:</strong> .NET loader/downloader with persistence and contact to a domain list (likely C2).</p>
<hr />
<h2>8. What I would do differently next time</h2>
<ol>
<li><p><strong>VT / MalwareBazaar in minute zero</strong> (context)</p>
</li>
<li><p><strong>Procmon + FakeNet first</strong>; deep RE later</p>
</li>
<li><p>Include <strong>children</strong> (<code>cmd</code>) in Procmon filters</p>
</li>
<li><p>Validate the pcap (open in Wireshark) <strong>before</strong> copying it out of the Share folder</p>
</li>
<li><p>Stop earlier when native code is clearly <strong>JIT</strong></p>
</li>
<li><p>Write a checkpoint after every stage (hash, path, “what it does”)</p>
</li>
</ol>
<hr />
<h2>IOCs (summary)</h2>
<table>
<thead>
<tr>
<th>Type</th>
<th>Value</th>
</tr>
</thead>
<tbody><tr>
<td>SHA-256</td>
<td><code>af86d6a54fc8fb39239878a6b6e5a8f558ca0bba1bc85af7d5e076a59be18f67</code></td>
</tr>
<tr>
<td>Drop</td>
<td><code>%USERPROFILE%\Desktop\PI-0023455.exe</code></td>
</tr>
<tr>
<td>Persistence</td>
<td><code>HKCU\Software\Microsoft\Windows\CurrentVersion\Run\PI-0023455</code></td>
</tr>
<tr>
<td>Technique</td>
<td><code>cmd</code> + <code>ping</code> delay + <code>REG ADD</code> + <code>copy</code></td>
</tr>
<tr>
<td>Network</td>
<td><code>www[.]…</code> list above (DNS); TCP 80/443 attempted</td>
</tr>
</tbody></table>
<p>VT (reference):<br /><a href="https://www.virustotal.com/gui/file/af86d6a54fc8fb39239878a6b6e5a8f558ca0bba1bc85af7d5e076a59be18f67">https://www.virustotal.com/gui/file/af86d6a54fc8fb39239878a6b6e5a8f558ca0bba1bc85af7d5e076a59be18f67</a></p>
<hr />
<h2>Closing</h2>
<p>I started with a sample that <em>looked</em> like a document and ended up mapping a full .NET chain: AES resource decryption, Reactor + trial-date friction, persistence in Procmon, C2 candidates in FakeNet, and — the hard way — learning that not every <code>PUSH EBP</code> in RWX is the “final payload”.</p>
<p>If this write-up saves someone two weeks chasing JIT stubs, it already paid off.</p>
<hr />
<p><em>Based on my lab notes (Notion) and analysis on FlareVM. Written by me; AI helped with the English rewrite. Feedback welcome.</em></p>
]]></content:encoded></item></channel></rss>